Bu yazımız Android ile gerçekleştirdiğimiz yazılımlarımızda eş zamansız veya zaman uyumsuz (asenkron) olarak nitelendirdiğimiz iş parçacıklarının, ana programınız üzerinde nasıl ele alınması gerektiğine dair olacaktır.
Asenkron Programlamanın nedeni nedir?
Varsayılan olarak, bir uygulama kodu tek bir iş parçacığında (main thread) çalışır. Android bu iş parçasındaki tüm olayları bir sıra içinde toplar ve her bildiriyi sırayla yürütür. Uzun süren bir işlem gerçekleştirirseniz, ilgili işlem tamamlanıncaya kadar uygulama engellenir. Android platformunda Bir uygulama etkinliği , 5 saniye içinde tepki vermezse, Android sistemi Uygulama yanıt vermiyor (ANR) iletişim kutusu görüntüler. Kullanıcı deneyimleri bize Bu diyalogdan kullanıcı uygulamayı durdurmayı seçebileceğini göstermiştir.
İyi bir kullanıcı deneyimi sağlamak için, bir Android uygulamasındaki tüm potansiyel olarak yavaş çalışan işlemler zaman uyumsuz olarak çalışmalıdır! Bu, Java dili veya Android çerçevesinin eş zamanlılık yapılarıyla ancak düzenlenebilir. İşte tam burada asenkron programa devreye girmektedir. Başlatıldıktan sonra, ne kadar bir zaman süre sonucunu alacağını bilmediğimiz işlemler, her zaman ana iş parçasından bağımsız olarak ayrı bir iş parçacığı üzerinde yürütülmelidir. Buna asenkron programlama denmektedir. Potansiyel olarak yavaş işlemlerden kastımız, örneğin ağ işlemleri, dosyalama işlemleri, veritabanı erişim süreçleri veya uzun zaman alabilecek karmaşık hesaplamalar görülebilir.
Uzun süren bir işlem yapıyorsanız, çalışmakta olan işlem hakkında kullanıcıya geribildirim vermek her zaman iyi bir yoldur. Burada iki seçeneğiniz var. İlki bir Eylem çubuğu (ProgressBar) aracılığıyla ilerleme hakkında geri bildirimi sağlayabilirsiniz . Bir ProgressBar’ı görünür olarak ayarlar ve uzun süren bir işlem sırasında onu güncelleyebilirsiniz. Bir diğer yol ise Engellemeyen geribildirimleri tercih edebilirsiniz. Böylece kullanıcı uygulama ile etkileşime devam edebilir. Geribildirim geldiğinde uygulama arayüzü güncellenebilir. Burada genel tercihinizi ikincisinden yana yapmak, kullanıcı arayüzünün (UI) her zaman cevap verebilen (responsive) şekilde olmasına olanak sağlar.
Android ile basit bir asenkron iş parçacıcığını tanıyalım;
Asenkron iş parçalarını yürütebileceğimiz bir kaç farklı yöntem vardır. Bunların ilki java.lang kütüphanesi ile gelen ve Java dilinden de bileceğimiz. Runnable sınıfını kullanmaktır.
Aşağıdaki örneğimizde 10 kez tekrar eden 2 sn bekleme süreli bir iş Runnable sınıfı kullanılarak arkaplanda işletilen bir iş parçacığı şeklinde programlanmıştır.
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.sample.oruvar.runnablethreadsample.MainActivity"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:indeterminate="false" android:max="10" android:padding="4dip" > </ProgressBar> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" > </TextView> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="startTask" android:text="Görev Başlat" > </Button> </LinearLayout> </android.support.constraint.ConstraintLayout> |
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package com.sample.oruvar.runnablethreadsample; import android.os.SystemClock; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private ProgressBar progress; private TextView text; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progress = (ProgressBar) findViewById(R.id.progressBar1); text = (TextView) findViewById(R.id.textView1); } public void startTask(View view) { // Arkaplanda yürümesi için basit iş parçacığı tanımlanıyor. Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i <= 10; i++) { final int value = i; doSleepWork(); progress.post(new Runnable() { @Override public void run() { text.setText("Güncelleniyor"); progress.setProgress(value); } }); } } }; new Thread(runnable).start(); } // Uzun süre alabilecek bir temsili işlem // Arkaplanda çalışmasını istediğimiz metod private void doSleepWork() { SystemClock.sleep(2000); } } |
Runnable sınıfına ait Run() metodu içerisinde bildireceğiniz kod, ana iş parçacığından bağımsız olarak Thread sınıfının .start() metodu ile işletilecektir. Ancak karmaşık alt düzey yapısı, iş parçacıkları arasında oluşan veri transfer sorunları, çok fazla açılabilecek iş parçacıklarının performansa dayalı problemleri ve tabiki yaşam döngülerinin kontrol zorlukları gibi dezavantajları göz önüne alınmalıdır.
Yukarıdaki örnegimizin kaynak kodlarını RunnableThreadSample linkinden indirebilirsiniz.
AsyncTask Sınıfı
Eşzamansız bir görev, bir arka plan iş parçacığında çalışan ve sonuç UI iş parçacığında yayınlanan bir hesaplama ile tanımlanır. Bu bağlamda AsyncTask sınıfı UI iş parçacığının doğru ve kolay kullanımını sağlar. Bu sınıf, iş parçacıklarını ve / veya işleyicileri değiştirmek zorunda kalmadan arka plan işlemlerini gerçekleştirmenize ve sonuçları kullanıcı arabirimi dizininde yayınlamanıza izin verir.
AsyncTask, Thread sınıfına yardımcı sınıf olarak tasarlanmıştır ve genel bir iş parçacığı çerçevesi oluşturmaz. AsyncTask’ler ideal olarak kısa işlemler için kullanılmalıdır (en fazla birkaç saniye.) İş parçacıkları uzun süre çalışmaya devam etmeniz gerekiyorsa java.util.concurrent
paketinde sağlanan çeşitli API’leri kullanmanız şiddetle önerilir. Executor, ThreadPoolExecutor ve FutureTask gibi sınıfları inceleyebilirsiniz.
AsyncTask sınıf yapısında Params
, Progress
ve Result
adı verilen 3 genel tür ve onPreExecute()
, doInBackground()
, onProgressUpdate()
ve onPostExecute()
adındaki 4 metod ile birlikte gelmektedir.
AsyncTask sınıfını abstract bir sınıftır. Kullanabilmek için öncelikle kendi özel sınıfınıza extend deyimi ile bildirmelisiniz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class ArkaplanIslemOzelSinif extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Void doInBackground(Void... params) { return null; } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); } @Override protected void onProgressUpdate(Void... values) { super.onProgressUpdate(values); } @Override protected void onCancelled(Void result) { super.onCancelled(result); } } |
Bu metodlardan yalnızca doInBackground metodu bulunması zorunludur ve arka planda gerçekleştirilecek bütün işlemler bu metod içerisinde ele alınır. İsteğe bağlı olarak diğer metodları kullanabilirsiniz. Şimdi bu metodların kullanım amaçlarına bir göz atalım.
- onPreExecute – Görev yürütülmeden önce UI iş parçacığında çağrılan metottur. Arka plan işlemi başlamadan önce ekranda değiştirilmesi istenen veya kullanıcıyı bilgilendirme amaçlı çıkan(lütfen bekleyin, yükleniyor, ayarlar yapılıyor gibi) ve gerekli değişkenlerin değer atamasının yapıldığı yerdir.
- donInBackground – Arka planda yapılması istenen bütün işlemler bu metot içerisinde gerçekleştirilmelidir. Burada gerçekleşen işlemler kullanıcının uygulamayı kullanmasını engellemez. Burada ki işlemler sonucunda MainThread’e (ana akış) bir değer gönderilmesi gerekiyorsa “return” ile bu değişken onPostExecute() metoduna gönderilir.
- onPostExecute – doInBackground() üzerinde ele alınan arka plan işleminiz tamamlandıktan sonra sonuç bu metoda gelecektir. Burada yapılan işlemler ana akışı etkiler ancak özel bir hataya neden olmaz. Arka planda gerçekleşmiş bir işlemden gelen veri bu metot vasıtasıyla uygulama arayüzünde gösterime sunulabilir.
- onProgressUpdate – doInBackground metodu içinde bulunan işlemin durumu bu metot vasıtasıyla yayınlanır. Örneğin dosya indirime yüzdesi gibi bir bilgiyi UI tarafında bu metod vasıtasıyla işleyebilirsiniz.
- onCancelled – AsyncTask üzerinde bulunan işlem iptal edilirse bu metot tetiklenir.
AsyncTask<Tür1,Tür2,Tür3> şeklinde görmüş olduğunuz yapıyı tanıyalım. Burada Tür1 doInBackground() metoduna dışarıdan gelmesini isteyebileceğiniz parametrelere türüdür. Herhangi özel bir tür vermek istememeniz halinde (örnekte olduğu gibi) void kullanılmalıdır. Tür2 doInBackground() metodunun işleyişi sırasında onProgressUpdate() metoduna gönderilecek nesnenin türündür. Tür3 ise onPostExecute() metodu için doInBackground() metodunun return ile gönderilecek nesnenin türüdür. Buradaki türleri istediğiniz gibi değiştirebilir, bir değişken tipi (int, bool, long) olabileceği gibi bir sınıf türü de olabilir.
AsyncTask’lar, içerisinde çalıştıkları Activity herhangi bir sebepten dolayı sonlanmayabilir veya görev ölebilir (deadLine) Bu yüzden uygulama içerisinde kısa süreli işlemler ya da Activity yaşadığı sürece gerçekleşmesi beklenen işlemler için kullanılmaları tavsiye edilir.
Arka Planda çalışmasını istediğimiz çağırabilmek için yapısında tutan ve AyncTask sınıfından extend ile kalıttığımız sınıftan bir örnekleme yapılıp .execute() metodunu tetiklememiz yeterli olacaktır. Görev çağırımı uygulamanızın ana iş parçacığından yapılmalıdır. Örneğin Activity üzerinden olabilir.
1 2 3 |
new ArkaplanIslemOzelSinif().execute(parametre); |
Aşağıdaki örneğimizde 10 kez tekrar eden 2 sn bekleme süreli bir iş AsyncTask sınıfı kullanılarak arka planda asenkron olarak işletilen bir iş parçacığı şeklinde programlanmıştır. Amacımız AsyncTask sınıfının nasıl kullanılacağını göstermektedir.
Activity.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.sample.oruvar.asynctasksample.MainActivity"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:indeterminate="false" android:max="10" android:padding="4dip" > </ProgressBar> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" > </TextView> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="startTask" android:text="Görev Başlat" > </Button> </LinearLayout> </android.support.constraint.ConstraintLayout> |
MainActivity.Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
package com.sample.oruvar.asynctasksample; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends AppCompatActivity { Button btn; private ProgressBar progressBar; TextView txt; Integer count =1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progressBar = (ProgressBar) findViewById(R.id.progressBar1); progressBar.setMax(10); txt = (TextView) findViewById(R.id.textView1); btn = (Button) findViewById(R.id.button1); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { count =1; progressBar.setVisibility(View.VISIBLE); progressBar.setProgress(0); // MyTask arka plan olarak yürütülüyor. new MyTask().execute(10); } }); } class MyTask extends AsyncTask<Integer, Integer, String> { @Override protected String doInBackground(Integer... params) { for (; count <= params[0]; count++) { try { Thread.sleep(1000); publishProgress(count); } catch (InterruptedException e) { e.printStackTrace(); } } return "Görev Tamamlandı."; } @Override protected void onPostExecute(String result) { progressBar.setVisibility(View.GONE); txt.setText(result); btn.setText("Tekrar Başlat"); } @Override protected void onPreExecute() { txt.setText("Görev Başlatılıyor..."); } @Override protected void onProgressUpdate(Integer... values) { txt.setText("Görev Çalışıyor..."+ values[0]); progressBar.setProgress(values[0]); } } } |
AsynTask kullanımında, parametrelere vereceğiniz türlere ve metodları görevlerine göre kullanmaya dikkat ediniz.
Uygulamayı test ettiğinizde AsyncTask sınıfını arka planda işletilirken metodları vasıtasıyla UI tarafına mesajlarını gönderdiğini görebilirsiniz.
Yukarıdaki örnek uygulamanın kaynak kodlarını AsyncTaskSample linkinden indirebilirsiniz.