AndroidQ RRO(Runtime Resource Overlay)机制(3)

本文详细剖析了Android系统中当通过OverlayManager设置Overlay包状态时,如何触发并更新目标应用资源的过程。涉及的关键步骤包括判断Overlay有效性、持久化设置、更新Asset路径、通知AMS以及APP进程的资源变更。核心在于将Overlay信息保存到PMS,并通过AMS通知APP进程更新ApplicationInfo中的resourceDirs属性,以实现Overlay生效。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

上一篇文章讲到当我们通过OMS的setEnabled方法去改变一个Overlay包的状态时会经过一系列的判断,如Overlay包是否存在,Overlay包的目标应用是否存在,目标应用的Idmap文件是否存在,Overlay包是否为静态等等,如果最终通过判断且Overlay包的状态确实发生了改变则会调onOverlaysChanged方法去通知目标应用其资源包发生了改变,进而使Overlay能够生效。

 boolean setEnabled(@NonNull final String packageName, final boolean enable,
            final int userId) {
            
            ......
            
            //此方法里面会计算Overlay包的状态,返回值作为判断是否更新Overlay的依据
            modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
            //如果需要更新Overlay,则回调onOverlaysChanged
            if (modified) {
                mListener.onOverlaysChanged(oi.targetPackageName, userId);
            }
            return true;
        } catch (OverlayManagerSettings.BadKeyException e) {
            return false;
        }
    }

onOverlaysChanged

mListener定义在OMS内部:

private final class OverlayChangeListener
            implements OverlayManagerServiceImpl.OverlayChangeListener {
        @Override
        public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) {
            //持久化
            schedulePersistSettings();
            FgThread.getHandler().post(() -> {

                updateAssets(userId, targetPackageName);
                
               //发送OVERLAY_CHANGED的广播,不重要,省略
                ....
                try {
                    ......
                } catch (RemoteException e) {
                    // Intentionally left empty.
                }
            });
        }
    }

OverlayManagerSettings.schedulePersistSettings

schedulePersistSettings负责将Overlay的信息持久化保存到设备中,路径为:/data/system/overlays.xml

private void schedulePersistSettings() {
        //避免重复调用
        if (mPersistSettingsScheduled.getAndSet(true)) {
            return;
        }
        IoThread.getHandler().post(() -> {
            mPersistSettingsScheduled.set(false);
          
            synchronized (mLock) {
                FileOutputStream stream = null;
                try {
                    stream = mSettingsFile.startWrite();
                    mSettings.persist(stream);
                    mSettingsFile.finishWrite(stream);
                } catch (IOException | XmlPullParserException e) {
                    mSettingsFile.failWrite(stream);
                    Slog.e(TAG, "failed to persist overlay state", e);
                }
            }
        });
    }

持久化其实就是一个写xml的过程,具体实现在OverlayManagerSettings中,OverlayManagerSettings内部提供了一个工具类Serializer来操作写入流:

 void persist(@NonNull final OutputStream os) throws IOException, XmlPullParserException {
        Serializer.persist(mItems, os);
    }
 public static void persist(@NonNull final ArrayList<SettingsItem> table,
                @NonNull final OutputStream os) throws IOException, XmlPullParserException {
            final FastXmlSerializer xml = new FastXmlSerializer();
            xml.setOutput(os, "utf-8");
            xml.startDocument(null, true);
            xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
            xml.startTag(null, TAG_OVERLAYS);
            XmlUtils.writeIntAttribute(xml, ATTR_VERSION, CURRENT_VERSION);

            final int n = table.size();
            //遍历系统所有的Overlay所对应的SettingsItem
            for (int i = 0; i < n; i++) {
                final SettingsItem item = table.get(i);
                persistRow(xml, item);
            }
            xml.endTag(null, TAG_OVERLAYS);
            xml.endDocument();
        }

上面代码很好理解,整个写入过程就是遍历mItems(前面讲过,mItems存储了系统所有Overlay包所对应的SettingsItem),依次将每一个SettingsItem的信息写入overlays.xml,文件内容如下:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<overlays version="3">
    <item packageName="com.caic.car.rroresource" userId="0" targetPackageName="com.example.overlaydemo|com.android.systemui" baseCodePath="/product/overlay/RROResource/RROResource.apk" state="3" isEnabled="true" isStatic="false" priority="1" category="com.caic.car.theme.customization.rroresource" />
    <item packageName="com.caic.car.rroresource" userId="10" targetPackageName="com.example.overlaydemo|com.android.systemui" baseCodePath="/product/overlay/RROResource/RROResource.apk" state="3" isEnabled="true" isStatic="false" priority="1" category="com.caic.car.theme.customization.rroresource" />
