前言
前面講解了Service的一些基本內容。但是對於綁定服務傳遞數據,只局限於本地服務,無法使用服務進行跨進程間的交互。如果需要用到跨進程交互的話,需要用到一個新的技術-AIDL,這篇博客就針對AIDL如何傳遞內置類型數據進行講解。對於Service不熟悉的朋友,可以先看看之前的博客:Service基礎、Service高級、Service應用。
本篇博客內容如下:
先來回顧一下,Android在本地的Service中如何與其它組件進行交互的,首先Service必須實現其onBind()方法,然后在onBind方法傳遞一個IBinder接口的實現,而在其它組件中使用bindService()綁定一個服務,再通過其中的參數ServiceConnection對象獲取到Service中定義的IBinder接口的實現。那么與Service進行數據交互,其實就是傳遞一個IBinder,通過這個IBinder進行交互。
而現在就碰到一個問題,在同一個進程中,是可以獲取到這個Service類的,也就可以獲得這個Service中定義的IBinder,但是如果在不同的應用中,即遠程服務,如何獲取IBinder呢?僅僅是在不同的應用定義一相同的類是沒有用的,所以Android為我們提供了AIDL語言,它需要先定義一個遠程調用接口,然后為該接口提供一個實現類,通過共享這個遠程調用接口來達到進程間數據交互的目的,而這個接口的代碼是有很多共性的,並且編寫過程相當枯燥乏味,所以Android開發者為我們提供了ADIL來簡化通訊接口的開發。
AIDL(Android Interface Definition Language)是Android遠程調用接口的定義語言。它有它自己的一套語法規范,但是和Java類似,這並不難理解,詳細的這個會后面介紹。而當你定義好一個AIDL接口之后,你會發現在gen/目錄下,多出一個與定義的AIDL包名相同,文件名相同的一個Java類,這個類是編譯器根據定義的AIDL接口自動生成的代碼,觀察之后發現其實它也是繼承了Binder類(Binder是IBinder的實現類),所以它可以通過ServiceConnection進行數據傳遞。Service只需要暴露這個AIDL接口給客戶端,讓客戶端也定義它,這樣兩個應用進程就可以通訊了。
AIDL的語法與Java接口的語法非常相似,但是存在一些差異:
- AIDL定義接口的源代碼后綴必須以.aidl結尾。
- AIDL一樣要指定AIDL接口的包信息package *。
- AIDL接口無需指定public、private、protected等作用域,可以理解為就是public。
- AIDL默認情況下只能傳遞基本類型、String、List、Map、CharSequence。
- 如果需要傳遞其他類型的對象,需要import對象的包名,並需要對對象進行特殊處理(之后會介紹)。
例如:
1 package com.example.aidlservicedemo.domain; 2 3 interface IDog{ 4 String getName(); 5 int getAge(); 6 }
當你聲明完一個AIDL接口的時候,你會發現在項目的gen/目錄下,對應包中存在一個同名的Java文件,這個文件是Android幫我們自動生成的,里面有很多代碼,這里只講一下需要注意的。查看自動生成的這個Java文件代碼,會發現它定義了一個名為Stub的靜態抽象類,這個Stub繼承了Binder,實現了AIDL接口,當然其中也實現了AIDL接口的兩個方法,粗略看一下會發現它對數據做了一個序列化和反序列化的操作。正因為AIDL對數據進行了序列化和反序列化,所以才可以在進程間傳遞。
定義好AIDL接口之后,就需要通過服務把接口暴露給客戶端,這里Service.onBind()傳遞的就是這個Stub靜態抽象類的實現類,其他沒什么特別的。
下面通過一個Demo來演示ADIL如何傳遞數據的,在示例中,給出兩個應用,分別實現Server與調用客戶端,使用的AIDL接口就是上面給出的AIDL示例代碼,這里不再重復定義。
AIDL服務:BaseTypeService.java
1 package com.example.aidlservicedemo; 2 3 import java.util.Random; 4 5 import com.example.aidlservicedemo.domain.IDog.Stub; 6 7 import android.app.Service; 8 import android.content.Intent; 9 import android.os.IBinder; 10 import android.os.RemoteException; 11 import android.util.Log; 12 13 public class BaseTypeService extends Service { 14 private final String TAG="main"; 15 private DogBinder binder=null; 16 private String[] names=new String[]{"小白","旺財","小黑"}; 17 private int[] ages=new int[]{1,2,3}; 18 19 /** 20 * Stub的實現類,Stub內部實現了Binder 21 * 內部實現AIDL定義的方法 22 */ 23 public class DogBinder extends Stub{ 24 25 @Override 26 public String getName() throws RemoteException { 27 Random random=new Random(); 28 int nextInt = random.nextInt(2); 29 return names[nextInt]; 30 } 31 32 @Override 33 public int getAge() throws RemoteException { 34 Random random=new Random(); 35 int nextInt = random.nextInt(2); 36 return ages[nextInt]; 37 } 38 } 39 40 @Override 41 public void onCreate() { 42 super.onCreate(); 43 // 實例化Binder對象 44 binder=new DogBinder(); 45 Log.i(TAG, "創建服務成功"); 46 } 47 48 @Override 49 public IBinder onBind(Intent intent) { 50 Log.i(TAG, "綁定服務成功"); 51 // 返回Binder對象 52 return binder; 53 } 54 }
客戶端調用服務獲取數據:
1 package com.example.aidlClientdemo; 2 3 import com.example.aidlservicedemo.domain.IDog; 4 import android.app.Activity; 5 import android.content.ComponentName; 6 import android.content.Intent; 7 import android.content.ServiceConnection; 8 import android.os.Bundle; 9 import android.os.IBinder; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.widget.Button; 13 import android.widget.Toast; 14 15 public class BaseTypeActivity extends Activity { 16 private Button btn_startService, btn_endService,btn_getServiceData; 17 private IDog dogService; 18 19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_service); 23 24 btn_startService = (Button) findViewById(R.id.btn_startService); 25 btn_endService = (Button) findViewById(R.id.btn_endService); 26 btn_getServiceData = (Button) findViewById(R.id.btn_getServiceData); 27 28 btn_startService.setOnClickListener(click); 29 btn_endService.setOnClickListener(click); 30 btn_getServiceData.setOnClickListener(click); 31 } 32 33 private View.OnClickListener click = new OnClickListener() { 34 35 @Override 36 public void onClick(View v) { 37 switch (v.getId()) { 38 case R.id.btn_startService: 39 startService(); 40 break; 41 case R.id.btn_endService: 42 endService(); 43 break; 44 case R.id.btn_getServiceData: 45 getServiceDate(); 46 break; 47 } 48 } 49 }; 50 /* 51 * 獲取數據 52 */ 53 private void getServiceDate() { 54 try { 55 if(dogService!=null){ 56 StringBuilder sBuilder=new StringBuilder(); 57 sBuilder.append("name:"+dogService.getName()); 58 sBuilder.append("\nage:"+dogService.getAge()); 59 Toast.makeText(BaseTypeActivity.this, sBuilder.toString(), Toast.LENGTH_SHORT).show(); 60 } 61 else 62 { 63 Toast.makeText(BaseTypeActivity.this, "請先綁定服務", Toast.LENGTH_SHORT).show(); 64 } 65 } catch (Exception e) { 66 e.printStackTrace(); 67 } 68 } 69 70 private ServiceConnection connBase=new ServiceConnection() { 71 72 @Override 73 public void onServiceDisconnected(ComponentName name) { 74 dogService=null; 75 } 76 77 @Override 78 public void onServiceConnected(ComponentName name, IBinder service) { 79 // IDog.Stub.asInterface,獲取接口 80 dogService=IDog.Stub.asInterface(service); 81 } 82 }; 83 84 /** 85 * 開始服務 86 */ 87 private void startService(){ 88 Intent intent=new Intent(); 89 intent.setAction("cn.bgxt.Service.BASE_TYPE_SERVICE"); 90 bindService(intent, connBase, BIND_AUTO_CREATE); 91 Toast.makeText(BaseTypeActivity.this, "開始綁定服務", Toast.LENGTH_SHORT).show(); 92 } 93 /** 94 * 停止服務 95 */ 96 private void endService(){ 97 if(connBase!=null) 98 { 99 unbindService(connBase); 100 // 接觸綁定的時候需要回收dogService連接資源 101 // 在源碼中漏了,這是后來加上的 102 dogService=null; 103 Toast.makeText(BaseTypeActivity.this, "服務解除綁定", Toast.LENGTH_SHORT).show(); 104 } 105 } 106 }
效果展示:先運行服務應用,再運行客戶端應用。
總結
本篇博客只介紹了AIDL的基本結構,以及如何通過AIDL接口傳遞一個系統內置類型的數據。下一篇博客將介紹一下AIDL的高級應用,如何傳遞一個自定義對象。