五種交互方式,分別是:通過廣播交互、通過共享文件交互、通過Messenger(信使)交互、通過自定義接口交互、通過AIDL交互。(可能更多)
Service與Thread的區別
Thread:Thread 是程序執行的最小單元,可以用 Thread 來執行一些異步的操作。
Service:Service 是android的一種機制,當它運行的時候如果是Local Service,那麼對應的 Service 是運行在主進程的 main 線程上的。如果是Remote Service,那麼對應的 Service 則是運行在獨立進程的 main 線程上。
Thread 的運行是獨立的,也就是說當一個 Activity 被 finish 之後,如果沒有主動停止 Thread 或者 Thread 裡的 run 方法沒有執行完畢的話,Thread 也會一直執行。因此這裡會出現一個問題:當 Activity 被 finish 之後,不再持有該 Thread 的引用,也就是不能再控制該Thread。另一方面,沒有辦法在不同的 Activity 中對同一 Thread 進行控制。
例如:如果 一個Thread 需要每隔一段時間連接服務器校驗數據,該Thread需要在後台一直運行。這時候如果創建該Thread的Activity被結束了而該Thread沒有停止,那麼將沒有辦法再控制該Thread,除非kill掉該程序的進程。這時候如果創建並啟動一個 Service ,在 Service 裡面創建、運行並控制該 Thread,這樣便解決了該問題(因為任何 Activity 都可以控制同一個Service,而系統也只會創建一個對應 Service 的實例)。
因此可以把 Service 想象成一種消息服務,可以在任何有 Context 的地方調用 Context.startService、Context.stopService、Context.bindService、Context.unbindService來控制它,也可以在 Service 裡注冊 BroadcastReceiver,通過發送 broadcast 來達到控制的目的,這些都是 Thread 做不到的。
Service的生命周期
1. 被啟動的服務(startService())的生命周期。
如果一個Service被某個Activity 調用Context.startService() 方法啟動,那麼不管是否有Activity使用bindService()綁定或unbindService()解除綁定到該Service,該Service都在後台運行。如果一個Service被多次執行startService(),它的onCreate()方法只會調用一次,也就是說該Service只會創建一個實例,而它的onStartCommand()將會被調用多次(對應調用startService()的次數)。該Service將會一直在後台運行,直到被調用stopService(),或自身的stopSelf方法。當然如果系統資源不足,系統也可能結束服務。
2. 被綁定的服務(bindService())的生命周期。
如果一個Service被調用 Context.bindService ()方法綁定啟動,不管調用bindService()調用幾次,onCreate()方法都只會調用一次,而onStartCommand()方法始終不會被調用,這時會調用onBind()方法。當連接建立之後,Service將會一直運行,除非調用Context.unbindService() 斷開連接或者之前調用bindService() 的 Context 不存在了(如該Activity被finish),系統將會自動停止Service,對應onDestroy()將被調用。
3. 被啟動又被綁定的服務的生命周期。
如果一個Service又被啟動又被綁定,則該Service將會一直在後台運行。調用unbindService()將不會停止Service,而必須調用stopService()或Service的stopSelf()方法來停止服務。
4. 當服務被停止時清除服務。
當一個Service被終止時,Service的onDestroy()方法將會被調用,在這裡應當做一些清除工作,如停止在Service中創建並運行的線程等。
Process的生命周期
當Service運行在低內存的環境時,系統會kill掉一些進程。因此進程的優先級將會狠重要:
1. 如果Service當前正在執行onCreate()、onStartCommand()、onDestroy()方法,那麼此時主進程將會成為前台進程來保證代碼可以執行完成而避免被kill。
2. 如果Service已經啟動,那麼主進程將會比其他可見的進程的重要性低,但比其他看不見的進程高。這裡說的可見指的是對用戶來講,可見的進程優先級永遠是最高的,用戶至上嘛。但只有少部分進程始終是用戶可見的,因此除非系統處於極度低內存的時候,不然 service是不會被kill的。
3. 如果有Client端連到Service,那麼Service永遠比Client端重要。
4. Service可以使用startForeground()將Service放到前台狀態。這樣在低內存時被kill的幾率更低,但如果在極低內存的情況下,該Service理論上還是會被kill掉。但這個情況基本不用考慮。
廣播交互
1提到Activity與Service的交互,可能狠多人首先想到的就是BroadCast——廣播。在Android中,廣播是系統提供的一種很好的交互方式。比如:在電池電量過低,開機完成等情況下,系統都會發出相應的系統廣播,我們的應用程序只需要注冊相應的廣播接收器,就可以接收到這些系統的廣播。同時,我們也可以定義自己的廣播,這樣在不同的Activity、Service以及應用程序之間,就可以通過廣播來實現交互。我們通過模擬應用程序後台下載的情況來分析Service與Activity的交互方式。
當我們點擊StartService按鈕之後,界面上的進度條將會每隔一秒加1。因為是模擬下載,因此下載動作我們在Service中通過一個Timer定時器來實現,在Timer中對一個整型數據i進行自加(i++),然後Client端獲取Server端的i值並顯示在界面上,從而達到模擬的目的。
1.1. 實現原理
Server端將目前的下載進度,通過廣播的方式發送出來,Client端注冊此廣播的監聽器,當獲取到該廣播後,將廣播中當前的下載進度解析出來並更新到界面上。
1.2. 實現步驟
1.2.1 在Client端中通過startService()啟動Service。
if(v == startBtn){
Log.i(TAG, "start button clicked...pid: "+Process.myPid());
mIntent.setClass(BroadCastService.this, DownLoadService.class);
startService(mIntent);
}
這裡的mIntent = new Intent();Process.myPid()方法可以獲取當前進程的ID號。
1.2.2 DownLoadService接到啟動的命令之後,執行onCreate()方法,並在其中開啟timer計數模擬下載。
復制代碼
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "DownLoadService.onCreate()...pid: "+Process.myPid());
intent = new Intent("com.seven.broadcast");
mTimer = new Timer();
mTimer.schedule(new MyTimerTask(), 0 , TIME * 1000);
}
復制代碼
這裡的intent是Server端向Client端傳送數據用的,使用的action是”com.seven.broadcast”,Client端只有註冊了相應action才能夠接收到Server端的廣播,並解析其中的內容。Process.myPid()是獲取當前進程的ID。
1.2.3 在Server端的timer計數其中發送廣播,告知Client端目前下載進度。
復制代碼
class MyTimerTask extends TimerTask{
@Override
public void run() {
if(i==100){
i=0;
}
intent.putExtra("CurrentLoading", i);
sendBroadcast(intent);
i++;
Log.e(TAG, "i= "+i);
}
}
復制代碼
通過intent.putExtra(key,value);設置intent的值,然後通過sendBroadcast(intent)方法,將廣播發送出去。
1.2.4 在Client端通過匿名內部類的方式實例化BroadcastReceiver並覆寫其中的onReceive()方法。
復制代碼
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(MYACTION.equals(intent.getAction())){
Log.i(TAG, "get the broadcast from DownLoadService...");
curLoad = intent.getIntExtra("CurrentLoading", ERROR);
mHandler.sendMessage(mHandler.obtainMessage());
}
}
};
復制代碼
在onReceive()方法中,判斷是否為Server端發送的廣播,如果是則對廣播中攜帶的intent數據進行解包處理。這裡也可以單獨寫一個類繼承自BroadcastReceiver,在其中覆寫onReceive()方法,在Client端中實例化其對象,同樣可以達到相應的效果,這樣做可以為後面實現靜態注冊廣播。
1.2.5 更新主介面下載進度。
復制代碼
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i(TAG, "current loading: "+curLoad);
if(curLoad<0||curLoad>100){
Log.e(TAG, "ERROR: "+curLoad);
return;
}
mProgressBar.setProgress(curLoad);
mTextView.setText(curLoad+"%");
}
};
復制代碼
這裡對獲取到的進度進行了一次判斷,如果獲取到的值沒有異常,那麼將會顯示到界面,並更新進度條的進度,如果異常則返回。
1.2.6 一定要對Broadcast進行注冊和取消注冊。只有注冊之後相應的broadcast之後才能接收到廣播注冊方法有兩種。
動態注冊/取消注冊:
復制代碼
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, "register the broadcast receiver...");
IntentFilter filter = new IntentFilter();
filter.addAction(MYACTION);
registerReceiver(receiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, "unregister the broadcast receiver...");
unregisterReceiver(receiver);
}
復制代碼
動態註冊可以隨時註冊隨時取消。
靜態註冊:
<receiver android:name="MyBroadcastReceiver">
<intent-filter>
<action android:name="com.seven.broadcast" />
</intent-filter>
</receiver>
注:這裡的MyBroadcastReceiver是一個繼承自BroadcastReceiver的類。靜態注冊只要注冊了一次那麼只要該程序沒有被卸載那麼該廣播將一直有效。
最後貼出整個AndroidManifest.xml文件
復制代碼
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".BroadCastService"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="DownLoadService" android:process=":remote"/>
</application>
復制代碼
這裡的android:process =”:remote”可以使該Service運行在單獨進程中,從而可以模擬跨進程通信。
1.3 小結
通過廣播的方式實現Activity與Service的交互操作簡單且容易實現,可以勝任簡單級的應用。但缺點也十分明顯,發送廣播受到系統制約。系統會優先發送系統級廣播,在某些特定的情況下,我們自定義的廣播可能會延遲。同時在廣播接收器中不能處理長耗時操作,否則系統會出現ANR即應用程序無響應。
共享文件交互
2這裡提到的共享文件指的是Activity和Service使用同一個文件來達到傳遞數據的目的。我們使用SharedPreferences來實現共享,當然也可以使用其它IO方法實現,通過這種方式實現交互時需要注意,對於文件的讀寫的時候,同一時間只能一方讀一方寫,不能兩方同時寫。
2.1 實現原理
Server端將當前下載進度寫入共享文件中,Client端通過讀取共享文件中的下載進度,並更新到主界面上。
2.2 實現步驟
2.2.1 在Client端通過startService()啟動Service。
復制代碼
if(startSerBtn==v){
Log.i(TAG, "Start Button Clicked.");
if(intent!=null){
startService(intent);
timer.schedule(new MyTimerTask(), 0, TIME * 1000);
}
}
復制代碼
2.2.2 Server端收到啟動intent之後執行onCreate()方法,並開啟timer,模擬下載,以及初始化SharedPreferences對象preferences。
復制代碼
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "DownLoadService.onCreate()...");
preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);
timer = new Timer();
timer.schedule(new MyTimerTask(), 0, TIME*1000);
}
復制代碼
2.2.3 開始計數並將下載進度寫入shared_prefs文件夾下的xml文件中,內容以鍵值對的方式保存。
復制代碼
class MyTimerTask extends TimerTask{
@Override
public void run() {
setCurrentLoading();
if(100==i){
i=0;
}
i++;
}
}
private void setCurrentLoading() {
preferences.edit().putInt("CurrentLoading", i).commit();
}
復制代碼
對於SharedPreferences的使用需要注意一下幾點:
首先,使用sharedPreferences前需要獲取文件引用。
preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);
其次,使用sharedpreferences寫數據方式。
preferences.edit().putInt("CurrentLoading", i).commit();
最後,讀取數據的方式。
int couLoad = preferences.getInt("CurrentLoading", 0);
2.2.4 Client端通過讀取/data/data/com.seven.servicetestdemo/shared_prefs文件夾下的xml文件,並取得裡面的鍵值對,從而獲取到當前的下載進度,並更新到主界面上。
復制代碼
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int couLoad = preferences.getInt("CurrentLoading", 0);
mProgressBar.setProgress(couLoad);
currentTv.setText(couLoad+"%");
}
};
復制代碼
2.3 小結
因為方法簡單,因此就不貼出AndroidManifest.xml文件了。對於這種方式實現Activity與Service的交互,可以說很方便,就像使用管道,一個往裡寫,一個往外讀。但這種方式也有缺陷,寫入數據較為復雜以及數據量較大時,就有可能導致寫入與讀數據出不一致的錯誤。同時因為經過了一個中轉站,這種操作將更耗時。
Messenger交互(信使交互)
3Messenger翻譯過來指的是信使,它引用了一個Handler對象,別人能夠向它發送消息(使用mMessenger.send(Message msg)方法)。該類允許跨進程間基於Message通信,在服務端使用Handler創建一個 Messenger,客戶端只要獲得這個服務端的Messenger對象就可以與服務端通信了。也就是說我們可以把Messenger當做Client端與Server端的傳話筒,這樣就可以溝通交流了。
3.1 實現原理
在Server端與Client端之間通過一個Messenger對象來傳遞消息,該對象類似於信息中轉站,所有信息通過該對象攜帶。
3.2 Messenger的一般用法
(1). 在Server端創建信使對象。
mMessenger = new Messenger(mHandler)
(2). Client端使用bindService()綁定Server端。
(3). Server端的onBind()方法返回一個binder對象。
return mMessenger.getBinder();
(4). Client端使用返回的binder對象得到Server端信使。
public void onServiceConnected(ComponentName name, IBinder service) {
rMessenger = new Messenger(service);
......
}
這裡雖然是new了一個Messenger,但我們查看它的實現
public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
發現它的mTarget是通過AIDL得到的,實際上就是遠程創建的那個。
(5). Client端可以使用這個Server端的信使對象向Server端發送消息。
rMessenger.send(msg);
這樣Server端的Handler對象就能收到消息了,然後可以在其handlerMessage(Message msg)方法中進行處理。經過這5個步驟之後只有Client端向Server端發送消息,這樣的消息傳遞是單向的,那麼如何實現消息的雙向傳遞呢?
首先需要在第5步做修改,在send(msg)前通過msm.replyTo = mMessenger將Client端自己的信使設置到消息中,這樣Server端接收到消息時同時也得到了Client端的信使對象,然後Server端也可以通過使用得到的Client端的信使對象來項Client端發送消息 cMessenger = msg.replyTo2 cMessenger.send(message);
這樣即完成了從Server端向Client端發送消息的功能,這樣Client端可以在自己的Handler對象的handlerMessage()方法中接收服務端發送來的message進行處理。
3.3 實現步驟
3.3.1 創建並初始化Server端的信使對象。
復制代碼
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case TEST:
Log.e(TAG, "Get Message from MainActivity.");
cMessenger = msg.replyTo;
mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);
break;
default:
break;
}
}
};
//It's the messenger of server
private Messenger mMessenger = new Messenger(mHandler);
復制代碼
3.3.2 在Client端使用bindService()方法綁定Server端。
private void doBindService(){
Log.i(TAG, "doBindService()...");
mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);//if bind success return true
Log.e(TAG, "Is bind: "+mIsBind);
}
3.3.3 在Server端的onBind()方法中返回一個binder對象。
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "MessengerService.onBind()...");
return mMessenger.getBinder();
}
這裡的mMessenger就是Server端的信使對象。
3.3.4 Client端使用ServiceConnected()方法來獲取Server端的信使對象。
復制代碼
private ServiceConnection serConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected()...");
rMessenger = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected()...");
rMessenger = new Messenger(service);//get the object of remote service
mMessenger = new Messenger(mHandler);//initial the object of local service
sendMessage();
}
};
復制代碼
獲取Server端的信使對象的同時,也初始化Client端的自己的信使對象,並且通過sendMessage()方法發送消息給Server端,表示可以開始下載了。
3.3.5 Client端使用獲取到的rMessenger來發送消息給Server端,同時將Client端的信使封裝到消息中,一並發送給Server端。
復制代碼
private void sendMessage() {
Message msg = Message.obtain(null, MessengerService.TEST);//MessengerService.TEST=0
msg.replyTo = mMessenger;
try {
rMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
復制代碼
這裡的MessengerService.TEST為Server端裡的一個靜態常量。Msg.replyTo=mMessenger;表示發送給Server端的信息裡攜帶Client端的信使。
3.3.6 Server端獲取Client端發送的消息並得到Client端的信使對象。
復制代碼
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case TEST:
Log.e(TAG, "Get Message from MainActivity.");
cMessenger = msg.replyTo;//get the messenger of client
mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);
break;
default:
break;
}
}
};
復制代碼
在接收到Client端的信息之後,Server端開啟timer模擬下載,並接收Client端的信使對象。
3.3.7 Server端向Client端發送數據。
復制代碼
class MyTimerTask extends TimerTask {
@Override
public void run() {
if (i == 100) {
i = 0;
}
try {
//send the message to the client
Message message = Message.obtain(null, MessengerService.TEST,i, 0);
cMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
i++;
}
}
復制代碼
直接使用接收到的Client端的信使對象來發送當前下載進度給Client端。
3.3.8 Client端接收來自Server端的數據。
復制代碼
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MessengerService.TEST:
Log.e(TAG, "Get Message From MessengerService. i= "+msg.arg1);
int curLoad = msg.arg1;
mTextView.setText(curLoad+"%");
mProgressBar.setProgress(curLoad);
break;
default:
break;
}
}
};
復制代碼
Client端的接收和Server端的接收狠類似。接收到Server端傳過來的數據之後進行介面更新,以及下載進度更新。
以下是AndroidManifest.xml文件:
復制代碼
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.seven.messengerservicedemo"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="MessengerService">
<intent-filter>
<action ndroid:name="com.seven.messagerservice.MessengerService" />
</intent-filter>
</service>
</application>
</manifest>
復制代碼
這裡在Service的註冊中加入了過濾動作,只有相匹配的action才能啟動相應的Service。
3.4 小結
通過Messenger來實現Activity和Service的交互,稍微深入一點我們就可以知道,其實Messenger也是通過AIDL來實現的。對於前兩種實現方式,Messenger方式總體上來講也是比較容易理解的,這就和平時使用Handler和Thread通信一個道理。
自定義接口交互
4何謂自定義接口呢,其實就是我們自己通過接口的實現來達到Activity與Service交互的目的,我們通過在Activity和Service之間架設一座橋樑,從而達到數據交互的目的,而這種實現方式和AIDL非常類似(後文會說到)。
4.1 實現原理
自定義一個接口,該接口中有一個獲取當前下載進度的空方法。Server端用一個類繼承自Binder並實現該接口,覆寫了其中獲取當前下載進度的方法。Client端通過ServiceConnection獲取到該類的對象,從而能夠使用該獲取當前下載進度的方法,最終實現實時交互。
4.2 實現步驟
4.2.1 新建一個Interface,並在其中創建一個用於獲取當前下載進度的的空方法getCurrentLoad()。
public interface ICountService {
public int getCurrentLoad();
}
4.2.2 新建Server端DownService實現ICountService並在其中通過一個內部類ServiceBinder繼承自Binder並實現ICoutService接口。
復制代碼
public class DownLoadService extends Service implements ICountService{
private ServiceBinder serviceBinder = new ServiceBinder();
public class ServiceBinder extends Binder implements ICountService{
@Override
public int getCurrentLoad() {
Log.i(TAG, "ServiceBinder getCurrentLoad()... i=:"+i);
return i;
}
}
@Override
public int getCurrentLoad() {
return 0;
}
}
復制代碼
在Server端中,實現獲取下載進度的空方法getCurrentLoad();這是Eclipse自動生成的,重點不在這裡。我們需要在ServiceBinder類中覆寫getCurrentLoad()方法,這裡我們返回當前的下載進度i。
4.2.3 Client端使用bindService()綁定Server端。
if (startSerBtn == v) {
Log.i(TAG, "Start Button Clicked.");
bindService(intent, serConn, BIND_AUTO_CREATE);
timer.schedule(new MyTimerTask(), 1000, TIME * 1000);//這裡一定要延遲一下再開始獲取數據,不然會報空指針異常
}
在Client端綁定Server端的同時,延遲1s開始獲取下載進度。其中的intent = new Intent(“com.seven.test”);com.seven.test該字符串要與在AndroidManifest.xml中申明的一致
4.2.4 Server端返回binder對象。
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "DownLoadService.onBind()...");
return serviceBinder;
}
這裡的serviceBinder因為繼承了Binder因此也是Binder對象。
4.2.5 Client端通過ServiceConnection來獲取Server端的binder對象。
復制代碼
private ServiceConnection serConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
iCountService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected()...");
iCountService = (ICountService)service;
}
};
復制代碼
獲取的過程是在bindService()過程中完成的,這裡的iCountService是接口ICountService的對象,在這裡得到實例化。
4.2.6 在綁定完成之後,Server端會開啟下載,在實際情況中Server端會開啟獨立線程用於下載,這裡用i++來代替。
復制代碼
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "DownLoadService.onCreate()...");
timer = new Timer();
timer.schedule(new MyTimerTask(), 0, TIME*1000);
}
class MyTimerTask extends TimerTask{
@Override
public void run() {
if(100==i){
i=0;
}
i++;
}
}
復制代碼
bindService()方法執行之後會調用DownLoadService中的onCreate()方法,在其onCreate()方法中開啟timer使得i++。
4.2.7 Server端已經開啟了下載,那麼Client端需要及時獲取下載進度並在主界面上更新。
復制代碼
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i(TAG, "handleMessage...");
int curLoad = iCountService.getCurrentLoad();
mProgressBar.setProgress(curLoad);
currentTv.setText(curLoad+"%");
}
};
class MyTimerTask extends TimerTask{
@Override
public void run() {
mHandler.sendMessage(mHandler.obtainMessage());
}
}
復制代碼
Client端的Timer在bindService()完成之後1秒再開始獲取下載進度,獲取方法是直接通過int curLoad = iCountService.getCurrentLoad();這裡的getCurrentLoad()方法是DownLoadService內部類ServiceBinder中的方法。Client端將獲取到的下載進度更新到介面上並更新進度條。
4.3 小結
通過上面的例子可以知道,這種方法簡單實用,擴展性強,但其也有一些缺點,比如需要延遲一些再開始獲取Server端的數據,從而無法完全實現從零開始同步更新。綜其所述,通過自定義接口實現Activity與Service交互的方法還是比較實用的。適用於同進程中通信,不能進行跨進程通信。
AIDL交互
AIDL是Android Interface Definition Language的首字母縮寫, 也就是Android接口定義語言。提及AIDL就不得不說下Android的服務,Android 支持兩種服務類型的服務即本地服務和遠程服務。
本地服務無法供在設備上運行的其他應用程序訪問,也就是說只能該應用程序內部調用,比如某些應用程序中的下載類服務,這些服務只能由內部調用。而對於遠程服務,除了可以由本應用程序調用,還可以允許其他應用程序訪問。遠程服務一般通過AIDL來實現,可以進行進程間通信,這種服務也就是遠程服務。
本地服務與遠程服務還是有一些重要的區別。具體來講,如果服務完全只供同一進程中的組件使用(運行後台任務),客戶端一邊通過調用 Context.startService()來啟動該服務。這種類型的服務為本地服務,它的一般用途是後台執行長耗時操作。而遠程服務一般通過bindService()方法啟動,主要為不同進程間通信。我們也將遠程服務稱為AIDL支持服務,因為客戶端使用 AIDL 與服務通信。Android中對於遠程服務有多種叫法:遠程服務、AIDL服務、外部服務和RPC服務。
5.1 AIDL實現流程圖
5
這屬於代理/存根結構,通過這張AIDL的流程圖,很容易發現Android實現IPC其實是在原來的C/S框架上加入了代理/存根結構。
比如,你到自動取款機上去取款。那麼你就是客戶(Client),取款機就是你的代理(Proxy);你不會在乎錢具體放在那裡,你只想將你的錢從取款機中取出來。你同銀行之間的操作完全是取款機代理實現。你的取款請求通過取款機傳到另一邊,即銀行的服務器(Server)。它也沒有必要知道你在哪兒取錢,它所關心的是你的身份和你取款多少。當它確認你的權限,就進行相應的操作,返回操作結果給取款機,取款機根據服務器返回結果,從保險櫃裡取出相應數量的錢給你。你取出卡後,操作完成。取款機不是直接同服務器連接的,他們之間還有一個“存根(Stub)”,取款機與存根通信,服務器與存根通信,從某種意義上說存根就是服務器的代理。
6
5.3 實現原理7
AIDL屬於Android的IPC機制,常用於跨進程通信,主要實現原理基於底層Binder機制。
5.4 實現步驟
5.4.1 建立工程。建立AIDLServer端以及AIDLClient端。在AIDLServer端中只有一個服務程序,沒有主界面,其主要功能就是負責下載。AIDLClient端從AIDLServer端獲取當前下載進度(注:AIDLServer端和AIDLClient端是不同的兩個APK,在模擬本例的時候,需要先在模擬器上安裝AIDLServer編譯出來的APK,安裝方法可以直接在模擬器上運行一次,可以通過adb install your.apk 來安裝)。
AIDLServer端中新建了一個ICountService.aidl的文件,該文件內容如下:
interface ICountService{
int getCount();
}
aidl文件的書寫規范如下:
(1). Android支持String和CharSequence(以及Java的基本數據類型);
(2). 如果需要在aidl中使用其它aidl接口類型,需要import,即使是在相同包結構下;
(3). Android允許傳遞實現Parcelable接口的類,需要import;
(4). Android支持集合接口類型List和Map,但是有一些限制,元素必須是基本型或者前面三種情況,不需要import集合接口類,但是需要對元素涉及到的類型import;
(5). 非基本數據類型,也不是String和CharSequence類型的,需要有方向指示,包括in、out和inout,in表示由客戶端設置,out表示由服務端設置,inout是兩者均可設置。
11
AIDLClient端需要將AIDLServer端的ICountService.aidl文件復製過去,這裡為了方便,新建了一個和Server端同名的包,並將ICountService.aidl放與其中。
5.4.2 我們在Server端建立好ICoutService.aidl文件之後,Eclipse會在/gen/com.seven.aidlserver/目錄下自動生成ICountService.java文件。該文件由Eclipse自動生成,請勿隨便修改,後文我們需引用到的內容如下:
復制代碼
public static com.seven.aidlserver.ICountService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.seven.aidlserver.ICountService))) {
return ((com.seven.aidlserver.ICountService) iin);
}
return new com.seven.aidlserver.ICountService.Stub.Proxy(obj);
}
復制代碼
5.4.3 在Server端新建一個內部類繼承自ICountService.Stub並覆寫其中的getCount()方法,以及實例化該類的一個對象serviceBinder。
復制代碼
private AIDLServerBinder serviceBinder = new AIDLServerBinder();
class AIDLServerBinder extends ICountService.Stub{
@Override
public int getCount() throws RemoteException {
return i;
}
}
復制代碼
這裡與前面提到的“通過接口實現交互”非常類似。
5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder對象。
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "AIDLServer.onBind()...");
return serviceBinder;
}
5.4.5 在Server端的onCreate()方法中,開啟timer,模擬下載。在Client端通過bindService()綁定Server端的時候,會首先執行Server端的onCreate()方法。
復制代碼
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "AIDLServer.onCreate()...");
mTimer = new Timer();
mTimer.schedule(new MyTimerTask(), 0,TIME * 1000);
}
class MyTimerTask extends TimerTask{
@Override
public void run() {
if(i==100){
i=0;
}
i++;
}
}
復制代碼
5.4.6 Client端通過bindService()綁定Server端。
if(startBtn==v){
Log.i(TAG, "start button click.");
mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);
mTimer.schedule(new MyTimerTask(), 1000 ,TIME * 1000);
}
這裡的intent = new Intent(“com.seven.aidlserver”);這裡跟Server端注冊Service時過濾的要一致,也就是說只有發出相同的action才會啟動該Service。同時開啟了一個timer用於獲取下載進度。
5.4.7 Client端通過ServiceConnection來獲取Server端的binder對象。
復制代碼
private ServiceConnection serConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
iCountService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "AIDLClient.onServiceConnected()...");
iCountService = ICountService.Stub.asInterface(service);
}
};
復制代碼
這裡的iCountService對象實際上就是ICountService的對象在此實例化。
5.4.8 獲取當前下載進度並更新到界面上。
復制代碼
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
try {
int count = iCountService.getCount();
mTextView.setText(count+"%");
mProgressBar.setProgress(count);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
復制代碼
通過更新介面上的進度條,可以狠容易的後去當前下載進度。因為AIDLServer端只是一個繼承自Service的服務,因此就不貼出其AndroidManifest.xml文件了。
5.5 小結
AIDL在Android中是進程間通信常用的方式,可能使用較為復雜,但效率高,擴展性好。同時很多系統服務就是以這種方式完成與應用程序通信的。