</overlays>

接下来方法updateAssets是本篇的重点:

OverlayManagerService.updateAssets

   private void updateAssets(final int userId, final String targetPackageName) {
        //将String转换为集合
        updateAssets(userId, Collections.singletonList(targetPackageName));
    }

    private void updateAssets(final int userId, List<String> targetPackageNames) {
        //根据目标应用更新Overlay的路径
        updateOverlayPaths(userId, targetPackageNames);
        final IActivityManager am = ActivityManager.getService();
        try {
            //通知AMS处理目标应用的ApplicationInfo发生变化
            am.scheduleApplicationInfoChanged(targetPackageNames, userId);
        } catch (RemoteException e) {
            // Intentionally left empty.
        }
    }

updateAssets中首先调了updateOverlayPaths的方法:

OverlayManagerService.updateOverlayPaths

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);
             //如果目标应用集合中包含包名为"android"的应用(也就是framework-res)
            final boolean updateFrameworkRes = targetPackageNames.contains("android");
            if (updateFrameworkRes) {
                //则会修改目标应用集合,赋值为系统已经安装的所有非Overlay的应用
                targetPackageNames = pm.getTargetPackageNames(userId);
            }
            //用来保存目标应用与Overlay包的对应关系
            final Map<String, List<String>> pendingChanges =
                    new ArrayMap<>(targetPackageNames.size());
            synchronized (mLock) {
                //获取包名为"android"(也就是framework-res)的Overlay包
                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);
                    //这个list用来保存目标应用的Overlay包的集合
                    List<String> list = new ArrayList<>();
                    if (!"android".equals(targetPackageName)) {
                        //对于非framework-res的目标应用来说,除了自身的Overlay包以外还需要加入
                        //framework-res的Overlay包
                        list.addAll(frameworkOverlays);
                    }
                    //获取目标应用的Overlay包集合,加入list
                    list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
                    //以目标应用与Overlay集合存入Map
                    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);
                }
                //将每一个目标应用的Ovelray包信息更新到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);
        }
    }

这个方法的作用就是找到目标应用所对应的所有Overlay包,并将所有Overlay包的路径以(Key,Value)的形式保存到PMS中
需要特殊处理的是包名为"android"的应用(framework-res),因为系统中的所有应用都可能使用了framework-res中的资源,所以一旦framework-res被Overlay,我们就需要将目标应用的集合改为整个系统中除开Overlay的所有应用了:


        @Override
        public List<String> getTargetPackageNames(int userId) {
            List<String> targetPackages = new ArrayList<>();
            synchronized (mPackages) {
                for (PackageParser.Package p : mPackages.values()) {
                    //mOverlayTarget为空说明这不是一个Overlay应用
                    if (p.mOverlayTarget == null) {
                        targetPackages.add(p.packageName);
                    }
                }
            }
            return targetPackages;
        }

并且还需要将framework-res的Overlay包加入到其他所有应用的Overlay集合中,也就是说,如果framework-res被Overlay,那么系统中所有应用的Overlay list中都至少会包含framework-res的Overlay包。

接着我们来看看如何根据目标包名获取其Overlay集合:

OverlayManagerServiceImpl.getEnabledOverlayPackageNames

List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
            final int userId) {
        //获取OverlayInfo集合
        final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
                userId);
        final List<String> paths = new ArrayList<>(overlays.size());
        final int n = overlays.size();
        for (int i = 0; i < n; i++) {
            final OverlayInfo oi = overlays.get(i);
            if (oi.isEnabled()) {
                //只获取状态为STATE_ENABLED的Overlay
                paths.add(oi.packageName);
            }
        }
        return paths;
    }

OverlayManagerSettings.getOverlaysForTarget

List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
            final int userId) {
        // Static RROs targeting "android" are loaded from AssetManager, and so they should be
        // ignored in OverlayManagerService.
        return selectWhereTarget(targetPackageName, userId)
                .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
                .map(SettingsItem::getOverlayInfo)
                .collect(Collectors.toList());
    }

OverlayManagerSettings中对集合的操作都是使用了java8新增的Stream,关于Stream用法很简单,如果对js,python等脚本语言有过了解的应该一看就懂了,归根结底还是对集合的遍历,然后根据条件过滤。这里遍历的就是OverlayManagerSettings内部的mItems,即存储系统所有Overlay包对应SettingsItem的集合,

