Android Thread (3): IntentService

Lanjutan dari tutorial pendahuluan tentang thread dan Asynctask.

Service cocok untuk task yang membutuhkan waktu lama, berjalan di background dan tidak membutuhkan akses ke user interface. Misalnya saat app mengirimkan data ke server, user dapat menekan tombol “send” dan kemudian melanjutkan menggunakan app untuk yang lain. Contoh yang lain adalah streaming musik di backround dan download.

Perbedaan utama antara service dengan Asynctask adalah Asynctask cocok untuk task yang berjalan di background yang terkait dengan user interface sedangkan service cocok untuk task background yang independen terhadap user interface. Untuk memberikan notifikasi ke user dari service, dapat digunakan toast atau notification bar. Perbedaan lain dengan AsyncTask adalah service mendapat prioritas lebih tinggi sehingga lebih kecil kemungkinannya untuk dimatikan, walaupun jika kehabisan memory service dapat dimatikan untuk kemudian direstart lagi.

Fungsi service ada dua: pertama meminta sistem menjadwalkan task di background yang akan terus berjalan sampai service berhenti. Fungsi kedua adalah menyediakan layanan pada app lain. Service bukan thread dan berada di thread utama (UI thread). Itu sebabnya kita tetap perlu membuat thread terpisah di service untuk task yang panjang karena tetap dapat menyebabkan error ANR.

Seperti halnya AsyncTask, Android menyediakan IntentService untuk memudahkan programmer menggunakan service. IntentService secara otomatis akan membuat thread baru (disebut worker thread) dan menangani antrian pemanggilan service (jika service dipanggil berkali-kali). Intinya programmer tidak perlu lagi memikirkan pengelolaan thread di dalam service.

Pada tutorial ini akan dibahas terlebih dulu tentang IntentService, baru kemudian dilanjutkan mengenai service yang lebih fleksibel tapi lebih kompleks.

Kita akan membuat app sederhana, yang akan menampilkan toast saat proses background dimulai dan toast saat proses background selesai.

Untuk menggunakan IntentService, extends kelas IntentService dan override method create() dan onHandleIntent(). Task yang berjalan di backround diletakkan di onHandleIntent().

Tetapi kita perlu membuat kelas khusus untuk menampilkan toast terlebih dulu. Toast tidak dapat langsung digunakan karena onHandleIntent berjalan di worker thread, bukan UI thread. Oleh karena itu perlu digunakan handler. Handler adalah objek yang dapat digunakan untuk berkomunikasi antar thread. Handler akan berada pada thread yang membuatnya. Karena service dicreate oleh UI thread, maka handler otomatis juga ada di UI thread. Jadi kita bisa menggunakan handler untuk berkomunikasi dari worker thread ke UI thread (pusing ya?).

public class DisplayToast implements Runnable {
   private final Context mContext;
   String mText;

   public DisplayToast(Context mContext, String text){
       this.mContext = mContext;
       mText = text;
   }

   public void run(){
       Toast.makeText(mContext, mText, Toast.LENGTH_SHORT).show();
   }
}

Buat project baru, buat kelas CobaService yang mengextend IntentService sebagai berikut :

