上篇文章以執行緒間的通訊方式Handler類結尾,服務Service還支援的程序間通訊,又是具體怎麼實現的呢?這就要用到載入服務一文中提到的AIDL語言規範了。

AIDL是 Android Interface Definition Language 的縮寫,即Android介面定義語言,使用其定義的規範程式設計,可實現Android系統上不同程序間的通訊。官網ADIL概述中以服務端和客戶端通訊為例做了大致講述。與執行緒間的通訊類似,不同程序間的通訊也是分為通訊訊息內容、訊息傳送方、訊息接收方三個部分的,其中的訊息內容也就是AIDL支援的資料型別;而由於服務已經位於當前應用程式所在的預設程序,所以服務中所實現的介面內容即對接收訊息的處理,也就是訊息接收方;相對的,其他需要傳送訊息的程序都可以看做是客戶端程序,只要繫結服務之後都可以呼叫服務的介面方法,也就是訊息傳送方。

確定通訊的訊息內容

在AndroidStudio中,通訊的訊息內容以 .aidl 字尾格式的檔案預設儲存在開發目錄 /src/main/aidl 下。在 .adil 格式的檔案中,編碼規則與Java語言一致,其中內容與Java語言中的介面定義類似。

其中支援通訊的訊息型別分為兩類,一類是基本型別,使用基本型別時不需要在檔案開頭增加匯入包名的宣告,這些基本型別包括Java語言中的八種基本資料型別, String, CharSequence, List, Map;還有一類是附加型別,除了第一類基本型別,其他在 .java 格式的檔案中定義的任何實現android.os.Parcelable介面的類都可以作為附加型別訊息傳遞。附加型別在使用時需要在 .aidl 檔案開頭增加匯入該型別所在包名的宣告。

關於附加型別的定義,以com.java.process.myinterface.Person型別為例。

不僅在 /src/main/java/com/java/process/myinterface 目錄下建立 Person.java ,在其中定義Person類並實現Parcelable介面的相關方法。

還要在 /src/main/aidl/com/java/process/myinterface 目錄下建立對應的 Person.aidl 檔案,在其中除了指定包名外,還要宣告該類為parcelable介面,示例程式碼如下

package com.java.process.myinterface;
parcelable Person;

.aidl.java 格式的檔案中定義的介面相比有兩處不同,其一是無需訪問修飾符,其二是對基本資料型別增加資料流向識別符號(包括資料流入接收方的in,資料在接收方修改後可流出的out, 資料可流向接收方並流出的inout )。

關於通訊訊息的定義,以com.java.process.myinterface.DataInterface型別為例。

只需要在 /src/main/aidl/com/java/process/myinterface 目錄下建立 DataInterface.aidl 檔案,在其中定義相關介面即可。示例程式碼如下

package com.java.process.myinterface;
import com.java.process.myinterface.Person;
interface DataInterface{
void setVersion(int version);
int getVersion();
void updatePerson(in Person person);
Person getMainPerson();
}

在通訊訊息定義之後,可以使用AndroidStudio的編譯指令 Build - Make Project 編譯當前專案,以使得AndroidStudio自動生成訊息定義的 .java 檔案。上文 DataInterface.aidl 檔案在專案編譯之後,會在 /build/generated/aidl_source_output_dir/debug/out/com/java/process/myinterface 目錄下生成不可編輯的 DataInterface.java 檔案,之後即可在專案中通過導包import com.java.process.myinterface.DataInterface;的形式正常使用該介面類了。

通訊接收方的服務接收處理

通訊接收方就是常說的服務端,通常是自定義的服務Service類,主要負責實現上述通訊訊息內容的介面,以此接收訊息內容並處理。

針對自定義的服務Service類,這裡就用到在載入服務文章中提到的繫結服務的生命週期了。重寫onBind(Intent intent)方法,在該方法中返回android.os.IBinder型別的物件例項。

而這個IBinder物件是怎麼建立的呢?在上文通訊訊息定義之後自動生成的介面類中,也自動生成了其Stub內部類,在建立該內部類的無參構造方法時,即可自動實現其相關介面方法,繼續使用上文示例,這裡在服務Service中的介面實現程式碼如下

package com.java.process.myinterface;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.support.annotation.Nullable; import com.java.process.myinterface.DataInterface; public class ProcessService extends Service { private String versionName;
private Person person;
private final DataInterface.Stub binder=new DataInterface.Stub() { @Override
public String getVersionName() throws RemoteException {
//這裡可以返回訊息versionName給客戶端
return versionName;
} @Override
public void setVersionName(String versionName) throws RemoteException {
//這裡接收處理來自客戶端的訊息versionName
ProcessService.this.versionName=versionName;
} @Override
public void updatePerson(Person person) throws RemoteException {
//這裡接收處理來自客戶端的訊息person
ProcessService.this.person.setName(person.getName());
ProcessService.this.person.setAge(person.getAge());
} @Override
public Person getMainPerson() throws RemoteException {
//這裡可以返回訊息person給客戶端
return ProcessService.this.person;
}
}; @Override
public void onCreate() {
super.onCreate();
versionName="defaultName";
person=new Person();
person.setName("initName");
person.setAge(10);
} @Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}

通訊傳送方的服務繫結

通訊傳送方就是所謂的客戶端了,傳送方是與上文中的接收方不在同一個程序的,所以傳送方通常需要先繫結接收方的自定義服務Service,也就是在傳送方通過上下文環境Context物件呼叫bindService(Intent service, ServiceConnection conn, int flags)方法,這裡的引數在載入服務中已有詳細說明。

conn 引數的onServiceConnected(ComponentName name, IBinder service)方法中通過呼叫靜態方法(通訊訊息定義介面的內部類Stub.asInterface(IBinder service)方法)得到通訊訊息的定義介面的例項化物件。在當前客戶端繫結自定義服務Service成功之後,回撥該方法,即可將通訊訊息的定義介面的例項化物件賦值給全域性變數使用。

conn 引數的onServiceDisconnected(ComponentName name)方法中,要記得將全域性通訊訊息定義介面變數的例項化物件置為空,否則在當前客戶端與自定義服務Service斷開連線後,還呼叫全域性變數的通訊訊息定義介面的例項化物件的相關方法,可能會出現OOM記憶體洩露的問題。

最後在需要傳送訊息的位置,只需要呼叫全域性通訊訊息定義介面變數的相關方法即可。其中用到的示例程式碼如下

    private DataInterface dataInterface;
private ServiceConnection conn=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
dataInterface = DataInterface.Stub.asInterface(service);
} @Override
public void onServiceDisconnected(ComponentName name) {
dataInterface = null;
} //此處省略對文字編輯控制元件editText的定義
//該方法在點選editText控制元件時回撥
public void clickName(View view) {
try {
String setName = editText.getText().toString();
dataInterface.setVersionName(setName);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};