selectWhereTarget会从mItems中筛选出匹配目标包名的Overlay应用集合,接着通过filter进行过滤,过滤条件为:目标应用包名是"android"的静态Overlay,注释说的很清楚,目标应用包名是"android"的静态Overlay会在AssetManager中加载,然后通过map对得到的Overlay集合的每一个成员执行getOverlayInfo方法,这里的作用就是将SettingsItem转换为OverlayInfo

       private OverlayInfo getOverlayInfo() {
            if (mCache == null) {
                mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName,
                        mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsStatic);
            }
            return mCache;
        }

这里转换就很直接了,如果没缓存就new一个OverlayInfo,所以最后就通过目标包名得到一个OverlayInfo的集合,拿到OverlayInfo集合后,状态为STATE_ENABLED或者STATE_ENABLED_STATIC才是目标应用有效的Overlay,关于Overlay包的状态更新在上篇文章分析过。

到此我们已经看完了系统是如何根据目标包名获取Overlay集合的,本质上就是遍历OverlayManagerSettings内部的mItems,然后根据条件进行过滤,最终返回的就是满足条件的集合。

再回到updateOverlayPaths方法,目标应用与Overlay包以(key,value)的形式保存在了pendingChanges中,一个目标应用可以对应多个Overlay,接着会将它们的对应关系更新到PMS:

PackageManagerService.setEnabledOverlayPackages

@Override
        public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
                @Nullable List<String> overlayPackageNames) {
            synchronized (mPackages) {
                //目标应用异常的情况,mPackages保存了PMS解析过的所有系统应用
                if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
                    Slog.e(TAG, "failed to find package " + targetPackageName);
                    return false;
                }
                ArrayList<String> overlayPaths = null;
                if (overlayPackageNames != null && overlayPackageNames.size() > 0) {
                    final int N = overlayPackageNames.size();
                    overlayPaths = new ArrayList<>(N);
                    //遍历Overlay包的集合
                    for (int i = 0; i < N; i++) {
                        final String packageName = overlayPackageNames.get(i);
                        final PackageParser.Package pkg = mPackages.get(packageName);
                        //系统中并没有这个Overlay包,这是异常情况
                        if (pkg == null) {
                            Slog.e(TAG, "failed to find package " + packageName);
                            return false;
                        }
                        //保存Overlay包的安装路径
                        overlayPaths.add(pkg.baseCodePath);
                    }
                }
                //PackageSetting用来描述一个应用包的设置相关信息
                final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
                //将Overlay的路径更新到PackageSetting
                ps.setOverlayPaths(overlayPaths, userId);
                return true;
            }
        }

PackageSettingBase.setOverlayPaths

void setOverlayPaths(List<String> overlayPaths, int userId) {
        modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
            overlayPaths.toArray(new String[overlayPaths.size()]);
    }

PackageSettingBase.modifyUserState

 private PackageUserState modifyUserState(int userId) {
        PackageUserState state = mUserState.get(userId);
        if (state == null) {
            state = new PackageUserState();
            mUserState.put(userId, state);
        }
        return state;
    }

最终Overlay包的路径被保存到了目标应用对应的PackageUserStateoverlayPaths中,记住这个位置,后面会有用。

到此updateOverlayPaths方法已经分析完了,接着回到前面的updateAssets方法:

private void updateAssets(final int userId, List<String> targetPackageNames) {
        updateOverlayPaths(userId, targetPackageNames);
        final IActivityManager am = ActivityManager.getService();
        try {
            am.scheduleApplicationInfoChanged(targetPackageNames, userId);
        } catch (RemoteException e) {
            // Intentionally left empty.
        }
    }

接下来要进入AMS了:

AMS.scheduleApplicationInfoChanged

    @Override
    public void scheduleApplicationInfoChanged(List<String> packageNames, int userId) {
        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                "scheduleApplicationInfoChanged()");

        synchronized (this) {
            final long origId = Binder.clearCallingIdentity();
            try {
                updateApplicationInfoLocked(packageNames, userId);
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    }

AMS.updateApplicationInfoLocked

void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
        final boolean updateFrameworkRes = packagesToUpdate.contains("android");
        ...
        mProcessList.updateApplicationInfoLocked(packagesToUpdate, userId, updateFrameworkRes);

        if (updateFrameworkRes) {
             //如果被Overlay的目标应用中包含framework-res,则会回调onOverlayChanged,SystemUI很多主要控件
             //都实现onOverlayChanged,以便在framework-res被Overlay之后能迅速改变界面
             ....
        }
    }

