Layanan API yang tenang

226

Saya ingin membuat layanan yang dapat saya gunakan untuk membuat panggilan ke REST API berbasis web.

Pada dasarnya saya ingin memulai layanan pada aplikasi init maka saya ingin dapat meminta layanan itu untuk meminta url dan mengembalikan hasilnya. Sementara itu saya ingin dapat menampilkan jendela progres atau yang serupa.

Saya telah membuat layanan saat ini yang menggunakan IDL, saya telah membaca di suatu tempat bahwa Anda hanya benar-benar membutuhkan ini untuk komunikasi lintas aplikasi, jadi anggap ini perlu dihapus tetapi tidak yakin bagaimana melakukan panggilan balik tanpa itu. Juga ketika saya menekan post(Config.getURL("login"), values)aplikasi tampaknya berhenti untuk sementara waktu (tampak aneh - mengira ide di balik layanan adalah bahwa ia berjalan di utas yang berbeda!)

Saat ini saya memiliki layanan dengan posting dan mendapatkan metode http di dalamnya, beberapa file AIDL (untuk komunikasi dua arah), ServiceManager yang berhubungan dengan memulai, menghentikan, mengikat dll untuk layanan dan saya secara dinamis membuat Handler dengan kode tertentu untuk panggilan balik sesuai kebutuhan.

Saya tidak ingin ada yang memberi saya basis kode yang lengkap untuk dikerjakan, tetapi beberapa petunjuk akan sangat dihargai.

Kode dalam (kebanyakan) lengkap:

public class RestfulAPIService extends Service  {

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
    return binder;
}
public void onCreate() {
    super.onCreate();
}
public void onDestroy() {
    super.onDestroy();
    mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
    public void doLogin(String username, String password) {

        Message msg = new Message();
        Bundle data = new Bundle();
        HashMap<String, String> values = new HashMap<String, String>();
        values.put("username", username);
        values.put("password", password);
        String result = post(Config.getURL("login"), values);
        data.putString("response", result);
        msg.setData(data);
        msg.what = Config.ACTION_LOGIN;
        mHandler.sendMessage(msg);
    }

    public void registerCallback(IRemoteServiceCallback cb) {
        if (cb != null)
            mCallbacks.register(cb);
    }
};

private final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        // Broadcast to all clients the new value.
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                switch (msg.what) {
                case Config.ACTION_LOGIN:
                    mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
                    break;
                default:
                    super.handleMessage(msg);
                    return;

                }
            } catch (RemoteException e) {
            }
        }
        mCallbacks.finishBroadcast();
    }
    public String post(String url, HashMap<String, String> namePairs) {...}
    public String get(String url) {...}
};

Beberapa file AIDL:

package com.something.android

oneway interface IRemoteServiceCallback {
    void userLogIn(String result);
}

dan

package com.something.android
import com.something.android.IRemoteServiceCallback;

interface IRestfulService {
    void doLogin(in String username, in String password);
    void registerCallback(IRemoteServiceCallback cb);
}

dan manajer layanan:

public class ServiceManager {

    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
    public IRestfulService restfulService;
    private RestfulServiceConnection conn;
    private boolean started = false;
    private Context context;

    public ServiceManager(Context context) {
        this.context = context;
    }

    public void startService() {
        if (started) {
            Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.startService(i);
            started = true;
        }
    }

    public void stopService() {
        if (!started) {
            Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.stopService(i);
            started = false;
        }
    }

    public void bindService() {
        if (conn == null) {
            conn = new RestfulServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.bindService(i, conn, Context.BIND_AUTO_CREATE);
        } else {
            Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

    protected void destroy() {
        releaseService();
    }

    private void releaseService() {
        if (conn != null) {
            context.unbindService(conn);
            conn = null;
            Log.d(LOG_TAG, "unbindService()");
        } else {
            Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
        }
    }

    class RestfulServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className, IBinder boundService) {
            restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
            try {
            restfulService.registerCallback(mCallback);
            } catch (RemoteException e) {}
        }

        public void onServiceDisconnected(ComponentName className) {
            restfulService = null;
        }
    };

    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        public void userLogIn(String result) throws RemoteException {
            mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));

        }
    };

    private Handler mHandler;

    public void setHandler(Handler handler) {
        mHandler = handler;
    }
}

Layanan init dan ikat:

// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);

panggilan fungsi layanan:

// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();

application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);

try {
    servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
    e.printStackTrace();
}