public class CobaService extends IntentService {
    Handler mHandler; //untuk komunikasi dengan UI thread 


public CobaService() {
   super("cobaservice"); //untuk debug saja
   mHandler = new Handler();
}

@Override
protected void onHandleIntent(Intent intent) {

   //ambil data dari intent
   String pesan;
   Bundle extras = intent.getExtras();
   if(extras == null) {
       pesan= null;
   } else {
       pesan=extras.getString("PESAN");
   }
   
  mHandler.post(new DisplayToast(this, "Service mulai.... Pesan="+pesan));

  //proses background ada disini 
  for (int i = 0; i<50;i++) {
       try {
           Thread.sleep(50);  //simulasikan proses panjang
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
   mHandler.post(new DisplayToast(this, "Service selesai"));
}

}

Selanjutnya, service perlu didaftarkan di dalam manifest, dengan membuat elemen service di dalam elemen application.


<application
   ...
   <service android:name=".CobaService" android:exported="false"/>
</application>

android:exported diset false agar service ini hanya dapat dipanggil lokal oleh app. Ingat bahwa salah satu fungsi service adalah menyediakan layanan bagi app lain. Untuk faktor keamanan, kecuali jika memang ingin service dapat diakses app lain, selalu set exported dengan false.

Selanjutnya kita akan membuat tombol yang saat ditekan akan memanggil service ini: (jangan lupa set onClick button dengan method dibawah).

public void klikMulai(View v) {
   //intent digunakan sebagai paramater
   serviceIntent = new Intent(getApplicationContext(), CobaService.class);
   serviceIntent.putExtra("PESAN", "hello world"); //data yang dikirim
   getApplicationContext().startService(serviceIntent); //mulai jalankan 
}

Coba tekan tombol berulang-ulang, maka akan muncul secara berurutan pesan “service mulai,” “service selesai”, “service mulai” dan seterusnya. Intentservice akan menangani request secara berurutan, bukan paralel. Lalu coba tekan tombol dan langsung hide app, maka walaupun kita berada di app lain, tulisan “service selesai” tetap akan muncul.

Menampilkan Notifikasi

Pada contoh sebelumnya kita menggunakan toast untuk menampilkan pesan bahwa service sudah selesai. Kelemahan toast adalah hanya muncul sebentar. Untuk menjamin agar pesan dibaca user dapat digunakan notification (gambar bawah).

screen1

Untuk menambahkan notifikasi, gunakan NotificationCompat.Builder untuk membuat notifikasi dan NotifcacationManager untuk menampilkannya. Perlu digunakan TaskStackBuilder agar tombol back tetap berfungsi konsisten. Tambahkan kode berikut dibagian akhir dari onHandleIntent.

@Override
protected void onHandleIntent(Intent intent) {
...sama dengan sebelumnya

// sudah selesai, siapkan notif builder
NotificationCompat.Builder notifBuilder =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("My notification")
               .setContentText("Selesai! Tap untuk menampilkan app");

// explisit intent untuk memulai activity
Intent resultIntent = new Intent(this, MainActivity.class);
// Gunakan taskstack agar saat user menekan tombol back tetap konsisten
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(resultIntent);

PendingIntent resultPendingIntent =
       stackBuilder.getPendingIntent(
               0,
               PendingIntent.FLAG_UPDATE_CURRENT
       );
notifBuilder.setContentIntent(resultPendingIntent);
notifBuilder.setAutoCancel(true);//agar setelah ditap tutup otomatis

//notifmanager untuk menampilkan
NotificationManager mNotificationManager =
       (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 999 id untuk jika perlu modif nanti (bagusnya jadi konstanta).
mNotificationManager.notify(999, notifBuilder.build());

}

Menampilkan Notifikasi dengan Progressbar

Untuk proses yang panjang, dari sisi user interface akan lebih bagus jika user mendapat informasi kemajuan proses seperti gambar dibawah:

screen3

Code untuk membuat notification dengan progressbar adalah sebagai berikut. Ubah project sebelumnya dibagian loop.

....
//proses background ada disini
mHandler.post(new DisplayToast(this, "Service mulai.... Pesan="+pesan));

//init notification manager
NotificationManager mNotificationManager =
       (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);


//notifikasi dengan progressbar
NotificationCompat.Builder notifBuilderProgress =
       new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("Progress")
               .setContentText("Sedang proses");

//loop proses yang panjang
for (int i = 0; i<100;i++) {
   try {
       Thread.sleep(50);  //simulasi proses yang panjang
       //max diisi 100, false artinya kita tahu selesainya kapan
       notifBuilderProgress.setProgress(100, i, false);
       // tampilkan progress bar
       // 998 adalah id, bisa diganti dengan nilai lain
       mNotificationManager.notify(998, notifBuilderProgress.build());
   } catch (InterruptedException e) {
       e.printStackTrace();
   }
}

// update notif
// buang progress bar
notifBuilderProgress.setContentText("Proses selesai").setProgress(0,0,false);
mNotificationManager.notify(998, notifBuilderProgress.build());

mHandler.post(new DisplayToast(this, "Service selesai"));
...

Video contoh service dengan notification [youtube]: https://youtu.be/XE4HXXDNCpI

Menerima Status IntentService sudah selesai

Terkadang kita memerlukan informasi bahwa service sudah selesai. Masalahnya, tidak ada hubungan langsung antara service dengan activity yang memanggilnya. Untuk ini, dapat digunakan LocalBroadcastManager. Jadi service akan mengirimkan Intent, dan activity akan “menangkapnya”.

Menggunakan contoh tutorial IntentService sebelumnya, kita perlu ubah MainActivity agar dapat menangkap broadcast yang dikirim oleh service. Buat kelas yang meng-extends BroadcastReceiver. Method onReceive() berisi penanganan jika menerima broadcast dari service. Pada contoh ini, saat kita menerima broadcast, yang akan kita lakukan adalah menampilkan pesan yang dikirimkan service.

Kemudian pada onResume, kita mendaftarkan receiver dan meng-unregister-nya pada saat app di pause.

public class MainActivity extends AppCompatActivity {
   Intent serviceIntent;
   TextView tvHasil;

   //konstanta yang mendefinisikan aksi
   public static final String  ACTION_TERIMA = "aksi-cobaservice";

   //definisikan aksi jika mendapat broadcast
   private  class MyBroadcastReceiver extends BroadcastReceiver {
       //terima pesan, update UI
       //tapi jika app berada di background, tidak akan terupdate
       @Override
       public void onReceive(Context context, Intent intent) {
           // ambil extra data yang ada Intent
           String pesan = intent.getStringExtra("PESAN");
           //tulis pesan di textview
           tvHasil.setText(pesan);
       }
   }

   private MyBroadcastReceiver mReceiver;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     tvHasil = (TextView) findViewById(R.id.tvHasil);
     mReceiver = new MyBroadcastReceiver();
}

@Override
protected void onResume() {
   // onResume juga dipanggil saat app dimulai
   // daftar broadcastreciver untuk menerima intent.
   // tapi khusus untuk action ACTION_TERIMA yang sudah kita definisikan
   LocalBroadcastManager.getInstance(this).registerReceiver(
           mReceiver, new IntentFilter(ACTION_TERIMA));
   super.onResume();
}

@Override
protected void onPause() {
   // Unregister karena activity dipause.
   LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
   super.onPause();
}

   
   //...	
   //yang lain sama dengan sebelumnya   
   //...
   
}

Sekarang kita akan membuat service mengirimkan pesan. Kelas yang digunakan adalah LocalBroadcastManager. Menggunakan contoh sebelumnya, coba buka class CobaIntentService, tambahkan code berikut di onHandleIntent setelah proses berakhir:

@Override
protected void onHandleIntent(Intent intent) {
   //...	
   //sama dengan sebelumnya   
   //...

   //setelah proses selesai, kirim pesan ke MainActivity bahwa service sudah selesai
   
   //kirim pesan ke activity
   Intent localIntent = new Intent(MainActivity.ACTION_TERIMA);
   localIntent.putExtra("PESAN", "Sudah selesai!");
   //menggunakan LocalBroadCastManager 
   //agar pengiriman dijamin tidak bisa dilihat app lain
   LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}

Jalankan app, maka saat sudah selesai textview akan terupdate dengan pesan selesai. Tetapi kelemahannya jika app berada di background, maka textview tidak akan terupdate.

Source code untuk tutorial intentservice ini: https://github.com/yudiwbs/CobaIntentService

3 comments on “Android Thread (3): IntentService

  1. nuhun pisan pak sangat lengkap jarang2 nemu tutorial threading selengkap ini dalam bahasa indonesia hehe

  2. mau tanya kalo misalnya notifikasinya kita atur untuk muncul di tanggal tertentu apakah bisa?
    apakah ada tutorialnya?
    terima kasih

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.