ProcessList.updateApplicationInfoLocked

 @GuardedBy("mService")
    void updateApplicationInfoLocked(List<String> packagesToUpdate, int userId,
            boolean updateFrameworkRes) {
        //遍历系统当前存活的所有进程
        for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
            final ProcessRecord app = mLruProcesses.get(i);
            
            if (app.thread == null) {
                continue;
            }

            if (userId != UserHandle.USER_ALL && app.userId != userId) {
                continue;
            }
            //packageCount表示同一进程的package数量,一般一个进程只有一个
            final int packageCount = app.pkgList.size();
            for (int j = 0; j < packageCount; j++) {
                final String packageName = app.pkgList.keyAt(j);
                //如果被Overlay的目标包含framework-res,或者当前进程中有被Overlay的目标package则会通知到APP进程
                if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
                    try {
                        //获取目标package对应的ApplicationInfo
                        final ApplicationInfo ai = AppGlobals.getPackageManager()
                                .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
                        if (ai != null) {
                            //通知APP进程
                            app.thread.scheduleApplicationInfoChanged(ai);
                        }
                    } catch (RemoteException e) {
                        Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
                                packageName, app));
                    }
                }
            }
        }
    }

上面的方法会遍历系统中存活的所有进程,如果updateFrameworkRes为true即被Overlay的目标包含framework-res则AMS会通知所有进程,它们的资源文件发生了改变,它们需要对这种情况进行处理。如果updateFrameworkRes为false,则AMS只需要通知具体被Overlay的目标进程。

我们看通知APP进程调用的方法scheduleApplicationInfoChanged,从名字能看出来这是APP的ApplicationInfo发生了变化,那ApplicationInfo的什么属性发生了变化呢?
接着来看getApplicationInfo

PackageManagerService.getApplicationInfo

@Override
    public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
        return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId);
    }

    private ApplicationInfo getApplicationInfoInternal(String packageName, int flags,
            int filterCallingUid, int userId) {
       //省略非本篇重点
        .....
    
        synchronized (mPackages) {
           ...
           
            PackageParser.Package p = mPackages.get(packageName);
           
           
            if (p != null) {
                PackageSetting ps = mSettings.mPackages.get(packageName);
                
                //省略非本篇重点
                ....
                
                ApplicationInfo ai = PackageParser.generateApplicationInfo(
                        p, flags, ps.readUserState(userId), userId);
                if (ai != null) {
                    ai.packageName = resolveExternalPackageNameLPr(p);
                }
                return ai;
            }
            //省略非本篇重点
            ...
        }
        return null;
    }

前面在看updateOverlayPaths方法时分析过目标应用对应的Overlay包集合会被更新到PMS,最终Overlay包的路径是被保存到了目标应用对应的PackageUserStateoverlayPaths属性中,这里构造ApplicationInfo时调用了PackageParser.generateApplicationInfo,传递的参数有一个是ps.readUserState,这个参数实际上就是去拿到目标应用对应的PackageUserState对象,有了PackageUserState,也就有了前面更新到PMS的目标应用的Overlay路径,接着看:

PackageParser.generateApplicationInfo

@UnsupportedAppUsage
    public static ApplicationInfo generateApplicationInfo(Package p, int flags,
            PackageUserState state, int userId) {
       //省略无关代码
        .....
       //ApplicationInfo的构造是根据已有的,再copy一份,然后修改一些属性
        ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
        //省略无关代码
        ...
        updateApplicationInfo(ai, flags, state);
        return ai;
    }

PackageParser.updateApplicationInfo

 private static void updateApplicationInfo(ApplicationInfo ai, int flags,
            PackageUserState state) {
        //省略无关属性更新
        ....
      //目标应用对应的Overlay包路径最终被更新到了ApplicationInfo的resourceDirs属性中
        ai.resourceDirs = state.overlayPaths;
        ...
    }

我们只关注和Overlay包相关的信息更新,发现最终目标应用对应的Overlay包路径就被更新到了目标应用对应的ApplicationInforesourceDirs属性中,这个ApplicationInfo被传递到目标应用所在的APP进程之后,APP进程就可以根据Overlay包来更新资源了,下一篇我们继续看APP进程的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值