...later in the same file...

Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        switch (msg.what) {
        case Config.ACTION_LOGIN:

            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }

            try {
                ...process login results...
                }
            } catch (JSONException e) {
                Log.e("JSON", "There was an error parsing the JSON", e);
            }
            break;
        default:
            super.handleMessage(msg);
        }

    }

};
Martyn
sumber
5
Ini mungkin sangat membantu bagi orang yang mempelajari implementasi klien Android REST. Presentasi Dobjanschi ditranskripsi ke dalam PDF: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…
Kay Zed
Karena banyak orang merekomendasikan presentasi Virgil Dobjanschi dan tautan ke IO 2010 terputus sekarang, inilah tautan langsung ke video YT: youtube.com/watch?v=xHXn3Kg2IQE
Jose_GD

Jawaban:

283

Jika layanan Anda akan menjadi bagian dari aplikasi Anda, maka Anda membuatnya menjadi jauh lebih kompleks daripada yang seharusnya. Karena Anda memiliki kasus penggunaan sederhana untuk mendapatkan beberapa data dari Layanan Web RESTful , Anda harus melihat dalam ResultReceiver dan IntentService .

Pola Service + ResultReceiver ini berfungsi dengan memulai atau mengikat ke layanan dengan startService () ketika Anda ingin melakukan beberapa tindakan. Anda dapat menentukan operasi yang akan dilakukan dan lulus dalam ResultReceiver Anda (aktivitas) melalui ekstra di Intent.

Di layanan Anda menerapkan onHandleIntent untuk melakukan operasi yang ditentukan dalam Intent. Ketika operasi selesai, Anda menggunakan yang diteruskan dalam ResultReceiver untuk mengirim pesan kembali ke Aktivitas di mana titik onReceiveResult akan dipanggil.

Jadi misalnya, Anda ingin menarik beberapa data dari Layanan Web Anda.

  1. Anda membuat maksud dan memanggil startService.
  2. Operasi dalam layanan dimulai dan mengirimkan aktivitas pesan yang mengatakan itu dimulai
  3. Aktivitas memproses pesan dan menunjukkan kemajuan.
  4. Layanan ini menyelesaikan operasi dan mengirimkan beberapa data kembali ke aktivitas Anda.
  5. Aktivitas Anda memproses data dan dimasukkan ke dalam tampilan daftar
  6. Layanan mengirimi Anda pesan yang mengatakan bahwa itu sudah selesai, dan ia bunuh diri.
  7. Aktivitas mendapatkan pesan selesai dan menyembunyikan dialog progres.

Saya tahu Anda menyebutkan bahwa Anda tidak menginginkan basis kode tetapi aplikasi Google I / O 2010 open source menggunakan layanan dengan cara yang saya uraikan ini.

Diperbarui untuk menambahkan kode sampel:

Kegiatannya.

public class HomeActivity extends Activity implements MyResultReceiver.Receiver {

    public MyResultReceiver mReceiver;

    public void onCreate(Bundle savedInstanceState) {
        mReceiver = new MyResultReceiver(new Handler());
        mReceiver.setReceiver(this);
        ...
        final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
        intent.putExtra("receiver", mReceiver);
        intent.putExtra("command", "query");
        startService(intent);
    }

    public void onPause() {
        mReceiver.setReceiver(null); // clear receiver so no leaks.
    }

    public void onReceiveResult(int resultCode, Bundle resultData) {
        switch (resultCode) {
        case RUNNING:
            //show progress
            break;
        case FINISHED:
            List results = resultData.getParcelableList("results");
            // do something interesting
            // hide progress
            break;
        case ERROR:
            // handle the error;
            break;
    }
}

Layanan:

public class QueryService extends IntentService {
    protected void onHandleIntent(Intent intent) {
        final ResultReceiver receiver = intent.getParcelableExtra("receiver");
        String command = intent.getStringExtra("command");
        Bundle b = new Bundle();
        if(command.equals("query") {
            receiver.send(STATUS_RUNNING, Bundle.EMPTY);
            try {
                // get some data or something           
                b.putParcelableArrayList("results", results);
                receiver.send(STATUS_FINISHED, b)
            } catch(Exception e) {
                b.putString(Intent.EXTRA_TEXT, e.toString());
                receiver.send(STATUS_ERROR, b);
            }    
        }
    }
}

Ekstensi ResultReceiver - diedit untuk mengimplementasikan MyResultReceiver.Receiver

public class MyResultReceiver implements ResultReceiver {
    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

