Binder總結篇2-Binder使用
在上一篇文章Binder總結篇1-Binder原理中,我們大概理解了Binder的執行原理,那麼我們在什麼樣子的應用場景下會使用到Binder呢?
就我個人而言,是在IM系統開發當中使用到多程式開發,也就需要Binder來進行通訊了,本文是編寫實際的例子的,涉及到的點有:
- AIDL
包括支援的資料型別,定義以及使用等
- Service
包括一些啟動,資料獲取以及資料回傳等。
本文的demo地址是:AndroidBinderSample
AIDL
關於AIDL的詳細描述,可以看官網Android 介面定義語言 (AIDL)
他是Android的介面定義語言,用來具體實現Binder通訊過程的資料傳遞,格式跟java的介面程式碼的編寫差不多。
他是使用.aidl檔案結尾,存放在man資料夾下的aidl資料夾下,當然你可以通過在gradle中配置aidl.srcDirs
來指定。
AIDL支援的資料型別
- Java的基本型別,也包括String型別和CharSequence型別
- List 和Map,其中List和Map中的元素必須是AIDL支援的資料型別,而且在Server端必須使用ArrayList或者是HashMap來接收。
- 其他的AIDL生成的介面
- 實現了Parcelable介面的實體,可以看詳細介紹Android中Parcelable的原理和使用方法
AIDL檔案,總得來說,AIDL分為兩類檔案,一種是介面型別,就是需要被呼叫被實現的。一種是宣告Parcelable資料,作用就是把對應的Java實現了Parcelable介面的類對映到AIDL中,然後被AIDL的介面檔案引用,需要注意的是這個AIDL檔案的包名需要與Java實現Parcelable檔案對應的包名一致。
例如:
我們宣告瞭一個JavaDomain,在包app.androidbinder.domain
下,大致如下
package app.androidbinder.domain;
import android.os.Parcel;
import android.os.Parcelable;
/**
* 作者:黎偉傑 on 2018/8/13.
* 郵箱:liweijie@qq.com
* description:
* update by:
* update day:
*
* @author liweijie
*/
public class UserInfo implements Parcelable {
//省略程式碼
public UserInfo() {
}
protected UserInfo(Parcel in) {
//省略程式碼
}
public static final Creator<UserInfo> CREATOR = new Creator<UserInfo>() {
@Override
public UserInfo createFromParcel(Parcel in) {
return new UserInfo(in);
}
@Override
public UserInfo[] newArray(int size) {
return new UserInfo[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//省略程式碼
}
public void readFromParcel(Parcel in) {
//省略程式碼
}
}
複製程式碼
然後在AIDL檔案下也需要建立對應的包名,然後編寫UserInfo.aidl,如下:
package app.androidbinder.domain;
/**
* 作者:黎偉傑 on 2018/8/14.
* 郵箱:liweijie@qq.com
* description:
* update by:
* update day:
*
* @author liweijie
*/
parcelable UserInfo;
複製程式碼
這樣子,在別的AIDL中就可以引用這個UserInfo
了。
定義介面型別的AIDL如下:
// UserService.aidl
package app.androidbinder;
import java.util.List;
import app.androidbinder.domain.UserInfo;
// Declare any non-default types here with import statements
interface UserService {
String getUserName(int userId);
void saveUser(in UserInfo param);
UserInfo getUserInfo(int userId);
List<UserInfo> queryUser();
//for in out inout
UserInfo handleIn(in UserInfo info);
UserInfo handleOu(out UserInfo info);
UserInfo handleInOut(inout UserInfo info);
}
複製程式碼
這就是兩種AIDL檔案型別以及對應的大致編寫。
in、out、inout
在官方文件中支出,所有的非原語引數需要指示資料的方向標記,可以是in、out、inout。預設的原語是in,不能是其他流向。 這裡指定的非原語是指:除了Java的基本型別外的其他引數,也就是物件。我們在AIDL使用的時候需要知道這個引數的流向。 那什麼是資料的方向標記呢? 首先,資料的方向標記是針對客戶端中的那個傳入的方法引數而言。資料流向的識別符號不能使用在返回引數上,只能使用在方法引數上面。
- in:他表示的是這個引數只能從客戶端流向服務端,比如客戶端傳遞了一個User物件給服務端,服務端會收到一個完整的User物件,然後假如在服務端對這個物件進行操作,那麼這個改變是不會反映到客戶端的,這個流向也就是隻能從客戶端到服務端。
- out:他表示,當客戶端傳遞引數到服務端的時候,服務端將會收到一個空的物件,假如服務端對該物件進行操作,將會反映到客戶端。比如,客戶端傳遞一個User物件到服務端,服務端接收到的是一個空的User物件(不是null,只是有點像new一個User物件)。當服務端對這個User物件進行改變的時候,他的值變化將會反映到客戶端。
- inout,它具有這二者的功能,也就是客戶端傳遞物件到服務端,可以接收到完整的物件,同時服務端改變物件,也會反映到客戶端。 總結來說,in類似於傳值,out類似於傳引用,只是out的引用值到了服務端為空,inout則具有二者的功能,預設的是in。
Service
多程式之間的通訊離不開的是Service,Android四大元件之一,這裡不過多的贅述,只是需要知道我們在多程式通訊當中,服務端最少提供一個Service
來,在onBind()
方法中返回實現AIDL介面的IBinder
物件。然後客戶端通過bindService()
來連線,通過在ServiceConnection
的onServiceConnected
方法,通過呼叫AIDL 生成的檔案中的Stub
的asInterface()
來在客戶端獲取服務例項,繼而呼叫服務端的方法。需要注意,在跨app的程式呼叫中,對外暴露的Service
需要在清單檔案中把android:exported
設定為true
。一般而言,我們還會配置一些fillter來進行過濾。
關於如何使用Service以及他的一些生命週期,一些方法區別(比如startService和bindService)請自己另外查閱文件,這裡就不描述了。以下是本文Demo中的例項,使用的還是上面的AIDL,使用之前確保成功編譯出對應的aidl生成檔案:
服務端的Service
package app.androidbinder2.services;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import app.androidbinder.UserService;
import app.androidbinder.domain.UserInfo;
/**
* 作者:黎偉傑 on 2018/8/12.
* 郵箱:liweijie@qq.com
* description:
* update by:
* update day:
*
* @author liweijie
*/
public class App2Service extends Service {
/**
* 模擬一些測試資料
*/
private List<UserInfo> data = new ArrayList<>();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
data.clear();
data.add(new UserInfo(1, "A", 20));
data.add(new UserInfo(2, "B", 30));
data.add(new UserInfo(3, "C", 40));
data.add(new UserInfo(4, "D", 50));
return userService;
}
private Binder userService = new UserService.Stub() {
@Override
public String getUserName(int userId) throws RemoteException {
for (UserInfo item : data) {
if (item.getUserId() == userId) {
return item.getUserName();
}
}
return null;
}
@Override
public void saveUser(UserInfo param) throws RemoteException {
data.add(param);
}
@Override
public UserInfo getUserInfo(int userId) throws RemoteException {
for (UserInfo item : data) {
if (item.getUserId() == userId) {
return item;
}
}
return null;
}
@Override
public List<UserInfo> queryUser() throws RemoteException {
return data;
}
@Override
public UserInfo handleIn(UserInfo info) throws RemoteException {
info.setUserName("嘿嘿嘿");
return info;
}
@Override
public UserInfo handleOu(UserInfo info) throws RemoteException {
if (info == null) {
Log.e("App2Service", "UserInfi server is null");
info = new UserInfo();
}
info.setUserName("嘻嘻嘻");
return info;
}
@Override
public UserInfo handleInOut(UserInfo info) throws RemoteException {
info.setUserName("哈哈哈");
return info;
}
};
}
複製程式碼
清單檔案的配置是:
<service
android:name=".services.App2Service"
android:exported="true">
<intent-filter>
<action android:name="app.androidbinder2.user_service.action"/>
</intent-filter>
</service>
複製程式碼
客戶端
主要的程式碼如下:
//...省略程式碼
private UserService userServerService;
@Override
protected void onCreate(Bundle savedInstanceState) {
//...省略程式碼
}
ServiceConnection userConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
showToast("連線成功");
userServerService = UserService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
showToast("與伺服器斷開連線");
}
};
public void startServer(View view) {
Intent startServer = new Intent();
startServer.setAction("app.androidbinder2.user_service.action");
startServer.setComponent(new ComponentName("app.androidbinder2", "app.androidbinder2.services.App2Service"));
bindService(startServer, userConnection, Service.BIND_AUTO_CREATE);
}
public void getUserName(View view) {
try {
//實際呼叫
String name = userServerService.getUserName(1);
showToast(name);
} catch (Exception e) {
e.printStackTrace();
showToast("出現錯誤");
}
}
//...省略程式碼
複製程式碼
AIDL的實際使用
我們就以上面定義的AIDL檔案作為我們的例子。我們需要實現的一個需求是兩個App之間通過AIDL實現資料互通,至於一個App之間多程式也是類似,後面再做例子。 其實上面已經是把例子的程式碼羅列的差不多了。這裡列一下跨APP程式呼叫demo一般的步驟:
- 假如有需要自定義的資料需要通過實現Parcelable傳遞,那麼先編寫這種java檔案
- 在客戶單編寫一份引入上面編寫的Parcelable實體的AIDL檔案,再編寫一份AIDL介面(一般而言會把所有跨程式的介面編寫在一起)
- 通過編譯,當沒有問題之後,需要把上面的需要跨程式的Parcelable的Java檔案以及AIDL檔案複製到Server端,包名需要一致,然後編譯。
- 在Server端,新建繼承Service的服務實現,然後在onBinder()中,返回實現我們編譯生成的AIDL介面檔案的Stub子類。
- 在Client端,通過ServiceConnection獲取到Server端的Binder例項,進行呼叫。這裡主要用到的是Stub的靜態方法asInterface()。客戶端就可以通過這個例項進行跨程式呼叫了。
同一個APP中跨程式通訊
我們其實在實際中,使用最多的還是這一種情況,目前我所接觸到的就是自己設計的IM系統中使用到。
他其實跟跨APP沒有什麼區別,同一個APP中跨程式,他只是需要一份AIDL檔案即可。因為系統分配給一個程式的記憶體是有限的,而且預設的主程式處理的事務較多,在保活方面,在資料接收處理方面,使用多程式是有優勢的。
Android的APP啟用多程式方式是在清單檔案中新增android:process
,設定該元件所處的程式名稱既可。
比如
android:process=":local"//他設定所處的程式名稱是包名+local,他表示的是該元件處於自己的私有程式,其他程式的元件不可以跑在同一個程式中。
android:process="com.app.sample.local"//這裡的name就是程式名稱。這種則是宣告他是全域性程式,其他應用的元件可以通過ShareUID來達到跟他位於同一個程式執行。
複製程式碼
以上就是AIDL的基本使用,當然我們在實際使用中,會更加的複雜,比如我們會在IM程式中開啟多個執行緒來處理訊息,接受訊息,輪訓訊息等,而且需要通過回撥的方式主動通知主程式,一般需要IM端一個Service,主程式端一個Service等,這些我們後面在做講述。
本文如有錯誤或侵權還請指出或聯絡刪除,謝謝。