资讯动态

Android 7系统国际化(六)ActivityThread —— 应用侧配置响应与资源更新

发布时间:2026/8/13 20:26:26 来源:尧图企业网站定制
系列目录第一篇全景图与调用链路概览 | 第二篇Locale 与 LocaleList | 第三篇Configuration | 第四篇LocalePicker | 第五篇ActivityManagerService | 第六篇ActivityThread | 第七篇Resources 与 AssetManager | 第八篇总结与实战建议一、为什么需要 ActivityThread第五篇中AMS 遍历所有进程对每个进程调用了app.thread.scheduleConfigurationChanged(configCopy)。但问题来了AMS 的调用在 Binder 线程池中执行而更新 Resources、回调onConfigurationChanged()这些操作必须在主线程完成——谁来处理这个线程切换更关键的是AMS 发给每个进程的只是一个Configuration对象但一个进程中可能运行着多个 Activity、Service、ContentProvider。如何确保所有这些组件都能收到配置变更通知ActivityThread就是应用进程侧负责这两件事的核心类。它接收 AMS 的远程调用通过 Handler 切换到主线程然后更新本进程的 Resources并通知所有运行中的组件。源码路径frameworks/base/core/java/android/app/ActivityThread.java二、ApplicationThread —— Binder 通信的服务端ActivityThread内部有一个名为ApplicationThread的内部类它是IApplicationThread.Stub的实现充当 AMS 反向调用应用进程的 Binder 服务端AMS ──Binder IPC──→ ApplicationThread (ActivityThread 的内部类) │ ├── 通过 Handler (H) 转发到主线程 │ ▼ ActivityThread.handleXxx()源码路径frameworks/base/core/java/android/app/ActivityThread.javapublicfinalclassActivityThread{// ...privateclassApplicationThreadextendsIApplicationThread.Stub{// AMS 通过 Binder 远程调用此方法OverridepublicvoidscheduleConfigurationChanged(Configurationconfig){updatePendingConfiguration(config);sendMessage(H.CONFIGURATION_CHANGED,config);}}}HHandler的消息分发privateclassHextendsHandler{publicstaticfinalintCONFIGURATION_CHANGED118;publicvoidhandleMessage(Messagemsg){switch(msg.what){caseCONFIGURATION_CHANGED:// 切换到主线程处理handleConfigurationChanged((Configuration)msg.obj,null);break;// ...}}}关键设计ApplicationThread.scheduleConfigurationChanged()在 AMS 的 Binder 线程池中被调用而 Resources 更新和 Activity 生命周期回调都必须在主线程执行。通过HHandler 发送消息利用主线程的Looper切换到主线程。三、handleConfigurationChanged() —— 应用侧响应的核心入口源码路径frameworks/base/core/java/android/app/ActivityThread.javapublicfinalclassActivityThread{privateConfigurationmConfiguration;// 当前进程的配置privateConfigurationmPendingConfiguration;// 延迟生效的配置privateResourcesManagermResourcesManager;// 资源管理器finalvoidhandleConfigurationChanged(Configurationconfig,CompatibilityInfocompat){synchronized(mResourcesManager){// 1. 处理延迟生效的配置if(mPendingConfiguration!null){if(!mPendingConfiguration.isOtherSeqNewer(config)){configmPendingConfiguration;mCurDefaultDisplayDpiconfig.densityDpi;updateDefaultDensity();}mPendingConfigurationnull;}if(confignull)return;// 2. ⭐ 更新 Resources核心步骤mResourcesManager.applyConfigurationToResourcesLocked(config,compat);// 3. 更新 Application Context 的 LocaleListupdateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),mResourcesManager.getConfiguration().getLocales());// 4. 更新本进程的 Configuration 副本if(mConfigurationnull){mConfigurationnewConfiguration();}if(!mConfiguration.isOtherSeqNewer(config)compatnull){return;// 序列号没变说明配置未实际变更}configDiffmConfiguration.updateFrom(config);// 5. 更新系统主题finalThemesystemThemegetSystemContext().getTheme();if((systemTheme.getChangingConfigurations()configDiff)!0){systemTheme.rebase();}}// 6. 收集所有组件逐个通知ArrayListComponentCallbacks2callbackscollectComponentCallbacks(false,config);freeTextLayoutCachesIfNeeded(configDiff);// 清除文本布局缓存if(callbacks!null){finalintNcallbacks.size();for(inti0;iN;i){ComponentCallbacks2cbcallbacks.get(i);if(cbinstanceofActivity){performConfigurationChangedForActivity(mActivities.get(((Activity)cb).getActivityToken()),config,REPORT_TO_ACTIVITY);}else{performConfigurationChanged(cb,null,config,null,REPORT_TO_ACTIVITY);}}}}}3.1 步骤①处理延迟生效的配置if(mPendingConfiguration!null){if(!mPendingConfiguration.isOtherSeqNewer(config)){configmPendingConfiguration;}mPendingConfigurationnull;}isOtherSeqNewer()通过比较 Configuration 的seq序列号来判断哪个配置更新。如果mPendingConfiguration的序列号比传入的config更新则用mPendingConfiguration替代。关键设计mPendingConfiguration是一个延迟生效机制。某些场景下如 Activity 正在执行转场动画配置变更不会立即生效而是先存入mPendingConfiguration等待下次处理。3.2 步骤②更新 Resources —— 最核心的步骤mResourcesManager.applyConfigurationToResourcesLocked(config,compat);这一步委托给ResourcesManager将在第七篇中详细展开。简单来说它做三件事将新 Configuration 传入ResourcesManagerResourcesManager更新所有缓存的Resources对象Resources内AssetManager重新设置 Configuration触发资源重新匹配。3.3 步骤④更新进程级 Configuration 副本mConfiguration.updateFrom(config);mConfiguration是ActivityThread中保存的进程级配置副本。它被用于后续的配置比较如diff()计算以及在applyCompatConfiguration()中生成兼容性配置。3.4 步骤⑥通知所有组件这是handleConfigurationChanged的关键职责——遍历所有运行中的组件逐个调用它们的onConfigurationChanged()。注意 Activity 与非 Activity 组件的处理方式不同Activity 通过performConfigurationChangedForActivity()处理会考虑overrideConfig等因素非 Activity 组件则通过performConfigurationChanged()直接通知。四、collectComponentCallbacks() —— 全局组件收集器这个方法确保配置变更能通知到应用进程中的所有相关组件。源码路径frameworks/base/core/java/android/app/ActivityThread.javapublicfinalclassActivityThread{// 所有应用程序Application 实例privatefinalArrayListApplicationmAllApplicationsnewArrayList();// 所有 Activity通过 ArrayMap 存储key 为 tokenprivateArrayMapIBinder,ActivityClientRecordmActivities;// 所有 ServiceprivateArrayMapIBinder,ServicemServices;// 所有 ContentProviderprivateArrayListProviderClientRecordmLocalProviders;ArrayListComponentCallbacks2collectComponentCallbacks(booleanallActivities,ConfigurationnewConfig){ArrayListComponentCallbacks2callbacksnewArrayList();synchronized(mResourcesManager){// 1. 收集所有 Application先添加finalintNAPPmAllApplications.size();for(inti0;iNAPP;i){callbacks.add(mAllApplications.get(i));}// 2. 收集 Activity处理 paused/finished 状态finalintNACTmActivities.size();for(inti0;iNACT;i){ActivityClientRecordarmActivities.valueAt(i);Activityaar.activity;if(a!null){if(!a.mFinished(allActivities||!ar.paused)){callbacks.add(a);}else{ar.newConfignewConfig;// 延迟通知}}}// 3. 收集 ServicefinalintNSVCmServices.size();for(inti0;iNSVC;i){callbacks.add(mServices.valueAt(i));}}synchronized(mProviderMap){// 4. 收集 ContentProviderfinalintNPRVmLocalProviders.size();for(inti0;iNPRV;i){callbacks.add(mLocalProviders.valueAt(i).mLocalProvider);}}returncallbacks;}}被通知的四类组件按回调顺序组件类型接口收集来源ApplicationComponentCallbacks2mAllApplicationsActivityComponentCallbacks2mActivitiespaused 的 Activity 延迟通知ServiceComponentCallbacks2mServicesContentProviderComponentCallbacks2mLocalProviders关键设计Application 先于 Activity 收到回调这允许 Application 在 Activity 响应之前完成全局配置适应。Activity 还有特殊处理paused 或 finished 的 Activity 不会立即收到回调而是将新配置存入ar.newConfig待 Activity 恢复时再应用。五、performConfigurationChanged() —— 实际触发回调源码路径frameworks/base/core/java/android/app/ActivityThread.javaprivatevoidperformConfigurationChanged(ComponentCallbacks2cb,IBinderactivityToken,ConfigurationnewConfig,ConfigurationamOverrideConfig,booleanreportToActivity){Activityactivity(cbinstanceofActivity)?(Activity)cb:null;if(activity!null){activity.mCalledfalse;}booleanshouldChangeConfigfalse;if(activitynull||activity.mCurrentConfignull){shouldChangeConfigtrue;}else{// 比较新旧配置的差异intdiffactivity.mCurrentConfig.diff(newConfig);if(diff!0||!mResourcesManager.isSameResourcesOverrideConfig(activityToken,amOverrideConfig)){// 如果 Activity 未声明处理这些变更无需通知即将被销毁if(!mUpdatingSystemConfig||(~activity.mActivityInfo.getRealConfigChanged()diff)0||!reportToActivity){shouldChangeConfigtrue;}}}if(shouldChangeConfig){cb.onConfigurationChanged(newConfig);}}关键设计这里有一个隐式优化——如果 Activity 的getRealConfigChanged()表明它未声明处理任何变更且mUpdatingSystemConfig为 true系统正在更新配置则直接跳过回调因为该 Activity 后续会被 AMS 销毁重建。5.1 Activity 中的 onConfigurationChanged 分发当 Activity 收到回调后它会进一步分发到子组件源码路径frameworks/base/core/java/android/app/Activity.javapublicvoidonConfigurationChanged(ConfigurationnewConfig){mCalledtrue;// 1. 分发到所有 FragmentmFragments.dispatchConfigurationChanged(newConfig);// 2. 分发到 Window处理 DecorView 等if(mWindow!null){mWindow.onConfigurationChanged(newConfig);}// 3. 分发到 ActionBarif(mActionBar!null){mActionBar.onConfigurationChanged(newConfig);}}六、applyConfigurationToResources() —— 桥梁方法这是连接第六篇和第七篇的桥梁方法将配置变更传递给ResourcesManager源码路径frameworks/base/core/java/android/app/ActivityThread.javapublicfinalvoidapplyConfigurationToResources(Configurationconfig){synchronized(mResourcesManager){mResourcesManager.applyConfigurationToResourcesLocked(config,null);}}这个方法在 handleBindApplication 和 handleConfigurationChanged 中都被调用确保 Application 启动时和运行时配置变更都能正确更新 Resources。具体实现将在第七篇ResourcesManager中详细展开。七、应用启动时的配置获取路径除了运行时接收配置变更应用在启动时也需要获取系统当前的 Configuration源码路径frameworks/base/core/java/android/app/ActivityThread.javaprivatevoidhandleBindApplication(AppBindDatadata){// ...// AMS 在 bindApplication 时传入了当前的 Configurationif(data.config!null){applyConfigurationToResources(data.config,data.compatInfo);}// ...}data.config来自 AMS 的attachApplicationLocked()AMS 在其中附带了mConfiguration当前全局配置。应用进程在创建Resources之前就拿到了正确的语言配置因此启动时就能匹配正确的资源。八、小结要点说明Binder 入口ApplicationThread.scheduleConfigurationChanged()Binder 线程线程切换H.CONFIGURATION_CHANGED消息 → 主线程handleConfigurationChanged()资源更新mResourcesManager.applyConfigurationToResourcesLocked()配置副本mConfiguration.updateFrom(config)更新进程级配置快照组件收集collectComponentCallbacks()按 Application → Activity → Service → Provider 顺序收集回调触发performConfigurationChanged()→onConfigurationChanged()→ Fragment / Window / ActionBar文本缓存freeTextLayoutCachesIfNeeded()在语言变更时清除文本布局缓存启动时路径handleBindApplication()→applyConfigurationToResources()下一篇预告第七篇深入Resources和AssetManager解开资源如何按语言匹配的最后一层谜题——ResourcesManager如何管理多配置下的 Resources 缓存以及 Native 层AssetManager如何根据 Configuration 选择正确的资源文件。本系列源码基于 AOSP 7.xAPI 24/25各版本可能存在差异请以实际代码为准。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价