如图,首先我们回答一个问题即
如何将Activity与指定Service绑定?
答:绑定其实就是拿到Service的引用,用于和Service通信,需要通过中介者AMS,所以只要AMS拿到Service句柄就好办了因为AMS通过Activity的bindService方法就很容易拿到句柄了,大概分为三步
1.AMS持有Activity的句柄(引用)
通过bindService就可以知道是哪个activity要绑定服务,
2.AMS先拿到Service的句柄(引用)
首先Activity通过一次进程通信让AMS去启动指定的Service,但AMS自己newInstance Service是不行的,因为都不在一个进程,拿不到.Class文件,但是APP进程是可以的,所以只能又来一次进程通信了,之后APP进程(具体的创建者是ActivityThread)就可以用service的实例去构造binder了,然后通过有一次进程通信将构造的binder返回给AMS,AMS在回调给对应的Activity即可
3.AMS回调Service的句柄给Activity
AMS通过onServiceConnected将Service的句柄回调给Activity
后续的StartService
后续Activity如果再调用StartService,AMS就要检查服务是否启动了,如果启动了就只执行onStartCommand,否则还是需要回调onCreate
应用
通过上述分析,我们其实可以解决android8.0后中一个经常发生的ANR问题
Context.startForegroundService() did not then call Service.startForeground()? 即调用了startForegroundService 五秒后没有调用startForeground?
分析:这5s有可能是AMS通信比较耗时,另外如果你的Service是在子进程的话,进程启动也需要耗时,解决方案便是先通过bindService,等待服务启动,启动完成后再调用startForegroundService,可以解决的原因
- bindService没有startForground的限制
- startForegroundService和startService一样,会走checkServiceRecord方法,然后执行onStartCommand,所以我们在onStartCommand内部在执行startForeground即可
如下代码所示
private static void reliableStartService(Context context, Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 存在clip进程就没有启动进程的时间了
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O || ProcessUtils.isExistClipProcess()) {
context.startForegroundService(intent);
return;
}
final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
context.unbindService(this);
MMKVUtil.INSTANCE.putLong("start_time_key", System.currentTimeMillis());
context.startForegroundService(intent);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
// 不存在,先用bind启动服务
context.bindService(intent, connection , Context.BIND_AUTO_CREATE);
} else {
context.startService(intent);
}
}