Android的 IPC api 有很多,但是很多都是一过性的,并不会保存连接状态,比如 provider 的 call 方法就是一种非常直接的方法,但是被call的进程也容易挂掉。bindService 是一种可以保证连接的跨进程通信机制,在Android系统设计中,它对后台进程具有很强的保活效应(假设前台进程bind了它),但是美中不足的是,调用方式就有些麻烦了。因为不管客户端啥时候调用都要提前预留时间进行bind,而等待时间又不确定,此时我们有几种处理方法
1. 让调用线程等待,当bind成功后再放开
2. 将接口改为回调式的
第二种方式我们先不讨论,因为实现起来比较简单。
第一种方式,直接hold住调用线程,就有问题了,如果调用线程是主线程,而bindService 的ServiceConnection 的回调线程也是主线程,那么显然你就永远锁住了,主线程将永远不能被唤醒。因此我们需要将 ServiceConnection 回调到其他线程。
怎样实现呢?
我们看了 Context 代码发现有这样一个方法可以被我们利用,这个方法在至少 Android 7 就已经存在了,所以可用性还是比较强的。
/**
* Same as {@link #bindService(Intent, ServiceConnection, int, UserHandle)}, but with an
* explicit non-null Handler to run the ServiceConnection callbacks on.
*
* @hide
*/
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
Handler handler, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
我们通过反射来调用它
HandlerThread thread = new HandlerThread("my_thread");
thread.start();
Handler handler = new Handler(thread.getLooper());
handler.post(() -> {
aidlConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
log("on aidl service connected!!, service = " + service + ", is main thread = " + (Looper.getMainLooper() == Looper.myLooper()));
caller = IpcCaller.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName className) {
caller = null;
}
};
try {
Intent intent = new Intent(Contract.ACTION_MSGR);
intent.setPackage(getPackageName());
intent.setAction(Contract.ACTION_AIDL);
Method bindServiceAsUser = Context.class.getDeclaredMethod("bindServiceAsUser"
, Intent.class, ServiceConnection.class,
int.class, Handler.class, UserHandle.class);
bindServiceAsUser.setAccessible(true);
bindServiceAsUser.invoke(getApplicationContext(), intent, aidlConnection, Context.BIND_AUTO_CREATE,
handler, UserHandle.getUserHandleForUid(Process.myUid()));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
log(e);
}
});
注意bindService也必须在这个线程调用,否则会有问题
Caused by: java.lang.RuntimeException: ServiceConnection com.xbb.ipcdemo.MainActivity$3@9fef978 registered with differing handler (was Handler (android.os.Handler) {c62f851} now Handler (android.app.ActivityThread$H) {d7298b6})
at android.app.LoadedApk$ServiceDispatcher.validate(LoadedApk.java:1473)
at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:1363)
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1624)
at android.app.ContextImpl.bindService(ContextImpl.java:1596)
at android.content.ContextWrapper.bindService(ContextWrapper.java:636)
at com.xbb.ipcdemo.MainActivity.onStart(MainActivity.java:155)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1254)
at android.app.Activity.performStart(Activity.java:6930)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2767)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2875)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1578)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6623)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
这样就可以放心大胆地挂起任何调用,然后在 onServiceConnected 成功后再唤醒它。
如果帮助到了你,点个赞吧