Android 車載應用開發與分析 (4)- 編寫基於AIDL 的 SDK

語言: CN / TW / HK

前言

之前介紹了車載應用開發體系中如何使用Jetpack在HMI中構建MVVM架構Android 車載應用開發與分析 (3)- 構建 MVVM 架構(Java版),通過之前的介紹,也瞭解到在大多數車載系統應用架構中,一個完整的應用往往會包含三層,分別是 * HMI Human Machine Interface,顯示UI資訊,進行人機互動。

  • Service 在系統後臺進行資料處理,監控資料狀態。

  • SDK 根據業務邏輯,需要Service對外暴露的通訊介面,其他模組通過SDK來完成與Service通訊,通常是基於AIDL介面。

本篇主要講編寫基於AIDL的SDK時的一種思路,本文涉及的原始碼請根據實際需要進行修改

AIDL 介紹

AIDL,Android 介面定義語言,是Android開發中常用的一種程序間通訊方式。關於如何使用 AIDL 請參考 Android 介面定義語言 (AIDL)  |  Android 開發者  |  Android Developers

這裡介紹一些 AIDL 使用過程中容易混淆的關鍵字:

  • in interface HvacInterface { void setData(in Hvac hvac); } 單向資料流向。被in修飾的引數,會順利傳到Server端,但Servier端對實參的任何改變,都不會回撥給Client端。

  • out interface HvacInterface { void getData(out Hvac hvac); } 單向資料流向。被out修飾的引數,只有預設值會傳到Server端,Servier端對實參的改變,在呼叫結束後,會回撥給Client端。

  • inout interface HvacInterface { void getData(inout Hvac hvac); } inout 則是上面二者的結合,實參會順利傳到Server,且Server對實參的修改,在呼叫結束後會返回Client端。

  • oneway AIDL 定義的介面預設是同步呼叫。舉個例子:Client端呼叫setData方法,setData在Server端執行需要耗費5秒鐘,那麼Client端呼叫setData方法的執行緒就會被block5秒鐘。如果在setData方法上加上oneway,將介面修改為非同步呼叫就可以避免這個問題。

interface HvacInterface { oneway void setData(in Hvac hvac); }

oneway不僅可以修飾方法,也可以用來修飾在interface本身,這樣interface內所有的方法都隱式地帶上oneway。被oneway修飾了的方法不可以有返回值,也不可以再用out或inout修飾引數。

AIDL 常規用法

``` 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); } }

```

常規的用法中,我們需先判斷Client端是否已經繫結上Server端,不僅Client端對Server端的介面呼叫,也要防止繫結失敗導致的空指標。

車載應用中上述的常規用法不僅會使HMI開發變得繁瑣,還需要處理Service異常狀態下解除繫結後的狀態。下面介紹如何簡便的封裝SDK

封裝SDK Base類

實際開發中,我們把Client端對Service的繫結、重連、執行緒切換等細節隱藏到SDK中並封裝成一個BaseConnectManager,使用時只需要繼承BaseConnectManager並傳入Service的包名、類名和期望的斷線重連時間即可。

``` public abstract class BaseConnectManager {

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

開發中多數時候我們只有一個用於操作Service Interface,如下所示:

``` interface HvacInterface {

oneway void setTemperature(int temperature);

oneway void requestTemperature();

boolean registerCallback(in HvacCallback callback);

boolean unregisterCallback(in HvacCallback callback);

} ```

用於回撥Server端處理結果的Callback

``` interface HvacCallback {

oneway void onTemperatureChanged(double temperature);

} ```

基於BaseConnectManager封裝一個HvacManager

``` public class HvacManager extends BaseConnectManager {

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 {
            // 將此方法放入佇列中,等Service重新連線後,會依次呼叫
            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;
        }
    });
}

}

```

上述程式碼中,我們需要注意一點,每次呼叫遠端方法都需要判斷當前service是否處於連線,如果與Service的連線被斷開了,我們要把方法放入一個佇列中去,當Service重新被繫結上後,佇列中的方法,會依次被取出執行。

最後,我們在SDK 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(); } 實際使用時,呼叫方既不需要關心Service的繫結狀態,也不需要主動進行執行緒切換,極大的簡便了HMI的開發。 demo地址: http://github.com/linux-link/CarServerArch

參考資料

Android 介面定義語言 (AIDL)  |  Android 開發者  |  Android Developers