【聲明】 html
歡迎轉載,但請保留文章原始出處→_→ java
生命壹號:http://www.cnblogs.com/smyhvae/android
文章來源:http://www.cnblogs.com/smyhvae/p/4070518.html安全
【前言】服務器
花了週末兩天的時間,整理了一下做爲Android四大組件之一的Service的基礎知識,經過這篇文章,應該能夠明白:對Service的理解、在什麼地方使用、怎麼使用、要注意哪些問題等。多線程
【本文主要內容】併發
1、Service的基本概念(四大組件之一)
2、定義(啓動)一個Serviceapp
3、IntentService框架
4、使用Bind Service完成Service和Activity之間的通訊異步
5、使用Bind Service完成IPC進程間通訊:(在同一個APP內模擬)
6、使用Bind Service完成IPC進程間通訊:(兩個APP之間)
7、Messenger的使用
【正文】
1、Service的基本概念(四大組件之一)
Service是Android中實現程序後臺運行的解決方案,很是適合用於去執行哪些不須要和用戶交互並且還要求長期運行的任務。不能運行在一個獨立的進程當中,而是依賴與建立服務時所在的應用程序進程。只能在後臺運行,而且能夠和其餘組件進行交互。
Service能夠在不少場合使用,好比播放多媒體的時候用戶啓動了其餘Activity,此時要在後臺繼續播放;好比檢測SD卡上文件的變化;好比在後臺記錄你的地理信息位置的改變等等,總之服務是藏在後臺的。
服務不會自動開啓線程,咱們須要在服務的內部手動建立子線程,並在這裏執行具體的任務。關於多線程的知識:能夠參考另一篇文章:Android多線程----異步消息處理機制之Handler詳解
2、定義(啓動)一個Service:
一、如何定義(啓動)一個Service:
核心步驟以下:
新建一個Android項目ServiceTest,具體步驟以下:
(1)新建一個MyService類,繼承自Service,並重寫父類的onCreate()、onStartCommand()和onDestroy()方法,代碼以下:
1 package com.example.servicetest; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.util.Log; 7 8 public class MyService extends Service { 9 10 public static final String TAG = "MyService"; 11 12 //建立服務時調用 13 @Override 14 public void onCreate() { 15 super.onCreate(); 16 Log.d(TAG, "onCreate"); 17 } 18 19 //服務執行的操做 20 @Override 21 public int onStartCommand(Intent intent, int flags, int startId) { 22 Log.d(TAG, "onStartCommand"); 23 return super.onStartCommand(intent, flags, startId); 24 } 25 26 //銷燬服務時調用 27 @Override 28 public void onDestroy() { 29 super.onDestroy(); 30 Log.d(TAG, "onDestroy"); 31 } 32 33 @Override 34 public IBinder onBind(Intent intent) { 35 return null; 36 } 37 }
能夠看到,咱們只是在onCreate()、onStartCommand()和onDestroy()方法中分別打印了一句話,並無進行其它任何的操做,注意代碼註釋中這三個方法的做用。
onBind()方法是Service中惟一的一個抽象方法,因此必需要在子類裏實現。咱們知道,Service能夠有兩種啓動方式:一種是startService(),另外一種是bindService()。第二種啓動方式纔會用到onBind()方法。咱們這先用第一種方式啓動Service,因此暫時忽略onBind()方法。
(2)在清單文件中聲明:(和Activity標籤並列)
<service android:name=".MyService"> </service>
(3)修改activity_main.xml代碼,以下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button1_start_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start Service" /> <Button android:id="@+id/button2_stop_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Stop Service" /> </LinearLayout>
咱們在佈局文件中加入了兩個按鈕,一個用於啓動Service,一個用於中止Service。
(4)在MainActivity做爲程序的主Activity,在裏面加入啓動Service和中止Service的邏輯,代碼以下:
1 package com.example.servicetest; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.view.View; 7 import android.view.View.OnClickListener; 8 import android.widget.Button; 9 10 11 public class MainActivity extends Activity implements OnClickListener { 12 13 private Button button1_start_service; 14 15 private Button button2_stop_service; 16 17 @Override 18 protected void onCreate(Bundle savedInstanceState) { 19 super.onCreate(savedInstanceState); 20 setContentView(R.layout.activity_main); 21 button1_start_service = (Button) findViewById(R.id.button1_start_service); 22 button2_stop_service = (Button) findViewById(R.id.button2_stop_service); 23 button1_start_service.setOnClickListener(this); 24 button2_stop_service.setOnClickListener(this); 25 } 26 27 @Override 28 public void onClick(View v) { 29 switch (v.getId()) { 30 case R.id.button1_start_service: 31 Intent startIntent = new Intent(this, MyService.class); 32 startService(startIntent); 33 break; 34 case R.id.button2_stop_service: 35 Intent stopIntent = new Intent(this, MyService.class); 36 stopService(stopIntent); 37 break; 38 default: 39 break; 40 } 41 } 42 43 }
核心代碼:31行至32行、35行至36行。
能夠看到,在Start Service按鈕的點擊事件裏,咱們構建出了一個Intent對象,並調用startService()方法來啓動MyService。而後在Stop Serivce按鈕的點擊事件裏,咱們一樣構建出了一個Intent對象,並調用stopService()方法來中止MyService。代碼的邏輯很是簡單。
這樣的話,一個簡單的帶有Service功能的程序就寫好了。
啓動和中止服務:
定義好服務以後,接下來看一下如何啓動和中止一個服務,這主要是藉助Intent來實現的。注意startService()和stopService()方法都是定義在Context類當中的,因此能夠在MainActivity中直接調用這兩個方法。
運行上面的程序,點擊button1_start_service按鈕,啓動服務,後臺打印日誌以下:
說明服務啓動成功。
那麼若是我再連續點三次button1_start_service按鈕,後臺增長的日誌以下:
事實上,onCreate()方法只會在Service第一次被建立的時候調用,而onStartCommand()方法在每次啓動服務的時候都會調用。
咱們還能夠在正在「設置--應用---運行」中找到這個服務,以下圖所示:
點開上圖中的紅框部分,能夠看到:
若是咱們再點擊button2_stop_service按鈕或者點擊上圖中的「Stop」,MyService服務就中止掉了:
須要注意的是:
二、中止一個started服務有兩種方法:
(1)在外部使用stopService()
(2)在服務內部(onStartCommand方法內部)使用stopSelf()方法。
三、onStartCommand方法的返回值:
onStartCommand方法執行時,返回的是一個int型。這個整型能夠有三個返回值:START_NOT_STICKY、START_STICKY、START_REDELIVER_INTENT
3、IntentService
一、IntentService的引入:
咱們在第一段中就已經說了,服務中的代碼默認運行在主線程中,若是直接在服務裏執行一些耗時操做,容易形成ANR(Application Not Responding)異常,因此就須要用到多線程的知識了。
所以一個比較標準的服務能夠這樣寫:
1 package com.example.servicetest; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 7 public class MyService extends Service { 8 9 public static final String TAG = "MyService"; 10 11 //服務執行的操做 12 @Override 13 public int onStartCommand(Intent intent, int flags, int startId) { 14 new Thread(new Runnable() { 15 public void run() { 16 //處理具體的邏輯 17 stopSelf(); //服務執行完畢後自動中止 18 } 19 }).start(); 20 return super.onStartCommand(intent, flags, startId); 21 } 22 23 @Override 24 public IBinder onBind(Intent intent) { 25 // TODO Auto-generated method stub 26 return null; 27 } 28 29 }
核心代碼:14至19行,在子線程中處理具體的邏輯。
須要注意的是,若是沒有第17行的stopSelf(),服務一旦啓動後,就會一直處於運行狀態,必須調用stopService()或者stopSelf()方法才能讓服務中止下來;因此咱們添加了17行的stopSelf(),服務執行完畢後會自動中止。
雖然說上面的這種寫法並不複雜,但總會有一些程序猿忘記開啓線程,或者忘記調用stopSelf()方法。爲了能夠簡單地建立一個異步的、會自動中止的服務,Android專門提供了一個IntentService類,這個類就很好的解決了上面所提到的兩種尷尬。另外,能夠啓動IntentService屢次,而每個耗時操做會以工做隊列的方式在IntentService的onHandleIntent()回調方法中執行,而且每次只會執行一個工做線程,執行完第一個後,再執行第二個,以此類推。
二、IntentService的做用:
當咱們須要這樣一次性完成的任務時,就可使用IntentService來完成。
三、IntentService的用法:
咱們在上面的項目ServiceTest基礎上進行修改,步驟以下:
(1)新建一個MyIntentService類,繼承自IntentService,並重寫父類的onHandleIntent()方法,代碼以下:
1 package com.example.servicetest; 2 3 import android.app.IntentService; 4 import android.content.Intent; 5 import android.util.Log; 6 7 public class MyIntentService extends IntentService{ 8 9 public MyIntentService() { 10 super("MyIntentService");//調用父類有參構造函數。這裏咱們手動給服務起個名字爲:MyIntentService 11 // TODO Auto-generated constructor stub 12 } 13 14 //該方法在會在一個單獨的線程中執行,來完成工做任務。任務結束後,該Service自動中止 15 @Override 16 protected void onHandleIntent(Intent intent) { 17 // TODO Auto-generated method stub 18 for(int i = 0;i<3;i++) { 19 //打印當前線程的id 20 Log.d("MyIntentService","IntentService線程的id是:"+Thread.currentThread().getId()); 21 try { 22 Thread.sleep(1000); 23 } catch (InterruptedException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } 27 } 28 } 29 30 @Override 31 public void onDestroy() { 32 // TODO Auto-generated method stub 33 super.onDestroy(); 34 Log.d("MyIntentService","onDestroy"); 35 } 36 }
這裏首先要提供一個無參的構造方法,而且必須在其內部調用父類的有參構造方法(9至12行),咱們在第10行手動將服務的名字改成「MyIntentService」。
而後在子類中實現onHandleIntent()這個抽象方法,能夠在這個方法裏去處理一些具體的邏輯,咱們就用三次for循環,打印當前線程的id,每次延時1秒。
由於這個服務在運行結束後會自動中止,因此咱們在onDestroy()方法中打印日誌驗證一下。
(2)在清單文件中對服務進行註冊服務:
<service android:name=".MyIntentService"> </service>
(3)在activity_main.xml中添加一個按鈕button3_stop_intentservice,用於啓動MyIntentService服務,代碼略。
(4)在MainActivity裏面加入啓動IntentService的邏輯,核心代碼以下:
1 case R.id.button3_stop_intentservice: 2 Log.d("MainActivity","主線程的id是:"+Thread.currentThread().getId()); 3 Intent intentService = new Intent(this,MyIntentService.class); 4 startService(intentService); 5 default:
咱們在第02行中,打印主線程的id。
運行程序,點擊按鈕button3_stop_intentservice,顯示以下:
因而可知,啓動一個IntentService和啓動一個普通的Service,步驟是同樣的。
四、Service和Thread的關係:
很多Android初學者均可能會有這樣的疑惑,Service和Thread到底有什麼關係呢?何時應該用Service,何時又應該用Thread?答案可能會有點讓你吃驚,由於Service和Thread之間沒有任何關係!
之因此有很多人會把它們聯繫起來,主要就是由於Service的後臺概念。Thread咱們你們都知道,是用於開啓一個子線程,在這裏去執行一些耗時操做就不會阻塞主線程的運行。而Service咱們最初理解的時候,總會以爲它是用來處理一些後臺任務的,一些比較耗時的操做也能夠放在這裏運行,這就會讓人產生混淆了。可是,若是我告訴你Service實際上是運行在主線程裏的,你還會以爲它和Thread有什麼關係嗎?
其實,後臺和子線程是兩個徹底不一樣的概念:
Android的後臺就是指,它的運行是徹底不依賴UI的。即便Activity被銷燬,或者程序被關閉,只要進程還在,Service就能夠繼續運行。好比說一些應用程序,始終須要與服務器之間始終保持着心跳鏈接,就可使用Service來實現。你可能又會問,Service既然是運行在主線程裏,在這裏一直執行着心跳鏈接,難道就不會阻塞主線程的運行嗎?固然會,可是咱們能夠在Service中再建立一個子線程,而後在這裏去處理耗時邏輯就沒問題了。
既然在Service裏也要建立一個子線程,那爲何不直接在Activity裏建立呢?這是由於Activity很難對Thread進行控制,當Activity被銷燬以後,就沒有任何其它的辦法能夠再從新獲取到以前建立的子線程的實例;並且在一個Activity中建立的子線程,另外一個Activity沒法對其進行操做。可是Service就不一樣了,全部的Activity均可以與Service進行關聯,而後能夠很方便地操做其中的方法,即便Activity被銷燬了,以後只要從新與Service創建關聯,就又可以獲取到原有的Service中Binder的實例。所以,使用Service來處理後臺任務,Activity就能夠放心地finish,徹底不須要擔憂沒法對後臺任務進行控制的狀況。
因此說,一個比較標準的Service,就能夠寫成本段中第1節的樣子。
4、使用Bind Service完成Service和Activity之間的通訊
有沒有什麼辦法能讓它們倆的關聯更多一些呢?好比說在Activity中指揮Service去幹什麼,Service就去幹什麼。固然能夠,只須要讓Activity和Service創建關聯就行了。
一、Bind Service的介紹:
應用程序組件(客戶端)經過調用bindService()方法可以綁定服務,而後Android系統會調用服務的onBind()回調方法,則個方法會返回一個跟服務器端交互的Binder對象。
這個綁定是異步的,bindService()方法當即返回,而且不給客戶端返回IBinder對象。要接收IBinder對象,客戶端必須建立一個ServiceConnection類的實例,而且把這個實例傳遞給bindService()方法。ServiceConnection對象包含了一個系統調用的傳遞IBinder對象的回調方法。
注意:只有Activity、Service、Content Provider可以綁定服務;BroadcastReceiver廣播接收器不能綁定服務。
二、實現Service和Activity之間通訊步驟:
咱們依然在第二段中的項目ServiceTest基礎上進行修改。
觀察上面第二段中MyService中的代碼,你會發現一直有一個onBind()方法咱們都沒有使用到,這個方法其實就是用於和Activity創建關聯的,修改MyService中的代碼,以下所示:
1 package com.example.servicetest; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.Binder; 6 import android.os.IBinder; 7 import android.util.Log; 8 9 public class MyService extends Service { 10 11 public static final String TAG = "MyService"; 12 13 private MyBinder mBinder = new MyBinder(); 14 15 @Override 16 public void onCreate() { 17 super.onCreate(); 18 Log.d(TAG, "onCreate"); 19 } 20 21 @Override 22 public int onStartCommand(Intent intent, int flags, int startId) { 23 Log.d(TAG, "onStartCommand"); 24 return super.onStartCommand(intent, flags, startId); 25 } 26 27 @Override 28 public void onDestroy() { 29 super.onDestroy(); 30 Log.d(TAG, "onDestroy"); 31 } 32 33 @Override 34 public IBinder onBind(Intent intent) { 35 return mBinder; //在這裏返回新建的MyBinder類 36 } 37 38 //MyBinder類,繼承Binder:讓裏面的方法執行下載任務,並獲取下載進度 39 class MyBinder extends Binder { 40 41 public void startDownload() { 42 Log.d("TAG", "startDownload() executed"); 43 // 執行具體的下載任務 44 } 45 public int getProgress(){ 46 Log.d("TAG", "getProgress() executed"); 47 return 0; 48 } 49 50 } 51 52 }
38至50行:新建一個MyBinder類,繼承Binder:讓裏面的方法執行下載任務,並獲取下載進度。固然,這裏只是兩個模擬方法,並無實現真正的功能,咱們經過打印日誌的形式來體現。
接着建立MyBinder的實例(13行),而後在onBind()方法裏返回這個實例(35行)。
核心代碼是35行,返回這個mBinder,是一個IBinder類型,就能夠把這個IBinder類型傳遞到MainActivity中,從而調用Service裏面的方法。下面就要看一看,在MainActivity是如何調用Service裏面的兩個方法的。
(2)檢查清單文件,是否已經對Service進行註冊:
<service android:name=".MyService" > </service>
(3)在activity_main.xml中繼續添加兩個按鈕button3_bind_service和button4_unbind_service,用於綁定服務和取消綁定服務。最終,activity_main.xml的完整代碼以下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button1_start_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start Service" /> <Button android:id="@+id/button2_stop_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Stop Service" /> <Button android:id="@+id/button3_bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="bind Service" /> <Button android:id="@+id/button4_unbind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="unbind Service" /> </LinearLayout>
(4)接下來再修改MainActivity中的代碼,讓MainActivity和MyService之間創建關聯,代碼以下所示:
1 package com.example.servicetest; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.util.Log; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 14 public class MainActivity extends Activity implements OnClickListener { 15 16 private Button button1_start_service; 17 private Button button2_stop_service; 18 private Button button3_bind_service; 19 private Button button4_unbind_service; 20 21 private MyService.MyBinder myBinder; 22 23 //匿名內部類:服務鏈接對象 24 private ServiceConnection connection = new ServiceConnection() { 25 26 //當服務異常終止時會調用。注意,解除綁定服務時不會調用 27 @Override 28 public void onServiceDisconnected(ComponentName name) { 29 } 30 31 //和服務綁定成功後,服務會回調該方法 32 @Override 33 public void onServiceConnected(ComponentName name, IBinder service) { 34 myBinder = (MyService.MyBinder) service; 35 //在Activity中調用Service裏面的方法 36 myBinder.startDownload(); 37 myBinder.getProgress(); 38 } 39 }; 40 41 @Override 42 protected void onCreate(Bundle savedInstanceState) { 43 super.onCreate(savedInstanceState); 44 setContentView(R.layout.activity_main); 45 button1_start_service = (Button) findViewById(R.id.button1_start_service); 46 button2_stop_service = (Button) findViewById(R.id.button2_stop_service); 47 button3_bind_service = (Button) findViewById(R.id.button3_bind_service); 48 button4_unbind_service = (Button) findViewById(R.id.button4_unbind_service); 49 50 button1_start_service.setOnClickListener(this); 51 button2_stop_service.setOnClickListener(this); 52 button3_bind_service.setOnClickListener(this); 53 button4_unbind_service.setOnClickListener(this); 54 } 55 56 @Override 57 public void onClick(View v) { 58 switch (v.getId()) { 59 case R.id.button1_start_service: 60 Intent startIntent = new Intent(this, MyService.class); 61 startService(startIntent); 62 break; 63 case R.id.button2_stop_service: 64 Intent stopIntent = new Intent(this, MyService.class); 65 stopService(stopIntent); 66 break; 67 case R.id.button3_bind_service: 68 Intent bindIntent = new Intent(this, MyService.class); 69 bindService(bindIntent, connection, BIND_AUTO_CREATE); 70 break; 71 case R.id.button4_unbind_service: 72 unbindService(connection); 73 break; 74 75 default: 76 break; 77 } 78 } 79 80 }
能夠看到,這裏咱們首先建立了一個ServiceConnection的匿名類(24行),在裏面重寫了onServiceConnected()方法和onServiceDisconnected()方法,若是當前Activity與服務鏈接成功後,服務會回調onServiceConnected()方法,
在onServiceConnected()方法中,咱們又經過向下轉型獲得了MyBinder的實例(34行),有了這個實例,Activity和Service之間的關係就變得很是緊密了。如今咱們能夠在Activity中根據具體的場景來調用MyBinder中的任何public方法(3六、37行),即實現了Activity指揮Service幹什麼Service就去幹什麼的功能。
固然,如今Activity和Service其實還沒關聯起來了呢,這個功能是在Bind Service按鈕的點擊事件裏完成的。能夠看到,這裏咱們仍然是構建出了一個Intent對象,而後調用bindService()方法將Activity和Service進行綁定。bindService()方法接收三個參數,第一個參數就是剛剛構建出的Intent對象,第二個參數是前面建立出的ServiceConnection的實例,第三個參數是一個標誌位,這裏傳入BIND_AUTO_CREATE表示在Activity和Service創建關聯後會自動建立Service(即便以前沒有建立Service也沒有關係),這會使得MyService中的onCreate()方法獲得執行,但onStartCommand()方法不會執行。
而後如何咱們想解除Activity和Service之間的關聯怎麼辦呢?調用一下unbindService()方法就能夠了,這也是Unbind Service按鈕的點擊事件裏實現的邏輯。
如今讓咱們從新運行一下程序吧,在MainActivity中點擊一下Bind Service按鈕,LogCat裏的打印日誌以下圖所示:
能夠看到,只點擊了Bind Service按鈕,可是oncreate()方法獲得了執行,而onStartCommand()方法不會執行。
另外須要注意,任何一個Service在整個應用程序範圍內都是通用的,即MyService不只能夠和MainActivity創建關聯,還能夠和任何一個Activity創建關聯,並且在創建關聯時它們均可以獲取到相同的MyBinder實例。
如何銷燬Service:
根據上面第一段的知識,咱們介紹了銷燬Service最簡單的一種狀況:如今卸載程序,從新運行程序,點擊Start Service按鈕啓動Service,再點擊Stop Service按鈕中止Service,這樣MyService就被銷燬了:
如今回到本段內容。卸載程序,從新開始。那麼若是咱們只點擊的Bind Service按鈕呢?因爲在綁定Service的時候指定的標誌位是BIND_AUTO_CREATE,說明點擊Bind Service按鈕的時候Service也會被建立,這時應該怎麼銷燬Service呢?其實也很簡單,點擊一下Unbind Service按鈕,將Activity和Service的關聯解除就能夠了:
以上這兩種銷燬的方式都很好理解。那麼若是咱們既點擊了Start Service按鈕,又點擊了Bind Service按鈕會怎麼樣呢?這個時候你會發現,無論你是單獨點擊Stop Service按鈕仍是Unbind Service按鈕,Service都不會被銷燬,必要將Unbind Service按鈕和Stop Service按鈕都點擊一下(沒有前後順序),Service纔會被銷燬。也就是說,點擊Stop Service按鈕只會讓Service中止,點擊Unbind Service按鈕只會讓Service和Activity解除關聯,一個Service必需要在既沒有和任何Activity關聯又處理中止狀態的時候纔會被銷燬。
點擊Unbind Service按鈕後,再次點擊Unbind Service按鈕按鈕引起的問題:
假設如今Service和Activity已經相關聯了,點擊Unbind Service按鈕可以解除綁定,若是繼續點擊Unbind Service按鈕,程序會異常退出,這說明代碼不夠完善,咱們須要在代碼中加一個判斷是否綁定的標記mBound。在改MainActivity中增長一部分代碼,最終改MainActivity的完整代碼以下:(加粗字體是添加的內容)
1 package com.example.servicetest02; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.util.Log; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 14 public class MainActivity extends Activity implements OnClickListener { 15 private Button button1_start_service; 16 private Button button2_stop_service; 17 private Button button3_bind_service; 18 private Button button4_unbind_service; 19 private MyService.MyBinder myBinder; 20 21 boolean mBound = false; //一開始,並無和Service綁定.這個參數是用來顯示綁定狀態 22 23 //匿名內部類:服務鏈接對象 24 private ServiceConnection connection = new ServiceConnection() { 25 26 //當服務異常終止時會調用。注意,解除綁定服務時不會調用 27 @Override 28 public void onServiceDisconnected(ComponentName name) { 29 mBound = false; //服務異常終止時,狀態爲未綁定 30 } 31 32 //和服務綁定成功後,服務會回調該方法 33 @Override 34 public void onServiceConnected(ComponentName name, IBinder service) { 35 myBinder = (MyService.MyBinder) service; 36 //在Activity中調用Service裏面的方法 37 myBinder.startDownload(); 38 myBinder.getProgress(); 39 mBound = true; //true說明是綁定狀態 40 } 41 }; 42 @Override 43 protected void onCreate(Bundle savedInstanceState) { 44 super.onCreate(savedInstanceState); 45 setContentView(R.layout.activity_main); 46 button1_start_service = (Button) findViewById(R.id.button1_start_service); 47 button2_stop_service = (Button) findViewById(R.id.button2_stop_service); 48 button3_bind_service = (Button) findViewById(R.id.button3_bind_service); 49 button4_unbind_service = (Button) findViewById(R.id.button4_unbind_service); 50 button1_start_service.setOnClickListener(this); 51 button2_stop_service.setOnClickListener(this); 52 button3_bind_service.setOnClickListener(this); 53 button4_unbind_service.setOnClickListener(this); 54 } 55 @Override 56 public void onClick(View v) { 57 switch (v.getId()) { 58 case R.id.button1_start_service: 59 Intent startIntent = new Intent(this, MyService.class); 60 startService(startIntent); 61 break; 62 case R.id.button2_stop_service: 63 Intent stopIntent = new Intent(this, MyService.class); 64 stopService(stopIntent); 65 break; 66 case R.id.button3_bind_service: 67 Intent bindIntent = new Intent(this, MyService.class); 68 bindService(bindIntent, connection, BIND_AUTO_CREATE); 69 break; 70 case R.id.button4_unbind_service: 71 //若是和Service是綁定的狀態,就解除綁定。 72 if(mBound){ 73 unbindService(connection); 74 mBound=false; 75 } 76 break; 77 78 default: 79 break; 80 } 81 } 82 }
添加的代碼是第21行、29行、72行至74行。
這樣的話,連續點擊Unbind Service按鈕,就不會使程序出現異常。
三、started服務與bind服務的區別:
區別一:生命週期
區別二:參數傳遞
實際開發中的技巧;
四、Service的生命週期:
一旦在項目的任何位置調用了Context的startService()方法,相應的服務就會啓動起來,並回調onstartCommand()方法。若是這個服務以前尚未建立過,onCreate()方法會先於onstartCommand()方法執行。服務啓動事後,會一直保持運行狀態,直到stopService()或stopself()方法被調用。注意雖然每次調用一次startService()方法,onstartCommand()方法就會以執行一次,但實際上每一個服務都只會存在一個實例。因此無論你調用了多少次startService()方法,只需調用一次stopService()或stopself()方法,服務就會中止。
另外,還能夠調用Context的bindService()來獲取一個服務的持久鏈接,這時就會回調服務中的onBind()方法。相似地,若是這個服務以前尚未建立過,onCreate()方法會先於onBind()方法執行。以後調用方能夠獲取到onBind()方法裏返回的IBinder對象的實例,這樣,就能自由地和服務進行通訊了。只要調用方和服務之間的鏈接沒有斷開,服務就會一直保持運行狀態。
5、使用Bind Service完成IPC進程間通訊:(在同一個APP內模擬)
既然是在在同一個APP內模擬進程間通訊,其實就是完成進程內通訊,可是原理都是同樣的嘛。
也就是說,要實現:讓Activity與一個遠程Service創建關聯,這就要使用AIDL來進行跨進程通訊了(IPC)。這裏把Bind Service及其餘的概念再重複一下:
一、Bind Service的介紹:
應用程序組件(客戶端)經過調用bindService()方法可以綁定服務,而後Android系統會調用服務的onBind()回調方法,則個方法會返回一個跟服務器端交互的Binder對象。
這個綁定是異步的,bindService()方法當即返回,而且不給客戶端返回IBinder對象。要接收IBinder對象,客戶端必須建立一個ServiceConnection類的實例,而且把這個實例傳遞給bindService()方法。ServiceConnection對象包含了一個系統調用的傳遞IBinder對象的回調方法。
注意:只有Activity、Service、Content Provider可以綁定服務;BroadcastReceiver廣播接收器不能綁定服務。
二、在客戶端綁定一個服務的步驟:
(1)實現ServiceConnection抽象類。實現過程當中,必須重寫一下兩個回調方法:
(2)調用bindService()方法來傳遞ServiceConnection類的實現;
(3)當系統調用你的onServiceConnected()回調方法時,你就能夠開始使用接口中定義的方法來調用服務了
(4)調用unbindService()方法斷開與服務的連接。
注:bindService()和unbindService()方法都是Context類中的方法。
三、IPC(Inter-Process Communication)進程間通訊機制:
在同一進程中,各個組件進行通訊是十分方便的,普通的函數調用就能夠解決;可是對於不一樣的進程中的組件來講,要進行通訊,就須要用到Android的IPC機制了。
對應用開發者來講,Android的IBinder/Binder框架實現了Android的IPC通訊。固然,IBinder/Binder框架也能夠用來實現進程內通訊(本地通訊),也能夠實現進程間通訊(遠程通訊)
從Android SDK中對IBinder/Binder的解釋可知,IBinder/Binder是Android遠程對象的基本接口,它是Android用於提供高性能IPC通訊而設計的一套輕量級遠程調用機制的核心部分。該接口描述了與一個遠程對象進行通訊的抽象協議。
四、AIDL(Android Interface Definition Language)Android接口定義語言:
AIDL它能夠用於讓某個Service與多個應用程序組件之間進行跨進程通訊,從而能夠實現多個應用程序共享同一個Service的功能。
AIDL支持的類型:八大基本數據類型、String類型、CharSequence、List、Map、自定義。
來看下面的這張原理圖:
上圖中,若是A應用程序想訪問B應用程序中的業務對象,能夠先讓A綁定B應用中的Service,而後經過Service去訪問B中的業務對象。咱們能夠用AIDL來描述須要被別人調用的接口(即B中的業務對象)。
五、IPC(進程間通信)具體的步驟以下:
下面就經過代碼來實現。
六、讓Activity與一個遠程Service創建關聯的步驟:(在同一個APP內模擬)
新建一個全新的Android工程ServiceTest02。
(1)新建IPerson.aidl文件,代碼以下所示:
1 package com.example.servicetest02; 2 interface IPerson{ 3 void setName(String name); 4 void setSex(String sex); 5 void setAge(int age); 6 String getPerson(); 7 }
這個文件裏,添加咱們須要的業務方法。第01行是包名。注意不要寫public等修飾符。(若是這個文件寫錯了,程序會報錯,後面的Java文件也不會自從生成)
文件保存以後,ADT會在gen目錄下自動生成一個對應的Java文件,以下圖所示:
以後,程序運行的時候使用的是這個Java文件,與aidl文件就沒有關係了。
咱們來大體分析一下這個自動生成的Java文件。完整版代碼以下:
1 /* 2 * This file is auto-generated. DO NOT MODIFY. 3 * Original file: E:\\workspace\\xiongdilian\\ServiceTest02\\src\\com\\example\\servicetest02\\IPerson.aidl 4 */ 5 package com.example.servicetest02; 6 7 public interface IPerson extends android.os.IInterface { 8 /** Local-side IPC implementation stub class. */ 9 public static abstract class Stub extends android.os.Binder implements 10 com.example.servicetest02.IPerson { 11 private static final java.lang.String DESCRIPTOR = "com.example.servicetest02.IPerson"; 12 13 /** Construct the stub at attach it to the interface. */ 14 public Stub() { 15 this.attachInterface(this, DESCRIPTOR); 16 } 17 18 /** 19 * Cast an IBinder object into an com.example.servicetest02.IPerson 20 * interface, generating a proxy if needed. 21 */ 22 public static com.example.servicetest02.IPerson asInterface( 23 android.os.IBinder obj) { 24 if ((obj == null)) { 25 return null; 26 } 27 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 28 if (((iin != null) && (iin instanceof com.example.servicetest02.IPerson))) { 29 return ((com.example.servicetest02.IPerson) iin); 30 } 31 return new com.example.servicetest02.IPerson.Stub.Proxy(obj); 32 } 33 34 @Override 35 public android.os.IBinder asBinder() { 36 return this; 37 } 38 39 @Override 40 public boolean onTransact(int code, android.os.Parcel data, 41 android.os.Parcel reply, int flags) 42 throws android.os.RemoteException { 43 switch (code) { 44 case INTERFACE_TRANSACTION: { 45 reply.writeString(DESCRIPTOR); 46 return true; 47 } 48 case TRANSACTION_setName: { 49 data.enforceInterface(DESCRIPTOR); 50 java.lang.String _arg0; 51 _arg0 = data.readString(); 52 this.setName(_arg0); 53 reply.writeNoException(); 54 return true; 55 } 56 case TRANSACTION_setSex: { 57 data.enforceInterface(DESCRIPTOR); 58 java.lang.String _arg0; 59 _arg0 = data.readString(); 60 this.setSex(_arg0); 61 reply.writeNoException(); 62 return true; 63 } 64 case TRANSACTION_setAge: { 65 data.enforceInterface(DESCRIPTOR); 66 int _arg0; 67 _arg0 = data.readInt(); 68 this.setAge(_arg0); 69 reply.writeNoException(); 70 return true; 71 } 72 case TRANSACTION_getPerson: { 73 data.enforceInterface(DESCRIPTOR); 74 java.lang.String _result = this.getPerson(); 75 reply.writeNoException(); 76 reply.writeString(_result); 77 return true; 78 } 79 } 80 return super.onTransact(code, data, reply, flags); 81 } 82 83 private static class Proxy implements com.example.servicetest02.IPerson { 84 private android.os.IBinder mRemote; 85 86 Proxy(android.os.IBinder remote) { 87 mRemote = remote; 88 } 89 90 @Override 91 public android.os.IBinder asBinder() { 92 return mRemote; 93 } 94 95 public java.lang.String getInterfaceDescriptor() { 96 return DESCRIPTOR; 97 } 98 99 @Override 100 public void setName(java.lang.String name) 101 throws android.os.RemoteException { 102 android.os.Parcel _data = android.os.Parcel.obtain(); 103 android.os.Parcel _reply = android.os.Parcel.obtain(); 104 try { 105 _data.writeInterfaceToken(DESCRIPTOR); 106 _data.writeString(name); 107 mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0); 108 _reply.readException(); 109 } finally { 110 _reply.recycle(); 111 _data.recycle(); 112 } 113 } 114 115 @Override 116 public void setSex(java.lang.String sex) 117 throws android.os.RemoteException { 118 android.os.Parcel _data = android.os.Parcel.obtain(); 119 android.os.Parcel _reply = android.os.Parcel.obtain(); 120 try { 121 _data.writeInterfaceToken(DESCRIPTOR); 122 _data.writeString(sex); 123 mRemote.transact(Stub.TRANSACTION_setSex, _data, _reply, 0); 124 _reply.readException(); 125 } finally { 126 _reply.recycle(); 127 _data.recycle(); 128 } 129 } 130 131 @Override 132 public void setAge(int age) throws android.os.RemoteException { 133 android.os.Parcel _data = android.os.Parcel.obtain(); 134 android.os.Parcel _reply = android.os.Parcel.obtain(); 135 try { 136 _data.writeInterfaceToken(DESCRIPTOR); 137 _data.writeInt(age); 138 mRemote.transact(Stub.TRANSACTION_setAge, _data, _reply, 0); 139 _reply.readException(); 140 } finally { 141 _reply.recycle(); 142 _data.recycle(); 143 } 144 } 145 146 @Override 147 public java.lang.String getPerson() 148 throws android.os.RemoteException { 149 android.os.Parcel _data = android.os.Parcel.obtain(); 150 android.os.Parcel _reply = android.os.Parcel.obtain(); 151 java.lang.String _result; 152 try { 153 _data.writeInterfaceToken(DESCRIPTOR); 154 mRemote.transact(Stub.TRANSACTION_getPerson, _data, _reply, 155 0); 156 _reply.readException(); 157 _result = _reply.readString(); 158 } finally { 159 _reply.recycle(); 160 _data.recycle(); 161 } 162 return _result; 163 } 164 } 165 166 static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); 167 static final int TRANSACTION_setSex = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); 168 static final int TRANSACTION_setAge = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); 169 static final int TRANSACTION_getPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); 170 } 171 172 public void setName(java.lang.String name) 173 throws android.os.RemoteException; 174 175 public void setSex(java.lang.String sex) throws android.os.RemoteException; 176 177 public void setAge(int age) throws android.os.RemoteException; 178 179 public java.lang.String getPerson() throws android.os.RemoteException; 180 }
分析:
這個Java文件其實是一個接口,同時生成了在aidl文件中定義的四個方法,並拋出了遠程調用的異常。咱們按住Ctrl鍵,點開上圖中藍框部分的IInterface,查看一下源代碼:
能夠看到,在IInterface接口裏,定義了一個接口IBinder,這是IPC機制的核心接口。
再回來看IPerson.java文件的第9行定義了這樣一個抽象類:
上圖中的Stub類能夠比做存根。Stub類繼承了Binder類,同時實現了IPerson接口(沒有實現IPerson裏的方法)。因此進一步理解爲:Stub既是IPerson裏的內部類,也是一個IPerson。
(2)新建PersonImpl類,繼承IPerson.Stub類,重寫父類裏的方法。代碼以下:(也就是說,根據上面的java類,生成業務對象,即原理圖中B應用的業務對象)
1 package com.example.servicetest02; 2 3 import android.os.RemoteException; 4 5 public class PersonImpl extends IPerson.Stub{ 6 7 private String name; 8 private String sex; 9 private int age; 10 11 @Override 12 public void setName(String name) throws RemoteException { 13 // TODO Auto-generated method stub 14 this.name = name; 15 16 } 17 18 @Override 19 public void setSex(String sex) throws RemoteException { 20 // TODO Auto-generated method stub 21 this.sex = sex; 22 23 } 24 @Override 25 public void setAge(int age) throws RemoteException { 26 // TODO Auto-generated method stub 27 this.age = age; 28 29 } 30 31 @Override 32 public String getPerson() throws RemoteException { 33 // TODO Auto-generated method stub 34 return "name="+name+",sex="+sex+",age="+age; 35 } 36 }
(3)新建類MyService,代碼以下:
1 package com.example.servicetest02; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.util.Log; 7 8 public class MyService extends Service { 9 10 public static final String TAG = "MyService"; 11 12 PersonImpl mBinder = new PersonImpl(); 13 14 @Override 15 public void onCreate() { 16 super.onCreate(); 17 Log.d(TAG, "onCreate"); 18 } 19 20 @Override 21 public int onStartCommand(Intent intent, int flags, int startId) { 22 Log.d(TAG, "onStartCommand"); 23 return super.onStartCommand(intent, flags, startId); 24 } 25 26 @Override 27 public void onDestroy() { 28 super.onDestroy(); 29 Log.d(TAG, "onDestroy"); 30 } 31 32 @Override 33 public IBinder onBind(Intent intent) { 34 Log.d("MyService", "onBind"); 35 return mBinder; //在這裏返回新建的MyBinder類 36 } 37 38 }
核心代碼:12行和35行。
由於PersonImpl類繼承了IPerson.Stub,而Stub繼承了Binder,Binder又實現了IBinder。因此,PersonImpl能夠理解爲一個IBinder。因而能夠在第35行返回PersonImpl的實例。
(4)在清單文件中添加權限:
<service android:name=".MyService"> </service>
如今,B應用的業務對象和服務創建好了。B應用的業務對象經過與Service綁定,讓Service把業務對象暴露給了A應用或者其餘的應用。也就是說,Service最終並無實現業務功能。
若是要讓A應用來訪問,該怎麼作呢?
(5)在activity_main.xml中添加兩個按鈕button_bind_service和button_unbind_service,用於綁定遠程服務和取消綁定。activity_main.xml的代碼以下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button_bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="bind Service" /> <Button android:id="@+id/button_unbind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="unbind Service" /> </LinearLayout>
(6)MainActivity中的代碼,以下所示:
1 package com.example.servicetest02; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.RemoteException; 10 import android.util.Log; 11 import android.view.View; 12 import android.view.View.OnClickListener; 13 import android.widget.Button; 14 15 public class MainActivity extends Activity implements OnClickListener { 16 17 public static final String TAG = "MainActivity"; 18 private Button button_bind_service; 19 private Button button_unbind_service; 20 21 private IPerson person; 22 23 boolean mBound = false; // 一開始,並無和Service綁定.這個參數是用來判斷綁定狀態 24 25 // 匿名內部類:服務鏈接對象 26 private ServiceConnection connection = new ServiceConnection() { 27 28 // 當服務異常終止時會調用。注意,解除綁定服務時不會調用 29 @Override 30 public void onServiceDisconnected(ComponentName name) { 31 mBound = false; // 服務異常終止時,狀態爲未綁定 32 } 33 34 // 和服務綁定成功後,服務會回調該方法。在這個方法裏調用的業務對象中的內容 35 @Override 36 public void onServiceConnected(ComponentName name, IBinder service) { 37 Log.d(TAG, "onServiceConnected"); 38 person = IPerson.Stub.asInterface(service); // 獲得person對象 39 Log.d("person", "person對象的內存地址是" + person); // 打印出person對象的內存地址 40 try { 41 person.setName("生命壹號"); 42 person.setAge(22); 43 person.setSex("男"); 44 String p = person.getPerson(); 45 Log.d("person", "person的信息是" + p); 46 } catch (RemoteException e) { 47 // TODO Auto-generated catch block 48 e.printStackTrace(); 49 } 50 mBound = true; //true說明是綁定狀態 51 52 } 53 }; 54 55 @Override 56 protected void onCreate(Bundle savedInstanceState) { 57 super.onCreate(savedInstanceState); 58 setContentView(R.layout.activity_main); 59 button_bind_service = (Button) findViewById(R.id.button_bind_service); 60 button_unbind_service = (Button) findViewById(R.id.button_unbind_service); 61 button_bind_service.setOnClickListener(this); 62 button_unbind_service.setOnClickListener(this); 63 64 } 65 66 @Override 67 public void onClick(View v) { 68 switch (v.getId()) { 69 case R.id.button_bind_service: 70 Intent bindIntent = new Intent(this, MyService.class); 71 bindService(bindIntent, connection, BIND_AUTO_CREATE); 72 break; 73 case R.id.button_unbind_service: 74 // 若是和Service是綁定的狀態,就解除綁定。 75 if (mBound) { 76 unbindService(connection); 77 mBound = false; 78 } 79 break; 80 81 default: 82 break; 83 } 84 } 85 86 }
核心代碼是第38行:能夠看到,這裏首先使用了MyAIDLService.Stub.asInterface()方法將傳入的IBinder對象傳換成了IPerson對象,接下來就能夠調用在IPerson.aidl文件中定義的全部接口了(41至44行)。調用以後,咱們在後臺打印輸出。
運行程序,點擊按鈕,效果以下:
因而可知,咱們確實已經成功實現跨進程通訊了,在一個進程中訪問到了另一個進程中的方法。 注意,這個Service是運行在主線程當中的,畢竟咱們是在本地模擬的嘛。
另外注意藍色箭頭處,能夠看出,這個person其實就是personImpl,由於是在本地調用。因此說,目前的跨進程通訊其實並無什麼實質上的做用,由於這只是在一個Activity裏調用了同一個應用程序的Service裏的方法。而跨進程通訊的真正意義是爲了讓一個應用程序去訪問另外一個應用程序中的Service,以實現共享Service的功能。那麼下面咱們天然要學習一下,如何才能在其它的應用程序中調用到MyService裏的方法。
繼續回顧第(1)步中自動生成的IPerson.java文件,截取第22至32行,摘抄以下:
1 public static com.example.servicetest02.IPerson asInterface( 2 android.os.IBinder obj) { 3 if ((obj == null)) { 4 return null; 5 } 6 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 7 if (((iin != null) && (iin instanceof com.example.servicetest.IPerson))) { 8 return ((com.example.servicetest.IPerson) iin); 9 } 10 return new com.example.servicetest.IPerson.Stub.Proxy(obj); 11 }
代碼解釋:
上方第06行的iin表明的是,查詢本地對象返回的結果。
07行:若是iin不爲空,而且iin爲IPerson,那就將iin強制轉換爲IPerson(08行)。很顯然,這裏是進程內通訊(本地通訊)要用到的。也就是本段中的例子。
10行:表明的是進程間通訊(遠程通訊),此時第07行的if語句不成立,因而返回第10行的代理對象Proxy(obj)。
實現進程間通訊的兩個辦法:
若是要實現進程間通訊,就必須讓MainActivity和Service相互獨立。有兩個辦法:
(1)辦法一:將本地的Service設置爲遠程。只須要在清單文件中註冊Service的時候將它的android:process屬性指定成:remote就能夠了,代碼以下所示:
<service android:name=".MyService" android:process=":remote"> </service>
後臺打印日誌以下:
上圖的紅框部分顯示,Service和Activity並不是在同一個線程內,連包名都不同。而IPeron也並不是是本地的IPeron。
若是將這種方法應用到上面的第四段中,情形是這樣的:
點擊button1_start_service,服務啓動。既然已經將Service的android:process屬性指定成:remote,此時Service和Activity不在同一個線程內,那麼即便在Service的onStartCommand()方法中執行耗時操做而不從新開啓子線程,程序也不會阻塞。
可是,若是點擊button3_bind_service按鈕綁定服務,程序會崩潰的。這是由於,目前MyService已是一個遠程Service了,Activity和Service運行在兩個不一樣的進程當中,這時就不能再使用傳統的創建關聯的方式,程序也就崩潰了。
如今咱們總結一下:
第四段中使用的是傳統的方式和Service創建關聯,默認MainActivity和MyService在同一個線程內,若是將Service的android:process屬性指定成:remote,此時MainActivity和MyService將在不一樣的線程內,可是沒法綁定服務。
本段中(第五段)使用的是IPC跨進程通訊,MainActivity和MyService在不一樣的進程中,能夠綁定遠程服務。
(2)辦法二:新建另一個工程,真正實現遠程通訊。這就是咱們下一段(第六段)要講的內容。
咱們仍是先回過頭來再鞏固一下本段中AIDL的知識吧。
七、AIDL支持的自定義數據類型:
咱們在本段中的第4小結講到,AIDL支持的類型:八大基本數據類型、String類型、CharSequence、List、Map、自定義,那咱們就來詳細說下這個自定義數據類型。
因爲這是在不一樣的進程之間傳遞數據,Android對這類數據的格式支持是很是有限的,基本上只能傳遞Java的基本數據類型、字符串、List或Map等。那麼若是我想傳遞一個自定義的類該怎麼辦呢?這就必需要讓這個類去實現Parcelable接口,而且要給這個類也定義一個同名的AIDL文件進行聲明。這部份內容並不複雜,並且和Service關係不大。具體操做以下:
從新建一個工程ServiceTest03。步驟以下:
(1)新建一個Student類去實現Parcelable接口。Student類是做爲傳遞的自定義類:
1 package com.example.servicetest; 2 3 import android.os.Parcel; 4 import android.os.Parcelable; 5 6 public class Student implements Parcelable { 7 private String name; 8 private String sex; 9 10 public Student() { 11 super(); 12 } 13 14 public String getName() { 15 return name; 16 } 17 18 public void setName(String name) { 19 this.name = name; 20 } 21 22 public String getSex() { 23 return sex; 24 } 25 26 public void setSex(String sex) { 27 this.sex = sex; 28 } 29 30 @Override 31 public int describeContents() { 32 // TODO Auto-generated method stub 33 return 0; 34 } 35 36 // 重寫父類的方法:將須要傳送的數據放進來 37 @Override 38 public void writeToParcel(Parcel dest, int flags) { 39 // TODO Auto-generated method stub 40 dest.writeString(name); 41 dest.writeString(name); 42 } 43 44 public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() { 45 public Student createFromParcel(Parcel in) { 46 Student s = new Student(); 47 s.setName(in.readString()); 48 s.setSex(in.readString()); 49 return s; 50 } 51 52 public Student[] newArray(int size) { 53 return new Student[size]; 54 } 55 }; 56 57 }
咱們在這個類中放入了name和age這兩個參數,並實現了Parcelable接口。注意第44行至55行代碼的修改。
接着,新建一個和類同名的aidl文件,即新建Student.aidl,代碼以下:
parcelable Student;
注意這個parcelable的第一個字母是小寫。
繼續,新建IStudent.aidl,做爲須要遠程傳遞的業務方法。代碼以下:
1 package com.example.servicetest03; 2 3 import com.example.servicetest03.Student; 4 interface IStudent{ 5 void setStudent(String name,String sex); 6 Student getStudent(); 7 }
核心代碼是第03行,雖然Student類文件和本文件是在同一個包下,可是依然要導包,不然將沒法識別Student類。而後在第06行代碼中,就能夠把Student這個類傳遞出去了。注意了,第06行返回的是Student類型,這不就是ADIL所支持的自定義類型嘛。
文件結構以下:
綜上所述,傳遞自定義類,有三個步驟:
那麼接下來的步驟就和本段中的第6小節同樣了,就再也不多解釋了,這裏只貼代碼:
(2)新建StudentImpl類,繼承IStudent.Stub類。代碼以下:(也就是說,根據步驟(1)中的java類,生成業務對象,即原理圖中B應用的業務對象)
1 package com.example.servicetest03; 2 3 import android.os.RemoteException; 4 5 //業務對象的實現 6 public class StudentImpl extends IStudent.Stub{ 7 8 private Student student; 9 10 public StudentImpl(){ 11 student = new Student(); 12 } 13 @Override 14 public void setStudent(String name, String sex) throws RemoteException { 15 // TODO Auto-generated method stub 16 student.setName(name); 17 student.setSex(sex); 18 19 } 20 21 @Override 22 public Student getStudent() throws RemoteException { 23 // TODO Auto-generated method stub 24 return student; 25 } 26 27 }
(3)新建Service類,代碼以下:
1 package com.example.servicetest03; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.util.Log; 7 8 public class MyService extends Service { 9 10 public static final String TAG = "MyService"; 11 12 private StudentImpl studentImpl ; 13 14 @Override 15 public void onCreate() { 16 super.onCreate(); 17 Log.d(TAG, "onCreate"); 18 } 19 20 @Override 21 public int onStartCommand(Intent intent, int flags, int startId) { 22 Log.d(TAG, "onStartCommand"); 23 return super.onStartCommand(intent, flags, startId); 24 } 25 26 @Override 27 public void onDestroy() { 28 super.onDestroy(); 29 Log.d(TAG, "onDestroy"); 30 } 31 32 @Override 33 public IBinder onBind(Intent intent) { 34 Log.d("MyService", "onBind"); 35 studentImpl = new StudentImpl(); 36 return studentImpl; //在這裏返回新建的MyBinder類 37 } 38 39 }
核心代碼:12行、35行、36行。
(4)在清單文件中添加權限:
<service android:name=".MyService"> </service>
如今,B應用的業務對象和服務創建好了。B應用的業務對象經過與Service綁定,讓Service把業務對象暴露給了A應用或者其餘的應用。也就是說,Service最終並無實現業務功能。
若是要讓A應用來訪問,該怎麼作呢?
(5)在activity_main.xml中添加兩個按鈕button1_setStudent和button2_getStudent。activity_main.xml的代碼以下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button1_setStudent" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="調用setStudent方法" /> <Button android:id="@+id/button2_getStudent" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="調用getStudent方法" /> </LinearLayout>
注:佈局文件裏再也不添加綁定服務和取消綁定的按鈕,咱們稍後在Activity的生命週期裏完成這件事。
(6)MainActivity中的代碼,以下所示:
1 package com.example.servicetest03; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.RemoteException; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 import android.widget.Toast; 14 15 16 public class MainActivity extends Activity implements OnClickListener { 17 public static final String TAG = "MainActivity"; 18 private Button button1_setStudent; 19 private Button button2_getStudent; 20 private IStudent studentImpl; 21 boolean mBound = false; // 一開始,並無和Service綁定.這個參數是用來判斷綁定狀態 22 23 @Override 24 protected void onCreate(Bundle savedInstanceState) { 25 super.onCreate(savedInstanceState); 26 setContentView(R.layout.activity_main); 27 button1_setStudent = (Button) findViewById(R.id.button1_setStudent); 28 button2_getStudent = (Button) findViewById(R.id.button2_getStudent); 29 button1_setStudent.setOnClickListener(this); 30 button2_getStudent.setOnClickListener(this); 31 } 32 33 34 // 匿名內部類:服務鏈接對象 35 private ServiceConnection connection = new ServiceConnection() { 36 // 當服務異常終止時會調用。注意,解除綁定服務時不會調用 37 @Override 38 public void onServiceDisconnected(ComponentName name) { 39 mBound = false; // 服務異常終止時,狀態爲未綁定 40 } 41 // 和服務綁定成功後,服務會回調該方法。在這個方法裏調用的業務對象中的內容 42 @Override 43 public void onServiceConnected(ComponentName name, IBinder service) { 44 studentImpl = IStudent.Stub.asInterface(service); // 獲得person對象 45 mBound = true; //true說明是綁定狀態 46 } 47 }; 48 49 //程序啓動時,開始綁定服務 50 @Override 51 protected void onStart() { 52 super.onStart(); 53 Intent bindIntent = new Intent(this, MyService.class); 54 bindService(bindIntent, connection, BIND_AUTO_CREATE); 55 } 56 57 //程序退出時,取消綁定服務 58 @Override 59 protected void onDestroy() { 60 super.onDestroy(); 61 // 若是和Service是綁定的狀態,就解除綁定。 62 if (mBound) { 63 unbindService(connection); 64 mBound = false; 65 } 66 } 67 68 69 @Override 70 public void onClick(View v) { 71 switch (v.getId()) { 72 //點擊button1_setStudent按鈕,設置Student的值 73 case R.id.button1_setStudent: 74 try { 75 studentImpl.setStudent("生命壹號", "男"); 76 Toast.makeText(this, "設置成功", Toast.LENGTH_SHORT).show(); 77 } catch (RemoteException e) { 78 // TODO Auto-generated catch block 79 e.printStackTrace(); 80 } 81 break; 82 //點擊button2_getStudent按鈕,獲取Student的值 83 case R.id.button2_getStudent: 84 Student s; 85 try { 86 s = studentImpl.getStudent(); 87 Toast.makeText(this, "name="+s.getName()+",sex="+s.getSex(), Toast.LENGTH_SHORT).show(); 88 } catch (RemoteException e) { 89 // TODO Auto-generated catch block 90 e.printStackTrace(); 91 } 92 break; 93 94 default: 95 break; 96 } 97 } 98 }
核心代碼是第44行。
咱們在第75行、86至87行使用到了IStudent中的業務方法。
運行程序,點擊第一個按鈕,而後點擊第二個按鈕,效果以下:
這樣,Acitivity就成功調用了遠程Service的自定義類。
6、使用Bind Service完成IPC進程間通訊:(兩個APP之間)
上一段中的跨進程通訊其實並無什麼實質上的做用,由於這只是在一個Activity裏調用了同一個應用程序的Service裏的方法。而跨進程通訊的真正意義是爲了讓一個應用程序去訪問另外一個應用程序中的Service,以實現共享Service的功能。那麼下面咱們天然要學習一下,如何才能在其它的應用程序中調用到MyService裏的方法。
在第四段中咱們已經知道,若是想要讓Activity與Service之間創建關聯,須要調用bindService()方法,並將Intent做爲參數傳遞進去,在Intent裏指定好要綁定的Service,核心代碼以下:
Intent bindIntent = new Intent(this, MyService.class); bindService(bindIntent, connection, BIND_AUTO_CREATE);
這裏在構建Intent的時候是使用MyService.class來指定要綁定哪個Service的,可是在另外一個應用程序中去綁定Service的時候並無MyService這個類,這時就必須使用到隱式Intent了。
具體步驟以下:
咱們在第六段中的MyService02這個工程文件中進行修改。代碼實現以下:
(1)如今修改AndroidManifest.xml中的代碼,給MyService加上一個action,以下所示:
1 <service android:name=".MyService" > 2 <intent-filter> 3 <action android:name="com.example.servicetest02.MyService" /> 4 </intent-filter> 5 </service>
這就說明,MyService能夠響應帶有com.example.servicetest02.MyService這個action的Intent。
如今從新運行一下MyService02這個程序,這樣就把遠程Service端的工做所有完成了。
而後新建一個新的工程,起名爲ClientTest,咱們就嘗試在這個程序中遠程調用MyService中的方法。
ClientTest中的Activity若是想要和MyService創建關聯其實也不難,首先須要將IPerson.aidl文件從ServiceTest02項目中拷貝過來,注意要將原有的包路徑一塊兒拷貝過來,完成後項目的結構以下圖所示:
(2)在activity_main.xml中添加兩個按鈕button_bind_service和button_unbind_service,用於綁定遠程服務和取消綁定。activity_main.xml的代碼以下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button_bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="bind Service" /> <Button android:id="@+id/button_unbind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="unbind Service" /> </LinearLayout>
(3)在MainActivity中加入和遠程的MyService創建關聯的代碼,以下所示:
1 package com.example.clienttest; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.RemoteException; 10 import android.util.Log; 11 import android.view.View; 12 import android.view.View.OnClickListener; 13 import android.widget.Button; 14 15 import com.example.servicetest02.IPerson; 16 17 18 public class MainActivity extends Activity implements OnClickListener { 19 public static final String TAG = "MainActivity"; 20 private Button button_bind_service; 21 private Button button_unbind_service; 22 private IPerson person; 23 boolean mBound = false; // 一開始,並無和Service綁定.這個參數是用來判斷綁定狀態 24 // 匿名內部類:服務鏈接對象 25 private ServiceConnection connection = new ServiceConnection() { 26 // 當服務異常終止時會調用。注意,解除綁定服務時不會調用 27 @Override 28 public void onServiceDisconnected(ComponentName name) { 29 mBound = false; // 服務異常終止時,狀態爲未綁定 30 } 31 // 和服務綁定成功後,服務會回調該方法。在這個方法裏調用的業務對象中的內容 32 @Override 33 public void onServiceConnected(ComponentName name, IBinder service) { 34 Log.d(TAG, "onServiceConnected"); 35 person = IPerson.Stub.asInterface(service); // 獲得person對象 36 Log.d("person", "person對象的內存地址是" + person); // 打印出person對象的內存地址 37 try { 38 person.setName("生命壹號"); 39 person.setAge(22); 40 person.setSex("男"); 41 String p = person.getPerson(); 42 Log.d("person", "person的信息是" + p); 43 } catch (RemoteException e) { 44 // TODO Auto-generated catch block 45 e.printStackTrace(); 46 } 47 mBound = true; //true說明是綁定狀態 48 } 49 }; 50 @Override 51 protected void onCreate(Bundle savedInstanceState) { 52 super.onCreate(savedInstanceState); 53 setContentView(R.layout.activity_main); 54 button_bind_service = (Button) findViewById(R.id.button_bind_service); 55 button_unbind_service = (Button) findViewById(R.id.button_unbind_service); 56 button_bind_service.setOnClickListener(this); 57 button_unbind_service.setOnClickListener(this); 58 } 59 @Override 60 public void onClick(View v) { 61 switch (v.getId()) { 62 case R.id.button_bind_service: 63 Intent bindIntent = new Intent("com.example.servicetest02.MyService"); 64 bindService(bindIntent, connection, BIND_AUTO_CREATE); 65 break; 66 case R.id.button_unbind_service: 67 // 若是和Service是綁定的狀態,就解除綁定。 68 if (mBound) { 69 unbindService(connection); 70 mBound = false; 71 } 72 break; 73 default: 74 break; 75 } 76 } 77 }
這部分代碼你們必定會很是眼熟吧?沒錯,這和在ServiceTest02的MainActivity中的代碼幾乎是徹底相同的,只是在讓Activity和Service創建關聯的時候咱們使用了隱式Intent,將Intent的action指定成了com.example.servicetest02.MyAIDLService(63行)。
在當前Activity和MyService創建關聯以後,咱們仍然是調用了setName、setAge、setSex、getPerson()這幾個方法,遠程的MyService會對傳入的參數進行處理並返回結果,而後將結果打印出來。
這樣的話,ClientTest中的代碼也就所有完成了,如今運行一下這個項目,而後點擊Bind Service按鈕,此時就會去和遠程的MyService創建關聯,觀察LogCat中的打印信息以下所示:
注意紅框部分,包名是不同的哦。因而可知,咱們確實已經成功實現跨進程通訊了,在一個程序中訪問到了另一個程序中的方法。
7、Messenger的使用:
public final class Messenger extends Object implements Parcelable
介紹:Messenger實現了IPC通訊,底層也是使用了AIDL方式。和AIDL方式不一樣的是,Messenger方式是利用Handler形式處理,所以,它是線程安全的,這也表示它不支持併發處理;而AIDL方式是非線程安全的,支持併發處理,所以,咱們使用AIDL方式時,須要保證代碼的線程安全。大部分狀況下,應用中不須要併發處理,所以咱們一般只須要使用Messenger方式。
過程:在進程A中建立一個Message,將這個Message對象經過Messenger.send(message)方法傳遞到進程B的消息隊列裏,而後交給Handler去處理。
固然,Message對象自己是沒法被傳遞到進程B的,send(message)方法會使用一個Pacel對象對Message對象編集,再將Pacel對象傳遞到進程B中,而後解編集,獲得一個和進程A中的Message對象內容同樣的對象。
關於多線程的Handler機制,若是不清楚的話,能夠參考本人另一篇博客:
使用Messenger來實現IPC的步驟:
那咱們經過代碼來實現如下吧。新建一個全新的工程MessengerTest。步驟以下:
(1)新建一個MessengerService類,繼承Service類,代碼以下:
1 package com.example.messengertest; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.Handler; 6 import android.os.IBinder; 7 import android.os.Message; 8 import android.os.Messenger; 9 import android.util.Log; 10 import android.widget.Toast; 11 12 public class MessengerService extends Service{ 13 14 public static final int MSG_SAY_HELLO = 1; 15 16 private Handler handler = new Handler() { 17 public void handleMessage(Message msg) { 18 switch (msg.what) { 19 case MSG_SAY_HELLO: 20 //在服務裏定義業務方法 21 Toast.makeText(MessengerService.this, "hello", Toast.LENGTH_SHORT).show(); 22 Log.d("MessengerService", "MessengerService thread id is " + Thread.currentThread().getId()); //打印MessengerService的線程id 23 break; 24 default: 25 break; 26 } 27 } 28 }; 29 30 31 private Messenger messenger = new Messenger(handler); 32 33 34 @Override 35 public IBinder onBind(Intent intent) { 36 // TODO Auto-generated method stub 37 return messenger.getBinder(); 38 } 39 }
核心代碼:16至28行、31行、37行。
37行中,將IBinder類型返回以後,就已經和Messenger進行綁定了。
(2)在清單文件中註冊服務:(和Activity標籤並列)
<service android:name=".MessengerService"> </service>
(3)修改activity_main.xml代碼,添加一個按鈕,用於發送Message,代碼以下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button_messenger" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="使用messenger" /> </LinearLayout>
(4)在MainActivity做爲程序的主Activity,在裏面加入發送Message消息和創建Service鏈接的邏輯,代碼以下:
1 package com.example.messengertest; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.Message; 10 import android.os.Messenger; 11 import android.os.RemoteException; 12 import android.util.Log; 13 import android.view.View; 14 import android.view.View.OnClickListener; 15 import android.widget.Button; 16 17 public class MainActivity extends Activity implements OnClickListener { 18 19 private Button button_messenger; 20 21 private Messenger messenger; 22 boolean mBound = false; 23 24 @Override 25 protected void onCreate(Bundle savedInstanceState) { 26 super.onCreate(savedInstanceState); 27 setContentView(R.layout.activity_main); 28 button_messenger = (Button) findViewById(R.id.button_messenger); 29 button_messenger.setOnClickListener(this); 30 } 31 32 @Override 33 protected void onStart() { 34 // TODO Auto-generated method stub 35 super.onStart(); 36 Intent bindIntent = new Intent(this, MessengerService.class); 37 bindService(bindIntent, connection, BIND_AUTO_CREATE); 38 } 39 40 @Override 41 protected void onDestroy() { 42 // TODO Auto-generated method stub 43 super.onDestroy(); 44 if (mBound) { 45 unbindService(connection); 46 mBound = false; 47 } 48 } 49 50 private ServiceConnection connection = new ServiceConnection() { 51 @Override 52 public void onServiceConnected(ComponentName name, IBinder service) { 53 // TODO Auto-generated method stub 54 messenger = new Messenger(service); 55 56 mBound = true; 57 } 58 59 @Override 60 public void onServiceDisconnected(ComponentName name) { 61 // TODO Auto-generated method stub 62 mBound = false; 63 64 } 65 66 }; 67 68 //點擊按鈕,發送Message消息,在MessengerService裏接收,從而執行Service裏面的方法 69 @Override 70 public void onClick(View v) { 71 switch (v.getId()) { 72 case R.id.button_messenger: 73 Message msg = Message.obtain(); 74 msg.what = MessengerService.MSG_SAY_HELLO; 75 try { 76 messenger.send(msg); 77 Log.d("MainActivity", "MainActivity thread id is " + Thread.currentThread().getId()); //打印MainActivity的線程id 78 } catch (RemoteException e) { 79 // TODO Auto-generated catch block 80 e.printStackTrace(); 81 } 82 break; 83 84 default: 85 break; 86 } 87 } 88 89 }
咱們在上一步的MessengerService類新建了一個Messenger,在這裏又新建另外一個Messenger(54行)。兩個Messenger綁定了同一個服務,Activity就能夠和Service實現通信了。
點擊按鈕(72行),發送消息,讓MessengerService類裏的Messenger去接收,而後交給handler去處理,從而執行handleMessage()裏方法,也就是說,執行了Service裏面的方法。
運行程序,點擊按鈕,顯示效果以下:
後臺打印日誌以下:
說明這個MessengerService和普通Service同樣,也是運行在主線程當中的。
固然了,這裏的Messenger的實現比較簡單,若是之後須要實現複雜IPC訪問,仍是須要本身去寫AIDL才更加直接有效,可控性強。