Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中跨進程通信

Android中跨進程通信

編輯:關於Android編程

Android跨進程通信的功能,下面就讓我們具體地學習一下。

在上篇文章中我們知道了,Service其實是運行在主線程裡的,如果直接在Service中處理一些耗時的邏輯,就會導致程序ANR。

讓我們來做個實驗驗證一下吧,修改上一篇文章中創建的ServiceTest項目,在MyService的onCreate()方法中讓線程睡眠60秒,如下所示:

  1. publicclassMyServiceextendsService{
  2.  
  3. ......
  4.  
  5. @Override
  6. publicvoidonCreate(){
  7. super.onCreate();
  8. Log.d(TAG,"onCreate()executed");
  9. try{
  10. Thread.sleep(60000);
  11. }catch(InterruptedExceptione){
  12. e.printStackTrace();
  13. }
  14. }
  15.  
  16. ......
  17.  
  18. }

重新運行後,點擊一下Start Service按鈕或Bind Service按鈕,程序就會阻塞住並無法進行任何其它操作,過一段時間後就會彈出ANR的提示框,如下圖所示。

\

之前我們提到過,應該在Service中開啟線程去執行耗時任務,這樣就可以有效地避免ANR的出現。

那麼本篇文章的主題是介紹遠程Service的用法,如果將MyService轉換成一個遠程Service,還會不會有ANR的情況呢?讓我們來動手嘗試一下吧。

將一個普通的Service轉換成遠程Service其實非常簡單,只需要在注冊Service的時候將它的android:process屬性指定成:remote就可以了,代碼如下所示:

  1.  
  2. android:versionCode="1"
  3. android:versionName="1.0">
  4.  
  5. ......
  6.  
  7. android:name="com.example.servicetest.MyService"
  8. android:process=":remote">
  9.  
  10.  
現在重新運行程序,並點擊一下Start Service按鈕,你會看到控制台立刻打印了onCreate() executed的信息,而且主界面並沒有阻塞住,也不會出現ANR。大概過了一分鐘後,又會看到onStartCommand() executed打印了出來。

為什麼將MyService轉換成遠程Service後就不會導致程序ANR了呢?這是由於,使用了遠程Service後,MyService已經在另外一個進程當中運行了,所以只會阻塞該進程中的主線程,並不會影響到當前的應用程序。
為了證實一下MyService現在確實已經運行在另外一個進程當中了,我們分別在MainActivity的onCreate()方法和MyService的onCreate()方法裡加入一行日志,打印出各自所在的進程id,如下所示:

Log.d("TAG","processidis"+Process.myPid()); 再次重新運行程序,然後點擊一下Start Service按鈕,打印結果如下圖所示:

 

\

可以看到,不僅僅是進程id不同了,就連應用程序包名也不一樣了,MyService中打印的那條日志,包名後面還跟上了:remote標識。

那既然遠程Service這麼好用,干脆以後我們把所有的Service都轉換成遠程Service吧,還省得再開啟線程了。其實不然,遠程Service非但不好用,甚至可以稱得上是較為難用。一般情況下如果可以不使用遠程Service,就盡量不要使用它。

下面就來看一下它的弊端吧,首先將MyService的onCreate()方法中讓線程睡眠的代碼去除掉,然後重新運行程序,並點擊一下Bind Service按鈕,你會發現程序崩潰了!為什麼點擊Start Service按鈕程序就不會崩潰,而點擊Bind Service按鈕就會崩潰呢?這是由於在Bind Service按鈕的點擊事件裡面我們會讓MainActivity和MyService建立關聯,但是目前MyService已經是一個遠程Service了,Activity和Service運行在兩個不同的進程當中,這時就不能再使用傳統的建立關聯的方式,程序也就崩潰了。

那麼如何才能讓Activity與一個遠程Service建立關聯呢?這就要使用AIDL來進行跨進程通信了(IPC)。

