Android Threading: Bilmeniz Gereken Her Şey
Yayınlanan: 2022-03-11Her Android geliştiricisi, bir noktada, uygulamalarında iş parçacıklarıyla uğraşmak zorundadır.
Android'de bir uygulama başlatıldığında, "ana" iş parçacığı olarak bilinen ilk yürütme iş parçacığını oluşturur. Ana iş parçacığı, olayları uygun kullanıcı arabirimi widget'larına göndermekten ve Android UI araç setindeki bileşenlerle iletişim kurmaktan sorumludur.
Uygulamanızı yanıt vermeye devam etmek için, bloke edilmesine neden olabilecek herhangi bir işlemi gerçekleştirmek için ana iş parçacığını kullanmaktan kaçınmak önemlidir.
Ağ işlemleri ve veritabanı çağrılarının yanı sıra belirli bileşenlerin yüklenmesi, ana iş parçacığında kaçınılması gereken yaygın işlem örnekleridir. Ana iş parçacığında çağrıldıklarında eşzamanlı olarak çağrılırlar; bu, işlem tamamlanana kadar UI'nin tamamen yanıtsız kalacağı anlamına gelir. Bu nedenle, genellikle ayrı iş parçacıklarında gerçekleştirilirler, bu sayede gerçekleştirilirken UI'nin engellenmesini önler (yani, UI'den eşzamansız olarak gerçekleştirilirler).
Android, iş parçacığı oluşturma ve yönetmenin birçok yolunu sunar ve iş parçacığı yönetimini çok daha keyifli hale getiren birçok üçüncü taraf kitaplığı vardır. Ancak, bu kadar çok farklı yaklaşımla, doğru olanı seçmek oldukça kafa karıştırıcı olabilir.
Bu makalede, Android geliştirmede iş parçacığı oluşturmanın gerekli hale geldiği bazı yaygın senaryolar ve bu senaryolara uygulanabilecek bazı basit çözümler ve daha fazlasını öğreneceksiniz.
Android'de iş parçacığı oluşturma
Android'de, tüm iş parçacığı bileşenlerini iki temel kategoriye ayırabilirsiniz:
- Bir aktiviteye/parçaya bağlı olan iş parçacıkları : Bu iş parçacıkları, aktivitenin/parçanın yaşam döngüsüne bağlıdır ve aktivite/parça yok edilir edilmez sonlandırılır.
- Herhangi bir etkinliğe/parçaya bağlı olmayan diziler: Bu diziler, oluşturuldukları etkinlik/parçanın (varsa) ömrü boyunca çalışmaya devam edebilir.
Bir Aktiviteye/Parçaya Eklenen Bileşenler
zaman uyumsuz görev
AsyncTask
, iş parçacığı için en temel Android bileşenidir. Kullanımı basittir ve temel senaryolar için iyi olabilir.
Örnek kullanım:
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 } } }
Ancak, etkinliğin/parçanın ömrünün ötesine geçmek için ertelenmiş görevinize ihtiyacınız varsa, AsyncTask
yetersiz kalır. Ekran döndürme kadar basit bir şeyin bile aktivitenin yok olmasına neden olabileceğini belirtmekte fayda var.
yükleyiciler
Yükleyiciler yukarıda bahsedilen problemin çözümüdür. Yükleyiciler, etkinlik yok edildiğinde otomatik olarak durabilir ve etkinlik yeniden oluşturulduktan sonra kendilerini yeniden başlatabilir.
Esas olarak iki tür yükleyici vardır: AsyncTaskLoader
ve CursorLoader
. Bu makalenin ilerleyen bölümlerinde CursorLoader
hakkında daha fazla bilgi edineceksiniz.
AsyncTaskLoader
, AsyncTask
benzer, ancak biraz daha karmaşıktır.
Örnek kullanım:
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(); } } }
Bir Aktiviteye/Fragmana Eklenmeyen İş Parçacığı Bileşenleri
Hizmet
Service
, herhangi bir kullanıcı arabirimi olmadan uzun (veya potansiyel olarak uzun) işlemleri gerçekleştirmek için yararlı olan bir bileşendir.
Service
, barındırma işleminin ana iş parçacığında çalışır; hizmet kendi iş parçacığını oluşturmaz ve siz aksini belirtmedikçe ayrı bir işlemde çalışmaz.
Örnek kullanım:
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; } }
Service
ile, çalışması tamamlandığında stopSelf()
veya stopService()
yöntemini çağırarak onu durdurmak sizin sorumluluğunuzdadır.
IntentService
Service
gibi, IntentService
ayrı bir iş parçacığı üzerinde çalışır ve işini tamamladıktan sonra kendini otomatik olarak durdurur.
IntentService
genellikle herhangi bir kullanıcı arayüzüne eklenmesi gerekmeyen kısa görevler için kullanılır.
Örnek kullanım:
public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }
Android'de Yedi Diş Açma Modeli
Kullanım Durumu No. 1: Sunucudan yanıt gerektirmeden ağ üzerinden istekte bulunma
Bazen, yanıtı hakkında endişelenmenize gerek kalmadan bir sunucuya bir API isteği göndermek isteyebilirsiniz. Örneğin, uygulamanızın arka ucuna bir anında iletme kayıt belirteci gönderiyor olabilirsiniz.
Bu, ağ üzerinden bir istek yapmayı içerdiğinden, bunu ana iş parçacığı dışındaki bir iş parçacığından yapmalısınız.
Seçenek 1: AsyncTask veya yükleyiciler
Arama yapmak için AsyncTask
veya yükleyicileri kullanabilirsiniz ve işe yarayacaktır.
Ancak, AsyncTask
ve yükleyicilerin her ikisi de etkinliğin yaşam döngüsüne bağlıdır. Bu, ya çağrının yürütülmesini beklemeniz ve kullanıcının etkinlikten ayrılmasını engellemeye çalışmanız ya da etkinlik yok edilmeden önce yürütüleceğini ummanız gerektiği anlamına gelir.
Seçenek 2: Servis
Service
, herhangi bir etkinliğe bağlı olmadığı için bu kullanım durumu için daha uygun olabilir. Bu nedenle, aktivite yok edildikten sonra bile ağ aramasına devam edebilecektir. Ayrıca, sunucudan yanıt gerekmediğinden burada da bir hizmet sınırlayıcı olmayacaktır.
Ancak, UI iş parçacığında bir hizmet çalışmaya başlayacağından, iş parçacığı oluşturmayı yine de kendiniz yönetmeniz gerekir. Şebeke araması tamamlandığında hizmetin durdurulduğundan da emin olmanız gerekir.
Bu, böyle basit bir eylem için gerekli olandan daha fazla çaba gerektirecektir.
Seçenek 3: IntentService
Bu, bence, en iyi seçenek olacaktır.
IntentService
herhangi bir etkinliğe bağlanmadığından ve UI olmayan bir iş parçacığında çalıştığından, buradaki ihtiyaçlarımıza mükemmel bir şekilde hizmet eder. Ayrıca, IntentService
kendini otomatik olarak durdurur, bu nedenle manuel olarak yönetmeye de gerek yoktur.
Örnek 2'yi kullanın: Bir ağ araması yapmak ve sunucudan yanıt almak
Bu kullanım durumu muhtemelen biraz daha yaygındır. Örneğin, arka uçta bir API'yi çağırmak ve ekrandaki alanları doldurmak için yanıtını kullanmak isteyebilirsiniz.
Seçenek 1: Hizmet veya IntentService
Bir Service
veya bir IntentService
önceki kullanım durumu için iyi sonuç vermiş olsa da, bunları burada kullanmak iyi bir fikir olmaz. Bir Service
veya bir IntentService
ana UI iş parçacığına veri almaya çalışmak, işleri çok karmaşık hale getirir.
Seçenek 2: AsyncTask veya yükleyiciler
İlk bakışta, AsyncTask
veya yükleyiciler burada bariz çözüm gibi görünebilir. Kullanımı kolaydır - basit ve anlaşılır.
Ancak, AsyncTask
veya yükleyicileri kullanırken, bazı ortak kod yazma ihtiyacı olduğunu fark edeceksiniz. Ayrıca, hata işleme, bu bileşenlerle büyük bir angarya haline gelir. Basit bir ağ aramasında bile olası istisnaların farkında olmanız, onları yakalamanız ve buna göre hareket etmeniz gerekir. Bu, yanıtımızı, olası hata bilgileriyle birlikte verileri içeren özel bir sınıfa sarmaya zorlar ve bir bayrak, eylemin başarılı olup olmadığını gösterir.
Bu, her bir arama için yapılacak oldukça fazla iş. Neyse ki, artık çok daha iyi ve daha basit bir çözüm mevcut: RxJava.
Seçenek 3: RxJava
Netflix tarafından geliştirilen kütüphane RxJava'yı duymuşsunuzdur. Java'da neredeyse sihir.
RxAndroid, Android'de RxJava'yı kullanmanıza izin verir ve eşzamansız görevlerle uğraşmayı çok kolaylaştırır. Android'de RxJava hakkında daha fazla bilgiyi buradan edinebilirsiniz.
RxJava iki bileşen sağlar: Observer
ve Subscriber
.
Bir gözlemci , bazı eylemleri içeren bir bileşendir. Bu eylemi gerçekleştirir ve başarılı olursa sonucu, başarısız olursa bir hata döndürür.
Abone ise gözlemlenebilir bir nesneye abone olarak sonucu (veya hatayı) alabilen bir bileşendir.
RxJava ile önce bir gözlemlenebilir yaratırsınız:
Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })
Gözlenebilir oluşturulduktan sonra, ona abone olabilirsiniz.
RxAndroid kitaplığı ile, gözlemlenebilirdeki eylemi gerçekleştirmek istediğiniz iş parçacığını ve yanıt almak istediğiniz iş parçacığını (yani sonuç veya hata) kontrol edebilirsiniz.
Bu iki işlevle gözlemlenebiliri zincirlersiniz:
.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()
Zamanlayıcılar, eylemi belirli bir iş parçacığında yürüten bileşenlerdir. AndroidSchedulers.mainThread()
, ana iş parçacığıyla ilişkili zamanlayıcıdır.
API çağrımızın mRestApi.getData()
olduğu ve bir Data
nesnesi döndürdüğü göz önüne alındığında, temel çağrı şöyle görünebilir:
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()));
RxJava kullanmanın diğer faydalarına bile girmeden, RxJava'nın iş parçacığı oluşturmanın karmaşıklığını ortadan kaldırarak daha olgun kodlar yazmamıza nasıl izin verdiğini zaten görebilirsiniz.
Örnek 3'ü kullanın: Şebeke çağrılarını zincirleme
Sırayla gerçekleştirilmesi gereken ağ aramaları için (yani, her işlemin bir önceki işlemin yanıtına/sonucuna bağlı olduğu durumlarda), spagetti kodu oluştururken özellikle dikkatli olmanız gerekir.
Örneğin, önce başka bir API çağrısı yoluyla getirmeniz gereken bir belirteçle bir API çağrısı yapmanız gerekebilir.
Seçenek 1: AsyncTask veya yükleyiciler
AsyncTask
veya yükleyicileri kullanmak neredeyse kesinlikle spagetti koduna yol açacaktır. Genel işlevselliği doğru bir şekilde elde etmek zor olacak ve projeniz boyunca çok büyük miktarda yedek ortak kod gerektirecektir.
Seçenek 2: flatMap kullanarak RxJava
RxJava'da flatMap
operatörü, gözlemlenebilir kaynaktan yayılan bir değer alır ve başka bir gözlemlenebilir değeri döndürür. Bir gözlemlenebilir oluşturabilir ve ardından ilkinden yayılan değeri kullanarak temelde onları zincirleyecek olan başka bir gözlemlenebilir oluşturabilirsiniz.
Adım 1. Belirteci getiren gözlemlenebiliri oluşturun:
public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }
Adım 2. Belirteci kullanarak verileri alan gözlemlenebiliri oluşturun:
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); } }); }
Adım 3. İki gözlemlenebiliri birbirine zincirleyin ve abone olun:
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));
Bu yaklaşımın kullanımının şebeke aramalarıyla sınırlı olmadığını unutmayın; bir sırayla, ancak ayrı iş parçacıkları üzerinde çalıştırılması gereken herhangi bir eylem kümesiyle çalışabilir.
Yukarıdaki kullanım durumlarının tümü oldukça basittir. İş parçacıkları arasında geçiş, yalnızca her biri görevini bitirdikten sonra oldu. Daha gelişmiş senaryolar (örneğin, iki veya daha fazla iş parçacığının birbiriyle etkin bir şekilde iletişim kurması gerektiği) bu yaklaşımla da desteklenebilir.
Vaka No. 4'ü kullanın: Başka bir iş parçacığından UI iş parçacığıyla iletişim kurun
Bir dosya yüklemek ve tamamlandıktan sonra kullanıcı arayüzünü güncellemek istediğiniz bir senaryo düşünün.
Bir dosyanın yüklenmesi uzun sürebileceğinden kullanıcıyı bekletmeye gerek yoktur. Buradaki işlevi uygulamak için bir hizmet ve muhtemelen IntentService
kullanabilirsiniz.
Ancak bu durumda, daha büyük zorluk, dosya yüklemesi (ayrı bir iş parçacığında gerçekleştirilen) tamamlandıktan sonra UI iş parçacığında bir yöntemi çağırabilmektir.
Seçenek 1: Hizmetin içinde RxJava
RxJava, tek başına veya bir IntentService
içinde ideal olmayabilir. Bir Observable
abone olurken geri arama tabanlı bir mekanizma kullanmanız gerekecek ve IntentService
geri aramalar için değil, basit eşzamanlı aramalar yapmak için oluşturulmuştur.

