概要
本文主要講解Service與Activity進行通信的幾種方式,Activity主要通過Intent出傳遞數據到Service,而比較常見的Service傳遞數據到Activity有三種方式,但是這次所介紹的Service與Activity的通信方式都是位於同一應用進程中的,並沒有介紹不同進程之間如何通信,不同進程通過aidl或者Messenger在以后的文章中會再做詳細的介紹。
Service和Activity是Android中最常用的兩大組件,Activity的設計與Web頁面非常類似,從頁面的跳轉通過連接,以及從頁面的定位通過URL,從每個頁面的獨立封裝等方面都可以看出來,它主要負責與用戶進行交互。Service則是在后台運行,默默地為用戶提供功能,進行調度和統籌,它用來“執行”后台的、耗時的、重要的任務,三者缺一不可,而最重要的原因是第三點:要執行重要的任務。
從上面可以看出,Activity負責與用戶的直接交互,Service是運行在后台用戶不可見的而且常常執行的是重要的任務,既然是重要的任務然而用戶卻不知道任務的執行情況,比如下載,用戶想知道下載的進度,這種情況下就需要與Activity通信,方面用戶實時知道任務的執行情況。
Activity傳遞數據到Service
這種方式傳遞數據基本上是依賴Intent來傳遞數據的,更為詳細的請參考Intent學習筆記,在Activity中可以使用兩種方式來與Service進行交互,一種是通過bindService(Intent service, ServiceConnection conn, int flags)
方法,另一種是startService(Intent service)
,當然了也可以通過廣播來與服務通信,但是個人感覺Activity通過Broadcast來通信就有點多此一舉了,但是反過來則是可行的,在Service中通過Broadcast來傳遞數據到Activity。
接下來介紹最常見的通過Service傳遞數據到Activity的三種方式。
方式一:通過Binder
在以前的一篇文章Service簡介及啟動方式就已經介紹過,Service主要提供兩種功能,其中之一就是將應用程序的某些功能暴露給其它程序,該種方式就是通過Binder來實現的。
我們知道在每創建一個Service實例是都要實現一個方法onBind(Intent intent)
,該方法返回一個IBinder類型的實例,IBinder是遠程對象的基本接口,是為高性能而設計的輕量級遠程調用機制的核心部分。但它不僅用於遠程調用,也用於進程內調用。這個接口定義了與遠程對象交互的協議。不要直接實現這個接口,而應該從Binder派生。
下面是一個簡單的Service實現:
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
|
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder {
private MyService service;
public MyBinder() {
service = MyService.this;
}
public String getName() {
return "admin";
}
public void startPlay() {
service.startPlay();
}
}
private void startPlay() {
System.out.println("start play");
}
}
|
當我們在Activity通過bindService(Intent service, ServiceConnection conn, int flags)來綁定一個Service時,bindService會立即返回,它不會返回Ibinder給客戶端,要接收Ibinder,客戶端Activity必須創建一個長連接ServiceConnection的實例並傳給bindService(),ServiceConnection包含一個回調方法,系統調用這個方法來傳遞要返回的IBinder。
下面是Activity的代碼:
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
|
public class MainActivity extends Activity {
private MyBinder service;
private MyConnection conn;
private Intent intent;
private String str;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
conn = new MyConnection();
intent = new Intent(this, MyService.class);
}
public void binderClick(View v) {
bindService(intent, conn, Context.BIND_AUTO_CREATE);
//這里輸出null 因為conn還沒有執行完成回調函數
System.out.println("aaaaaaaaaaaaaaaa:" + str);
}
private class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
service = (MyBinder) binder;
str = service.getName();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//注銷長連接
unbindService(conn);
}
}
|
在本實例中,主要是通過IBinder來建立一個Service與Activity的一個橋梁,通過Binder對象來調用Service中的方法,當然了也可以在Service中在新建一個Binder對象時將Service對象本身作為一個方法的返回值返回,然后我們在Activity中就可以直接通過ServiceConnection長連接回調函數中的IBinder對象來得到一個Service實例,這樣就可以使用Service中暴露的特定方法了,這種方式好像更簡潔了,IBinder的目的就是為了將Service對象傳遞到Activity,其余的事情都不用管了。
方式二:通過Broadcast廣播
在上一篇文章中我們已經講過Broadcast可以用於應用程序之間的通信,更多的內容可以查看Android Broadcast廣播機制。
Service的代碼實現:
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
|
public class BService extends Service {
private int progress = 0;
//創建一個Intent,設置Action為com.sunny.demo.RECEIVER
private Intent intent = new Intent("com.sunny.demo.RECEIVER");
public IBinder onBind(Intent intent) {
return new BBinder();
}
public class BBinder extends Binder {
//返回Service對象
public BService getService() {
return BService.this;
}
}
//下載方法
public void startDownload() {
new Thread(new Runnable() {
public void run() {
while (progress < 100) {
progress++;
SystemClock.sleep(200);
//設置進度 發送廣播
intent.putExtra("progress", progress);
sendBroadcast(intent);
}
}
}).start();
}
}
|
Activity中代碼實現:
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
|
public class BActivity extends Activity {
private ProgressBar progressBar;
private BService service;
private Intent intent;
private MyConnection conn;
private MyReceiver receiver;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_b);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
// 動態注冊廣播接收器
receiver = new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.sunny.demo.RECEIVER");
registerReceiver(receiver, intentFilter);
conn = new MyConnection();
intent = new Intent(this, BService.class);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 拿到進度,更新UI
int progress = intent.getIntExtra("progress", 0);
progressBar.setProgress(progress);
}
}
//開始下載
public void binderClick(View v) {
service.startDownload();
}
private class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
service = ((BBinder) binder).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//注銷服務和廣播
unbindService(conn);
unregisterReceiver(receiver);
}
}
|
方式三:自定義接口回調
事實上方式一也是一種接口回調,只是回調接口中傳遞的是IBinder的一個實例而已,這里我們們仍然會借助第一種方式的接口回調來獲取IBinder的一個實例,然后通過該實例獲取相對應得Service實例,再通過Service中自定義的回調接口將下載的進度實時更新。先看一下回調接口的定義:
1
2
3
4
|
//自定義下載回調借口
public interface ProgressListener {
void onProgress(int progress);
}
|
Service代碼:
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
|
public class CallbackService extends Service {
private int progress = 0;
private ProgressListener progressListener;
public IBinder onBind(Intent intent) {
return new CallbackBinder();
}
public class CallbackBinder extends Binder {
//獲取service
public CallbackService getService() {
return CallbackService.this;
}
}
public void startDownload() {
new Thread(new Runnable() {
public void run() {
while (progress < 100) {
progress++;
SystemClock.sleep(200);
//更新進度
progressListener.onProgress(progress);
}
}
}).start();
}
public void setProgressListener(ProgressListener progressListener) {
this.progressListener = progressListener;
}
}
|
Activity的部分代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
public void onServiceConnected(ComponentName name, IBinder binder) {
//通過binder得到service
service = ((CallbackBinder) binder).getService();
//設置service的回調監聽
service.setProgressListener(new ProgressListener() {
@Override
public void onProgress(int progress) {
//設置進度更新
progressBar.setProgress(progress);
}
});
}
|
小結
本篇文章雖然說是三種方式實現Service傳遞數據到Activity,可是歸根到底仍然只是一種方式,都是借助於IBinder暴露Service中的相應操作。網上還有些文章中提到用觀察者模式,觀察者模式也是接口回調的一種方式,xUtils框架中的下載直接使用Service中的靜態方法來獲取了下載管理器DownloadManager,它的這種方式是直接在Activity或者Fragment中使用了DownloadService,然后在獲取下載管理器DownloadManager的方法中再啟動Service,簡單來說就是調用一個類的靜態方法,這種方式跟面向對象中在一個類中調用另一個類的靜態方法一樣,不需要借助任何紐帶橋梁直接創建調用,應用程序之間通信,大概就這么兩種形式,一種是通過一個橋梁紐帶來傳遞數據,另外一種就是直接在調用者中使用被調用者。