本文介紹Service與Activity之間的通信,文章包含以下內容:
- 一、Service基本用法
- 二、通過AIDL實現Service與Activity跨進程通信
- 三、Binder連接池
- 四、使用Messenger實現跨進程通信
- 五、本文的示例源碼地址
文章有點長,主要分為上面5個部分,由于沒找到在簡書設置文內鏈接的方法,所以要想直接跳過基礎看后面的部分,翻滾吧 !不過文章整體從簡到繁,前面的基礎對后面知識的理解會有幫助,所以建議按順序看。
一、Service基本用法
基本用法即同進程下Activity與Service雙向通信,先描述整體實現過程然后直接上代碼:
- 新建一個繼承自Service的類MyService,然后在AndroidManifest.xml里注冊這個Service
- Activity里面使用bindService方式啟動MyService,也就是綁定了MyService
(到這里實現了綁定,Activity與Service通信的話繼續下面的步驟) - 新建一個繼承自Binder的類MyBinder
- 在MyService里實例化一個MyBinder對象mBinder,并在onBind回調方法里面返回這個mBinder對象
- 第2步bindService方法需要一個ServiceConnection類型的參數,在ServiceConnection里可以取到一個IBinder對象,就是第4步onBinder返回的mBinder對象(也就是在Activity里面拿到了Service里面的mBinder對象)
- 在Activity里面拿到mBinder之后就可以調用這個binder里面的方法了(也就是可以給Service發消息了),需要什么方法在MyBinder類里面定義實現就行了。如果需要Service給Activity發消息的話,通過這個binder注冊一個自定義回調即可。
代碼如下,關鍵部分給出了對應上面步驟的注釋:
Activity
public class MainActivity extends Activity {
private static final String TAG = "zjy";
public MyBinder mBinder;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//第5步所說的在Activity里面取得Service里的binder對象
mBinder = (MyBinder)iBinder;
//第6步注冊自定義回調
mBinder.setOnTestListener(new MyBinder.OnTestListener() {
@Override
public void onTest(String str) {
Log.d(TAG, "receive msg from service: "+str);
}
});
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent,mConnection,BIND_AUTO_CREATE);
findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//點擊按鈕調用mBinder里面的方法,發送消息給Service
mBinder.testMethod("hi, service.");
}
});
}
}
Service
public class MyService extends Service {
private static final String TAG = "zjy";
// 第4步,實例化一個MyBinder對象
private MyBinder mBinder = new MyBinder(this);
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;//第4步,返回這個mBinder對象
}
public void serviceMethod(String str){
Log.d(TAG, "receive msg from activity: " + str);
}
}
Binder
public class MyBinder extends Binder {
private static final String TAG = "zjy";
private MyService mService;
private OnTestListener mListener;
public MyBinder(MyService service) {
this.mService = service;
}
public void testMethod(String str) {
// Activity通過Binder來調用Service的方法將消息傳給Service
mService.serviceMethod(str);
// 并回調mListener.onTest告訴Activity已收到消息
mListener.onTest("hi, activity.");
}
// MyBinder 里面提供一個注冊回調的方法
public void setOnTestListener(OnTestListener listener) {
this.mListener = listener;
}
//自定義一個回調接口
public interface OnTestListener {
void onTest(String str);
}
}
代碼很簡單,首先Activity綁定Service得到一個MyBinder實例并注冊MyBinder里面的OnTestListener回調監聽,然后點擊按鈕的時候調用MyBinder里面的testMethod(String)方法將消息發出去,MyBinder持有一個MyService的實例,testMethod(String)里面調用MyService里面的方法就可以把Activity的消息傳給Service了,然后testMethod(String)里面回調mListener.onTest(String)將Service的消息發給Activity。
MyBinder定義在MyService里面作為內部類也是很常見的寫法,這里為了方便后面的講解寫成了普通類的形式。
至此就實現了同進程下Activity與Service的雙向通信,運行代碼,點擊按鈕后log如下:
( 2360): receive msg from activity: hi, service.
( 2360): receive msg from service: hi, activity.
通過代碼可以看到,Activity和Service之間是通過一個binder對象來通信的。
二、通過AIDL實現Service與Activity跨進程通信
上面講了Activity和Service在同進程下的通信,結論是:Activity和Service之間是通過一個binder對象來通信的,其實,這句話在多進程中同樣有效,接下來就在多進程下驗證這句話。到這你可能已經想到了,AIDL其實就是利用Binder實現跨進程通信的。先看一下官方文檔是如何介紹AIDL的:
On Android, one process cannot normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and marshall the objects across that boundary for you. The code to do that marshalling is tedious to write, so Android handles it for you with AIDL.
大概意思就是說Android進程之間不能直接通信,需要把對象轉換成計算機能識別的原始語言,然后安排它跨越進程邊界。但是做這些事很繁瑣,于是Android提供了AIDL來做這件事。(換句話就是要實現跨進程需要編寫很多復雜的代碼,于是android提供了AIDL,通過編寫簡單的AIDL文件,編譯器根據AIDL的規則生成那些復雜的代碼)
總的來說,使用AIDL跨進程通信,整體過程和單進程一樣,都是通過一個Binder來通信的,區別在于單進程的Binder是自己通過繼承Binder類來手動實現的,而跨進程的Binder是通過AIDL自動生成的,那是一個牛逼的Binder。
對AIDL有個初步認識之后,開始實踐,這里使用AndroidStudio實現AIDL,參考文章:Android Studio中AIDL使用方法
首先修改上面的代碼,在AndroidManifest.xml里面用android:process=":remote"屬性把Service指定到另一個進程中,這時候直接運行代碼會報錯,因為自定義的MyBinder不具有跨進程的能力,綁定Service的時候無法得到Binder。那么接下來就使用AIDL生成一個可以跨進程的Binder,然后用這個可跨進程的Binder替換MyBinder。
1、新建一個AIDL文件
和新建類文件相似:右鍵 -> new -> AIDL -> AIDL File,然后輸入文件名點擊finish完成(這里的示例代碼是IMyAidlInterface)
上面的操作不管右鍵哪個目錄,完成之后都會在src/main目錄下生成了一個aidl目錄,新建的IMyAidlInterface.aidl文件就在這個目錄下,注意和eclipse的不同。
打開這個文件發現就是一個接口(可能會默認生成一個basicTypes方法,這是示例方法,不用管,可以刪掉),然后在里面定義一個自己的方法(需要其他的方法的話自己看著加)
代碼如下:
interface IMyAidlInterface {
void testMethod(String str);
}
2、編譯項目
Build -> Make Project
完成之后會在 app/build/generated/source/debug/ 目錄下生成一個和AIDL文件同名的java文件 IMyAidlInterface.java
這個類文件就是用來提供進程間通信的,需要的Binder類就在這里面。
簡單來說,AIDL就是一個用來生成代碼的工具,最終的目的就是得到IMyAidlInterface.java這個類。這個和數據庫框架GreenDao很像,都是通過一些簡單的做法生成很多復雜而有用的代碼,然后拿來直接用。當然那些復雜的代碼也是可以手動編寫的,比如可以嘗試仿照IMyAidlInterface.java或者直接把IMyAidlInterface.java復制到java目錄然后刪掉aidl文件實現進程間通信。
3、分析 IMyAidlInterface.java
AndroidStudio切換到Project工程模式在app/build/generated/source/debug/路徑下找到IMyAidlInterface.java文件并打開。生成的代碼格式很亂,為了方便查看,可以使用格式化代碼的快捷鍵格式化一下。
IMyAidlInterface.java里面是一個接口,接口里面有一個內部抽象類和一個方法。這個方法就是我們在aidl文件里定義的那個方法。內部抽象類就是我們要的Binder類,類名Stub。到這里不難想象接下來的工作:(1)Service里面new一個Stub實例并在onBinder里面返回這個Stub(或者說Binder)的實例 。(2)Activity里面綁定Service的時候取到這個Binder(強轉成Stub類型)。(3)調用這個Binder里面的testMethod方法實現Activity和Service的通信。大的思路是這樣,不過細節上還是有很多不同的。
IMyAidlInterface.java里面的其他代碼(主要是一些方法)暫時不用看,用到的時候會說。到這里只需要知道這個java文件里面有一個Stub類,有一個自定義的方法。
4、修改Service代碼
到這AIDL相關的代碼已經完成,接下來就是使用AIDL為我們生成的代碼。首先修改MyService只需把MyBinder替換成Stub,但是Stub是個抽象類,需要我們自己實現,那么新建一個繼承自Stub的類,類名隨意,這里取名AidlBinder,然后仿照同進程下的MyBinder實現testMethod()方法,代碼如下:
public class AidlBinder extends IMyAidlInterface.Stub {
private MyService mService;
public AidlBinder(MyService service) {
this.mService = service;
}
@Override
public void testMethod(String str) throws RemoteException {
mService.serviceMethod(str);
}
}
上面代碼中沒有回調相關的代碼,因為跨進程的回調和同進程下是不一樣的,后面會說到。另外,這里為了方便講解,專門定義了AidlBinder類作為Stub 的實現類,另一種在Service里面直接使用匿名內部類的方式實現Stub 也是很常見的。至于AidlBinder里面的代碼和同進程下很像,不解釋了。
然后MyService里面使用AidlBinder即可,代碼如下:
public class MyService extends Service {
private static final String TAG = "zjy";
private AidlBinder mBinder = new AidlBinder(this);
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public void serviceMethod(String str) {
Log.d(TAG, "receive msg from activity: " + str);
}
}
和同進程基本一樣,不解釋了。
總結一下:到這里為止,除去理論的分析之外,實際的操作只有兩步:(1)新建一個AIDL文件用來生成一些代碼。(2)實現抽象類Stub,實現類是AidlBinder。(3)Service里面使用Stub的實現類AidlBinder替換原來的MyBinder。
5、修改Activity代碼
先來分析一下,按照同進程通信的思路就是:聲明一個IMyAidlInterface.Stub類型的Binder,然后在綁定Service的時候初始化這個Binder:mBinder = (IMyAidlInterface.Stub)service;
然后使用這個Binder來跟Service通信。
其實這樣是不行的,如果這樣做,綁定服務的時候 mBinder = (IMyAidlInterface.Stub)service;
這行代碼會報一個異常java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.zjy.servicedemo.IMyAidlInterface$Stub
意思是傳過來的Binder是BinderProxy類型的不能轉換成Stub類型(因為Stub不是BinderProxy的子類而是Binder的子類)。
關于BinderProxy,我也不懂,通過一些資料了解到它與C++層有關,源碼中無對應的java類,編譯源碼后會生成BinderProxy.class類,和Binder一樣實現了IBinder接口。
源碼位置\frameworks\base\core\jni\android_util_Binder.cpp->static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,jint code, jobject dataObj,jobject replyObj, jint flags)
出自:Android FrameWork——Binder機制詳解(1)Java層的Activity透過BinderProxy來與遠距的(Remote)服務進行溝通。
從Java層而觀之,myActivity可以經由bindService()而建立它與myBinder之間的連結。然而,這個連結是透過C++層的機制而達成的。
出自:認識Android的BinderProxy和Binder類別 (應該是臺灣人寫的,繁體字不是亂碼 ^ ^!)
Activit如何使用傳過來的Binder呢?AIDL生成的代碼中提供了一個靜態方法asInterface(IBinder),可以將IBinder轉換成Aidl接口,所以可以這樣做:IMyAidlInterface mService = IMyAidlInterface.Stub.asInterface(service);
藝術探索這本書中是這樣介紹asInterface方法的:用于將服務端的Binder對象轉換成客戶端所需的AIDL接口類型的對象,這種轉換是區分進程的,如果客戶端和服務端位于同一進程,那么此方法返回的就是服務端的Stub對象本身,否則返回的是系統封裝后的Stub.proxy對象。
所以同進程下,Activity有以下3種方式使用Service傳過來的Binder:
IMyAidlInterface mService = IMyAidlInterface.Stub.asInterface(service);
IMyAidlInterface.Stub mBinder = (IMyAidlInterface.Stub)service;
IMyAidlInterface.Stub mService = (IMyAidlInterface.Stub)IMyAidlInterface.Stub.asInterface(service);
而跨進程只能使用第一種方式,最終Activity的代碼如下:
public class MainActivity extends Activity {
private static final String TAG = "zjy";
public IMyAidlInterface mService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mService = IMyAidlInterface.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, mConnection, BIND_AUTO_CREATE);
findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
mService.testMethod("hi, service.");
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
6、跨進程回調接口的實現
至此,實現了跨進程Activity給Service發送消息,接下來實現Service收到消息后回應Activity。大的方向還是和單進程一樣使用回調實現,不一樣的是細節。
首先,回調接口需要定義成aidl接口而不是普通接口,所以新建一個IMyCallbackListener.aidl文件,里面定義一個onRespond方法作為回調函數:
interface IMyCallbackListener {
void onRespond(String str);
}
擴展IMyAidlInterface.aidl,里面定義一個注冊回調監聽的方法(相當于基礎篇里面的那個setOnTestListener方法)
import com.zjy.servicedemo.IMyCallbackListener;
interface IMyAidlInterface {
void testMethod(String msg);
void registerListener(IMyCallbackListener listener);
}
注意aidl的語法規則,非系統的類即使在同一個包下也要import,比如上面代碼的IMyCallbackListener,而系統的類String就不用import
這時編譯會提示AidlBinder實現父類的抽象方法registerListener(),仿照同進程下的MyBinder里面的回調相關的代碼,修改AidlBinder如下:
public class AidlBinder extends IMyAidlInterface.Stub {
private MyService mService;
private IMyCallbackListener mListener;
public AidlBinder(MyService service) {
this.mService = service;
}
@Override
public void testMethod(String str) throws RemoteException {
mService.serviceMethod(str);
mListener.onRespond("hi, activity");
}
@Override
public void registerListener(IMyCallbackListener listener) throws RemoteException {
mListener = listener;
}
}
有同進程通信的基礎,看懂這個代碼很容易。然后Activity里面在合適的地方注冊回調,用來接收服務端的消息:
try{
mService.registerListener(new IMyCallbackListener.Stub() {
@Override
public void onRespond(String str) throws RemoteException {
Log.d(TAG, "receive message from service: "+str);
}
});
} catch (RemoteException e){
e.printStackTrace();
}
至此,跨進程下Activity與Service的雙向通信就完成了,運行代碼,點擊按鈕log如下:
(11597): receive msg from activity: hi, service.
(11579): receive message from service: hi, activity
就本應用中的代碼來看,代碼的執行流程和單進程一樣,只是一些實現的細節不同。另外,可以使用
adb shell ps | grep "本應用的包名"
命令查看進程信息,會看到如下兩個進程:
com.zjy.servicetest
com.zjy.servicetest:remote
com.zjy.servicetest:remote 是Service所在的進程。如果是不同應用下的多進程,使用AIDL通信和同應用多進程無本質區別。
7、跨進程下解注冊回調
Service回應Activity消息是通過注冊回調接口實現的,接下來介紹解注冊,和同進程的解注冊不同,多進程需要借助RemoteCallbackList來完成,所以注冊回調的相關方法也要改一下,改成使用RemoteCallbackList來注冊回調,AidlBinder代碼修改如下:
public class AidlBinder extends IMyAidlInterface.Stub {
private MyService mService;
private RemoteCallbackList<IMyCallbackListener> mListenerList = new RemoteCallbackList<>();
public AidlBinder(MyService service) {
this.mService = service;
}
@Override
public void testMethod(String str) throws RemoteException {
mService.serviceMethod(str);
// 調用mListenerList里面所有已注冊的監聽
int count = mListenerList.beginBroadcast();
for (int i = 0; i < count; i++) {
mListenerList.getBroadcastItem(i).onRespond("hi, activity");
}
mListenerList.finishBroadcast();
}
@Override
public void registerListener(IMyCallbackListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(IMyCallbackListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
}
上面代碼里的unregisterListener方法像registerListener一樣添加進去,并在里面實現解注冊的功能。RemoteCallbackList的用法很簡單,看代碼就行了。
最后在Activity里面加一些測試解注冊的代碼即可,比如加一個按鈕,點擊的時候調用遠程的解注冊方法,下面是Activity里面的最終完整代碼:
public class MainActivity extends Activity {
private static final String TAG = "zjy";
public IMyAidlInterface mService;
private IMyCallbackListener.Stub mListener = new IMyCallbackListener.Stub() {
@Override
public void onRespond(String str) throws RemoteException {
Log.d(TAG, "receive message from service: "+str);
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mService = IMyAidlInterface.Stub.asInterface(iBinder);
try{
//注冊回調
mService.registerListener(mListener);
} catch (RemoteException e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, mConnection, BIND_AUTO_CREATE);
findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
mService.testMethod("hi, service.");
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
findViewById(R.id.test2_bt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//解注冊回調
mService.unregisterListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
整個代碼最終的功能是:啟動Activity的時候綁定Service并注冊一個回調,點擊 send message 按鈕后Activity向Service發送消息"hi, service",然后Service收到消息后log打印 "receive message from activity: hi, service",并恢復一個消息 "hi, activity",Activity收到消息后log打印 "receive message from service: hi, activity"。然后點擊unregisterListener按鈕解注冊回調監聽,再點擊 send message 后就只打印log "receive message from activity: hi, service",說明解注冊成功。
AIDL的基本用法就介紹到這里,關于傳遞自定義的序列化對象和不同應用的多進程通信可以參考文章Android Studio中AIDL使用方法
這里總結一下:同進程下自定義MyBinder可以輕松實現Activity與Service通信,跨進程的話需要使用AIDL生成可以跨進程的Binder。至于Activity與Service里面的代碼,流程套路基本相同,不相同的只是一些很簡單的細節。
三、Binder連接池
通過上面的介紹,不難發現,一個Service對應一個Binder,實際項目總不能把所有邏輯都寫在一起的,不同業務邏輯是要分類的,難免會出現多個Binder的情況,總不能一個Binder對應一個Service,這時,就可以使用Binder連接池了。
首先,用一種簡單的方式介紹一下什么是Binder連接池:Binder連接池是一種類似設計模式的代碼結構。可以直接把它當作一種設計模式來看待。
然后,這種模式要解決的問題:用一個Service管理多個AIDL(或者說管理多個Binder),而不是一個AIDL對應一個Service。
再解釋一下,增強理解:我們知道設計模式對于編寫代碼、實現功能等并不是必須的,但是它有很多優點。Binder連接池也是一樣,要實現一個Service管理多個AIDL也可以不使用它。但是它可以讓代碼結構優雅清晰,使代碼維護擴展更加容易等。
簡單了解連接池之后,接下來動手實現一個例子。在動手之前先整體了解一下最終的項目的目錄結構,看下圖:
如圖,這里拿動物來舉例。下面一步步來實現圖中的代碼。
1、首先準備相應的類:新建一個Activity和一個Service,新建多個AIDL文件。
(1)Activity和Service先什么都不用做,它們與要實現的Binder連接池無關,它們只是用來使用Binder連接池的。
新建多個AIDL,文件名如下:
- IAnimal.aidl
- IBird.aidl
- IFish.aidl
- IMonkey.aidl
它們的代碼如下:
interface IAnimal {
IBinder queryAnimal(int animalCode);
}
interface IBird {
void fly();
}
interface IFish {
void swim();
}
interface IMonkey {
void climbTree();
}
以上代碼不難理解,每種動物包含一個它的專有方法,IAnimal接口管理其它三種動物,它里面的方法接收一個參數,這個參數代表動物種類,后面的實現會根據動物種類返回一個對應的動物的Binder。
(2)編譯項目,生成AIDL文件對應的Binder,AIDL生成的Binder是抽象類,接下來定義每個抽象Binder的實現類,類名分別為:AnimalBinder.java,BirdBinder.java,FishBinder.java,MonkeyBinder.java。代碼如下:
public class AnimalBinder extends IAnimal.Stub{
public static final int ANIMAL_CODE_BIRD = 1;
public static final int ANIMAL_CODE_FISH = 2;
public static final int ANIMAL_CODE_MONKEY = 3;
@Override
public IBinder queryAnimal(int animalCode) throws RemoteException {
IBinder binder = null;
switch (animalCode) {
case ANIMAL_CODE_BIRD:
binder = new BirdBinder();
break;
case ANIMAL_CODE_FISH:
binder = new FishBinder();
break;
case ANIMAL_CODE_MONKEY:
binder = new MonkeyBinder();
break;
default:
break;
}
return binder;
}
}
public class BirdBinder extends IBird.Stub{
private static final String TAG = "zjy";
@Override
public void fly() throws RemoteException {
Log.d(TAG, "I'm bird, I can fly.");
}
}
public class FishBinder extends IFish.Stub{
private static final String TAG = "zjy";
@Override
public void swim() throws RemoteException {
Log.d(TAG, "I'm fish, I can swim.");
}
}
public class MonkeyBinder extends IMonkey.Stub {
private static final String TAG = "zjy";
@Override
public void climbTree() throws RemoteException {
Log.d(TAG, "I'm monkey, I can climb the tree.");
}
}
代碼很簡單,不解釋了。有一點要說明一下,AnimalBinder作為管理,和三種動物Binder要區分開,更好的寫法是把AnimalBinder寫在代表連接池的類BinderPool里面作為內部類(BinderPool類是后面要講的),那樣的話結構上更加好看合理,示例的最終代碼是以內部類的方式來寫的。
2、編寫連接池代碼
連接池就是一個普通的java類,類名隨意取,這里取名:BinderPool.java
類里面的代碼主要分為幾個簡單的部分:
- 給BinderPool.java實現單例模式
- 綁定一個Service(綁定Service需要的Context是使用它的Activity傳過來的)
- 提供一個queryAnimal方法,根據參數給用戶提供不同的binder
- 以及前面說的把AnimalBinder作為BinderPool的內部類
BinderPool.java的全部代碼如下:
public class BinderPool {
private static final String TAG = "zjy";
public static final int NO_ANIMAL = 0;
public static final int ANIMAL_CODE_BIRD = 1;
public static final int ANIMAL_CODE_FISH = 2;
public static final int ANIMAL_CODE_MONKEY = 3;
private Context mContext;
@SuppressWarnings("all")
private static BinderPool sInstance;
private CountDownLatch mCountDownLatch;
private IAnimal mAnimalPool;
private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BinderPool getInstance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mCountDownLatch = new CountDownLatch(1);
Intent intent = new Intent(mContext, MyService.class);
mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mAnimalPool = IAnimal.Stub.asInterface(service);
mCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected: ");
}
};
public IBinder queryAnimal(int animalCode) {
IBinder binder = null;
try {
if (mAnimalPool != null) {
binder = mAnimalPool.queryAnimal(animalCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
public static class AnimalBinder extends IAnimal.Stub {
@Override
public IBinder queryAnimal(int animalCode) throws RemoteException {
IBinder binder = null;
switch (animalCode) {
case ANIMAL_CODE_BIRD:
binder = new BirdBinder();
break;
case ANIMAL_CODE_FISH:
binder = new FishBinder();
break;
case ANIMAL_CODE_MONKEY:
binder = new MonkeyBinder();
break;
default:
break;
}
return binder;
}
}
}
根據劃分的幾個部分來看代碼是很容易的,不過有一些細節需要注意:
- 關于單例的內存泄漏風險,代碼里把context成員轉換成了Application的context
- AIDL是支持并發訪問的,代碼里在綁定Service的時候使用synchronized和CountDownLatch做了線程同步處理,所以獲取BinderPool單例對象的時候不能在主線程里面。
3、使用Binder連接池
到這里Binder連接池的代碼就完成了,主要就是一個BinderPool類,接下來在Service和Activity中使用它。
Service的代碼:
public class MyService extends Service {
private BinderPool.AnimalBinder mBinder = new BinderPool.AnimalBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
注:不要忘記在AndroidManifest.xml里面用android:process=":remote"屬性把Service指定到另一個進程中。
Activity的代碼:
public class MainActivity extends Activity {
private static final String TAG = "zjy";
private BinderPool mBinderPool;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
mBinderPool = BinderPool.getInstance(MainActivity.this);
IBinder birdBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_BIRD);
IBinder fishBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_FISH);
IBinder monkeyBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_MONKEY);
IBird bird = IBird.Stub.asInterface(birdBinder);
IFish fish = IFish.Stub.asInterface(fishBinder);
IMonkey monkey = IMonkey.Stub.asInterface(monkeyBinder);
try {
bird.fly();
fish.swim();
monkey.climbTree();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
}
通過Service和Activity的代碼可以看到,BinderPool使用起來很簡單。從使用者的角度來看,Binder連接池就是把應該在Activity里面做的事封裝成了BinderPool類,比如綁定Service、客戶端通過Binder調用遠程服務端的方法等。
4、測試
通過測試代碼可以知道,Service是在Activity中點擊按鈕的時候通過初始化BinderPool單例對象的時候綁定的(也可以在其他地方初始化BinderPool對象,隨意,這里只是一種測試代碼,但是不要在主線程里面),所以程序剛運行的時候只有一個Activity所在的進程,點擊按鈕之后才會開啟Service進程。
(1)運行代碼,執行命令 adb shell ps | grep "com.zjy.servicedemo" 可以看到一個進程
u0_a97 2228 523 1012056 57324 00000000 f774c915 S com.zjy.servicedemo
(2)點擊按鈕,可以看到打印log
D/zjy ( 2264): I'm bird, I can fly.
D/zjy ( 2264): I'm fish, I can swim.
D/zjy ( 2264): I'm monkey, I can climb the tree.
(3)再次執行命令 adb shell ps | grep "com.zjy.servicedemo" ,此時可以看到有兩個進程,說明點擊按鈕后啟動了service并且service是運行在另一個進程的。
u0_a97 2228 523 1012056 57324 00000000 f774c915 S com.zjy.servicedemo
u0_a97 2264 523 995804 42180 00000000 f774c915 S com.zjy.servicedemo:remote
Binder連接池到此結束,主要就是一個BinderPool.java類
四、使用Messenger實現跨進程通信
Messenger也是用來做進程間通信的,與AIDL的區別,看官方文檔的一段話:
When you need to perform IPC, using a Messenger for your interface is simpler than implementing it with AIDL, because Messenger queues all calls to the service, whereas, a pure AIDL interface sends simultaneous requests to the service, which must then handle multi-threading.
For most applications, the service doesn't need to perform multi-threading, so using a Messenger allows the service to handle one call at a time. If it's important that your service be multi-threaded, then you should use AIDL to define your interface.
意思就是Messenger比AIDL用起來簡單,但是如果多個客戶端同時給服務發消息的話,Messenger一次只能處理一個消息,而AIDL可以多線程處理。
Messenger本質也是用AIDL實現的,可以瀏覽下Messenger的源碼(只有100多行),會看到一些AIDL相關的東西。
然后簡單介紹一下Messenger的使用,首先列一下使用流程:
- Service里面實現一個Handler用來接收消息用
- 使用這個Handler創建一個Messenger對象
- 使用這個Messenger對象創建一個Binder對象,并在onBind方法返回
- Activity里面綁定Service的時候使用傳過來的Binder創建一個Messenger對象
- Activity里面使用這個Messenger對象給Service發消息
- Service里面的Handler收到消息并處理
- Activity里面實現一個Handler用來接收Service回復的消息
- 第5步發送消息的時候消息中攜帶一個Messenger對象,這個Messenger是用第7步的Handler創建的
- 第6步Service收到消息的時候取出消息中攜帶的Messenger
- 用第9步取出的Messenger給Activity發消息
- Activity中第7步的Handler處理Service回復的消息
整個流程和單進程通信的過程很像,都是圍繞Binder完成的。上面第7步以后都是Service回復消息相關的。下面直接給出完整代碼,注釋與上面的流程相對應。
Service代碼
public class MyService extends Service {
private static final String TAG = "zjy";
//1.Service里面實現一個Handler用來接收消息用
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//6.Service里面的Handler收到消息并處理
if (msg.what==1) {
Bundle bundle = msg.getData();
Log.d(TAG, "receive message from activity: "+bundle.getString("string"));
//9.取出消息中的Messenger對象
Messenger replyMessenger = msg.replyTo;
Message replyMsg= new Message();
replyMsg.what = 2;
Bundle b = new Bundle();
b.putString("string", "hi, activity");
replyMsg.setData(b);
try {
//10.使用Messenger給Activity發消息
replyMessenger.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
};
// 2.使用這個Handler創建一個Messenger對象
private Messenger mMessenger = new Messenger(mHandler);
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//3.使用這個Messenger對象創建一個Binder對象,并在onBind方法返回
return mMessenger.getBinder();
}
}
Activity代碼
public class MainActivity extends Activity {
private static final String TAG = "zjy";
private Messenger mMessenger;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//4.Activity里面綁定Service的時候使用傳過來的Binder創建一個Messenger對象
mMessenger = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent,mConnection,BIND_AUTO_CREATE);
findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message msg = new Message();
msg.what = 1;
Bundle bundle = new Bundle();
bundle.putString("string", "hi, service");
msg.setData(bundle);
//8.發送消息的時候攜帶一個Messenger對象
msg.replyTo = new Messenger(mGetReplyMsg);
try {
//5.Activity里面使用這個Messenger對象給Service發消息
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
//7.Activity里面實現一個Handler用來接收Service回復的消息
private Handler mGetReplyMsg = new Handler(){
@Override
public void handleMessage(Message msg) {
//11.處理Service回復的消息
if (msg.what==2) {
Bundle bundle = msg.getData();
Log.d(TAG, "receive message from service: "+bundle.getString("string"));
}
}
};
}
需要注意的問題
(1)Messenger發送的消息是Message對象,組裝Message消息的時候不要使用Message的obj字段,而是借用Bundle來組裝數據。下面是《Android開發藝術探索》里面的一段話:
使用Messenger來傳輸Message,Message中能使用的載體只有what, arg1, arg2, Bundle以及replyTo。Message中的另一字段obj在同一個進程中很實用,但是在進程間通信的時候,在android2.2以前obj不支持跨進程,即便是2.2以后,也僅僅是系統提供的實現了Parcelable接口的對象才能通過它來傳輸。這就意味著自定義的Parcelable對象是無法通過obj字段來傳輸的。
(2)在接收端的代碼中,取消息的時候是先從Message里面取出Bundle,然后直接從Bundle取數據。如果數據是自定義的Parcelable對象,是不能直接從Bundle里面取的,需要在取數據之前先給Bundle設置一個ClassLoader。“取數據之前”的意思不單單是指取自定義的Parcelable對象,而是包括基本數據類型和系統提供的Parcelable對象等所有數據之前。示例代碼如下:
Bundle bundle = msg.getData();
bundle.setClassLoader(getClassLoader());//設置ClassLoader
bundle.getxxx(key);//取數據
關于這一點源碼里面已經有相關注釋說明了,Message類的getData方法注釋如下:
/**
* Obtains a Bundle of arbitrary data associated with this
* event, lazily creating it if necessary. Set this value by calling
* {@link #setData(Bundle)}. Note that when transferring data across
* processes via {@link Messenger}, you will need to set your ClassLoader
* on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
* Bundle.setClassLoader()} so that it can instantiate your objects when
* you retrieve them.
* @see #peekData()
* @see #setData(Bundle)
*/
public Bundle getData() {
if (data == null) {
data = new Bundle();
}
return data;
}
五、本文的示例源碼地址
https://github.com/developerzjy/ServiceDemo