当前位置: 首页 > wzjs >正文

模板素材库深圳seo优化外包公司

模板素材库,深圳seo优化外包公司,南昌网站建设方案详细版,wordpress幻灯片css记录一下遇见的这个问题和查看源码的过程 首先先说遇见的问题 Android 14 昼夜色切换多屏时候 非主屏的Activity 会经常收不到 onConfigurationChanged的回调 分析原因源码中ActivityThread::performActivityConfigurationChanged 里面 private Configuration performActivi…

记录一下遇见的这个问题和查看源码的过程

首先先说遇见的问题

Android 14 昼夜色切换多屏时候 非主屏的Activity 会经常收不到 onConfigurationChanged的回调

分析原因源码中ActivityThread::performActivityConfigurationChanged 里面

   private Configuration performActivityConfigurationChanged(ActivityClientRecord r,Configuration newConfig, Configuration amOverrideConfig, int displayId,boolean alwaysReportChange) {final Activity activity = r.activity;final IBinder activityToken = activity.getActivityToken();// WindowConfiguration differences aren't considered as public, check it separately.// multi-window / pip mode changes, if any, should be sent before the configuration// change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransitionhandleWindowingModeChangeIfNeeded(r, newConfig);final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),displayId);final Configuration currentResConfig =                 activity.getResources().getConfiguration();final int diff = currentResConfig.diffPublicOnly(newConfig);final boolean hasPublicResConfigChange = diff != 0;// TODO(b/173090263): Use diff instead after the improvement of AssetManager and// ResourcesImpl constructions.final boolean shouldUpdateResources = hasPublicResConfigChange|| shouldUpdateResources(activityToken, currentResConfig, newConfig,amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig, r.mSizeConfigurations,activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);// Nothing significant, don't proceed with updating and reporting.if (!shouldUpdateResources && !shouldReportChange) {return null;}。。。。省略shouldReportChange 这个值出问题时候是个false 也就是说系统认为没有变化 activity.mConfigChangeFlags = 0;if (shouldReportChange) {activity.mCalled = false;activity.mCurrentConfig = new Configuration(newConfig);activity.onConfigurationChanged(configToReport);if (!activity.mCalled) {throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +" did not call through to super.onConfigurationChanged()");}}}

shouldReportChange 这个值出问题时候是个false 也就是说系统认为没有变化 

可是为什么 shouldReportChange 是false?而不是true?昼夜色变化一定会发生改变的,我们看下这个shouldReportChange 方法
 

   @VisibleForTestingpublic static boolean shouldReportChange(@Nullable Configuration currentConfig,@NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,int handledConfigChanges, boolean alwaysReportChange) {final int publicDiff = currentConfig.diffPublicOnly(newConfig);// Don't report the change if there's no public diff between current and new config.if (publicDiff == 0) {return false;// Report the change regardless if the changes across size-config-buckets.if (alwaysReportChange) {return true;}final int diffWithBucket = SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig,newConfig, sizeBuckets);// Compare to the diff which filter the change without crossing size buckets with// {@code handledConfigChanges}. The small changes should not block Activity to receive// its handlConfigChanges}. The small changes should not block Activity to receive// its handled config updates. Also, if Activity handles all small changes, we should// dispatch the updated config to it.final int diff = diffWithBucket != 0 ? diffWithBucket : publicDiff;// If this activity doesn't handle any of the config changes, then don't bother// calling onConfigurationChanged. Otherwise, report to the activity for the// changes.return (~handledConfigChanges & diff) == 0;}

        final int publicDiff = currentConfig.diffPublicOnly(newConfig);
这个方法比较了新老的 config是否有区别的,而且大家可以感兴趣看一下这个currentConfig.diffPublicOnly方法,这个方法check了uimode也就是新的uimode和老的uimode是否一个值

但是不可能,因为新的uimode如果没有变化就不应该回调,但是回调了就是因为有变化,于是我们接着看这个地方newConfig 是从何而来

我们先看一下到这的调用栈

ActivityThread::handleActivityConfigurationChangedActivityThread::performConfigurationChangedForActivity (这步获取newconfig)           ActivityThread::performConfigurationChangedForActivityActivityThread::performActivityConfigurationChangedactivity::onConfigurationChanged

在ActivityThread::performConfigurationChangedForActivity  获取新的config

看看performConfigurationChangedForActivity 代码

 void handleActivityConfigurationChanged(ActivityClientRecord r,@NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {。。省略final Configuration reportedConfig = performConfigurationChangedForActivity(r,mConfigurationController.getCompatConfiguration(),movedToDifferentDisplay ? displayId : r.activity.getDisplayId(),alwaysReportChange);// Notify the ViewRootImpl instance about configuration changes. It may have initiated this// update to make sure that resources are updated before updating itself.if (viewRoot != null) {if (movedToDifferentDisplay) {viewRoot.onMovedToDisplay(displayId, reportedConfig);}viewRoot.updateConfiguration(displayId);}mSomeActivitiesChanged = true;
}

mConfigurationController.getCompatConfiguration() 就是我们后面拿到的newConfig

也就是说这个config其实不是系统直接发来的其实是APP进程内获取的,那么为什么APP进程为什么获取的错的?mConfigurationController.getCompatConfiguration()是什么时候更新的?为什么会偶尔拿错呢?到底是因为没回调还是时序错误呢?

我先说结论,这是个时序问题

这个地方Android14一共有3个回调,我给出的顺序也是正常应该回调的顺序

1. ConfigurationChangeItem::handleConfigurationChanged 更新 APP进程内cofig 就是这个方法更新完后去getCompatConfiguration 取的数据

 @Overridepublic void handleConfigurationChanged(Configuration config, int deviceId) {mConfigurationController.handleConfigurationChanged(config);
}

2. ActivityConfigurationChangeItem::handleActivityConfigurationChanged

    @Overridepublic void handleActivityConfigurationChanged(ActivityClientRecord r,@NonNull Configuration overrideConfig, int displayId) {handleActivityConfigurationChanged(r, overrideConfig, displayId,// This is the only place that uses alwaysReportChange=true. The entry point should// be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side// has confirmed the activity should handle the configuration instead of relaunch.// If Activity#onConfigurationChanged is called unexpectedly, then we can know it is// something wrong from server side.true /* alwaysReportChange */);}

3. ViewRootImpl::onConfigurationChanged 是解除冻屏后进行resize传来的,这个一会说

 private void init() {parent = null;embeddedID = null;paused = false;stopped = false;hideForNow = false;activityConfigCallback = new ViewRootImpl.ActivityConfigCallback() {@Overridepublic void onConfigurationChanged(Configuration overrideConfig,int newDisplayId) {if (activity == null) {throw new IllegalSttateException("Received config update for non-existing activity");}//我是在这个地方做了判断,没有修改就不回调了,有更好的办法可以讲下activity.mMainThread.handleActivityConfigurationChanged(ActivityClientRecord.this, overrideConfig, newDisplayId,false /* alwaysReportChange */);}

这三个回调按照这个顺序是不会错的,但是,在非主屏的情况下,可能会先收到ViewRootImpl::onConfigurationChanged然后剩下两个回调 这个时候就会出现我们说的当 ViewRootImpl::onConfigurationChanged回调时候APP进程内的config还没更信导致昼夜色取到的值是一样的
那问题来了

ActivityConfigurationChangeItem::handleActivityConfigurationChanged 回调时候为什么uimode不一样了还不进行回调?

  void handleActivityConfigurationChanged(ActivityClientRecord r,@NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {synchronized (mPendingOverrideConfigs) {final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) {if (DEBUG_CONFIGURATION) {Slog.v(TAG, "Activity has newer configuration pending so drop this"+ " transaction. overrideConfig=" + overrideConfig+ " pendingOverrideConfig=" + pendingOverrideConfig);}return;}mPendingOverrideConfigs.remove(r.token);}if (displayId == INVALID_DISPLAY) {// If INVALID_DISPLAY is passed assume that the activity should keep its current// display.displayId = r.activity.getDisplayId();}final boolean movedToDifferentDisplay = isDifferentDisplay(r.activity.getDisplayId(), displayId);if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)&& !movedToDifferentDisplay) {if (DEBUG_CONFIGURATION) {Slog.v(TAG, "Activity already handled newer configuration so drop this"+ " transaction. overrideConfig=" + overrideConfig + r.overrideConfig);}return;}// Perform updates.r.overrideConfig = overrideConfig;
这地方更新了config的版本号

r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
                && !movedToDifferentDisplay 这个isOtherSeqNewer 的判断阻拦了我们后续的回调

isOtherSeqNewer判断的是什么?是seq也就是版本号,因为ViewRootImpl::onConfigurationChanged 已经把我们config版本号给更新了,导致后续的回调都被屏蔽了

可能有人注意到 ConfigurationChangeItem::handleConfigurationChanged 里面 走到 handleConfigurationChanged 会更新APP的数据还分发过数据但是为什么收不到,我们来看代码

 void handleConfigurationChanged(@Nullable Configuration config,@Nullable CompatibilityInfo compat) {int configDiff;boolean equivalent;。。。省略直接看分发这步就是后续能get到更新的值  进行修改的地方config = applyCompatConfiguration();这个地方好像能进行分发,但是为什么收不到?final ArrayList<ComponentCallbacks2> callbacks =mActivityThread.collectComponentCallbacks(false /* includeUiContexts */);freeTextLayoutCachesIfNeeded(configDiff);if (callbacks != null) {final int size = callbacks.size();for (int i = 0; i < size; i++) {ComponentCallbacks2 cb = callbacks.get(i);if (!equivalent) {performConfigurationChanged(cb, config);}}}

ActivityThread::collectComponentCallbacks 这个方法获取到的并不是所有的Activity

   @Overridepublic ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts) {ArrayList<ComponentCallbacks2> callbacks= new ArrayList<ComponentCallbacks2>();ArrayList<ComponentCallbacks2> callbacks= new ArrayList<ComponentCallbacks2>();synchronized (mResourcesManager) {final int NAPP = mAllApplications.size();for (int i=0; i<NAPP; i++) {callbacks.add(mAllApplications.get(i));}这地方includeUiContexts 是个false 所以Activity根本加不进来if (includeUiContexts) {for (int i = mActivities.size() - 1; i >= 0; i--) {final Activity a = mActivities.valueAt(i).activity;if (a != null && !a.mFinished) {callbacks.add(a);}}}final int NSVC = mServices.size();for (int i=0; i<NSVC; i++) {final Service service = mServices.valueAt(i);// If {@code includeUiContext} is set to false, WindowProviderService should not be// collected because WindowProviderService is a UI Context.if (includeUiContext} is set to false, WindowProviderService should not be// collected because WindowProviderService is a UI Context.if (includeUiContexts || !(service instanceof WindowProviderService)) {callbacks.add(service);}}}synchronized (mProviderMap) {final int NPRV = mLocalProviders.size();for (int i=0; i<NPRV; i++) {callbacks.add(mLocalProviders.valueAt(i).mLocalProvider);}}return callbacks;}

 所以根本分发不到Activity,但是Application能接到,就是有点快

屏蔽了所以副屏的APP都无法回调了,那问题来了,到底是系统层的回调错了还是为什么会导致回调时序错误?

还是先说结论系统层分发来的时候时序是没问题的上代码看一下

首先先看昼夜色切换触发的方法

首先是UIModeManager::setNightModeActivated 

@RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)public boolean setNightModeActivated(boolean active) {if (mService != null) {try {return mService.setNightModeActivated(active);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return false;}

mService其实就是 UiModeManagerService

然后走到 UiModeManagerService

 @Overridepublic boolean setNightModeActivated(boolean active) {return setNightModeActivatedForModeInternal(mNightModeCustomType, active);}private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)!= PackageManager.PERMISSION_GRANTED) {Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");return false;}final int user = Binder.getCallingUserHandle().getIdentifier();if (user != mCurrentUser && getContext().checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)!= PackageManager.PERMISSION_GRANTED) {Slog.e(TAG, "Target user is not current user,"+ " INTERACT_ACROSS_USERS permission is required");return false;} // Store the last requested bedtime night mode state so that we don't need to notify// anyone if the user decides to switch to the night mode to bedtime.if (modeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {mLastBedtimeRequestedNightMode = active;}if (modeCustomType != mNightModeCustomType) {return false;}synchronized (mLock) {final long ident = Binder.clearCallingIdentity();try {if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {unregisterScreenOffEventLocked();mOverrideNightModeOff = !active;mOverrideNightModeOn = active;mOverrideNightModeUser = mCurrentUser;persistNightModeOverrides(mCurrrentUser);} else if (mNightMode == UiModeManager.MODE_NIGHT_NO&& active) {mNightMode = UiModeManager.MODE_NIGHT_YES;} else if (mNightMode == UiModeManager.MODE_NIGHT_YES&& !active) {mNightMode = UiModeManager.MODE_NIGHT_NO;}updateConfigurationLocked();applyConfigurationExternallyLocked();persistNightMode(mCurrentUser);return true;} finally {Binder.restoreCallingIdentity(ident);}}}注意一下                    applyConfigurationExternallyLocked();
方法

重点关注                    applyConfigurationExternallyLocked();方法

 private void applyConfigurationExternallyLocked() {if (mSetUiMode != mConfiguration.uiMode) {mSetUiMode = mConfiguration.uiMode;// load splash screen instead of screenshotmWindowManager.clearSnapshotCache();try {ActivityTaskManager.getService().updateConfiguration(mConfiguration);} catch (RemoteException e) {Slog.w(TAG, "Failure communicating with activity manager", e);}}}

很明显,在这走到了ATMS的方法里

   @Overridepublic boolean updateConfiguration(Configuration values) {。。。updateConfigurationLocked(values, null, false, false /* persistent */,UserHandle.USER_NULL, false /* deferResume */,mTmpUpdateConfigurationResult);。。。}boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,boolean initLocale, boolean persistent, int userId, boolean deferResume,ActivityTaskManagerService.UpdateConfigurationResult result) {int changes = 0;boolean kept = true; 
首先先冻屏deferWindowLayout();try {if (values != null) {1然后更新所有的config 给所有的config新的赋值changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);}if (!deferResume) {2更新所有的Activity把Activity数据更新 真正的分发kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);} } finally {3最后解除冻屏continueWindowLayout();}if (result != null) {result.changes = changes;result.activityRelaunched = !kept;}return kept;}

 updateConfigurationLocked 这个方法很重要,可以看到这三个方法不存在顺序出错的可能性

我们可以看到上面的三个APP层回调也是这三个方法分别回调

因为我们已经知道系统中三个回调顺序不可能出错,但是冻屏解除的resize居然先于上面两个方法到来,个人猜测是因为解除冻屏的AIDL回调跟 updateGlobalConfigurationLocked 和 ensureConfigAndVisibilityAfterUpdate两个方法回调的AIDL不是一个导致的

updateGlobalConfigurationLocked 和 ensureConfigAndVisibilityAfterUpdate两个方法回调都到了Application里,但是 continueWindowLayout走的是ViewRootImpl

还有一个问题,正常AIDL也是同步的,博主看了下系统到APP的代码,AIDL都带了ONEWAY标签,所以没法控制时序

所以看到这里,结论就是原生的调用链其实顺序是没问题的,问题是到达APP的顺序出错,所以此题有两个解法

1. 修改版本号

2. 检查是否改变,无改变不回调(需要验证)

那我们来看看版本号在哪改变的让我们看方法1

  int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,boolean persistent, int userId) {mTempConfig.setTo(getGlobalConfiguration());final int changes = mTempConfig.updateFrom(values);if (changes == 0) {return 0;}
。。。mTempConfig.seq = increaseConfigurationSeqLocked();
。。。
}int increaseConfigurationSeqLocked() {mConfigurationSeq = Math.max(++mConfigurationSeq, 1);return mConfigurationSeq;}

所以是这个地方进行了改变,这个版本号接下来回调也使用

http://www.dtcms.com/wzjs/101028.html

相关文章:

  • 傻瓜动态建站 工具产品关键词怎么找
  • wordpress post_id百度seo引流
  • 户县网站建设企业seo推广外包
  • 做网站 成都广州市最新消息
  • wordpress中文商城模板下载seo百度推广
  • 刷信誉网站开发关键词热度
  • 专门做电路图的网站国际时事新闻2022最新
  • 做网站还要买服务器吗品牌建设的五个要素
  • 建站公司网站社区产品如何做网络推广
  • 网页制作三剑客工具长沙seo推广外包
  • 网站去公安局备案怎样在百度上发布自己的信息
  • 网站哪个公司做的比较好的专业的推广公司
  • 济宁网站制作公司百度行发代理商
  • 专做韩餐网站常熟网络推广
  • 网站天下企业网络营销方案策划
  • 刚做的网站关键词就上来了今日国内新闻重大事件
  • 日韩男女直接做的视频网站推广自己的产品
  • 顺德做网站公司哪家好营销推广计划怎么写
  • wordpress仿百度文库优化网站建设
  • 外贸网站域名被封四大营销策略
  • 中联汇科 网站建设俄罗斯搜索引擎入口 yandex
  • 如何提升做网站的效率网络整合营销推广
  • 网站营销工具百度贴吧入口
  • 先建网站还是先做网页seo诊断
  • 微信官网网站模板下载不了目前疫情最新情况
  • 枣庄网站设计广州seo网站推广优化
  • 政府网站建设工作会议凡科建站怎么用
  • 做网站需要什么电脑西安seo优化
  • 国外做游戏的视频网站有哪些开电商需要多少钱
  • 手机怎么做微电影网站吗网站排名查询alexa