Android Vehicle Application Development and Analysis (4) - Writing an AIDL-based SDK

foreword

Before introducing how to use Jetpack to build MVVM architecture in HMI in the vehicle application development system Android vehicle application development and analysis (3) - Building MVVM architecture (Java version) , through the previous introduction, I also learned that in most vehicle system application architecture , a complete application often contains three layers, namely

  • HMI

Human Machine Interface, which displays UI information and performs human-computer interaction.

  • Service

Data processing is performed in the background of the system and data status is monitored.

  • SDK

According to the business logic, Servicethe communication interface that needs to be exposed to the outside world, and other modules complete and communicate through the SDK Service, usually based on the AIDL interface.

This article mainly talks about an idea when writing an AIDL-based SDK. Please modify the source code involved in this article according to actual needs.

Introduction to AIDL

AIDL, Android Interface Definition Language, is an inter-process communication method commonly used in Android development. For how to use AIDL , please refer to Android Interface Definition Language (AIDL) | Android Developers | Android Developers

Here are some keywords that are easily confused when using AIDL :

  • in
interface HvacInterface {
    void setData(in Hvac hvac);
}
复制代码

One-way data flow. The parameters modified by in will be successfully transmitted to the server side, but any changes to the actual parameters by the server side will not be called back to the client side.

  • out
interface HvacInterface {
    void getData(out Hvac hvac);
}
复制代码

One-way data flow. For the parameters modified by out, only the default value will be passed to the server side, and the change of the actual parameter by the server side will be called back to the client side after the call ends.

  • inout
interface HvacInterface {
    void getData(inout Hvac hvac);
}
复制代码

inout is a combination of the above two, the actual parameter will be successfully transmitted to the server, and the server's modification of the actual parameter will return to the client after the call ends.

  • 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<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

开发中多数时候我们只有一个用于操作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<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 {
                // 将此方法放入队列中,等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重新被绑定上后,队列中的方法,会依次被取出执行。

Finally, we add a script that can compile the jar to the build.gradle of the SDK module

// 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)
复制代码

Example of use

public void requestTemperature() {
    LogUtils.logI(TAG, "[requestTemperature]");
    HvacManager.getInstance().requestTemperature();
}
复制代码

In actual use, the caller does not need to care about the binding state of the Service, nor does it need to actively switch threads, which greatly simplifies the development of HMI. demo address: github.com/linux-link/…

References

Android Interface Definition Language (AIDL) | Android Developers | Android Developers

Guess you like

Origin juejin.im/post/7083140299916050468