preface
Android Vehicle application Development and Analysis (3) – Building THE MVVM Architecture (Java version). Through the previous introduction, we also learned that in most vehicle system application architecture, a complete application often contains three layers. , respectively,
- HMI
Human Machine Interface displays UI information for human-machine interaction.
- Service
In the background of the system for data processing, monitoring data status.
- SDK
According to the business logic, the communication interface exposed by the Service is required. Other modules communicate with the Service through THE SDK, usually based on the AIDL interface.
This article is mainly about writing an IDEA of SDK based on AIDL. Please modify the source code involved in this article according to actual needs
AIDL introduction
AIDL, the Android interface definition language, is a common way of interprocess communication in Android development. On how to use AIDL please refer to the Android interface definition language (AIDL) | | Android Developers Android Developers
Here are some confusing keywords in AIDL:
- in
interface HvacInterface {
void setData(in Hvac hvac);
}
Copy the code
One-way data flow. Arguments modified by in are passed to the Server, but any changes made by Servier to the arguments are not called back to the Client.
- out
interface HvacInterface {
void getData(out Hvac hvac);
}
Copy the code
One-way data flow. Only the default values of arguments modified by out are passed to the Server. Changes made by Servier to the arguments are called back to the Client after the call.
- inout
interface HvacInterface {
void getData(inout Hvac hvac);
}
Copy the code
Inout is a combination of the two. The arguments are passed to the Server, and changes made by the Server to the arguments are returned to the Client after the call.
- oneway
The interface defined by AIDL defaults to synchronous invocation. For example, when the Client calls the setData method, it takes 5 seconds to execute setData on the Server, so the thread that calls the setData method on the Client will be blocked for 5 seconds. This problem can be avoided by adding oneway to the setData method and modifying the interface to make an asynchronous call.
interface HvacInterface {
oneway void setData(in Hvac hvac);
}
Copy the code
Oneway can be used to modify not only methods, but also the interface itself, so that all methods within the interface implicitly include oneWay. Methods decorated with oneway may not return values, nor may they be decorated with out or inout parameters.
AIDL in general
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); }}Copy the code
In normal usage, we need to check whether the Client is bound to the Server first. Not only the Client calls the interface of the Server, but also to prevent null Pointers caused by the binding failure.
The above general usage in vehicular applications not only makes HMI development tedious, but also needs to deal with the unbound state in the case of Service exceptions. Here is how to easily package the SDK
Encapsulate SDK Base classes
In the actual development, we hid the details of binding, reconnection and thread switching between the Client side and the Service in the SDK and encapsulated it as a BaseConnectManager. You only need to inherit BaseConnectManager and pass in the package name, class name, and expected disconnection time of the 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(); }Copy the code
To encapsulate the SDK
Most of the time in development we have only one Service Interface to operate on, as follows:
interface HvacInterface {
oneway void setTemperature(int temperature);
oneway void requestTemperature();
boolean registerCallback(in HvacCallback callback);
boolean unregisterCallback(in HvacCallback callback);
}
Copy the code
Callback used to Callback the result of server-side processing
interface HvacCallback {
oneway void onTemperatureChanged(double temperature);
}
Copy the code
Encapsulate an HvacManager based on BaseConnectManager
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(); 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; }}); }}Copy the code
If the service is disconnected, we put the methods in a queue. When the service is bound again, the methods in the queue will be pulled out and executed.
Finally, we add a script to build the JAR in the SDK Module build.gradle
// 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)
Copy the code
Use the sample
public void requestTemperature() {
LogUtils.logI(TAG, "[requestTemperature]");
HvacManager.getInstance().requestTemperature();
}
Copy the code
In practice, callers do not need to care about the binding state of the Service, nor do they need to take the initiative to switch threads, which greatly simplifies the development of HMI. Demo address: github.com/linux-link/…
The resources
The Android interface definition language (AIDL) | | Android Developers Android Developers