Android资源管理框架-------之OverlayManagerService中overlay package的生效(三)

        在前一篇,也就是Android资源管理框架-------之OverlayManagerService简介及其数据的维护更新(二)中我们简单分析了OMS的架构,以及我们动态地去enable一个overlay package时OMS的主要操作流程:生成idmap文件,更新OverlayManagerSettings中的数据,然后就是在OverlayManagerServiceImplsetEnabled方法中通过mListener.onOverlaysChanged(oi.targetPackageName, userId);这一句来让我们的overlay包生效了。其中mListenerOverlayManagerService的内部类OverlayChangeListener的实例:

    //frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
    private final class OverlayChangeListener
            implements OverlayManagerServiceImpl.OverlayChangeListener {
        @Override
        public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) {
            //持久化OverlayManagerSettings中overlay相关的数据
            schedulePersistSettings();
            //FgThread中让overlay package 生效
            FgThread.getHandler().post(() -> {
                //overlay package 生效的核心实现在里面
                updateAssets(userId, targetPackageName);

                //发送overlay package 改变的广播
                final Intent intent = new Intent(Intent.ACTION_OVERLAY_CHANGED,
                        Uri.fromParts("package", targetPackageName, null));
                //该flag不会拉起未启动的应用
                intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                try {
                    ActivityManager.getService().broadcastIntent(null, intent, null, null, 0,
                            null, null, null, android.app.AppOpsManager.OP_NONE, null, false, false,
                            userId);
                } catch (RemoteException e) {
                    // Intentionally left empty.
                }
            });
        }
    }

        我们看到onOverlaysChanged主要是做了两件事:

  • 持久化OverlayManagerSettings中overlay相关的数据到/data/system/overlays.xml
  • 通过updateAssets让overlay包生效

        至于发送overlay包改变的广播给感兴趣的组件不是核心,也没啥好说的,我们这里就不详细介绍了。不过,onOverlaysChanged方法是在system_server的binder线程中执行的,而让overlay package生效显然是一个前台操作,放到FgThread里比较合适。我们先看持久化:

    //frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
    private void schedulePersistSettings() {
        /**
         * mPersistSettingsScheduled是个AtomicBoolean,先获取它之前的值,再将它的值
         * 设置为true.表示已经让IOThread去干活了,但是它还未开工.当然,如果它之前就是true,
         * 那就说明前面已经有人让它去持久化了,并且它还没开工,那么我们就直接return就好了,
         * 等它开工了,我们的改动自然也会被持久化
         */
        if (mPersistSettingsScheduled.getAndSet(true)) {
            return;
        }
        //持久化,IO操作当然要放到IoThread
        IoThread.getHandler().post(() -> {
            //表示已经开工了^_^
            mPersistSettingsScheduled.set(false);
            
            synchronized (mLock) {
                FileOutputStream stream = null;
                try {
                    //拿到IOStream,/data/system/overlays.xml
                    stream = mSettingsFile.startWrite();
                    //mSettings是OverlayManagerSettings的实例,上一篇已经提过
                    mSettings.persist(stream);
                    //关闭io流
                    mSettingsFile.finishWrite(stream);
                } catch (IOException | XmlPullParserException e) {
                    mSettingsFile.failWrite(stream);
                    Slog.e(TAG, "failed to persist overlay state", e);
                }
            }
        });
    }

        持久化的逻辑非常简单,主要就是让IOThread去干活,把内存中一个一个的SettingItem输出到/data/system/overlays.xml这个文件中。不过,mPersistSettingsScheduled的使用,可以避免频繁的不必要的持久化,提高效率,这种用法在AOSP的代码中非常常见,比如BroadcastQueue中对BroadcastRecord的处理也用到了类似的思想。然后,我们看看updateAssets方法是如何让overlay package生效的:

    //frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
    private void updateAssets(final int userId, final String targetPackageName) {
        //包了一层,看样子Android还是想支持一下子让多个target包生效的
        updateAssets(userId, Collections.singletonList(targetPackageName));
    }

    private void updateAssets(final int userId, List<String> targetPackageNames) {
        //把overlay信息更新到PMS里,后面AMS用到的时候,会去PMS里取
        updateOverlayPaths(userId, targetPackageNames);
        final IActivityManager am = ActivityManager.getService();
        try {
            //交给AMS啦,不过还是在system_server的FgThread中执行
            am.scheduleApplicationInfoChanged(targetPackageNames, userId);
        } catch (RemoteException e) {
            // Intentionally left empty.
        }
    }

        updateAssets方法主要也是两件事儿:把OverlayManagerSettings里的信息更新到PackageManagerService里面去(这时候我们可以简单认为OverlayManagerSettings/data/system/overlays.xml、PMS里的信息是一致的,都是最新的信息),后面会用到;再就是让ActivityManagerService通知到target package所在的进程,你的overlay包发生了改变哦!先看updateOverlayPaths:

    //frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
    private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
        try {
            traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
            if (DEBUG) {
                Slog.d(TAG, "Updating overlay assets");
            }
            final PackageManagerInternal pm =
                    LocalServices.getService(PackageManagerInternal.class);
            /**
             * 如果我们的target包中有android,也就是说我们要动态enable framework-res.apk的
             * overlay package。由于所有的应用都有可能使用使用里面的资源,所以这个时候我们可以认为:
             * 所有的target package都会被这个framework-res.apk的overlay package所覆盖,
             * 所以下面会对targetPackageNames做调整。
             */
            final boolean updateFrameworkRes = targetPackageNames.contains("android");
            if (updateFrameworkRes) {
                targetPackageNames = pm.getTargetPackageNames(userId);
            }
            /**
             * 调整后的结果放在pendingChanges里
             * 它的key是target package
             * value是这个target package的所有overlay package
             */
            final Map<String, List<String>> pendingChanges =
                    new ArrayMap<>(targetPackageNames.size());
            synchronized (mLock) {
                //拿到framework-res.apk的所有overlay package,
                //mImpl是OverlayManagerServiceImpl的实例
                final List<String> frameworkOverlays =
                        mImpl.getEnabledOverlayPackageNames("android", userId);
                final int n = targetPackageNames.size();
                for (int i = 0; i < n; i++) {
                    final String targetPackageName = targetPackageNames.get(i);
                    //存放target package的所有overlay package
                    List<String> list = new ArrayList<>();
                    /**
                     * 对于非framework-res.apk的所有target package而言
                     * framework-res.apk的所有overlay package,也是这个
                     * package的overlay package
                     */
                    if (!"android".equals(targetPackageName)) {
                        list.addAll(frameworkOverlays);
                    }
                    /**
                     * 再去OverlayManagerSettings中拿到这个package真正的
                     * overlay package,也添加到list中。这个时候list中存放的
                     * 才是这个package完整的所有的overlay package
                     * 
                     * mImpl.getEnabledOverlayPackageNames会从OverlayManagerSettings
                     * 中拿到overlay package.因为OverlayManagerServiceImpl本身并不存储
                     * overlay信息,它只是overlay信息的搬运工,哈哈
                     */
                    list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
                    pendingChanges.put(targetPackageName, list);
                }
            }

            final int n = targetPackageNames.size();
            for (int i = 0; i < n; i++) {
                final String targetPackageName = targetPackageNames.get(i);
                if (DEBUG) {
                    Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
                            + TextUtils.join(",", pendingChanges.get(targetPackageName))
                            + "] userId=" + userId);
                }
                //更新到PMS当中去
                if (!pm.setEnabledOverlayPackages(
                        userId, targetPackageName, pendingChanges.get(targetPackageName))) {
                    Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
                            targetPackageName, userId));
                }
            }
        } finally {
            traceEnd(TRACE_TAG_RRO);
        }
    }

        我们看到updateOverlayPaths并不是简单地直接把OverlayManagerSettings中的overlay信息给到PMS,而是在给到之前对OverlayManagerSettings中的overlay信息做了校正,添加了framework-res.apk的所有overlay package。这也是为什么我们前面说,经过updateOverlayPaths后,我也只是可以简单地以为OverlayManagerSettings/data/system/overlays.xml、PMS里的overlay信息是一致的。毕竟,这个时候PMS里的overlay信息比着OverlayManagerSettings/data/system/overlays.xml多了framework-res.apk的overlay package。说完了updateOverlayPaths的处理,我们看看AMS的处理流程:

    //frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    @Override
    public void scheduleApplicationInfoChanged(List<String> packageNames, int userId) {
        //权限检查
        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                "scheduleApplicationInfoChanged()");

        synchronized (this) {
            /**
             * 这时的calling uid pid还是调用setEnable方法的应用的,所以
             * 要换成system_server的
             */
            final long origId = Binder.clearCallingIdentity();
            try {
                updateApplicationInfoLocked(packageNames, userId);
            } finally {
                //完了calling uid pid换回来
                Binder.restoreCallingIdentity(origId);
            }
        }
    }

    void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
        /*...省略部分代码...*/
        mProcessList.updateApplicationInfoLocked(packagesToUpdate, userId, updateFrameworkRes);
        /*...省略部分代码...*/
    }

        mProcessListProcessList类的实例:

    //frameworksbase/services/core/java/com/android/server/am/ProcessList.java
    void updateApplicationInfoLocked(List<String> packagesToUpdate, int userId,
            boolean updateFrameworkRes) {
            /**
             * packagesToUpdate是我们的target包
             * updateFrameworkRes是我们是否enable了framework-res.apk的overlay包
             */

        for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
            final ProcessRecord app = mLruProcesses.get(i);
            //异常检查
            if (app.thread == null) {
                continue;
            }
            //user不对应的也跳过
            if (userId != UserHandle.USER_ALL && app.userId != userId) {
                continue;
            }

            final int packageCount = app.pkgList.size();
            for (int j = 0; j < packageCount; j++) {
                final String packageName = app.pkgList.keyAt(j);
                /**
                 * 对与要生效的target package,通过IApplicationThread这个应用端的binder接口(也就是app.thread),
                 * 告知target process,你的overlay 包状态有变化,你得处理一下.
                 * 
                 * 需要说明的是,如果我们enable了一个framework-res.apk的overlay包,由于所有应用都有可能使用里面的
                 * 资源,所以这个时候要通知所有的应用进程.
                 */
                if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
                    try {
                        /**
                         * 我们前面已经把overlay包状态改变了的信息更新到了PMS,
                         * 这里获取到的已经是更新过了的ApplicationInfo了
                         */
                        final ApplicationInfo ai = AppGlobals.getPackageManager()
                                .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
                        if (ai != null) {
                            app.thread.scheduleApplicationInfoChanged(ai);
                        }
                    } catch (RemoteException e) {
                        Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
                                packageName, app));
                    }
                }
            }
        }
    }

        mProcessList也只是找到target process,然后把这个信息通知到应用进程,然后看应用进程的处理:

    //frameworks/base/core/java/android/app/ActivityThread.java
    public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
        mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai);
        sendMessage(H.APPLICATION_INFO_CHANGED, ai);
    }


    //这个就是H.APPLICATION_INFO_CHANGED的消息处理方法
    public void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
        // Updates triggered by package installation go through a package update
        // receiver. Here we try to capture ApplicationInfo changes that are
        // caused by other sources, such as overlays. That means we want to be as conservative
        // about code changes as possible. Take the diff of the old ApplicationInfo and the new
        // to see if anything needs to change.
        
        //target process所属的APK
        LoadedApk apk;
        /**
         * resApk是干啥的,往下看
         */
        LoadedApk resApk;
        // Update all affected loaded packages with new package information
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref = mPackages.get(ai.packageName);
            //拿到 target process中我们的target package信息
            apk = ref != null ? ref.get() : null;
            /**
             * resApk表示我们的target process加载的其它资源包,比如我们通过
             * public Context createPackageContext(String packageName, int flags)
             * 方法访问别的APK的时候,就会为这个APK创建LoadedApk对象,并且如果这个APK中含有
             * dex字节码就会把它的LoadedApk对象放到mPackages中,如果这个APK中没有字节码,只有资源,
             * 那么就会把它放到mResourcePackages中去
             */
            ref = mResourcePackages.get(ai.packageName);
            resApk = ref != null ? ref.get() : null;
        }

        final String[] oldResDirs = new String[2];

        if (apk != null) {
            oldResDirs[0] = apk.getResDir();
            final ArrayList<String> oldPaths = new ArrayList<>();
            /**
             * 虽然我们前面更新了system_server中OverlayManagerSettings、
             * PMS的overlay信息,以及/data/system/voerlays.xml中的overlay
             * 信息,但是应用进程也就是apk.getApplicationInfo()获取到的ApplicationInfo
             * 仍然是老的。
             */
            LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths);
            /**
             * 更新LoadedApk对象中的Application信息,ai这个对象中存储的ApplicationInfo
             * 中包含的overlay信息是从PMS那里拿到的,也就是最新的。
             */
            apk.updateApplicationInfo(ai, oldPaths);
        }
        if (resApk != null) {
            oldResDirs[1] = resApk.getResDir();
            final ArrayList<String> oldPaths = new ArrayList<>();
            LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths);
            resApk.updateApplicationInfo(ai, oldPaths);
        }

        synchronized (mResourcesManager) {
            // Update all affected Resources objects to use new ResourcesImpl
            /**
             * 这里会更新ResourceImpl,也就是说,会创建新的AssetManager,加载resource.arsc等等
             */
            mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs);
        }

        /**
         * ApplicationPackageManager跑在应用进程中,它有一些方法,可以获取别的APK
         * 中的Drawable、String等资源,同时它还会把获取到的这些资源缓存起来,这样后面
         * 再次访问的时候就不用去获取了,直接返回。如果缓存里都是别的资源包里的资源,那这个时候
         * 可以不用清理缓存,但是如果缓存里有我们target pacakge自身的,那这个时候,由于
         * overlay package改变,缓存里的数据有可能已经过时了,所以要通过下面的这个方法去
         * 清理它里面的缓存,这个我们就不详细介绍了。
         */
        ApplicationPackageManager.configurationChanged();

        // Trigger a regular Configuration change event, only with a different assetsSeq number
        // so that we actually call through to all components.
        // TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to
        // store configurations per-process.
        Configuration newConfig = new Configuration();
        newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1;
        handleConfigurationChanged(newConfig, null);

        // 重启所有的Activity!!!
        relaunchAllActivities(true /* preserveWindows */);
    }

        熟悉的配方,熟悉的味道,又是发Message在ActivityThread中,scheduleXXX开头的方法都是这个模式 因为这些方法是通过binder接口调用过来的,运行在binder线程中, 而是要运行在应用的主线程中的,所以才会发消息了。另外,我们看到应用进程这边的处理也比较简单,主要就是:用新的ApplicationInfo(主要是ApplicationInfo.resourceDirs,也就是overlay包的路径)创建新的ResourcesImpl(其实也就是重新创建了AsssetManager对象),然后重启所有的Activity,这样新的Activity起来后,自然就会利用我们新创建的ResourcesImpl里面的资源重新绘制界面,这样我们的Overlay包就可以作用到target pacakge上了。
        总结一下,enable一个overlay package的大体流程:
在这里插入图片描述

  • enable一个overlay package后OMS模块会生成idmap文件,这个文件我们就不详细介绍了,感兴趣的同学点这里:Android资源管理中的Runtime Resources Overlay-------之PMS、installd和idmap的处理(二)
  • 然后OMS会更新overlay信息,信息在OverlayManagerSettings中。
  • 再然后的两步其实是并行的:持久化OverlayManagerSettings的信息到/data/system/overlays.xml,这个在IoThread中执行;修正OverlayManagerSettings中的overlay信息,然后把它同步到PMS中。
  • 后面的流程就交给AMS了,AMS根据target包的包名,找到target process对应的ProcessRecord,从PMS那里拿到修正后的overlay信息(封装在ApplicationInfo中啦)。
  • AMS通过ProcessRecord中的IApplicationThread接口通知target process,你的overlay信息变了,你得处理一下。变成啥样子了?新的样子会封装在ApplicationInfo参数中。
  • 应用进程收到这个binder请求后,会更新自己的 LoadedApk对象,重新创建AssetManager对象。当然,这个过程中就会加载新的overlay包,这个时候,新的Resources对象、AssetManager对象中,新的overlay包已经生效了。
  • 但是,我们看到的ActivityView还是老的,怎么办呢?重启所有的Activity,这样我们就新的Activity起来后,自然就会用新的Resources对象来绘制自己的内容了,我们看到的就是新的界面了!

        另外,前面我们在updateOverlayPaths的时候,通过pm.setEnabledOverlayPackages方法,会把overlay信息更新到PMS中,PMS中存储overlay信息的是PackageSetting对象,但是当我们在AMS中获取overlay信息时,却是通过AppGlobals.getPackageManager().getApplicationInfo方法获取的,那么PMS是怎么把PackageSetting对象中的overlay信息转移到ApplicationInfo对象中的呢:

    //frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
    public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
        @Nullable List<String> overlayPackageNames) {
        /**
         * mPackages中存放的是从已经安装的应用的AndroidManifest.xml中解析而来的包信息,
         * 包含overlay packages
         * 访问mPackages必须同步,否则把mPackages弄乱系统就gg了
         */
        synchronized (mPackages) {
            //异常情况检测
            if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
                Slog.e(TAG, "failed to find package " + targetPackageName);
                return false;
            }
            //获取overlay包的路径
            ArrayList<String> overlayPaths = null;
            if (overlayPackageNames != null && overlayPackageNames.size() > 0) {
                final int N = overlayPackageNames.size();
                overlayPaths = new ArrayList<>(N);
                for (int i = 0; i < N; i++) {
                    final String packageName = overlayPackageNames.get(i);
                    //确保overlay包存在
                    final PackageParser.Package pkg = mPackages.get(packageName);
                    if (pkg == null) {
                        Slog.e(TAG, "failed to find package " + packageName);
                        return false;
                    }
                    //拿到overlay包的路径 
                    overlayPaths.add(pkg.baseCodePath);
                }
            }
            /**
             * overlay包的路径信息更新到PackageSetting中,
             * PackageSetting表示一个package的设置信息,比如这个package是disable
             * 还是enable状态、包的路径、abi信息、versioncode、签名信息等等,
             * 当然还包括它的overlay包路径
             */
            final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
            ps.setOverlayPaths(overlayPaths, userId);
            return true;
        }
    }

        我们在AMS中通过getApplicationInfo方法获取ApplicationInfo的时候:

    //frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
    public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
        /**
         * packageName 我们的target pacakge的包名
         * 
         * flags STOCK_PM_FLAGS,这个flags表示如果我们要获取的package是个资源共享库,
         * 那么也同样会返回它的ApplicationInfo,如果没有这个flag,这种情况下则会返回null
         * 
         * Binder.getCallingUid(),其实就是通过binder接口调用OMS的setEnabled方法的应用的uid
         * 
         * userId,则是Android多用户中的user的ID,单用户的情况下是0
         */
        return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId);
    }

    private ApplicationInfo getApplicationInfoInternal(String packageName, int flags,
            int filterCallingUid, int userId) {
                                // .......省略非关键代码
                                
        // 根据包名拿到PackageSetting对象,这里面的overlay信息是我们已经更新过了的
        PackageSetting ps = mSettings.mPackages.get(packageName);
        //ps.readUserState(userId),返回的是一个PackageUserState对象,overlay包的路径就存在里面
        ApplicationInfo ai = PackageParser.generateApplicationInfo(
                        p, flags, ps.readUserState(userId), userId);
                        
                               // .......省略非关键代码
                               
       return ai;
    }

        看PackageParser的处理:

    public static ApplicationInfo generateApplicationInfo(Package p, int flags,
            PackageUserState state) {
        /**
         * 默认是调用者所属的user,这个UserHandle.getCallingUserId()是Android层面的userId,
         * 而非linux层面的uid
         */
        return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
    }

    public static ApplicationInfo generateApplicationInfo(Package p, int flags,
            PackageUserState state, int userId) {
            
        // .......省略非关键代码
        
        ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
        
        // .......省略非关键代码
        
        //state是我们传过来的PackageUserState,overlay包的路径就在里面
        updateApplicationInfo(ai, flags, state);
        return ai;
    }
    
    private static void updateApplicationInfo(ApplicationInfo ai, int flags,
            PackageUserState state) {
            
        // .......省略非关键代码

        // key code !!!
        ai.resourceDirs = state.overlayPaths;
        ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
    }

         Android 11 发布后,简单看了一下,资源管理这块儿,Google又有改动,没错还是关于RRO的,增加了一些接口,非常友好。没错就是Resource Loader框架我们抽时间分析一下。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值