AIDL(Android Interface Definition Language)是Android接口定義語言的意思,它可以用於讓某個Service與多個應用程序組件之間進行跨進程通信,從而可以實現多個應用程序共享同一個Service的功能。

下面我們就來一步步地看一下AIDL的用法到底是怎樣的。首先需要新建一個AIDL文件,在這個文件中定義好Activity需要與Service進行通信的方法。新建MyAIDLService.aidl文件,代碼如下所示:

  1. packagecom.example.servicetest;
  2. interfaceMyAIDLService{
  3. intplus(inta,intb);
  4. StringtoUpperCase(Stringstr);
  5. }
點擊保存之後,gen目錄下就會生成一個對應的Java文件,如下圖所示:

 

\

然後修改MyService中的代碼,在裡面實現我們剛剛定義好的MyAIDLService接口,如下所示:

  1. publicclassMyServiceextendsService{
  2.  
  3. ......
  4.  
  5. @Override
  6. publicIBinderonBind(Intentintent){
  7. returnmBinder;
  8. }
  9.  
  10. MyAIDLService.StubmBinder=newStub(){
  11.  
  12. @Override
  13. publicStringtoUpperCase(Stringstr)throwsRemoteException{
  14. if(str!=null){
  15. returnstr.toUpperCase();
  16. }
  17. returnnull;
  18. }
  19.  
  20. @Override
  21. publicintplus(inta,intb)throwsRemoteException{
  22. returna+b;
  23. }
  24. };
  25.  
  26. }
這裡先是對MyAIDLService.Stub進行了實現,重寫裡了toUpperCase()和plus()這兩個方法。這兩個方法的作用分別是將一個字符串全部轉換成大寫格式,以及將兩個傳入的整數進行相加。然後在onBind()方法中將MyAIDLService.Stub的實現返回。這裡為什麼可以這樣寫呢?因為Stub其實就是Binder的子類,所以在onBind()方法中可以直接返回Stub的實現。

接下來修改MainActivity中的代碼,如下所示:

  1. publicclassMainActivityextendsActivityimplementsOnClickListener{
  2.  
  3. privateButtonstartService;
  4.  
  5. privateButtonstopService;
  6.  
  7. privateButtonbindService;
  8.  
  9. privateButtonunbindService;
  10.  
  11. privateMyAIDLServicemyAIDLService;
  12.  
  13. privateServiceConnectionconnection=newServiceConnection(){
  14.  
  15. @Override
  16. publicvoidonServiceDisconnected(ComponentNamename){
  17. }
  18.  
  19. @Override
  20. publicvoidonServiceConnected(ComponentNamename,IBinderservice){
  21. myAIDLService=MyAIDLService.Stub.asInterface(service);
  22. try{
  23. intresult=myAIDLService.plus(3,5);
  24. StringupperStr=myAIDLService.toUpperCase("helloworld");
  25. Log.d("TAG","resultis"+result);
  26. Log.d("TAG","upperStris"+upperStr);
  27. }catch(RemoteExceptione){
  28. e.printStackTrace();
  29. }
  30. }
  31. };
  32.  
  33. ......
  34.  
  35. }
我們只是修改了ServiceConnection中的代碼。可以看到,這裡首先使用了MyAIDLService.Stub.asInterface()方法將傳入的IBinder對象傳換成了MyAIDLService對象,接下來就可以調用在MyAIDLService.aidl文件中定義的所有接口了。這裡我們先是調用了plus()方法,並傳入了3和5作為參數,然後又調用了toUpperCase()方法,並傳入hello world字符串作為參數,最後將調用方法的返回結果打印出來。
  1. IntentbindIntent=newIntent(this,MyService.class);
  2. bindService(bindIntent,connection,BIND_AUTO_CREATE);
