Mengikat 'Activity' ke Service

Sejauh ini, kita sudah tahu bagaimana service dibuat, dipanggil, dan diakhiri ketika sudah menyelesaikan tugasnya. Semua service yang sudah kita pelajari adalah gampang dan sederhana, baik service yang dimulai dengan suatu counter maupun increment dengan interval regular atau service yang mendownload sekumpulan file-file tertentu dari internet. Tetapi, service yang sesungguhnya kita lihat di dunia nyata biasanya jauh lebih canggih, dan perlu untuk melewatkan data sehingga bisa melakukan pekerjaannya dengan benar.

Pada latihan-latihan sebelumnya yang mendownload file-file dengan menggunakan service, andaikan kita sekarang menginginkan bahwa 'activity' yang memanggil bisa menentukan file-file apa yang di-download, sebagai ganti hardcode di dalam service, berikut di bawah ini-lah yang harus dilakukan.

Pertama, dalam 'activity' yang memanggil, kita buat object 'Intent', dengan menentukan nama service-nya:
public void startService(View view) {
Intent intent = new Intent(getBaseContext(), MyService.class);
}
Kemudian kita membuat array object URL dan menugaskannya ke object 'Intent' melalui method 'putExtra()'. Terakhir, kita me-start service dengan menggunakan object 'Intent':
public void startService(View view) {
Intent intent = new Intent(getBaseContext(), MyService.class);
try {
URL[] urls = new URL[]{
new URL("https://phpisus.blogspot.com/somefiles.pdf"),
new URL("https://beritati.blogspot.com/somefiles.pdf"),
new URL("https://acollectionofjokes.blogspot.com/somefiles.pdf"),
new URL("https://diansano.blogspot.com/somefiles.pdf")
};
intent.putExtra("URLs", urls);
} catch (MalformedURLException e) {
e.printStackTrace();
}
startService(i);
}
Harap dicatat bahwa array URL ditugaskan ke object 'Intent' sebagai array object.

Di ujung service, kita perlu mengekstrak data yang dilewatkan melalui object 'Intent' di dalam method 'onStartCommand()':
public int onStartCommand(Intent intent, int flags, int startId) {
/*kita ingin agar service ini terus berjalan hingga
di-stop secara eksplisit, sehingga akan mengembalikan/return sticky */
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
Object[] objUrls = (Object[]) intent.getExtras().get("URLs");
URL[] urls = new URL[objUrls.length];
for (int i=0; iurls[i] = (URL) objUrls[i];
}
new DoBackgroundTask().execute(urls);
return START_STICKY;
}

Kode tersebut pertama mengekstrak data dengan menggunakan method 'getExtras()' untuk mengembalikan object 'Bundle'. Kemudian menggunakan method 'get()' untuk mengekstrak array URL sebagai array Object. Karena di Java kita tidak bisa secara langsung mengubah satu jenis array ke jenis lainnya, kita harus membuat loop dan mengubah masing-masing anggotanya secara individual. Terakhir, kita mengeksekusi tugas di background dengan melewatkan array URL ke dalam method 'execute()'.

Ini adalah salah satu cara dimana 'activity' kita bisa melewatkan nilai/data ke suatu service. Seperti yang kita ketahui, bila kita memiliki data yang relatif kompleks untuk dilewatkan ke service, kita harus melakukan beberapa pekerjaan tambahan untuk memastikan bahwa data dilewatkan dengan benar. Cara yang lebih baik untuk melewatkan data adalah dengan mengikat 'activity' secara langsung ke service sehingga 'activity' bisa memanggil method-method dan variabel-variabelnya pada service secara langsung.

Berikut adalah latihan bagaimana mengikat 'activity' ke service.

1. Masih menggunakan project yang sama dengan latihan sebelumnya, kita modifikasi dan tambahkan ke file "MyService.java" (harap dicatat bahwa kita sedang memodifikasi method 'onStartCommand()' yang sudah ada), seperti berikut:
package com.example.services;

import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;

