Android 9.0之不同的Context导致广播接收者无法取
不同context注册和取消广播引发的问题
1:问题引入
private IntentFilter intentFilter = new IntentFilter();
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
}
}
};
@Override
protected void onResume() {
super.onResume();
intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
getApplicationContext().registerReceiver(mBroadcastReceiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
if (mBroadcastReceiver != null) {
unregisterReceiver(mBroadcastReceiver);
}
}
上面的逻辑是在一个activity的onResume生命周期中我们用application的context来注册一个关闭,用activity的context来取消注册
2:错误提示
09-05 10:21:28.548 10355 10355 E AndroidRuntime: FATAL EXCEPTION: main
09-05 10:21:28.548 10355 10355 E AndroidRuntime: Process: com.example.test.qkalertdialog, PID: 10355
09-05 10:21:28.548 10355 10355 E AndroidRuntime: java.lang.RuntimeException: Unable to pause activity {com.example.test.qkalertdialog/com.example.test.qkalertdialog.MainActivity}: java.lang.IllegalArgumentException: Receiver not registered: com.example.test.qkalertdialog.MainActivity$7@51af3c4
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.ActivityThread.performPauseActivityIfNeeded(ActivityThread.java:4047)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.ActivityThread.performPauseActivity(ActivityThread.java:4000)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:3952)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.servertransaction.PauseActivityItem.execute(PauseActivityItem.java:45)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1819)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.os.Looper.loop(Looper.java:193)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6695)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:870)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: Caused by: java.lang.IllegalArgumentException: Receiver not registered: com.example.test.qkalertdialog.MainActivity$7@51af3c4
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:1281)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:1508)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:667)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at com.example.yangyalin_os.qkalertdialog.MainActivity.onPause(MainActivity.java:156)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.Activity.performPause(Activity.java:7345)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.Instrumentation.callActivityOnPause(Instrumentation.java:1465)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: at android.app.ActivityThread.performPauseActivityIfNeeded(ActivityThread.java:4037)
09-05 10:21:28.548 10355 10355 E AndroidRuntime: ... 12 more
上面的提示是在onPause中无法取消注册,可以看到这个广播接收者是明明已经注册了,为啥造成上述问题咧
3:Application Context 和Activity Context
我们都知道,applicaiton和activity都是context,但是applicationContext和activity COntext是不是同一个对象呢
我们分析下应用的启动流程
3.1:application和applicaiton context关联的时机
ActivityThread.java
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
ContextImpl.java
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
context.setResources(packageInfo.getResources());
return context;
}
3.2:activity和activity context的关联时机
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
config.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
.......................................
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
final int displayId;
try {
displayId = ActivityManager.getService().getActivityDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getResources());
appContext = (ContextImpl) appContext.createDisplayContext(display);
break;
}
}
}
return appContext;
}
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
String[] splitDirs = packageInfo.getSplitResDirs();
ClassLoader classLoader = packageInfo.getClassLoader();
if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
try {
classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
} catch (NameNotFoundException e) {
// Nothing above us can handle a NameNotFoundException, better crash.
throw new RuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
? packageInfo.getCompatibilityInfo()
: CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
// Create the base resources for which all configuration contexts for this Activity
// will be rebased upon.
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
可以看到applicaiton Context 和Activity context是不同的对象
4:动态广播注册者流程
ContextImpl.java:registerReceiver
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return registerReceiver(receiver, filter, null, null);
}
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
return registerReceiverInternal(receiver, getUserId(),
filter, broadcastPermission, scheduler, getOuterContext(), 0);
}
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context, int flags) {
IIntentReceiver rd = null;
if (receiver != null) {
if (mPackageInfo != null && context != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
} else {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
rd = new LoadedApk.ReceiverDispatcher(
receiver, context, scheduler, null, true).getIIntentReceiver();
}
}
try {
final Intent intent = ActivityManager.getService().registerReceiver(
mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
broadcastPermission, userId, flags);
if (intent != null) {
intent.setExtrasClassLoader(getClassLoader());
intent.prepareToEnterProcess();
}
return intent;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
我们可以看到这个过程是将receiver转化成ReceiverDispatcher.InnerReceiver的binder对象,然后传到ams那一段,binder通信传递的都是binder对象
rd = mPackageInfo.getReceiverDispatcher(receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
....................................................................
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
Context context, Handler handler,
Instrumentation instrumentation, boolean registered) {
synchronized (mReceivers) {
LoadedApk.ReceiverDispatcher rd = null;
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
if (registered) {
map = mReceivers.get(context);
if (map != null) {
rd = map.get(r);
}
}
if (rd == null) {
rd = new ReceiverDispatcher(r, context, handler,
instrumentation, registered);
if (registered) {
if (map == null) {
map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
mReceivers.put(context, map);
}
map.put(r, rd);
}
} else {
rd.validate(context, handler);
}
rd.mForgotten = false;
return rd.getIIntentReceiver();
}
}
上面的过程就是从注册列表中查找注册记录,如果之前已经用相同的context注册过,那么就直接取之前注册过的,如果没有那么就重新创建一个
然后放在一个map中存储起来
ActivityManagerService.java::registerReceiver
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
int flags) {
enforceNotIsolatedCaller("registerReceiver");
ArrayList<Intent> stickyIntents = null;
ProcessRecord callerApp = null;
final boolean visibleToInstantApps
= (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;---->是否对instant app可见
int callingUid;
int callingPid;
boolean instantApp;
synchronized(this) {
if (caller != null) {
callerApp = getRecordForAppLocked(caller);
if (callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid()
+ ") when registering receiver " + receiver);
}
if (callerApp.info.uid != SYSTEM_UID &&
!callerApp.pkgList.containsKey(callerPackage) &&
!"android".equals(callerPackage)) {
throw new SecurityException("Given caller package " + callerPackage
+ " is not running in process " + callerApp);
}
callingUid = callerApp.info.uid;
callingPid = callerApp.pid;
} else {
callerPackage = null;
callingUid = Binder.getCallingUid();
callingPid = Binder.getCallingPid();
}
instantApp = isInstantApp(callerApp, callerPackage, callingUid);
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
ALLOW_FULL_ONLY, "registerReceiver", callerPackage);---->注册者的userId
Iterator<String> actions = filter.actionsIterator();------->intent filter中添加的action
if (actions == null) {------------------------------------->没有添加action
ArrayList<String> noAction = new ArrayList<String>(1);
noAction.add(null);
actions = noAction.iterator();
}
// Collect stickies of users
int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
while (actions.hasNext()) {
String action = actions.next();
for (int id : userIds) {
ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
if (stickies != null) {
ArrayList<Intent> intents = stickies.get(action);
if (intents != null) {
if (stickyIntents == null) {
stickyIntents = new ArrayList<Intent>();
}
stickyIntents.addAll(intents);
}
}
}
}
}
ArrayList<Intent> allSticky = null;
if (stickyIntents != null) {
final ContentResolver resolver = mContext.getContentResolver();
// Look for any matching sticky broadcasts...
for (int i = 0, N = stickyIntents.size(); i < N; i++) {
Intent intent = stickyIntents.get(i);
// Don't provided intents that aren't available to instant apps.
if (instantApp &&
(intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
continue;
}
// If intent has scheme "content", it will need to acccess
// provider that needs to lock mProviderMap in ActivityThread
// and also it may need to wait application response, so we
// cannot lock ActivityManagerService here.
if (filter.match(resolver, intent, true, TAG) >= 0) {
if (allSticky == null) {
allSticky = new ArrayList<Intent>();
}
allSticky.add(intent);
}
}
}
// The first sticky in the list is returned directly back to the client.
Intent sticky = allSticky != null ? allSticky.get(0) : null;
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
if (receiver == null) {
return sticky;
}
synchronized (this) {
if (callerApp != null && (callerApp.thread == null
|| callerApp.thread.asBinder() != caller.asBinder())) {
// Original caller already died
return null;
}
----->根据binder对象从已经注册过的动态注册者列表中查找ReceiverList
ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
if (rl == null) {
rl = new ReceiverList(this, callerApp, callingPid, callingUid,
userId, receiver);
if (rl.app != null) {
final int totalReceiversForApp = rl.app.receivers.size();
if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
throw new IllegalStateException("Too many receivers, total of "
+ totalReceiversForApp + ", registered for pid: "
+ rl.pid + ", callerPackage: " + callerPackage);
}
rl.app.receivers.add(rl);
} else {
try {
receiver.asBinder().linkToDeath(rl, 0);
} catch (RemoteException e) {
return sticky;
}
rl.linkedToDeath = true;
}
//如果之前没有注册过,那么就放在动态广播注册者列表中去
mRegisteredReceivers.put(receiver.asBinder(), rl);
} else if (rl.uid != callingUid) {------------->检查uid是否匹配
throw new IllegalArgumentException(
"Receiver requested to register for uid " + callingUid
+ " was previously registered for uid " + rl.uid
+ " callerPackage is " + callerPackage);
} else if (rl.pid != callingPid) {------------->检查pid
throw new IllegalArgumentException(
"Receiver requested to register for pid " + callingPid
+ " was previously registered for pid " + rl.pid
+ " callerPackage is " + callerPackage);
} else if (rl.userId != userId) {---------------->userid是否匹配
throw new IllegalArgumentException(
"Receiver requested to register for user " + userId
+ " was previously registered for user " + rl.userId
+ " callerPackage is " + callerPackage);
}
//广播过滤器
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
permission, callingUid, userId, instantApp, visibleToInstantApps);
if (rl.containsFilter(filter)) {
Slog.w(TAG, "Receiver with filter " + filter
+ " already registered for pid " + rl.pid
+ ", callerPackage is " + callerPackage);
} else {
rl.add(bf);
if (!bf.debugCheck()) {
Slog.w(TAG, "==> For Dynamic broadcast");
}
mReceiverResolver.addFilter(bf);---------->如果之前没有改intentfilter则添加
}
// Enqueue broadcasts for all existing stickies that match
// this filter.
if (allSticky != null) {------------------------->处理粘性广播,粘性广播是一注册立马回调给注册者
ArrayList receivers = new ArrayList();
receivers.add(bf);
final int stickyCount = allSticky.size();
for (int i = 0; i < stickyCount; i++) {
Intent intent = allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, -1, -1, false, null, null, OP_NONE, null, receivers,
null, 0, null, null, false, true, true, -1);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
}
return sticky;
}
}
上面是动态广播的注册流程
5:动态广播接收者的取消流程
动态广播注册有一个好处是:在不需要的时候我们可以取消注册
ContextImpl.java
public void unregisterReceiver(BroadcastReceiver receiver) {
if (mPackageInfo != null) {
IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher(
getOuterContext(), receiver);
try {
ActivityManager.getService().unregisterReceiver(rd);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} else {
throw new RuntimeException("Not supported in system context");
}
}
首先mPackgeInfo肯定不为null,因为它表示的是一个加载到内存的apk对象
public IIntentReceiver forgetReceiverDispatcher(Context context,
BroadcastReceiver r) {
synchronized (mReceivers) {
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = mReceivers.get(context);
LoadedApk.ReceiverDispatcher rd = null;
if (map != null) {
rd = map.get(r);
if (rd != null) {
map.remove(r);
if (map.size() == 0) {
mReceivers.remove(context);
}
if (r.getDebugUnregister()) {
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
= mUnregisteredReceivers.get(context);
if (holder == null) {
holder = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
mUnregisteredReceivers.put(context, holder);
}
RuntimeException ex = new IllegalArgumentException(
"Originally unregistered here:");
ex.fillInStackTrace();
rd.setUnregisterLocation(ex);
holder.put(r, rd);
}
rd.mForgotten = true;
return rd.getIIntentReceiver();
}
}
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
= mUnregisteredReceivers.get(context);----->已经取消注册的广播arraymap
if (holder != null) {-------------->第一次的时候显示是为null的
rd = holder.get(r);
if (rd != null) {
RuntimeException ex = rd.getUnregisterLocation();
throw new IllegalArgumentException(
"Unregistering Receiver " + r
+ " that was already unregistered", ex);
}
}
if (context == null) {--------------->context不为nul
throw new IllegalStateException("Unbinding Receiver " + r
+ " from Context that is no longer in use: " + context);
} else {
throw new IllegalArgumentException("Receiver not registered: " + r);
}
}
}
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
= new ArrayMap<>();
mReceivers存储的是广播注册者,其中key是context,所以这里能不能取到数据是跟context相关的,所以不同的context,取消的时候map为null
导致了取不到对应的广播注册者,因此抛出异常throw new IllegalArgumentException("Receiver not registered: " + r);