Öte yandan, bir Service
ile daha fazla çalışma gerektiren hizmeti manuel olarak durdurmanız gerekecektir.
Seçenek 2: Yayın Alıcısı
Android, küresel olayları (örneğin pil olayları, ağ olayları vb.) ve özel olayları dinleyebilen bu bileşeni sağlar. Yükleme tamamlandığında tetiklenen özel bir etkinlik oluşturmak için bu bileşeni kullanabilirsiniz.
Bunu yapmak için BroadcastReceiver
öğesini genişleten özel bir sınıf oluşturmanız, onu bildirimde kaydetmeniz ve özel olayı oluşturmak için Intent
ve IntentFilter
kullanmanız gerekir. Olayı tetiklemek için sendBroadcast
yöntemine ihtiyacınız olacak.
Belirgin:
<receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>
Alıcı:
public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }
Gönderen:
Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);
Bu yaklaşım uygun bir seçenektir. Ancak fark ettiğiniz gibi, biraz çalışma gerektiriyor ve çok fazla yayın işleri yavaşlatabilir.
Seçenek 3: İşleyiciyi Kullanma
Handler
, bir iş parçacığına eklenebilen ve daha sonra basit mesajlar veya Runnable
görevler aracılığıyla bu iş parçacığı üzerinde bazı eylemler gerçekleştirmesi için yapılan bir bileşendir. Belirli bir iş parçacığında mesaj işlemeden sorumlu olan başka bir bileşen olan Looper
ile birlikte çalışır.
Bir Handler
oluşturulduğunda, yapıcıda işleyicinin hangi iş parçacığına bağlı olduğunu gösteren bir Looper
nesnesi alabilir. Ana iş parçacığına bağlı bir işleyici kullanmak istiyorsanız, Looper.getMainLooper()
çağırarak ana iş parçacığıyla ilişkili ilmek yapıcıyı kullanmanız gerekir.
Bu durumda, UI'yi bir arka plan iş parçacığından güncellemek için UI iş parçacığına eklenmiş bir işleyici oluşturabilir ve ardından Runnable
olarak bir eylem gönderebilirsiniz:
Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });
Bu yaklaşım ilkinden çok daha iyi, ancak bunu yapmanın daha da basit bir yolu var…
Seçenek 3: EventBus'u Kullanma
GreenRobot'un popüler bir kütüphanesi olan EventBus
, bileşenlerin birbirleriyle güvenli bir şekilde iletişim kurmasını sağlar. Kullanım durumumuz yalnızca kullanıcı arayüzünü güncellemek istediğimiz bir durum olduğundan, bu en basit ve en güvenli seçim olabilir.
Adım 1. Bir olay sınıfı oluşturun. örneğin, UIEvent
.
Adım 2. Etkinliğe abone olun.
@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); }
Adım 3. Etkinliği gönderin: EventBus.getDefault().post(new UIEvent());
Açıklama kısmındaki ThreadMode
parametresi ile bu event için abone olmak istediğiniz thread'i belirtiyorsunuz. Buradaki örneğimizde, olayın alıcısının UI'yi güncelleyebilmesini isteyeceğimizden ana iş parçacığını seçiyoruz.
UIEvent
sınıfınızı, gerektiği şekilde ek bilgiler içerecek şekilde yapılandırabilirsiniz.
Serviste:
class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }
Etkinlik/parçada:
@Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};
EventBus library
kullanarak, iş parçacıkları arasında iletişim çok daha basit hale gelir.
Kullanım Durumu No. 5: Kullanıcı eylemlerine dayalı olarak iş parçacıkları arasında iki yönlü iletişim
Bir medya oynatıcı oluşturduğunuzu ve uygulama ekranı kapalıyken bile müzik çalmaya devam etmesini istediğinizi varsayalım. Bu senaryoda, Kullanıcı Arabiriminin medya ileti dizisiyle (örn. oynatma, duraklatma ve diğer eylemler) iletişim kurabilmesini ve ayrıca medya ileti dizisinin belirli olaylara (örn. hata, ara belleğe alma durumu) göre Kullanıcı Arabirimini güncellemesini isteyeceksiniz. , vb).
Tam bir medya oynatıcı örneği bu makalenin kapsamı dışındadır. Bununla birlikte, burada ve burada iyi öğreticiler bulabilirsiniz.
Seçenek 1: EventBus'u Kullanma
EventBus
burada kullanabilirsiniz. Ancak, UI iş parçacığından bir olay göndermek ve bunu bir hizmette almak genellikle güvenli değildir. Bunun nedeni, mesajı gönderdiğinizde hizmetin çalışıp çalışmadığını bilmenin hiçbir yolu olmamasıdır.
Seçenek 2: BoundService'i Kullanma
BoundService
, bir etkinliğe/parçaya bağlı bir Service
. Bu, faaliyetin/parçanın hizmetin çalışıp çalışmadığını her zaman bildiği ve ayrıca hizmetin genel yöntemlerine eriştiği anlamına gelir.
Bunu uygulamak için hizmet içinde özel bir Binder
oluşturmanız ve hizmeti döndüren bir yöntem oluşturmanız gerekir.
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; } }
Etkinliği hizmete bağlamak için hizmet durumunu izleyen sınıf olan ServiceConnection
öğesini uygulamanız ve bağlamayı yapmak için bindService
yöntemini kullanmanız gerekir:
// 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; } };
Tam bir uygulama örneğini burada bulabilirsiniz.
Kullanıcı Oynat veya Duraklat düğmesine dokunduğunda hizmetle iletişim kurmak için hizmete bağlanabilir ve ardından hizmetteki ilgili genel yöntemi çağırabilirsiniz.
Bir medya olayı olduğunda ve bunu aktiviteye/parçaya geri iletmek istediğinizde, önceki tekniklerden birini kullanabilirsiniz (örneğin, BroadcastReceiver
, Handler
veya EventBus
).
Örnek 6'yı kullanın: Eylemleri paralel olarak yürütme ve sonuç alma
Bir turist uygulaması oluşturduğunuzu ve birden fazla kaynaktan (farklı veri sağlayıcılar) alınan bir haritada turistik yerleri göstermek istediğinizi varsayalım. Tüm kaynaklar güvenilir olmayabileceğinden, başarısız olanları görmezden gelip yine de haritayı oluşturmaya devam etmek isteyebilirsiniz.
Süreci paralelleştirmek için her API çağrısı farklı bir iş parçacığında gerçekleşmelidir.
Seçenek 1: RxJava'yı Kullanma
RxJava'da, merge()
veya concat()
operatörlerini kullanarak birden çok gözlemlenebiliri bir tanede birleştirebilirsiniz. Daha sonra “birleştirilmiş” gözlemlenebilire abone olabilir ve tüm sonuçları bekleyebilirsiniz.
Ancak bu yaklaşım beklendiği gibi çalışmayacaktır. Bir API çağrısı başarısız olursa, birleştirilmiş gözlemlenebilir, genel bir başarısızlık bildirir.
Seçenek 2: Yerel Java bileşenlerini kullanma
Java'daki ExecutorService
, sabit (yapılandırılabilir) sayıda iş parçacığı oluşturur ve bunlar üzerinde eşzamanlı olarak görevleri yürütür. Hizmet, sonunda tüm sonuçları invokeAll()
yöntemi aracılığıyla döndüren bir Future
nesnesi döndürür.
ExecutorService
gönderdiğiniz her görev, bir istisna oluşturabilecek bir görev oluşturmaya yönelik bir arayüz olan Callable
interface'de yer almalıdır.
invokeAll()
'dan sonuçları aldıktan sonra, her sonucu kontrol edebilir ve buna göre ilerleyebilirsiniz.
Örneğin, üç farklı uç noktadan gelen üç çekim türünüz olduğunu ve üç paralel arama yapmak istediğinizi varsayalım:
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(); }
Bu şekilde, tüm eylemleri paralel olarak çalıştırıyorsunuz. Bu nedenle, her eylemdeki hataları ayrı ayrı kontrol edebilir ve uygun olduğu şekilde bireysel hataları göz ardı edebilirsiniz.
Bu yaklaşım, RxJava kullanmaktan daha kolaydır. Daha basit, daha kısadır ve bir istisna nedeniyle tüm eylemlerde başarısız olmaz.
Kullanım Örneği 7: Yerel SQLite veritabanını sorgulama
Yerel bir SQLite veritabanıyla uğraşırken, veritabanı çağrıları (özellikle büyük veritabanları veya karmaşık sorgular ile) zaman alıcı olabileceğinden ve kullanıcı arayüzünün donmasına neden olabileceğinden, veritabanının bir arka plan iş parçacığından kullanılması önerilir.
SQLite verilerini sorgularken, gerçek verileri almak için kullanılabilecek bir Cursor
nesnesi alırsınız.
Cursor cursor = getData(); String name = cursor.getString(<colum_number>);
Seçenek 1: RxJava'yı Kullanma
RxJava'yı kullanabilir ve tıpkı bizim arka uçtan veri aldığımız gibi, veri tabanından veri alabilirsiniz:
public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }
getLocalDataObservable()
tarafından döndürülen gözlemlenebiliri aşağıdaki gibi kullanabilirsiniz:
getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));
Bu kesinlikle iyi bir yaklaşım olsa da, tam da bu senaryo için oluşturulmuş bir bileşen olduğundan daha da iyi bir yaklaşım var.
Seçenek 2: CursorLoader + ContentProvider'ı Kullanma
Android, SQLite verilerini yüklemek ve ilgili iş parçacığını yönetmek için yerel bir bileşen olan CursorLoader
sağlar. Bu, getString()
, getLong()
vb. gibi basit yöntemleri çağırarak verileri almak için kullanabileceğimiz bir Cursor
döndüren bir Loader
.
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
, ContentProvider
bileşeniyle çalışır. Bu bileşen, geliştiricilerin daha iyi bir kullanıcı deneyimini çok daha kolay bir şekilde uygulamasını sağlayan çok sayıda gerçek zamanlı veritabanı özelliği (ör. değişiklik bildirimleri, tetikleyiciler vb.) sağlar.
Android'de Threading için Silver Bullet Çözümü yok
Android, konuları işlemek ve yönetmek için birçok yol sunar, ancak bunların hiçbiri gümüş kurşun değildir.
Kullanım durumunuza bağlı olarak doğru diş açma yaklaşımını seçmek, genel çözümün uygulanmasını ve anlaşılmasını kolaylaştırmada tüm farkı yaratabilir. Yerel bileşenler bazı durumlar için uygundur, ancak hepsi için geçerli değildir. Aynısı süslü üçüncü taraf çözümleri için de geçerlidir.
Bir sonraki Android projeniz üzerinde çalışırken bu makaleyi faydalı bulacağınızı umuyorum. Android'de iş parçacığı oluşturma deneyiminizi veya yukarıdaki çözümlerin iyi çalıştığı veya bu konuda işe yaramadığı herhangi bir kullanım durumunu aşağıdaki yorumlarda bizimle paylaşın.