標籤:
如果想要進行IPC通訊,一般寫一個AIDL介面,再寫一個Service子類,然後實現AIDL介面 當做IBinder返回給Activity介面層。
如果不想寫AIDL介面檔案,只是單線程中與Service進行通訊 我們可以用Android寫好的Messenger類來處理,一樣能將訊息傳遞給Service進行通訊。
什麼是aidl:
aidl是 Android Interface definition language的縮寫,它是一種android內部進程通訊介面的描述語言,通過它我們可以定義進程間的通訊介面
icp:interprocess communication :內部進程通訊。
在 Android中, 每個應用程式都有自己的進程,當需要在不同的進程之間傳遞對象時, Java中是不支援跨進程記憶體共用的。因此要傳遞對象, 需要把對象解析成作業系統能夠理解的資料格式, 以達到跨界對象訪問的目的。在JavaEE中,採用RMI通過序列化傳遞對象。在Android中, 則採用AIDL(Android Interface Definition Language:介面定義語言)方式實現。
1.在Android工程的Java包目錄中建立一個副檔名為aidl的檔案。該檔案的文法類似於Java代碼,但會稍有不同。
2.如果aidl檔案的內容是正確的,ADT會自動產生一個Java介面檔案(*.java)。
3.建立一個服務類(Service的子類)。
4.實現由aidl檔案產生的Java介面。
5.在AndroidManifest.xml檔案中配置AIDL服務,尤其要注意的是,<action>標籤中android:name的屬性值就是用戶端要引用該服務的ID,也就是Intent類的參數值。
AIDL 是一種介面定義語言,用於約束兩個進程間的通訊規則,供編譯器產生代碼,實現Android裝置上的兩個處理序間通訊(IPC)。進程之間的通訊資訊,首先會被轉換成AIDL協議訊息,然後發送給對方,對方收到AIDL協議訊息後再轉換成相應的對 象。由於進程之間的通訊資訊需要雙向轉換,所以android採用代理類在背後實現了資訊的雙向轉換,代理類由android編譯器產生,對開發人員來說 是透明的。
實現進程通訊,一般需要下面四個步驟:
假設A應用需要與B應用進行通訊,調用B應用中的download(String path)方法,B應用以Service方式向A應用提供服務。需要下面四個步驟:
一、 在B應用中建立*.aidl檔案,aidl檔案的定義和介面的定義很相類,如:在com.robert.aidl包下建立IDownloadService.aidl檔案,內容如下:
package com.robert.aidl;
interface IDownloadService {
void download(String path);
}
AIDL使用簡單的文法來聲明介面,描述其方法以及方法的參數和返回值。這些參數和返回值可以是任何類型,甚至是其他AIDL產生的介面。其中對於Java程式設計語言的基礎資料型別 (Elementary Data Type) (int, long, char, boolean等),String和CharSequence,集合介面類型List和Map,不需要import 語句。 而如果需要在AIDL中使用其他AIDL介面類型,需要import,即使是在相同包結構下。AIDL允許傳遞實現Parcelable介面的類,需要import.需要特別注意的是, 對於非基礎資料型別 (Elementary Data Type),也不是String和CharSequence類型的,需要有方向指示,包括in、out和inout,in表示由用戶端設定,out表示由服務端設定,inout是兩者均可設定AIDL只支援介面方法,不能公開static變數。
當完成aidl檔案建立後,項目的gen目錄中同步產生IDownloadService.java介面檔案。介面檔案中產生一個 Stub的抽象類別,裡麵包括aidl定義的方法,還包括一些其它輔助方法。值得關注的是asInterface(IBinder iBinder),它返回介面類型的執行個體,對於遠程服務調用,遠程服務返回給用戶端的對象為代理對象,用戶端在 onServiceConnected(ComponentName name, IBinder service)方法引用該對象時不能直接強轉成介面類型的執行個體,而應該使用asInterface(IBinder iBinder)進行類型轉換。
編寫Aidl檔案時,需要注意下面幾點:
1.介面名和aidl檔案名稱相同。
2.介面和方法前不用加存取權限修飾符public,private,protected等,也不能用final,static。
3.Aidl預設支援的類型包話java基本類型(int、long、boolean等)和(String、List、Map、 CharSequence),使用這些類型時不需要import聲明。對於List和Map中的元素類型必須是Aidl支援的類型。如果使用自訂類型作 為參數或返回值,自訂類型必須實現Parcelable介面。
4.自訂類型和AIDL產生的其它介面類型在aidl描述檔案中,應該顯式import,即便在該類和定義的包在同一個包中。
5.在aidl檔案中所有非Java基本型別參數必須加上in、out、inout標記,以指明參數是輸入參數、輸出參數還是輸入輸出參數。
6.Java原始類型預設的標記為in,不能為其它標記。
二、在B應用中實現aidl檔案產生的介面(本例是IDownloadService),但並非直接實現介面,而是通過繼承介面的Stub來實現,並且實現介面方法的代碼。內容如下:
public class ServiceBinder extends IDownloadService.Stub {
@Override
public void download(String path) throws RemoteException {
Log.i("DownloadService", path);
}
}
三、 在B應用中建立一個Service(服務),在服務的onBind(Intent intent)方法中返回實現了aidl介面的對象)。內容如下:
public class DownloadService extends Service {
private ServiceBinder serviceBinder = new ServiceBinder();
@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}
public class ServiceBinder extends IDownloadService.Stub {
@Override
public void download(String path) throws RemoteException {
Log.i("DownloadService", path);
}
}
}
其他應用可以通過隱式意圖訪問服務,意圖的動作可以自訂,AndroidManifest.xml配置代碼如下:
<service android:name=".DownloadService" >
<intent-filter>
<action android:name="com.robert.process.aidl.DownloadService" />
</intent-filter>
</service>
四、把B應用中aidl檔案所在package連同aidl檔案一起拷貝到用戶端A應用,A應用的gen目錄中為aidl檔案同步產生IDownloadService.java介面檔案,接下來就可以在A應用中實現與B應用通訊,代碼如下:
public class ClientActivity extends Activity {
private IDownloadService downloadService;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.bindService(new Intent("com.robert.process.aidl.DownloadService"), this.serviceConnection, BIND_AUTO_CREATE);//綁定到服務
}
@Override
protected void onDestroy() {
super.onDestroy();
this.unbindService(serviceConnection);//解除服務
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadService = IDownloadService.Stub.asInterface(service);
try {
downloadService.download("http://www.itcast.cn");
} catch (RemoteException e) {
Log.e("ClientActivity", e.toString());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
downloadService = null;
}
};
}
Aidl預設支援的類型包話java基本類型(int、long、boolean等)和(String、List、Map、CharSequence),如果要傳遞自訂的類型該實現方式如下
1. 建立自訂類型,並實現Parcelable介面,使其支援parcelable協議。如:在com.robert.domain包下建立Person.java:
package com.robert.domain;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable
private Integer id;
private String name;
public Person(){}
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {//把javanbean中的資料寫到Parcel
dest.writeInt(this.id);
dest.writeString(this.name);
}
//添加一個靜態成員,名為CREATOR,該對象實現了Parcelable.Creator介面
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>(){
@Override
public Person createFromParcel(Parcel source) {//從Parcel中讀取資料,返回person對象
return new Person(source.readInt(), source.readString());
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
2. 在自訂類型所在包下建立一個aidl檔案對自訂類型進行聲明,檔案的名稱與自訂類型同名。
package com.robert.domain;
parcelable Person;
3. 在介面aidl檔案中使用自訂類型,需要使用import顯式匯入,本例在com.robert.aidl包下建立IPersonService.aidl檔案,內容如下:
package com.robert.aidl;
import com.robert.domain.Person;
interface IPersonService {
void save(in Person person);
}
4. 在實現aidl檔案產生的介面(本例是IPersonService),但並非直接實現介面,而是通過繼承介面的Stub來實現,並且實現介面方法的代碼。內容如下:
public class ServiceBinder extends IPersonService.Stub {
@Override
public void save(Person person) throws RemoteException {
Log.i("PersonService", person.getId()+"="+ person.getName());
}
}
5. 建立一個Service(服務),在服務的onBind(Intent intent)方法中返回實現了aidl介面的對象。內容如下:
public class PersonService extends Service {
private ServiceBinder serviceBinder = new ServiceBinder();
@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}
public class ServiceBinder extends IPersonService.Stub {
@Override
public void save(Person person) throws RemoteException {
Log.i("PersonService", person.getId()+"="+ person.getName());
}
}
}
其他應用可以通過隱式意圖訪問服務,意圖的動作可以自訂,AndroidManifest.xml配置代碼如下:
<service android:name=".PersonService" >
<intent-filter>
<action android:name="com.robert.process.aidl.PersonService " />
</intent-filter>
</service>
6. 把應用中的aidl檔案和所在package一起拷貝到用戶端應用的src目錄下,用戶端應用的gen目錄中為aidl檔案同步生 成IPersonService.java介面檔案,接下來再把自訂類型檔案和型別宣告aidl檔案及所在package一起拷貝到用戶端應用的src 目錄下。
最後就可以在用戶端應用中實現與遠程服務的通訊,代碼如下:
public class ClientActivity extends Activity {
private IPersonService personService;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.bindService(new Intent("com.robert.process.aidl.PersonService"), this.serviceConnection, BIND_AUTO_CREATE);//綁定到服務
}
@Override
protected void onDestroy() {
super.onDestroy();
this.unbindService(serviceConnection);//解除服務
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
personService = IPersonService.Stub.asInterface(service);
try {
personService.save(new Person(56,"liming"));
} catch (RemoteException e) {
Log.e("ClientActivity", e.toString());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
personService = null;
}
};
}
本地服務和 AIDL服務的區別:
本地服務不支援onBind(),它從onBind()返回null,這種類型的服務只能由承載服務的應用程式組件訪問。可以調用 startService()來調用本地服務。AIDL服務可以同時供 同一進程內的組件和其他應用程式的組件使用。這種類型的服務在AIDL 檔案中為自身與其用戶端定義一個契約。服務實現 AIDL契約,而用戶端綁定到 AIDL定義。服務通過從 onBind()方法 返回AIDL介面的實現,來實現契約。用戶端通過調用 bindService()來綁定到AIDL服務,並調用 unBindService()來從服務斷開。
我們使用Handler都是在一個進程中使用的,我們可以使用另外一種方式,
android系統的android.os.Messenger可以很方便的跨進程使用Handler。
服務端:
public class MessengerTestService extends Service {
protected static final String TAG = "MessengerTestService";
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到訊息");
//擷取用戶端message中的Messenger,用於回調
final Messenger callback = msg.replyTo;
try {
// 回調
callback.send(Message.obtain(null, 0));
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
}
};
@Override
public IBinder onBind(Intent intent) {
return new Messenger(mHandler).getBinder();
}
}
用戶端
public class MainActivity extends Activity {
protected static final String TAG = "MainActivity";
Messenger messenger;
Messenger reply;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
reply = new Messenger(handler);
Intent intent = new Intent();
intent.setClassName("test.messenger", "test.messenger.MessengerTestService");
// 綁定服務
bindService(intent, new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Toast.makeText(MainActivity.this, "bind success", 0).show();
messenger = new Messenger(service);
}
}, Context.BIND_AUTO_CREATE);
}
public void sendMessage(View v) {
Message msg = Message.obtain(null, 1);
// 設定回調用的Messenger
msg.replyTo = reply;
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "回調成功");
}
};
}
用戶端綁定服務端,擷取遠程Messenger的binder對象。調用Messenger的send函數,就可以把Message發送至服務端的Handler。
同時,如果需要服務端回調用戶端,則可以在send的Message中設定replyTo,服務端就可以往用戶端發送訊息了。
下面我們看下Messenger的源碼
建構函式
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
Handler.getIMessenger()返回的是一個IMessenger的binder對象,它的send方法將會調用Handler的sendMessage方法。
public void send(Message message) throws RemoteException {
mTarget.send(message);
}
Android處理序間通訊