這裡在構建Intent的時候是使用MyService.class來指定要綁定哪一個Service的,但是在另一個應用程序中去綁定Service的時候並沒有MyService這個類,這時就必須使用到隱式Intent了。現在修改AndroidManifest.xml中的代碼,給MyService加上一個action,如下所示: 
  1.  
  2.  
  3. android:versionCode="1"
  4. android:versionName="1.0">
  5.  
  6. ......
  7.  
  8. android:name="com.example.servicetest.MyService"
  9. android:process=":remote">
  10.  
  11.  
  12.  
  13.  


這就說明,MyService可以響應帶有com.example.servicetest.MyAIDLService這個action的Intent。

現在重新運行一下程序,這樣就把遠程Service端的工作全部完成了。
  1. android:layout_height="match_parent"
  2. android:orientation="vertical"
  3. >
  4.  
  5. android:id="@+id/bind_service"
  6. android:layout_width="match_parent"
  7. android:layout_height="wrap_content"
  8. android:text="BindService"
  9. />
  10.  
接下來打開或新建MainActivity,在其中加入和MyService建立關聯的代碼,如下所示:
  1. publicclassMainActivityextendsActivity{
  2.  
  3. privateMyAIDLServicemyAIDLService;
  4.  
  5. privateServiceConnectionconnection=newServiceConnection(){
  6.  
  7. @Override
  8. publicvoidonServiceDisconnected(ComponentNamename){
  9. }
  10.  
  11. @Override
  12. publicvoidonServiceConnected(ComponentNamename,IBinderservice){
  13. myAIDLService=MyAIDLService.Stub.asInterface(service);
  14. try{
  15. intresult=myAIDLService.plus(50,50);
  16. StringupperStr=myAIDLService.toUpperCase("comesfromClientTest");
  17. Log.d("TAG","resultis"+result);
  18. Log.d("TAG","upperStris"+upperStr);
  19. }catch(RemoteExceptione){
  20. e.printStackTrace();
  21. }
  22. }
  23. };
  24.  
  25. @Override
  26. protectedvoidonCreate(BundlesavedInstanceState){
  27. super.onCreate(savedInstanceState);
  28. setContentView(R.layout.activity_main);
  29. ButtonbindService=(Button)findViewById(R.id.bind_service);
  30. bindService.setOnClickListener(newOnClickListener(){
  31. @Override
  32. publicvoidonClick(Viewv){
  33. Intentintent=newIntent("com.example.servicetest.MyAIDLService");
  34. bindService(intent,connection,BIND_AUTO_CREATE);
  35. }
  36. });
  37. }
  38.  
  39. }
這部分代碼大家一定會非常眼熟吧?沒錯,這和在ServiceTest的MainActivity中的代碼幾乎是完全相同的,只是在讓Activity和Service建立關聯的時候我們使用了隱式Intent,將Intent的action指定成了com.example.servicetest.MyAIDLService。

在當前Activity和MyService建立關聯之後,我們仍然是調用了plus()和toUpperCase()這兩個方法,遠程的MyService會對傳入的參數進行處理並返回結果,然後將結果打印出來。

這樣的話,ClientTest中的代碼也就全部完成了,現在運行一下這個項目,然後點擊Bind Service按鈕,此時就會去和遠程的MyService建立關聯,觀察LogCat中的打印信息如下所示:

\

不用我說,大家都已經看出,我們的跨進程通信功能已經完美實現了。

不過還有一點需要說明的是,由於這是在不同的進程之間傳遞數據,Android對這類數據的格式支持是非常有限的,基本上只能傳遞Java的基本數據類型、字符串、List或Map等。那麼如果我想傳遞一個自定義的類該怎麼辦呢?這就必須要讓這個類去實現Parcelable接口,並且要給這個類也定義一個同名的AIDL文件。這部分內容並不復雜,而且和Service關系不大,所以就不再詳細進行講解了,感興趣的朋友可以自己去查閱一下相關的資料。

好了,結合上下兩篇,這就是關於Service你所需知道的一切。


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved