Service基本用法java
基本用法即同進程下Activity與Service雙向通訊,先描述總體實現過程而後直接上代碼:android
新建一個繼承自Service的類MyService,而後在AndroidManifest.xml裏註冊這個Serviceshell
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註冊一個自定義回調便可。app
代碼以下,關鍵部分給出了對應上面步驟的註釋:框架
Activityeclipse
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實現跨進程通訊
上面講了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,參考下面文章:
http://blog.csdn.net/lambert_a/article/details/51567773
新建一個AIDL文件
和新建類文件類似:右鍵 -> new -> AIDL -> AIDL File,而後輸入文件名點擊finish完成(這裏的示例代碼是IMyAidlInterface)
上面的操做無論右鍵哪一個目錄,完成以後都會在src/main目錄下生成了一個aidl目錄,新建的IMyAidlInterface.aidl文件就在這個目錄下,注意和eclipse的不一樣。
打開這個文件發現就是一個接口(可能會默認生成一個basicTypes方法,這是示例方法,不用管,能夠刪掉),而後在裏面定義一個本身的方法(須要其餘的方法的話本身看着加)
代碼以下:
interface IMyAidlInterface { void testMethod(String str); }
編譯項目
Build -> Make Project
完成以後會在 app/build/generated/source/debug/ 目錄下生成一個和AIDL文件同名的java文件 IMyAidlInterface.java
這個類文件就是用來提供進程間通訊的,須要的Binder類就在這裏面。
簡單來講,AIDL就是一個用來生成代碼的工具,最終的目的就是獲得IMyAidlInterface.java這個類。這個和數據庫框架GreenDao很像,都是經過一些簡單的作法生成不少複雜而有用的代碼,而後拿來直接用。固然那些複雜的代碼也是能夠手動編寫的,好比能夠嘗試仿照IMyAidlInterface.java或者直接把IMyAidlInterface.java複製到java目錄而後刪掉aidl文件實現進程間通訊。
分析 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類,有一個自定義的方法。
修改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。
修改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接口。
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(); } } }); } }
跨進程回調接口的實現
至此,實現了跨進程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通訊和同應用多進程無本質區別。
跨進程下解註冊回調
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",說明解註冊成功。
這裏總結一下:同進程下自定義MyBinder能夠輕鬆實現Activity與Service通訊,跨進程的話須要使用AIDL生成能夠跨進程的Binder。至於Activity與Service裏面的代碼,流程套路基本相同,不相同的只是一些很簡單的細節。
經過代碼能夠看到,Activity和Service之間是經過一個binder對象來通訊的。
Binder鏈接池
經過上面的介紹,不難發現,一個Service對應一個Binder,實際項目總不能把全部邏輯都寫在一塊兒的,不一樣業務邏輯是要分類的,不免會出現多個Binder的狀況,總不能一個Binder對應一個Service,這時,就可使用Binder鏈接池了。
首先,用一種簡單的方式介紹一下什麼是Binder鏈接池:Binder鏈接池是一種相似設計模式的代碼結構。能夠直接把它看成一種設計模式來看待。
而後,這種模式要解決的問題:用一個Service管理多個AIDL(或者說管理多個Binder),而不是一個AIDL對應一個Service。
再解釋一下,加強理解:咱們知道設計模式對於編寫代碼、實現功能等並非必須的,可是它有不少優勢。Binder鏈接池也是同樣,要實現一個Service管理多個AIDL也能夠不使用它。可是它可讓代碼結構優雅清晰,使代碼維護擴展更加容易等。
簡單瞭解鏈接池以後,接下來動手實現一個例子。在動手以前先總體瞭解一下最終的項目的目錄結構,看下圖:
項目結構圖
如圖,這裏拿動物來舉例。下面一步步來實現圖中的代碼。
首先準備相應的類
新建一個Activity和一個Service,新建多個AIDL文件。
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。
編譯項目
生成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類是後面要講的),那樣的話結構上更加好看合理,示例的最終代碼是之內部類的方式來寫的。
編寫鏈接池代碼
鏈接池就是一個普通的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單例對象的時候不能在主線程裏面。
使用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調用遠程服務端的方法等。
測試
經過測試代碼能夠知道,Service是在Activity中點擊按鈕的時候經過初始化BinderPool單例對象的時候綁定的(也能夠在其餘地方初始化BinderPool對象,隨意,這裏只是一種測試代碼,可是不要在主線程裏面),因此程序剛運行的時候只有一個Activity所在的進程,點擊按鈕以後纔會開啓Service進程。
運行代碼,執行命令 adb shell ps | grep "com.zjy.servicedemo" 能夠看到一個進程
u0_a97 2228 523 1012056 57324 00000000 f774c915 S com.zjy.servicedemo
點擊按鈕,能夠看到打印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.
再次執行命令 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")); } } }; }
須要注意的問題
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字段來傳輸的。
在接收端的代碼中,取消息的時候是先從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; }