    public interface Receiver {
        public void onReceiveResult(int resultCode, Bundle resultData);
    }

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }
}
Robby Pond
sumber
1
Luar biasa - terima kasih! Saya akhirnya melalui aplikasi Google iosched dan wow ... itu cukup kompleks tapi saya punya sesuatu yang berfungsi, sekarang saya hanya perlu mencari tahu mengapa itu bekerja! Tapi ya, ini adalah pola dasar yang saya miliki. Terima kasih banyak.
Martyn
29
Satu tambahan kecil untuk jawabannya: seperti yang Anda lakukan pada mReceiver.setReceiver (null); dalam metode onPause, Anda harus melakukan mReceiver.setReceiver (ini); dalam metode onResume. Jika tidak, Anda mungkin tidak menerima acara jika aktivitas Anda dilanjutkan tanpa dibuat ulang
Vincent Mimoun-Prat
7
Tidakkah dokumen mengatakan bahwa Anda tidak perlu menelepon stopSelf, karena IntentService melakukan itu untuk Anda?
Mikael Ohlson
2
@MikaelOhlson Benar, Anda tidak boleh menelepon stopSelfjika Anda subkelas IntentServicekarena jika Anda melakukannya, Anda akan kehilangan permintaan yang tertunda karena hal yang sama IntentService.
quietmint
1
IntentServiceakan membunuh dirinya sendiri ketika tugas selesai, jadi this.stopSelf()tidak perlu.
Euporie
17

Mengembangkan aplikasi klien Android REST telah menjadi sumber yang luar biasa bagi saya. Pembicara tidak menunjukkan kode apa pun, ia hanya membahas pertimbangan desain dan teknik dalam mengumpulkan batu padat Rest Api di android. Jika podcast Anda agak orang atau tidak, saya sarankan memberikan yang ini setidaknya satu dengarkan tetapi, secara pribadi saya sudah mendengarkannya 4 atau lima kali sejauh ini dan saya mungkin akan mendengarkannya lagi.

Mengembangkan aplikasi klien Android REST
Penulis: Virgil Dobjanschi
Deskripsi:

Sesi ini akan menyajikan pertimbangan arsitektur untuk mengembangkan aplikasi RESTful di platform Android. Ini berfokus pada pola desain, integrasi platform, dan masalah kinerja khusus untuk platform Android.

Dan ada begitu banyak pertimbangan yang saya benar-benar tidak membuat versi pertama dari api saya bahwa saya harus refactor

Terrance
sumber
4
+1 Ini berisi jenis pertimbangan yang tidak akan pernah Anda pikirkan saat memulai.
Thomas Ahle
Ya perampokan pertama saya dalam mengembangkan klien Istirahat hampir persis deskripsinya tentang apa yang sebenarnya tidak boleh dilakukan (Syukurlah saya menyadari banyak dari ini salah sebelum saya menonton ini). Saya sedikit memberi tahu.
Terrance
Saya telah melihat video ini lebih dari sekali dan saya menerapkan pola kedua. Masalah saya adalah bahwa saya perlu menggunakan transaksi dalam model database kompleks saya untuk memperbarui data lokal dari data baru yang berasal dari server, dan antarmuka ContentProvider tidak memberi saya cara untuk melakukannya. Apakah Anda punya saran, Terrance?
Flávio Faria
2
NB: Komentar dari Dobjanschi di HttpClient tidak berlaku lagi. Lihat stackoverflow.com/a/15524143/939250
Donal Lafferty
Ya, HttpURLConnection sekarang lebih disukai. Selanjutnya, dengan rilis Android 6.0, dukungan untuk Apache HTTP Client telah secara resmi dihapus .
RonR
16

Juga ketika saya membentur tiang (Config.getURL ("login"), nilai-nilai) aplikasi tampaknya berhenti untuk sementara waktu (tampak aneh - mengira ide di balik layanan adalah bahwa ia berjalan pada utas yang berbeda!)

Tidak, Anda harus membuat utas sendiri, layanan Lokal berjalan pada utas UI secara default.

Soumya Simanta
sumber
5

Hanya ingin mengarahkan Anda semua ke arah kelas mandiri yang saya putar yang menggabungkan semua fungsi.

http://github.com/StlTenny/RestService

Itu mengeksekusi permintaan sebagai non-blocking, dan mengembalikan hasil dalam handler yang mudah diimplementasikan. Bahkan hadir dengan contoh implementasi.

StTTenny
sumber
4

Katakanlah saya ingin memulai layanan pada acara - onItemClicked () dari sebuah tombol. Mekanisme Receiver tidak akan berfungsi dalam kasus itu karena: -
a) Saya meneruskan Receiver ke layanan (seperti pada Intent extra) dari onItemClicked ()
b) Aktivitas bergerak ke latar belakang. Di onPause () saya mengatur referensi penerima di dalam ResultReceiver ke nol untuk menghindari bocornya Aktivitas.
c) Aktivitas dihancurkan.
d) Aktivitas akan dibuat lagi. Namun pada titik ini Layanan tidak akan dapat membuat panggilan balik ke Aktivitas karena referensi penerima hilang.
Mekanisme siaran terbatas atau PendingIntent tampaknya lebih berguna dalam skenario seperti itu - lihat aktivitas Beritahu dari layanan