public class MyService extends Service {
int counter = 0;
URL[] urls;
static final int UPDATE_INTERVAL = 1000;
private Timer timer = new Timer();
private final IBinder binder = new MyBinder();

public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}

@Override
public IBinder onBind(Intent arg0) {
return binder;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/*kita ingin agar service ini terus berjalan hingga
di-stop secara eksplisit, sehingga akan mengembalikan/return sticky */
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
new DoBackgroundTask().execute(urls);
return START_STICKY;
}

private void doSomethingRepeatedly() {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Log.d("MyService", String.valueOf(++counter));
}
}, 0, UPDATE_INTERVAL);
}

private int DownloadFile(URL url) {
try {
/*men-simulasi beberapa saat untuk download file*/
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*mengembalikan angka sembarang yang menyejikam ukuran file yang di-download*/
return 100;
}

private class DoBackgroundTask extends AsyncTask {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalBytesDownloaded = 0;
for (int i = 0; i < count; i++) {
totalBytesDownloaded += DownloadFile(urls[i]);
/*menghitung persentase yang sudah di-download dan
menunjukan progressnya **/
publishProgress((int) (((i+1) / (float) count) * 100));
}
return totalBytesDownloaded;
}

protected void onProgressUpdate(Integer... progress) {
Log.d("Downloading files", String.valueOf(progress[0]) + "% downloaded");
Toast.makeText(getBaseContext(), String.valueOf(progress[0]) + "% downloaded",
Toast.LENGTH_LONG).show();
}

protected void onPostExecute(Long result) {
Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes",
Toast.LENGTH_LONG).show();
stopSelf();
}
}

@Override
public void onDestroy() {
super.onDestroy();
if (timer != null) {
timer.cancel();
}
Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();
}
}
2. Di file "MainActivity.java", modifikasi dan tambahkan kode berikut (perhatikan perubahan pada method 'startService()' yang sudah ada):
package com.example.services;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

import java.net.MalformedURLException;
import java.net.URL;

public class MainActivity extends AppCompatActivity {

IntentFilter intentFilter;
MyService serviceBinder;
Intent i;

private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
/*dipanggil ketika koneksi dibuat*/
serviceBinder = ((MyService.MyBinder)service).getService();
try {
URL[] urls = new URL[]{
new URL("https://phpisus.blogspot.com/somefiles.pdf"),
new URL("https://beritati.blogspot.com/somefiles.pdf"),
new URL("https://acollectionofjokes.blogspot.com/somefiles.pdf"),
new URL("https://diansano.blogspot.com/somefiles.pdf")
};
/*menugaskan url-url ke service melalui object serviceBinder*/
serviceBinder.urls = urls;
} catch (MalformedURLException e) {
e.printStackTrace();
}
startService(i);
}

public void onServiceDisconnected(ComponentName componentName) {
/*dipanggil bila service disconnect*/
serviceBinder = null;
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public void onResume() {
super.onResume();

/*intent untuk memfilter file yang di-download intent*/
intentFilter = new IntentFilter();
intentFilter.addAction("FILE_DOWNLOADED_ACTION");

/*me-register penerima*/
registerReceiver(intentReceiver, intentFilter);
}

public void onPause() {
super.onPause();
/*unregister penerima*/
unregisterReceiver(intentReceiver);
}

public void startService(View view) {
/*startService(new Intent(getBaseContext(), MyIntentService.class));
* atau
* startService(new Intent("com.example.MyIntentService"));*/
i = new Intent(MainActivity.this, MyService.class);
bindService(i, connection, Context.BIND_AUTO_CREATE);
}

public void stopService(View view) {
stopService(new Intent(getBaseContext(), MyService.class));
}

private BroadcastReceiver intentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(getBaseContext(), "File downloaded!", Toast.LENGTH_LONG).show();
}
};
}

Kemudian jalankan di emulator Android Studio, klik tombol 'Start Service', service akan berjalan normal seperti biasa.


Penjelasan:

