我一直在研究这个问题,目前我发现如果我们有类似于以下代码,就可能会出现崩溃:
MyForegroundService.java
public class MyForegroundService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(...);
}
}
MainActivity.java
Intent serviceIntent = new Intent(this, MyForegroundService.class)
startForegroundService(serviceIntent)
...
stopService(serviceIntent)
异常在以下代码块中抛出:
ActiveServices.java
private final void bringDownServiceLocked(ServiceRecord r) {
...
if (r.fgRequired) {
Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
+ r);
r.fgRequired = false;
r.fgWaiting = false;
mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
mAm.mHandler.removeMessages(
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
if (r.app != null) {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
msg.obj = r.app;
msg.getData().putCharSequence(
ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
mAm.mHandler.sendMessage(msg);
}
}
...
}
这个方法在执行
MyForegroundService
的
onCreate()
之前被调用,因为 Android 将服务的创建安排在主线程处理程序上,但是
bringDownServiceLocked
在
BinderThread
上被调用,这是一种竞争条件。这意味着
MyForegroundService
没有机会调用
startForeground
,这将导致崩溃。
要解决这个问题,我们必须确保在调用
MyForegroundService
的
onCreate()
之前不会调用
bringDownServiceLocked
。
public class MyForegroundService extends Service {
private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";
private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
context.removeStickyBroadcast(intent);
stopForeground(true);
stopSelf();
}
};
@Override
public void onCreate() {
super.onCreate();
startForeground(...);
registerReceiver(
stopReceiver, new IntentFilter(ACTION_STOP));
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(stopReceiver);
}
public static void stop(Context context) {
context.sendStickyBroadcast(new Intent(ACTION_STOP));
}
}
通过使用粘性广播,我们确保广播不会丢失,并且
stopReceiver
在
MyForegroundService
的
onCreate()
中注册后尽快接收停止意图。此时,我们已经调用了
startForeground(...)
。我们还必须删除该粘性广播,以防止下次通知stopReceiver。
请注意,方法
sendStickyBroadcast
已被弃用,我仅将其用作暂时的解决方法来解决此问题。