Nikhil_Katre
sumber
1
ada masalah dengan apa yang Anda katakan. itu adalah bahwa ketika aktivitas bergerak ke latar belakang itu tidak hancur ... sehingga penerima masih ada dan konteks aktivitas juga.
DArkO
@DArkO Ketika suatu Aktivitas dijeda atau dihentikan, itu dapat dimatikan oleh sistem Android dalam situasi memori rendah. Lihat Siklus Hidup Aktivitas .
jk7
4

Perhatikan bahwa solusi dari Robby Pond entah bagaimana kurang: dengan cara ini Anda hanya mengizinkan untuk melakukan satu panggilan api sekaligus karena IntentService hanya menangani satu maksud pada satu waktu. Seringkali Anda ingin melakukan panggilan api paralel. Jika Anda ingin melakukan ini, Anda harus memperluas Layanan alih-alih IntentService dan membuat utas sendiri.

TjerkW
sumber
1
Anda masih dapat melakukan beberapa panggilan di IntentService dengan mendelegasikan panggilan api layanan web ke layanan eksekutor-utas, hadir sebagai variabel anggota dari kelas turunan IntentService Anda
Viren
2

Juga ketika saya membentur tiang (Config.getURL ("login"), nilai-nilai) aplikasi tampaknya berhenti untuk sementara waktu (tampak aneh - mengira ide di balik layanan adalah bahwa ia berjalan pada utas yang berbeda!)

Dalam hal ini lebih baik menggunakan asynctask, yang berjalan pada utas yang berbeda dan mengembalikan hasilnya ke utas setelah selesai.

Aakash
sumber
2

Ada pendekatan lain di sini yang pada dasarnya membantu Anda melupakan seluruh pengelolaan permintaan. Ini didasarkan pada metode antrian async dan respons berbasis callable / callback. Keuntungan utama adalah bahwa dengan menggunakan metode ini Anda akan dapat membuat seluruh proses (permintaan, dapatkan dan tanggapan parse, sabe ke db) sepenuhnya transparan untuk Anda. Setelah Anda mendapatkan kode respons, pekerjaan sudah dilakukan. Setelah itu Anda hanya perlu melakukan panggilan ke db Anda dan Anda selesai. Ini membantu juga dengan masalah apa yang terjadi ketika aktivitas Anda tidak aktif. Apa yang akan terjadi di sini adalah bahwa Anda akan menyimpan semua data Anda di basis data lokal Anda tetapi responsnya tidak akan diproses oleh aktivitas Anda, itu cara yang ideal.

Saya menulis tentang pendekatan umum di sini http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

Saya akan meletakkan kode sampel tertentu di posting mendatang. Semoga ini bisa membantu, jangan ragu untuk menghubungi saya untuk berbagi pendekatan dan menyelesaikan potensi keraguan atau masalah.

Jose L Ugia
sumber
Tautan mati. Domain telah kedaluwarsa.
jk7
1

Robby memberikan jawaban yang bagus, meskipun saya dapat melihat Anda masih mencari informasi lebih lanjut. Saya menerapkan REST api memanggil cara TAPI mudah salah. Tidak sampai menonton video Google I / O ini saya mengerti di mana saya salah. Ini tidak sesederhana mengumpulkan AsyncTask dengan panggilan panggilan / panggilan HttpUrlConnection.

Andrew Halloran
sumber
Tautan mati. Berikut ini yang diperbarui - Google I / O 2010 - Aplikasi klien Android REST
zim