12 Mart 2014 Çarşamba

Android Service'den Activity'e Deger Dondurme (Haberlesme)


Merhaba Arkadaşlar,

Android komponentleri arasında haberleşme genellikle intent'ler üzerinden gerçekleşir. Intent tipinin bol overload'lu putExtra() metoduyla, primitive tipler veya kendi oluşturduğunuz sınfların serilize edilebilir(parcelable, serializable) nesne örnekleri, iki android komponenti arasında taşınmasıyla haberleşme sağlanmış olur. Misal activity içerisinden service başlatırken intent ile service'e bir takım veriler aktarılabilir. Ne var ki aksi yönde bir iletişim bu denli basit değildir, zira service'ler genelde artalanda takılan ve önyüzle mümkün mertebe muhattap olmayan gizli kahramanlardır.


Android'de service'ten activity'ye doğru iletişim kurmak için iki yöntem mevcut:

  1. Bounded Service yapısı kurarak IBinder üzerinden standart java RPC(remote procedure call) mekanizmasını kullanmak. Bu yapı farklı process'ler arası, hızlı iletişime de destek sağladığı için uygulanması nispeten daha komplike.
  2. Bu blog girdisinde üzerinde duracağımız, daha basit olan diğer yöntem de Broadcast-Receiver mekanizması. 
Biz ikinci yöntemle servis tarafında, farklı thread üzerinden koşturulan asenkron işlemin sonucunu activity'e receiver ile aktaracağız. Sonuca üç adımda ulaşacağız:


  1. Service'te işlem bitince yayınlanacak olan broadcasti dinleyen receiver'ı yazmak ve sisteme register etmek
  2. İşlem yapacak olan servisi yazmak ve activity içinden start vermek
  3. Service tarafında işlem bitince ilgili broadcasti yayınlamak

Anlaşılabilirlik adına kodları sade tuttum, Acitivity sınıfı:
public class MainActivity extends Activity {
 
 public static final String FILTER = "just.a.filter";
 public static final String KEY = "key";
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  // service'ten yayınlanacak olan broadcast'i dinliyoruz
  LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
    // yanıt için geri bildirim
    Log.w("servisten gelen cevap", intent.getStringExtra(KEY));
    Toast.makeText(MainActivity.this, intent.getStringExtra(KEY), Toast.LENGTH_SHORT).show();
   }
  }, new IntentFilter(FILTER));
  
  // işlem yapacak olan service'i başlatıyoruz
  startService(new Intent(this, DownloadService.class));
 }
}
Service sınıfı:
public class DownloadService extends Service {

 @Override
 public IBinder onBind(Intent intent) {
  return null;
 }

 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  Log.d(" ", "onStartCommand");
  
  // farklı thread'e çıkarak asenkron işlem başlatılır
  new Thread(new Runnable() {
   @Override
   public void run() {
    try {
     String result = null;
     // uzun süreli işlem yapılır; dosya indirme, hesaplama vs.
     // gecikmeyi simule edelim
     Thread.sleep(3000);

     // temsili islem sonucu
     result = "tek gercek fenerbahce :]";

     // işlem bitti sonucu takipçi activity'e bildirelim
     LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(
       new Intent(MainActivity.FILTER).putExtra(MainActivity.KEY, result)
     );
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
  }).start();

  return super.onStartCommand(intent, flags, startId);
 }
}
Service tarafında hayali olarak bir web sayfasının html içeriğini indirip döndürüyormuş gibi işlem yaptım, siz burda farklı implementasyonlara gidebilirsiniz. Yine bir kereye mahsus activity ile iletişim kurdum oysa büyük boyutlu dosya indirme işlemi gibi bir senaryoda ilerleme bilgisi belirli aralıklarla activity'e gönderilip orada da progressbar gibi bir ui kontrolü ile kullanıcıya dönüş sağlanabilir.

# dipnot
Global Broadcast yayınlamak yerine LocalBroadCast yayınladım çünkü güvenlik, performans gibi avantajları var: http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html

## dipnot
Bu probleme çözüm olarak belki de daha pratik olarak, ilgili tüketici activity içine public static bir metod tanımlanıp, service'te işlem bitiminde çağrılabilir yahut herhangi bir yerde public static olarak tanımlanan bir callback tetiklenebilir fakat benim tavsiyem bu çözümlerden uzak durmanız zira bu çözümler kod akışını deyim yerideyse yaran, alabildiğine sert, acele çözümlerdir; kodun okunabilirliğini de, genişletilebilirliğini de, test edilebilirliğini de alır uzaaak diyarlara götürür.

Anlatımda oluşturduğum örnek  android projesine github üzinden buraya tıklayarak ulaşabilirsiniz.

Bu blog girdisinde service kullanımını bir adım öteye taşıyınca sık karşılaşılan bir probleme çözüm getirmeye çalıştım, umarım faydalı oluştur, bol yeşil robotlu günler.

Hiç yorum yok:

Yorum Gönder