Threading Android: Semua yang Perlu Anda Ketahui
Diterbitkan: 2022-03-11Setiap pengembang Android, pada satu titik atau lainnya, perlu berurusan dengan utas dalam aplikasi mereka.
Ketika sebuah aplikasi diluncurkan di Android, itu membuat utas eksekusi pertama, yang dikenal sebagai utas "utama". Utas utama bertanggung jawab untuk mengirimkan peristiwa ke widget antarmuka pengguna yang sesuai serta berkomunikasi dengan komponen dari toolkit UI Android.
Agar aplikasi Anda tetap responsif, penting untuk menghindari penggunaan utas utama untuk melakukan operasi apa pun yang mungkin membuat aplikasi tetap diblokir.
Operasi jaringan dan panggilan database, serta memuat komponen tertentu, adalah contoh umum dari operasi yang harus dihindari di thread utama. Ketika mereka dipanggil di utas utama, mereka dipanggil secara sinkron, yang berarti bahwa UI akan tetap sepenuhnya tidak responsif hingga operasi selesai. Untuk alasan ini, mereka biasanya dilakukan di utas terpisah, yang dengan demikian menghindari pemblokiran UI saat sedang dilakukan (yaitu, dilakukan secara asinkron dari UI).
Android menyediakan banyak cara untuk membuat dan mengelola utas, dan ada banyak perpustakaan pihak ketiga yang membuat pengelolaan utas jauh lebih menyenangkan. Namun, dengan begitu banyak pendekatan yang berbeda, memilih yang tepat bisa sangat membingungkan.
Dalam artikel ini, Anda akan mempelajari tentang beberapa skenario umum dalam pengembangan Android di mana threading menjadi penting dan beberapa solusi sederhana yang dapat diterapkan pada skenario tersebut dan banyak lagi.
Threading di Android
Di Android, Anda dapat mengkategorikan semua komponen threading ke dalam dua kategori dasar:
- Utas yang dilampirkan ke aktivitas/fragmen: Utas ini terikat pada siklus hidup aktivitas/fragmen dan dihentikan segera setelah aktivitas/fragmen dihancurkan.
- Utas yang tidak dilampirkan ke aktivitas/fragmen apa pun: Utas ini dapat terus berjalan di luar masa aktivitas/fragmen (jika ada) tempat asalnya.
Komponen Threading yang Melampirkan ke Aktivitas/Fragmen
Tugas Asinkron
AsyncTask
adalah komponen Android paling dasar untuk threading. Ini mudah digunakan dan bagus untuk skenario dasar.
Contoh penggunaan:
public class ExampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyTask().execute(url); } private class MyTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String url = params[0]; return doSomeWork(url); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // do something with result } } }
AsyncTask
, bagaimanapun, gagal jika Anda membutuhkan tugas yang ditangguhkan untuk berjalan di luar masa aktivitas/fragmen. Perlu dicatat bahwa bahkan sesuatu yang sederhana seperti rotasi layar dapat menyebabkan aktivitas tersebut dimusnahkan.
Pemuat
Loader adalah solusi untuk masalah yang disebutkan di atas. Loader dapat berhenti secara otomatis saat aktivitas dimusnahkan, dan juga dapat memulai ulang sendiri setelah aktivitas dibuat ulang.
Terutama ada dua jenis loader: AsyncTaskLoader
dan CursorLoader
. Anda akan mempelajari lebih lanjut tentang CursorLoader
nanti di artikel ini.
AsyncTaskLoader
mirip dengan AsyncTask
, tetapi sedikit lebih rumit.
Contoh penggunaan:
public class ExampleActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(1, null, new MyLoaderCallbacks()); } private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new MyLoader(ExampleActivity.this); } @Override public void onLoadFinished(Loader loader, Object data) { } @Override public void onLoaderReset(Loader loader) { } } private class MyLoader extends AsyncTaskLoader { public MyLoader(Context context) { super(context); } @Override public Object loadInBackground() { return someWorkToDo(); } } }
Komponen Threading yang Tidak Melampirkan ke Aktivitas/Fragmen
Melayani
Service
adalah komponen yang berguna untuk melakukan operasi yang lama (atau berpotensi lama) tanpa UI apa pun.
Service
berjalan di utas utama proses hostingnya; layanan tidak membuat utasnya sendiri dan tidak berjalan dalam proses terpisah kecuali jika Anda menentukan sebaliknya.
Contoh penggunaan:
public class ExampleService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { doSomeLongProccesingWork(); stopSelf(); return START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
Dengan Service
, Anda bertanggung jawab untuk menghentikannya ketika pekerjaannya selesai dengan memanggil metode stopSelf()
atau stopService()
.
Layanan Niat
Seperti Service
, IntentService
berjalan pada utas terpisah, dan berhenti sendiri secara otomatis setelah menyelesaikan pekerjaannya.
IntentService
biasanya digunakan untuk tugas-tugas singkat yang tidak perlu dilampirkan ke UI apa pun.
Contoh penggunaan:
public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }
Tujuh Pola Threading di Android
Kasus Penggunaan No. 1: Membuat permintaan melalui jaringan tanpa memerlukan respons dari server
Terkadang Anda mungkin ingin mengirim permintaan API ke server tanpa perlu khawatir tentang responsnya. Misalnya, Anda mungkin mengirim token pendaftaran push ke back-end aplikasi Anda.
Karena ini melibatkan pembuatan permintaan melalui jaringan, Anda harus melakukannya dari utas selain utas utama.
Opsi 1: AsyncTask atau pemuat
Anda dapat menggunakan AsyncTask
atau loader untuk melakukan panggilan, dan itu akan berfungsi.
Namun, AsyncTask
dan pemuat keduanya bergantung pada siklus hidup aktivitas. Ini berarti Anda harus menunggu panggilan dieksekusi dan mencoba mencegah pengguna meninggalkan aktivitas, atau berharap panggilan akan dijalankan sebelum aktivitas dimusnahkan.
Opsi 2: Layanan
Service
mungkin lebih cocok untuk kasus penggunaan ini karena tidak dilampirkan ke aktivitas apa pun. Oleh karena itu akan dapat melanjutkan panggilan jaringan bahkan setelah aktivitas dimusnahkan. Plus, karena respons dari server tidak diperlukan, layanan juga tidak akan membatasi di sini.
Namun, karena layanan akan mulai berjalan di thread UI, Anda masih perlu mengelola threading sendiri. Anda juga perlu memastikan bahwa layanan dihentikan setelah panggilan jaringan selesai.
Ini akan membutuhkan lebih banyak usaha daripada yang diperlukan untuk tindakan sederhana seperti itu.
Opsi 3: IntentService
Ini, menurut saya, akan menjadi pilihan terbaik.
Karena IntentService
tidak dilampirkan ke aktivitas apa pun dan berjalan pada utas non-UI, itu melayani kebutuhan kita dengan sempurna di sini. Selain itu, IntentService
berhenti sendiri secara otomatis, jadi tidak perlu mengelolanya secara manual.
Gunakan Kasus No. 2: Melakukan panggilan jaringan, dan mendapatkan respons dari server
Kasus penggunaan ini mungkin sedikit lebih umum. Misalnya, Anda mungkin ingin memanggil API di back-end dan menggunakan responsnya untuk mengisi bidang di layar.
Opsi 1: Layanan atau IntentService
Meskipun Service
atau IntentService
berjalan dengan baik untuk kasus penggunaan sebelumnya, menggunakannya di sini bukanlah ide yang baik. Mencoba mengeluarkan data dari Service
atau IntentService
ke dalam utas UI utama akan membuat segalanya menjadi sangat kompleks.
Opsi 2: AsyncTask atau pemuat
Pada awalnya, AsyncTask
atau loader tampaknya menjadi solusi yang jelas di sini. Mereka mudah digunakan—sederhana dan lugas.
Namun, saat menggunakan AsyncTask
atau loader, Anda akan melihat bahwa ada kebutuhan untuk menulis beberapa kode boilerplate. Selain itu, penanganan kesalahan menjadi tugas utama dengan komponen ini. Bahkan dengan panggilan jaringan yang sederhana, Anda perlu menyadari kemungkinan pengecualian, menangkapnya, dan bertindak sesuai dengan itu. Ini memaksa kami untuk membungkus respons kami di kelas khusus yang berisi data, dengan kemungkinan informasi kesalahan, dan sebuah tanda menunjukkan apakah tindakan itu berhasil atau tidak.
Itu cukup banyak pekerjaan yang harus dilakukan untuk setiap panggilan. Untungnya, sekarang ada solusi yang jauh lebih baik dan lebih sederhana yang tersedia: RxJava.
Opsi 3: RxJava
Anda mungkin pernah mendengar tentang RxJava, perpustakaan yang dikembangkan oleh Netflix. Ini hampir ajaib di Jawa.
RxAndroid memungkinkan Anda menggunakan RxJava di Android, dan membuat menangani tugas asinkron menjadi mudah. Anda dapat mempelajari lebih lanjut tentang RxJava di Android di sini.
RxJava menyediakan dua komponen: Observer
dan Subscriber
.
Pengamat adalah komponen yang mengandung beberapa tindakan. Itu melakukan tindakan itu dan mengembalikan hasilnya jika berhasil atau kesalahan jika gagal.
Pelanggan , di sisi lain, adalah komponen yang dapat menerima hasil (atau kesalahan) dari yang dapat diamati, dengan berlangganan.
Dengan RxJava, pertama-tama Anda membuat yang dapat diamati:
Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })
Setelah observable dibuat, Anda dapat berlangganan.
Dengan perpustakaan RxAndroid, Anda dapat mengontrol utas di mana Anda ingin menjalankan tindakan di yang dapat diamati, dan utas di mana Anda ingin mendapatkan respons (yaitu, hasil atau kesalahan).
Anda menghubungkan yang dapat diamati dengan dua fungsi ini:
.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()
Penjadwal adalah komponen yang menjalankan tindakan di utas tertentu. AndroidSchedulers.mainThread()
adalah penjadwal yang terkait dengan utas utama.
Mengingat bahwa panggilan API kami adalah mRestApi.getData()
dan mengembalikan objek Data
, panggilan dasar dapat terlihat seperti ini:
Observable.create((ObservableOnSubscribe<Data>) e -> { try { Data data = mRestApi.getData(); e.onNext(data); } catch (Exception ex) { e.onError(ex); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(match -> Log.i(“rest api, "success"), throwable -> Log.e(“rest api, "error: %s" + throwable.getMessage()));
Tanpa membahas manfaat lain dari menggunakan RxJava, Anda sudah dapat melihat bagaimana RxJava memungkinkan kita untuk menulis kode yang lebih matang dengan mengabstraksikan kompleksitas threading.
Gunakan Kasus No. 3: Merantai panggilan jaringan
Untuk panggilan jaringan yang perlu dilakukan secara berurutan (yaitu, di mana setiap operasi bergantung pada respons/hasil dari operasi sebelumnya), Anda harus sangat berhati-hati dalam membuat kode spaghetti.
Misalnya, Anda mungkin harus melakukan panggilan API dengan token yang perlu Anda ambil terlebih dahulu melalui panggilan API lain.
Opsi 1: AsyncTask atau pemuat
Menggunakan AsyncTask
atau loader hampir pasti akan mengarah ke kode spageti. Fungsionalitas keseluruhan akan sulit untuk diperbaiki dan akan membutuhkan sejumlah besar kode boilerplate redundan di seluruh proyek Anda.
Opsi 2: RxJava menggunakan flatMap
Di RxJava, operator flatMap
mengambil nilai yang dipancarkan dari sumber yang dapat diamati dan mengembalikan yang dapat diamati lainnya. Anda dapat membuat yang dapat diamati, dan kemudian membuat yang dapat diamati lainnya menggunakan nilai yang dipancarkan dari yang pertama, yang pada dasarnya akan mengikatnya.
Langkah 1. Buat observable yang mengambil token:
public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }
Langkah 2. Buat observable yang mendapatkan data menggunakan token:
public Observable<String> getDataObservable(String token) { return Observable.create(subscriber -> { try { Data data = mRestApi.getData(token); subscriber.onNext(data); } catch (IOException e) { subscriber.onError(e); } }); }
Langkah 3. Rantai dua yang dapat diamati bersama-sama dan berlangganan:
getTokenObservable() .flatMap(new Function<String, Observable<Data>>() { @Override public Observable<Data> apply(String token) throws Exception { return getDataObservable(token); } }) .subscribe(data -> { doSomethingWithData(data) }, error -> handleError(e));
Perhatikan bahwa penggunaan pendekatan ini tidak terbatas pada panggilan jaringan; itu dapat bekerja dengan serangkaian tindakan apa pun yang perlu dijalankan secara berurutan tetapi pada utas yang terpisah.
Semua kasus penggunaan di atas cukup sederhana. Perpindahan antar utas hanya terjadi setelah masing-masing menyelesaikan tugasnya. Skenario yang lebih maju—misalnya, di mana dua atau lebih utas perlu berkomunikasi secara aktif satu sama lain—dapat didukung oleh pendekatan ini juga.
Gunakan No. Kasus 4: Berkomunikasi dengan utas UI dari utas lain
Pertimbangkan skenario di mana Anda ingin mengunggah file dan memperbarui antarmuka pengguna setelah selesai.
Karena mengunggah file bisa memakan waktu lama, pengguna tidak perlu menunggu. Anda dapat menggunakan layanan, dan mungkin IntentService
, untuk mengimplementasikan fungsionalitas di sini.
Namun, dalam kasus ini, tantangan yang lebih besar adalah kemampuan untuk memanggil metode pada utas UI setelah pengunggahan file (yang dilakukan di utas terpisah) selesai.
Opsi 1: RxJava di dalam layanan
RxJava, baik sendiri atau di dalam IntentService
, mungkin tidak ideal. Anda perlu menggunakan mekanisme berbasis panggilan balik saat berlangganan Observable
, dan IntentService
dibuat untuk melakukan panggilan sinkron sederhana, bukan panggilan balik.

Di sisi lain, dengan Service
, Anda harus menghentikan layanan secara manual, yang membutuhkan lebih banyak pekerjaan.
Opsi 2: Penerima Siaran
Android menyediakan komponen ini, yang dapat mendengarkan peristiwa global (misalnya, peristiwa baterai, peristiwa jaringan, dll.) serta peristiwa khusus. Anda dapat menggunakan komponen ini untuk membuat peristiwa khusus yang dipicu saat pengunggahan selesai.
Untuk melakukannya, Anda perlu membuat kelas khusus yang memperluas BroadcastReceiver
, mendaftarkannya dalam manifes, dan menggunakan Intent
dan IntentFilter
untuk membuat peristiwa khusus. Untuk memicu acara, Anda memerlukan metode sendBroadcast
.
Tampak:
<receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>
Penerima:
public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }
Pengirim:
Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);
Pendekatan ini adalah pilihan yang layak. Tetapi seperti yang Anda perhatikan, ini melibatkan beberapa pekerjaan, dan terlalu banyak siaran dapat memperlambat segalanya.
Opsi 3: Menggunakan Handler
Handler
adalah komponen yang dapat dilampirkan ke utas dan kemudian dibuat untuk melakukan beberapa tindakan pada utas itu melalui pesan sederhana atau tugas Runnable
. Ia bekerja bersama dengan komponen lain, Looper
, yang bertanggung jawab atas pemrosesan pesan di utas tertentu.
Saat Handler
dibuat, ia bisa mendapatkan objek Looper
di konstruktor, yang menunjukkan thread mana yang dilampirkan handler. Jika Anda ingin menggunakan handler yang dilampirkan ke utas utama, Anda perlu menggunakan looper yang terkait dengan utas utama dengan memanggil Looper.getMainLooper()
.
Dalam hal ini, untuk memperbarui UI dari utas latar belakang, Anda dapat membuat pengendali yang dilampirkan ke utas UI, lalu memposting tindakan sebagai Runnable
:
Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });
Pendekatan ini jauh lebih baik daripada yang pertama, tetapi ada cara yang lebih sederhana untuk melakukan ini…
Opsi 3: Menggunakan EventBus
EventBus
, perpustakaan populer oleh GreenRobot, memungkinkan komponen untuk berkomunikasi dengan aman satu sama lain. Karena kasus penggunaan kami adalah kasus di mana kami hanya ingin memperbarui UI, ini bisa menjadi pilihan paling sederhana dan paling aman.
Langkah 1. Buat kelas acara. misalnya, UIEvent
.
Langkah 2. Berlangganan ke acara.
@Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {/* Do something */}; register and unregister eventbus : @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }
Langkah 3. Posting acara: EventBus.getDefault().post(new UIEvent());
Dengan parameter ThreadMode
dalam anotasi, Anda menentukan utas tempat Anda ingin berlangganan acara ini. Dalam contoh kami di sini, kami memilih utas utama, karena kami ingin penerima acara dapat memperbarui UI.
Anda dapat menyusun kelas UIEvent
Anda untuk memuat informasi tambahan seperlunya.
Dalam layanan:
class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }
Dalam aktivitas/fragmen:
@Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};
Menggunakan EventBus library
, berkomunikasi antar utas menjadi lebih sederhana.
Use Case No. 5: Komunikasi dua arah antar thread berdasarkan tindakan pengguna
Misalkan Anda sedang membuat pemutar media dan Anda ingin agar pemutar media dapat terus memutar musik bahkan saat layar aplikasi ditutup. Dalam skenario ini, Anda ingin UI dapat berkomunikasi dengan utas media (mis., putar, jeda, dan tindakan lainnya) dan juga ingin utas media memperbarui UI berdasarkan peristiwa tertentu (mis. kesalahan, status buffering , dll).
Contoh pemutar media lengkap berada di luar cakupan artikel ini. Namun, Anda dapat menemukan tutorial yang bagus di sini dan di sini.
Opsi 1: Menggunakan EventBus
Anda bisa menggunakan EventBus
di sini. Namun, biasanya tidak aman untuk memposting acara dari utas UI dan menerimanya dalam layanan. Ini karena Anda tidak memiliki cara untuk mengetahui apakah layanan sedang berjalan ketika Anda telah mengirim pesan.
Opsi 2: Menggunakan Layanan Terikat
BoundService
adalah Service
yang terikat pada aktivitas/fragmen. Ini berarti bahwa aktivitas/fragmen selalu tahu apakah layanan sedang berjalan atau tidak dan, selain itu, ia mendapatkan akses ke metode publik layanan.
Untuk mengimplementasikannya, Anda perlu membuat Binder
khusus di dalam layanan dan membuat metode yang mengembalikan layanan.
public class MediaService extends Service { private final IBinder mBinder = new MediaBinder(); public class MediaBinder extends Binder { MediaService getService() { // Return this instance of LocalService so clients can call public methods return MediaService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }
Untuk mengikat aktivitas ke layanan, Anda perlu mengimplementasikan ServiceConnection
, yang merupakan kelas yang memantau status layanan, dan menggunakan metode bindService
untuk membuat pengikatan:
// in the activity MediaService mService; // flag indicates the bound status boolean mBound; @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, MediaService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { MediaBinder binder = (MediaBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } };
Anda dapat menemukan contoh implementasi lengkap di sini.
Untuk berkomunikasi dengan layanan saat pengguna mengetuk tombol Putar atau Jeda, Anda dapat mengikat ke layanan dan kemudian memanggil metode publik yang relevan pada layanan.
Ketika ada peristiwa media dan Anda ingin mengomunikasikannya kembali ke aktivitas/fragmen, Anda dapat menggunakan salah satu teknik sebelumnya (mis., BroadcastReceiver
, Handler
, atau EventBus
).
Gunakan Kasus No. 6: Menjalankan tindakan secara paralel dan mendapatkan hasil
Katakanlah Anda sedang membangun aplikasi wisata dan Anda ingin menampilkan atraksi pada peta yang diambil dari berbagai sumber (penyedia data yang berbeda). Karena tidak semua sumber dapat diandalkan, Anda mungkin ingin mengabaikan sumber yang gagal dan tetap melanjutkan rendering peta.
Untuk memparalelkan proses, setiap panggilan API harus dilakukan di utas yang berbeda.
Opsi 1: Menggunakan RxJava
Di RxJava, Anda dapat menggabungkan beberapa yang dapat diamati menjadi satu menggunakan operator merge()
atau concat()
. Anda kemudian dapat berlangganan pada "gabungan" yang dapat diamati dan menunggu semua hasil.
Pendekatan ini, bagaimanapun, tidak akan bekerja seperti yang diharapkan. Jika satu panggilan API gagal, gabungan observable akan melaporkan kegagalan keseluruhan.
Opsi 2: Menggunakan komponen Java asli
ExecutorService
di Java membuat sejumlah utas tetap (dapat dikonfigurasi) dan menjalankan tugas pada utas tersebut secara bersamaan. Layanan mengembalikan objek Future
yang pada akhirnya mengembalikan semua hasil melalui metode invokeAll()
.
Setiap tugas yang Anda kirim ke ExecutorService
harus dimuat dalam antarmuka Callable
, yang merupakan antarmuka untuk membuat tugas yang dapat menimbulkan pengecualian.
Setelah Anda mendapatkan hasil dari invokeAll()
, Anda dapat memeriksa setiap hasil dan melanjutkannya.
Katakanlah, misalnya, Anda memiliki tiga jenis daya tarik yang datang dari tiga titik akhir yang berbeda dan Anda ingin membuat tiga panggilan paralel:
ExecutorService pool = Executors.newFixedThreadPool(3); List<Callable<Object>> tasks = new ArrayList<>(); tasks.add(new Callable<Object>() { @Override public Integer call() throws Exception { return mRest.getAttractionType1(); } }); // ... try { List<Future<Object>> results = pool.invokeAll(tasks); for (Future result : results) { try { Object response = result.get(); if (response instance of AttractionType1... {} if (response instance of AttractionType2... {} ... } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { e.printStackTrace(); }
Dengan cara ini, Anda menjalankan semua tindakan secara paralel. Oleh karena itu, Anda dapat memeriksa kesalahan dalam setiap tindakan secara terpisah dan mengabaikan kegagalan individu sebagaimana mestinya.
Pendekatan ini lebih mudah daripada menggunakan RxJava. Ini lebih sederhana, lebih pendek, dan tidak menggagalkan semua tindakan karena satu pengecualian.
Gunakan Kasus #7: Query database SQLite lokal
Ketika berhadapan dengan database SQLite lokal, disarankan agar database digunakan dari utas latar belakang, karena panggilan database (terutama dengan database besar atau kueri kompleks) dapat memakan waktu, mengakibatkan pembekuan UI.
Saat menanyakan data SQLite, Anda mendapatkan objek Cursor
yang kemudian dapat digunakan untuk mengambil data aktual.
Cursor cursor = getData(); String name = cursor.getString(<colum_number>);
Opsi 1: Menggunakan RxJava
Anda dapat menggunakan RxJava dan mendapatkan data dari database, sama seperti kami mendapatkan data dari back-end kami:
public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }
Anda dapat menggunakan observable yang dikembalikan oleh getLocalDataObservable()
sebagai berikut:
getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));
Meskipun ini tentu saja merupakan pendekatan yang baik, ada satu yang bahkan lebih baik, karena ada komponen yang dibuat hanya untuk skenario ini.
Opsi 2: Menggunakan CursorLoader + ContentProvider
Android menyediakan CursorLoader
, komponen asli untuk memuat data SQLite dan mengelola utas yang sesuai. Ini adalah Loader
yang mengembalikan Cursor
, yang dapat kita gunakan untuk mendapatkan data dengan memanggil metode sederhana seperti getString()
, getLong()
, dll.
public class SimpleCursorLoader extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { public static final String TAG = SimpleCursorLoader.class.getSimpleName(); private static final int LOADER_ID = 0x01; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_cursor_loader); textView = (TextView) findViewById(R.id.text_view); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { return new CursorLoader(this, Uri.parse("content://com.github.browep.cursorloader.data") , new String[]{"col1"}, null, null, null); } public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { if (cursor != null && cursor.moveToFirst()) { String text = textView.getText().toString(); while (cursor.moveToNext()) { text += "<br />" + cursor.getString(1); cursor.moveToNext(); } textView.setText(Html.fromHtml(text) ); } } public void onLoaderReset(Loader<Cursor> cursorLoader) { } }
CursorLoader
bekerja dengan komponen ContentProvider
. Komponen ini menyediakan banyak fitur basis data waktu nyata (misalnya, pemberitahuan perubahan, pemicu, dll.) yang memungkinkan pengembang untuk menerapkan pengalaman pengguna yang lebih baik dengan lebih mudah.
Tidak ada Solusi Silver Bullet untuk Threading di Android
Android menyediakan banyak cara untuk menangani dan mengelola utas, tetapi tidak ada satu pun yang berhasil.
Memilih pendekatan threading yang tepat, tergantung pada kasus penggunaan Anda, dapat membuat semua perbedaan dalam membuat solusi keseluruhan mudah diterapkan dan dipahami. Komponen asli cocok untuk beberapa kasus, tetapi tidak untuk semua. Hal yang sama berlaku untuk solusi pihak ketiga yang mewah.
Saya harap Anda akan menemukan artikel ini berguna saat mengerjakan proyek Android Anda berikutnya. Bagikan kepada kami pengalaman Anda tentang threading di Android atau kasus penggunaan apa pun di mana solusi di atas bekerja dengan baik—atau tidak, dalam hal ini—di komentar di bawah.