Untuk mengikat 'activity' ke service, pertamakali kita harus mendeklarasikan inner class di dalam service kita yang men-'extends' class 'Binder':
public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}
Di dalam classs ini kita mengimplementasikan method 'getService()', yang mengembalikan instans service. Kemudian kita membuat instans class 'MyBinder':
private final IBinder binder = new MyBinder();
Kita juga memodifikasi method 'onBind()' untuk mengembalikan instans 'MyBinder':
@Override
public IBinder onBind(Intent arg0) {
return binder;
}
Di dalam method 'onStartCommand()', kemudian kita memanggil method 'execute()' dengan menggunakan array urls yang sudah kita deklarasikan sebagai variable public di dalam service kita:
public class MyService extends Service {
int counter = 0;
URL[] urls;
...

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/*kita ingin agar service ini terus berjalan hingga
di-stop secara eksplisit, sehingga akan mengembalikan/return sticky */
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
new DoBackgroundTask().execute(urls);
return START_STICKY;
}
Array URL tersebut bisa di-set secara langsung dari 'activity' yang kita lakukan selanjutnya. Di dalam file "MinActivity.java", kita pertama mendeklarasikan instans dari service kita dab suatu object 'Intent':
MyService serviceBinder;
Intent i;

Object 'serviceBinder' akan digunakan sebagai referensi ke service yang kita akses secara langsung.

Kemudian kita membuat instans class 'ServiceConnection' sehingga kita bisa memonitor status service:
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
/*dipanggil ketika koneksi dibuat*/
serviceBinder = ((MyService.MyBinder)service).getService();
try {
URL[] urls = new URL[]{
new URL("https://phpisus.blogspot.com/somefiles.pdf"),
new URL("https://beritati.blogspot.com/somefiles.pdf"),
new URL("https://acollectionofjokes.blogspot.com/somefiles.pdf"),
new URL("https://diansano.blogspot.com/somefiles.pdf")
};
/*menugaskan url-url ke service melalui object serviceBinder*/
serviceBinder.urls = urls;
} catch (MalformedURLException e) {
e.printStackTrace();
}
startService(i);
}

public void onServiceDisconnected(ComponentName componentName) {
/*dipanggil bila service disconnect*/
serviceBinder = null;
}
};
Kita perlu mengimplementaskan dua method: 'onServiceConnected()' dan 'onServiceDisconnected()'. Method 'onServiceConnected()' dipanggil ketika 'activity' dikoneksikan ke service; method 'onServiceDisconnected()' dipanggil ketika service di-diskoneksi dari 'activity'.

Di dalam method 'onServiceConnected()', ketika 'activity' terkoneksi ke 'service', kita memperoleh suatu instans dari service dengan menggunakan method 'getService()' dari argumen 'service' dan kemudian menugaskannya ke object 'serviceBinder'. Object 'serviceBinder' adalah referensi ke service, dan semua variabel dan methodnya di dalam service bisa diakses melalui object ini. Disini, kita membuat array URL dan kemudian secara langsung menugaskannya ke variabel puublic di dalam service tersebut:
URL[] urls = new URL[]{
new URL("https://phpisus.blogspot.com/somefiles.pdf"),
new URL("https://beritati.blogspot.com/somefiles.pdf"),
new URL("https://acollectionofjokes.blogspot.com/somefiles.pdf"),
new URL("https://diansano.blogspot.com/somefiles.pdf")
};
/*menugaskan url-url ke service melalui object serviceBinder*/
serviceBinder.urls = urls;
Kita kemudian men-start service dengan menggunakan object 'Intent':
startService(i);
Sebelum kita bisa men-start service, kita harus mengikat 'activity' ke service. Hal ini sudah kita lakukan di dalam method 'startService()' dari tombol 'Start Service':
public void startService(View view) {
i = new Intent(MainActivity.this, MyService.class);
bindService(i, connection, Context.BIND_AUTO_CREATE);
}
Method 'bindService()' memungkinkan 'activity' kita untuk dikoneksikan ke service. Method ini memiliki tiga argumen: satu object 'Intent', satu object 'ServiceConnection', dan satu flag untuk menunjukkan bagaimana service diikat.

No comments: