前言
之前介紹了車載應(yīng)用開(kāi)發(fā)體系中如何使用Jetpack在HMI中構(gòu)建MVVM架構(gòu)Android 車載應(yīng)用開(kāi)發(fā)與分析 (3)- 構(gòu)建 MVVM 架構(gòu)(Java版)拐揭,通過(guò)之前的介紹,也了解到在大多數(shù)車載系統(tǒng)應(yīng)用架構(gòu)中朴爬,一個(gè)完整的應(yīng)用往往會(huì)包含三層荒澡,分別是
HMI
Human Machine Interface,顯示UI信息屉凯,進(jìn)行人機(jī)交互立帖。Service
在系統(tǒng)后臺(tái)進(jìn)行數(shù)據(jù)處理,監(jiān)控?cái)?shù)據(jù)狀態(tài)悠砚。SDK
根據(jù)業(yè)務(wù)邏輯晓勇,需要Service
對(duì)外暴露的通信接口,其他模塊通過(guò)SDK來(lái)完成與Service
通信灌旧,通常是基于AIDL接口绑咱。
本篇主要講解,如何編寫(xiě)基于 AIDL 的 SDK 枢泰。
AIDL 介紹
AIDL描融,Android 接口定義語(yǔ)言,是Android開(kāi)發(fā)中常用的一種進(jìn)程間通信方式衡蚂。關(guān)于如何使用 AIDL 請(qǐng)參考 Android 接口定義語(yǔ)言 (AIDL) | Android 開(kāi)發(fā)者 | Android Developers
這里介紹一些 AIDL 使用過(guò)程中容易混淆的關(guān)鍵字:
- in
interface HvacInterface {
void setData(in Hvac hvac);
}
單向數(shù)據(jù)流向窿克。被in修飾的參數(shù),會(huì)順利傳到Server端毛甲,但Servier端對(duì)實(shí)參的任何改變年叮,都不會(huì)回調(diào)給Client端。
- out
interface HvacInterface {
void getData(out Hvac hvac);
}
單向數(shù)據(jù)流向玻募。被out修飾的參數(shù)只损,只有默認(rèn)值會(huì)傳到Server端,Servier端對(duì)實(shí)參的改變七咧,在調(diào)用結(jié)束后改执,會(huì)回調(diào)給Client端。
- inout
interface HvacInterface {
void getData(inout Hvac hvac);
}
inout 則是上面二者的結(jié)合坑雅,實(shí)參會(huì)順利傳到Server辈挂,且Server對(duì)實(shí)參的修改,在調(diào)用結(jié)束后會(huì)返回Client端裹粤。
-
oneway
AIDL 定義的接口默認(rèn)是同步調(diào)用终蒂。舉個(gè)例子:Client端調(diào)用setData
方法蜂林,setData
在Server端執(zhí)行需要耗費(fèi)5秒鐘,那么Client端調(diào)用setData
方法的線程就會(huì)被block5秒鐘拇泣。如果在setData
方法上加上oneway噪叙,將接口修改為異步調(diào)用就可以避免這個(gè)問(wèn)題。
interface HvacInterface {
oneway void setData(in Hvac hvac);
}
oneway
不僅可以修飾方法霉翔,也可以用來(lái)修飾在interface本身睁蕾,這樣interface內(nèi)所有的方法都隱式地帶上oneway
。被oneway修飾了的方法不可以有返回值债朵,也不可以再用out或inout修飾參數(shù)子眶。
AIDL 常規(guī)用法
IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Following the example above for an AIDL interface,
// this gets an instance of the IRemoteInterface, which we can use to call on the service
iRemoteService = IRemoteService.Stub.asInterface(service);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
iRemoteService = null;
}
};
public void setData(Hvac havc){
if (iRemoteService!=null){
iRemoteService.setData(hvac);
}
}
常規(guī)的用法中,我們需先判斷Client端是否已經(jīng)綁定上Server端序芦,不僅Client端對(duì)Server端的接口調(diào)用臭杰,也要防止綁定失敗導(dǎo)致的空指針。
車載應(yīng)用中上述的常規(guī)用法不僅會(huì)使HMI開(kāi)發(fā)變得繁瑣谚中,還需要處理Service異常狀態(tài)下解除綁定后的狀態(tài)渴杆。下面介紹如何簡(jiǎn)便的封裝SDK
封裝SDK Base類
實(shí)際開(kāi)發(fā)中,我們把Client端對(duì)Service的綁定宪塔、重連磁奖、線程切換等細(xì)節(jié)隱藏到SDK中并封裝成一個(gè)BaseConnectManager
,使用時(shí)只需要繼承BaseConnectManager
并傳入Service的包名某筐、類名和期望的斷線重連時(shí)間即可比搭。
public abstract class BaseConnectManager<T extends IInterface> {
private final String TAG = SdkLogUtils.TAG_FWK + getClass().getSimpleName();
private static final String THREAD_NAME = "bindServiceThread";
private final Application mApplication;
private IServiceConnectListener mServiceListener;
private final Handler mChildThread;
private final Handler mMainThread;
private final LinkedBlockingQueue<Runnable> mTaskQueue = new LinkedBlockingQueue<>();
private final Runnable mBindServiceTask = this::bindService;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
SdkLogUtils.logV(TAG, "[onServiceConnected]");
mProxy = asInterface(service);
Remote.tryExec(() -> {
service.linkToDeath(mDeathRecipient, 0);
});
if (mServiceListener != null) {
mServiceListener.onServiceConnected();
}
handleTask();
mChildThread.removeCallbacks(mBindServiceTask);
}
@Override
public void onServiceDisconnected(ComponentName name) {
SdkLogUtils.logV(TAG, "[onServiceDisconnected]");
mProxy = null;
if (mServiceListener != null) {
mServiceListener.onServiceDisconnected();
}
}
};
private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
SdkLogUtils.logV(TAG, "[binderDied]");
if (mServiceListener != null) {
mServiceListener.onBinderDied();
}
if (mProxy != null) {
mProxy.asBinder().unlinkToDeath(mDeathRecipient, 0);
mProxy = null;
}
attemptToRebindService();
}
};
private T mProxy;
public BaseConnectManager() {
mApplication = SdkAppGlobal.getApplication();
HandlerThread thread = new HandlerThread(THREAD_NAME, 6);
thread.start();
mChildThread = new Handler(thread.getLooper());
mMainThread = new Handler(Looper.getMainLooper());
bindService();
}
private void bindService() {
if (mProxy == null) {
SdkLogUtils.logV(TAG, "[bindService] start");
ComponentName name = new ComponentName(getServicePkgName(), getServiceClassName());
Intent intent = new Intent();
if (getServiceAction() != null) {
intent.setAction(getServiceAction());
}
intent.setComponent(name);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mApplication.startForegroundService(intent);
} else {
mApplication.startService(intent);
}
boolean connected = mApplication.bindService(intent, mServiceConnection,
Context.BIND_AUTO_CREATE);
SdkLogUtils.logV(TAG, "[bindService] result " + connected);
if (!connected) {
attemptToRebindService();
}
} else {
SdkLogUtils.logV(TAG, "[bindService] not need");
}
}
protected void attemptToRebindService() {
SdkLogUtils.logV(TAG, "[attemptToRebindService]");
mChildThread.postDelayed(mBindServiceTask, getRetryBindTimeMill());
}
protected void handleTask() {
Runnable task;
while ((task = mTaskQueue.poll()) != null) {
SdkLogUtils.logV(TAG, "[handleTask] poll task form task queue");
mChildThread.post(task);
}
}
public void init() {
bindService();
}
public boolean isServiceConnected() {
return isServiceConnected(false);
}
public boolean isServiceConnected(boolean tryConnect) {
SdkLogUtils.logV(TAG, "[isServiceConnected] tryConnect " + tryConnect + ";isConnected " + (mProxy != null));
if (mProxy == null && tryConnect) {
attemptToRebindService();
}
return this.mProxy != null;
}
public void release() {
SdkLogUtils.logV(TAG, "[release]");
if (this.isServiceConnected()) {
this.mProxy.asBinder().unlinkToDeath(this.mDeathRecipient, 0);
this.mProxy = null;
this.mApplication.unbindService(mServiceConnection);
}
}
public void setStateListener(IServiceConnectListener listener) {
SdkLogUtils.logV(TAG, "[setStateListener]" + listener);
mServiceListener = listener;
}
public void removeStateListener() {
SdkLogUtils.logV(TAG, "[removeStateListener]");
mServiceListener = null;
}
protected T getProxy() {
return mProxy;
}
protected LinkedBlockingQueue<Runnable> getTaskQueue() {
return mTaskQueue;
}
public Handler getMainHandler() {
return mMainThread;
}
protected abstract String getServicePkgName();
protected abstract String getServiceClassName();
protected String getServiceAction() {
return null;
}
protected abstract T asInterface(IBinder service);
protected abstract long getRetryBindTimeMill();
}
封裝 SDK
開(kāi)發(fā)中多數(shù)時(shí)候我們只有一個(gè)用于操作Service Interface,如下所示:
interface HvacInterface {
oneway void setTemperature(int temperature);
oneway void requestTemperature();
boolean registerCallback(in HvacCallback callback);
boolean unregisterCallback(in HvacCallback callback);
}
用于回調(diào)Server端處理結(jié)果的Callback
interface HvacCallback {
oneway void onTemperatureChanged(double temperature);
}
基于BaseConnectManager
封裝一個(gè)HvacManager
public class HvacManager extends BaseConnectManager<HvacInterface> {
private static final String TAG = SdkLogUtils.TAG_FWK + HvacManager.class.getSimpleName();
private static volatile HvacManager sHvacManager;
public static final String SERVICE_PACKAGE = "com.fwk.service";
public static final String SERVICE_CLASSNAME = "com.fwk.service.SimpleService";
private static final long RETRY_TIME = 5000L;
private final List<IHvacCallback> mCallbacks = new ArrayList<>();
private final HvacCallback.Stub mSampleCallback = new HvacCallback.Stub() {
@Override
public void onTemperatureChanged(double temperature) throws RemoteException {
SdkLogUtils.logV(TAG, "[onTemperatureChanged] " + temperature);
getMainHandler().post(() -> {
for (IHvacCallback callback : mCallbacks) {
callback.onTemperatureChanged(temperature);
}
});
}
};
public static HvacManager getInstance() {
if (sHvacManager == null) {
synchronized (HvacManager.class) {
if (sHvacManager == null) {
sHvacManager = new HvacManager();
}
}
}
return sHvacManager;
}
@Override
protected String getServicePkgName() {
return SERVICE_PACKAGE;
}
@Override
protected String getServiceClassName() {
return SERVICE_CLASSNAME;
}
@Override
protected HvacInterface asInterface(IBinder service) {
return HvacInterface.Stub.asInterface(service);
}
@Override
protected long getRetryBindTimeMill() {
return RETRY_TIME;
}
/******************/
public void requestTemperature() {
Remote.tryExec(() -> {
if (isServiceConnected(true)) {
getProxy().requestTemperature();
} else {
// 將此方法放入隊(duì)列中来吩,等Service重新連接后敢辩,會(huì)依次調(diào)用
getTaskQueue().offer(this::requestTemperature);
}
});
}
public void setTemperature(int temperature) {
Remote.tryExec(() -> {
if (isServiceConnected(true)) {
getProxy().requestTemperature();
} else {
getTaskQueue().offer(() -> {
setTemperature(temperature);
});
}
});
}
public boolean registerCallback(IHvacCallback callback) {
return Remote.exec(() -> {
if (isServiceConnected(true)) {
boolean result = getProxy().registerCallback(mSampleCallback);
if (result) {
mCallbacks.remove(callback);
mCallbacks.add(callback);
}
return result;
} else {
getTaskQueue().offer(() -> {
registerCallback(callback);
});
return false;
}
});
}
public boolean unregisterCallback(IHvacCallback callback) {
return Remote.exec(() -> {
if (isServiceConnected(true)) {
boolean result = getProxy().unregisterCallback(mSampleCallback);
if (result) {
mCallbacks.remove(callback);
}
return result;
} else {
getTaskQueue().offer(() -> {
unregisterCallback(callback);
});
return false;
}
});
}
}
上述代碼中蔽莱,我們需要注意一點(diǎn)弟疆,每次調(diào)用遠(yuǎn)程方法都需要判斷當(dāng)前service是否處于連接,如果與Service的連接被斷開(kāi)了盗冷,我們要把方法放入一個(gè)隊(duì)列中去怠苔,當(dāng)Service重新被綁定上后,隊(duì)列中的方法仪糖,會(huì)依次被取出執(zhí)行柑司。
最后,我們?cè)赟DK module的 build.gradle中加入可以編譯出jar的腳本
// makeJar
def zipFile = file('build/intermediates/aar_main_jar/release/classes.jar')
task makeJar(type: Jar) {
from zipTree(zipFile)
archiveBaseName = "sdk"
destinationDirectory = file("build/outputs/")
manifest {
attributes(
'Implementation-Title': "${project.name}",
'Built-Date': new Date().getDateTimeString(),
'Built-With':
"gradle-${project.getGradle().getGradleVersion()},groovy-${GroovySystem.getVersion()}",
'Created-By':
'Java ' + System.getProperty('java.version') + ' (' + System.getProperty('java.vendor') + ')')
}
}
makeJar.dependsOn(build)
使用示例
public void requestTemperature() {
LogUtils.logI(TAG, "[requestTemperature]");
HvacManager.getInstance().requestTemperature();
}
實(shí)際使用時(shí)锅劝,調(diào)用方既不需要關(guān)心Service的綁定狀態(tài)攒驰,也不需要主動(dòng)進(jìn)行線程切換,極大的簡(jiǎn)便了HMI的開(kāi)發(fā)故爵。
本文源碼地址: https://github.com/linux-link/CarServerArch
參考資料
Android 接口定義語(yǔ)言 (AIDL) | Android 開(kāi)發(fā)者 | Android Developers