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

概述

上一篇文章讲了RRO的简单用法,本篇文章来看看支撑RRO的系统服务OverlayManagerService的实现细节,OverlayManagerService是Android8.0引入了一个系统服务来配合RRO,大致结构如下:

 * <pre>
 *         Android framework
 *            |         ^
 *      . . . | . . . . | . . . .
 *     .      |         |       .
 *     .    AIDL,   broadcasts  .
 *     .   intents      |       .
 *     .      |         |       . . . . . . . . . . . .
 *     .      v         |       .                     .
 *     .  OverlayManagerService . OverlayManagerTests .
 *     .                  \     .     /               .
 *     . (1)               \    .    /            (3) .
 *      . . . . . . . . . . \ . . . / . . . . . . . . .
 *     .                     \     /              .
 *     . (2)                  \   /               .
 *     .           OverlayManagerServiceImpl      .
 *     .                  |            |          .
 *     .                  |            |          .
 *     . OverlayManagerSettings     IdmapManager  .
 *     .                                          .
 *     . . . .  . . . . . . . . . . . . . . . . . .
 * </pre>

上面是从OverlayManagerService的注释中copy来的,它很好的描述了OverlayManagerService的架构:

  1. 首先OverlayManagerService运行在system_server进程,client端通过AIDL接口进行访问。
  2. 其次OverlayManagerService的具体逻辑放在OverlayManagerServiceImpl中实现,OverlayManagerSettingsIdmapManager用来辅助OverlayManagerServiceImpl完成一些逻辑。

那我们这篇文章就来分析分析RRO的这个架构,RRO系列文章都是基于AndroidQ代码分析。

OverlayManagerService初始化

OverlayManagerService开机时在SystemServer中启动:

private void startBootstrapServices() {
	......
		 // Manages Overlay packages
        traceBeginAndSlog("StartOverlayManagerService");
        mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
        traceEnd();
	......
}

从启动时机可以看出这个服务其实是非常重要的,它属于系统的启动引导服务,OverlayManagerServiceonStart方法是个空实现,它的所有初始化都放在了构造方法中:

public OverlayManagerService(@NonNull final Context context,
            @NonNull final Installer installer) {
        super(context);
        try {
            traceBegin(TRACE_TAG_RRO, "OMS#OverlayManagerService");
            //创建overlays.xml,全路径为/data/system/overlays.xml,这个xml文件主要用于保存Overlay包的相关信息
            mSettingsFile = new AtomicFile(
                    new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
            mPackageManager = new PackageManagerHelper();
            mUserManager = UserManagerService.getInstance();
            //Idmap文件的操作管理类,涉及Idmap文件的创建,删除等
            IdmapManager im = new IdmapManager(installer, mPackageManager);
            //这个类用来描述Overlay包相关状态的数据结构
            mSettings = new OverlayManagerSettings();
            //OverlayManagerService的具体实现
            mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
                    getDefaultOverlayPackages(), new OverlayChangeListener());
            //注册对Overlay包以及目标包的状态监听,被卸载,被覆盖等
            final IntentFilter packageFilter = new IntentFilter();
            packageFilter.addAction(ACTION_PACKAGE_ADDED);
            packageFilter.addAction(ACTION_PACKAGE_CHANGED);
            packageFilter.addAction(ACTION_PACKAGE_REMOVED);
            packageFilter.addDataScheme("package");
            getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL,
                    packageFilter, null, null);
            //多用户相关的广播
            final IntentFilter userFilter = new IntentFilter();
            userFilter.addAction(ACTION_USER_ADDED);
            userFilter.addAction(ACTION_USER_REMOVED);
            getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL,
                    userFilter, null, null);
            //读取保存在/data/system/overlays.xml中的overlay包的状态数据
            restoreSettings();

            initIfNeeded();
            onSwitchUser(UserHandle.USER_SYSTEM);
            //将OverlayManagerService内部的Binder服务注册到ServiceManager以便Client端使用
            publishBinderService(Context.OVERLAY_SERVICE, mService);
            //让OverlayManagerService只能被系统进程访问,不具备系统签名的三方应用是无法使用RRO的
            publishLocalService(OverlayManagerService.class, this);
        } finally {
            traceEnd(TRACE_TAG_RRO);
        }
    }

OverlayManagerService构造方法中初始化了一些重要的东西:
首先创建了文件/data/system/overlays.xml用于保存Overlay包的状态信息:

<overlays version="3">
    <item packageName="com.caic.car.rroresource" userId="0" targetPackageName="com.example.overlaydemo" baseCodePath="/product/overlay/RROResource/RROResource.apk" state="2" 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" baseCodePath="/product/overlay/RROResource/RROResource.apk" state="3" isEnabled="true" isStatic="false" priority="1" category="com.caic.car.theme.customization.rroresource" />
</overlays>

这个文件中一个个item对应的就是OverlayManagerSettings中的SettingsItem,即对Overlay包的描述信息。

然后创建了IdmapManagerOverlayManagerSettingsOverlayManagerServiceImpl,这三个类是RRO框架的重要组成部分,IdmapManager用于生成Idmap文件,Idmap文件用来描述Target应用与Overlay应用之间的资源映射关系,通过命令adb shell idmap2 dump --idmap-path xxx可以查看其中的具体映射详情:

target apk path  : /system/app/OverlayDemo/OverlayDemo.apk
overlay apk path : /product/overlay/RROResource/RROResource.apk
0x7f030000 -> 0x7f010001 drawable/ic_launcher_background
0x7f030002 -> 0x7f010003 drawable/test1
0x7f030003 -> 0x7f010004 drawable/test2

OverlayManagerSettings主要用来管理系统中所有的Overlay包,它内部通过SettingsItem来描述一个Overlay包的信息,更准确的说是通过SettingsItem内部的OverlayInfo来描述的,外部可以通过set和get来设置和获取Overlay包的状态。

OverlayManagerServiceImpl这个类就是OverlayManagerService的具体实现逻辑了,比如Overlay包的状态设置,信息获取,状态更新,Idmap创建与删除等,OverlayManagerServiceImpl中的很大部分逻辑是交给OverlayManagerSettingsIdmapManager去做的。

紧接着OMS构造方法中注册了几个对RRO影响非常大的操作的广播,分为两类,一类是包的状态更新(不管是Target包还是Overlay包,安装卸载都需要更新很多状态),二类是多用户相关的广播(RRO这种明显改变UI界面的操作必须考虑多用户,用户的增删都需要更新Target与Overlay的状态)。

剩下的操作也比较简单,restoreSettings用来读取/data/system/overlays.xml中保存的Overlay状态,拿到这些状态值之后会构造一个SettingsItempublishBinderService用来将OMS内部的Binder服务注册到ServiceManager以便Client端使用,publishLocalService使得此服务只能系统进程访问,RRO这个功能普通的三方应用是无法使用的,只有具有系统签名的应用才能使用。

OMS的构造方法比较简单,接着我们根据上一篇文章的例子来看看Overlay包是如何生效的,上一篇文章说道Overlay包编译好之后push进设备,使用命令adb shell dumpsys overlay可以查看Overlay的状态:

com.caic.car.rroresource:0 {
  mPackageName...........: com.caic.car.rroresource
  mUserId................: 0
  mTargetPackageName.....: com.example.overlaydemo
  mTargetOverlayableName.: null
  mBaseCodePath..........: /product/overlay/RROResource/RROResource.apk
  ------------------------------
  targetPackageName:com.example.overlaydemo,mState = :STATE_DISABLED
  ------------------------------
  mIsEnabled.............: false
  mIsStatic..............: false
  mPriority..............: 1
  mCategory..............: com.caic.car.theme.customization.rroresource
}

当前我们这个包状态为STATE_DISABLED,我们可以通过命令adb shell cmd overlay enable --user 0 com.caic.car.rroresource使Overlay生效,这个命令会通过OverlayManagerShellCommandonCommand为入口,调到OMS的setEnabled方法中,流程很简单不贴出来了。

我们直接来看OMS的setEnabled方法:

OverlayManagerService.setEnabled

@Override
        public boolean setEnabled(@Nullable final String packageName, final boolean enable,
                int userId) throws RemoteException {
            try {
                .....
                try {
                    synchronized (mLock) {
                        return mImpl.setEnabled(packageName, enable, userId);
                    }
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            } finally {
                traceEnd(TRACE_TAG_RRO);
            }
        }

具体实现在OverlayManagerServiceImpl中:

OverlayManagerServiceImpl.setEnabled

 boolean setEnabled(@NonNull final String packageName, final boolean enable,
            final int userId) {
        //获取overlay的PackageInfo,这里的packageName就是com.caic.car.rroresource
        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
        if (overlayPackage == null) {
            return false;
        }

        //如果Overlay是静态的,则不允许动态enable或者disable
        if (overlayPackage.isStaticOverlayPackage()) {
            return false;
        }

        try {
            //获取描述Overlay信息的OverlayInfo
            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
            //判断此次设置的状态和上一次状态是否一致,不一致则保存到SettingsItem并返回true,一致则返回false
            boolean modified = mSettings.setEnabled(packageName, userId, enable);
            //此方法里面会计算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;
        }
    }

这个方法首先会判断要Enable的Overlay是否为静态的,如果是静态的Overlay是不允许Enable或者Disable的,Android也提供了一种静态Overlay的方式,一般设备厂商可以在Vendor目录定义特定于项目的资源文件,在出厂是自动Overlay原生的资源,最常见的就是开关机动画了。

所以会有这个判断,它的值是怎么来的呢?做Overlay包时,在其AndroidManifest.xml的overlay标签中个属性isStatic,就是这个值,在PKMS解析Overlay包时会将isStatic的值存到PackageInfo中:

pkg.mOverlayIsStatic = sa.getBoolean(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic,
                        false);

接着来看getOverlayInfo

OverlayManagerSettings.getOverlayInfo

 @NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
            throws BadKeyException {
        final int idx = select(packageName, userId);
        if (idx < 0) {
            throw new BadKeyException(packageName, userId);
        }
        return mItems.get(idx).getOverlayInfo();
    }

select是其内部封装的一个对mItems的集合操作方法,mItemsOverlayManagerSettings内部用来存储系统所有Overlay包对应的SettingsItem的ArrayList,select的操作就是从中找到匹配packageNameuserId的目标,mItems是啥时候保存SettingsItem的呢?
来看看OverlayManagerSettingsinit方法:

private final ArrayList<SettingsItem> mItems = new ArrayList<>();

void init(@NonNull final String packageName, final int userId,
            @NonNull final String targetPackageName,  @Nullable final String targetOverlayableName,
            @NonNull final String baseCodePath, boolean isStatic, int priority,
            @Nullable String overlayCategory) {
        remove(packageName, userId);
        final SettingsItem item =
                new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
                        baseCodePath, isStatic, priority, overlayCategory);
        if (isStatic) {
            // All static overlays are always enabled.
            item.setEnabled(true);

            int i;
            for (i = mItems.size() - 1; i >= 0; i--) {
                SettingsItem parentItem = mItems.get(i);
                if (parentItem.mIsStatic && parentItem.mPriority <= priority) {
                    break;
                }
            }
            int pos = i + 1;
            if (pos == mItems.size()) {
                mItems.add(item);
            } else {
                mItems.add(pos, item);
            }
        } else {
            mItems.add(item);
        }
    }

这个方法里面会根据Overlay包的信息构造SettingsItem,然后对静态和动态的Overlay包分别存储,动态Overlay包从mItems头部开始依次存入,静态的Overlay包从mItems尾部开始添加并且会根据优先级来排序,大的在前小的在后。

再来看看init方法调用的地方,在OverlayManagerServiceImpl中有三处调用,最常见的调用来自onOverlayPackageAdded,很好理解。

接着我们继续看SettingsItemgetOverlayInfo方法:

SettingsItem.getOverlayInfo

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

可以看到这里就很直接的创建了一个OverlayInfo,这个类才是真正用来描述一个Overlay包的全部信息的数据结构。

回到OverlayManagerServiceImpl.setEnabled中,OverlayInfo拿到之后我们重点来看updateState方法:

OverlayManagerServiceImpl.updateState

    private boolean updateState(@NonNull final String targetPackageName,
            @NonNull final String overlayPackageName, final int userId, final int flags)
            throws OverlayManagerSettings.BadKeyException {

        final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
                userId);

        //目标应用包名为"android"的framework-res.apk的静态Overlay,不会为其创建Idmap,
        //因为在native层已经创建了
        if (targetPackage != null && overlayPackage != null
                && !("android".equals(targetPackageName)
                        && overlayPackage.isStaticOverlayPackage())) {
            //创建Idmap
            mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
        }

        boolean modified = false;
        if (overlayPackage != null) {
            //将Overlay包的安装目录和Category属性的值保存到SettingsItem
            
            modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
                    overlayPackage.applicationInfo.getBaseCodePath());
            modified |= mSettings.setCategory(overlayPackageName, userId,
                    overlayPackage.overlayCategory);
        }
                                 //当前状态
        final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
                                 //计算新的状态
        final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
                userId, flags);
        if (currentState != newState) {
            if (DEBUG) {
                Slog.d(TAG, String.format("%s:%d: %s -> %s",
                            overlayPackageName, userId,
                            OverlayInfo.stateToString(currentState),
                            OverlayInfo.stateToString(newState)));
            }
            //如果两个状态不等,则将新状态保存至SettingsItem
            modified |= mSettings.setState(overlayPackageName, userId, newState);
        }
        return modified;
    }

这个方法中最重要的两点就是创建Idmap文件和计算Overlay的状态了,先来看Idmap的创建:

IdmapManager.createIdmap

 boolean createIdmap(@NonNull final PackageInfo targetPackage,
            @NonNull final PackageInfo overlayPackage, int userId) {
        
        final int sharedGid = UserHandle.getSharedAppGid(targetPackage.applicationInfo.uid);
        final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
        final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
        try {
            //FEATURE_FLAG_IDMAP2默认为true
            if (FEATURE_FLAG_IDMAP2) {
                //计算Overlay包的安全策略
                int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
                //是否强制执行Overlay的检查策略,Q版本及之后默认为true
                boolean enforce = enforceOverlayable(overlayPackage);
                //判断是否有必要重新创建Idmap
                if (mIdmap2Service.verifyIdmap(overlayPath, policies, enforce, userId)) {
                    return true;
                }
                //创建Idmap
                return mIdmap2Service.createIdmap(targetPath, overlayPath, policies,
                        enforce, userId) != null;
            } else {
                mInstaller.idmap(targetPath, overlayPath, sharedGid);
                return true;
            }
        } catch (Exception e) {
          
            return false;
        }
    }

可以看到和Idmap文件相关的操作具体由mIdmap2Service来实现的,mIdmap2Service是名为IDMAP_SERVICE(“idmap”)的Binder服务端,其实现在native层:

IBinder binder = ServiceManager.getService(IDMAP_SERVICE);
mIdmap2Service = IIdmap2.Stub.asInterface(binder);

Idmap2Service::createIdmap

Status Idmap2Service::createIdmap(const std::string& target_apk_path,
                                  const std::string& overlay_apk_path, int32_t fulfilled_policies,
                                  bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
                                  std::unique_ptr<std::string>* _aidl_return) {
  ....
  const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
 //Idmap文件的生成路径:/data/resource-cache目录下,文件名为Overlay包路径拼接而成
 //例如:/data/resource-cache/product@overlay@RROResource@RROResource.apk@idmap
  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
   ....
  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
  if (!target_apk) {
    return error("failed to load apk " + target_apk_path);
  }
  //这里会加载Overlay包的resource.arsc
  const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
  if (!overlay_apk) {
    return error("failed to load apk " + overlay_apk_path);
  }

  const auto idmap = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path,
                                          *overlay_apk, policy_bitmask, enforce_overlayable);
  	
  	......
  	
  return ok();
}

Idmap的创建比较复杂,想要完全搞清楚原理与细节需要apk编译打包相关知识,并且需要对resource.arsc文件结构有一定了解,这部分也很复杂所以我们这里不会详细去看Idmap实现原理,只需要知道最关键的部分就行了:

Idmap::FromApkAssets

Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const std::string& target_apk_path,
                                                          const ApkAssets& target_apk_assets,
                                                          const std::string& overlay_apk_path,
                                                          const ApkAssets& overlay_apk_assets,
                                                          const PolicyBitmask& fulfilled_policies,
                                                          bool enforce_overlayable) {
   //省略一大堆条件判断
   ......

  // 查找目标应用和Overlay应用中都存在的资源
  MatchingResources matching_resources;
  const auto end = overlay_pkg->end();
  for (auto iter = overlay_pkg->begin(); iter != end; ++iter) {
    const ResourceId overlay_resid = *iter;
    Result<std::string> name = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid);
    if (!name) {
      continue;
    }
    // prepend "<package>:" to turn name into "<package>:<type>/<name>"
    const std::string full_name =
        base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name->c_str());
    const ResourceId target_resid = NameToResid(target_asset_manager, full_name);
    if (target_resid == 0) {
      continue;
    }

    if (enforce_overlayable) {
      Result<Unit> success =
          CheckOverlayable(*target_pkg, *overlay_info, fulfilled_policies, target_resid);
      if (!success) {
        LOG(WARNING) << "overlay \"" << overlay_apk_path
                     << "\" is not allowed to overlay resource \"" << full_name
                     << "\": " << success.GetErrorMessage();
        continue;
      }
    }
   //将目标应用和Overlay应用中都存在的资源保存下来
    matching_resources.Add(target_resid, overlay_resid);
  }

  if (matching_resources.Map().empty()) {
    return Error("overlay \"%s\" does not successfully overlay any resource",
                 overlay_apk_path.c_str());
  }

  //创建Idmap文件,走到这里基本上就会创建成功了
  .....

  idmap->data_.push_back(std::move(data));

  return {std::move(idmap)};
}

上述方法就是Idmap创建的核心代码了,省略了一堆和编译打包相关的错误,这种错误一般不太会遇到,留下来的代码也比较简单,目的就一个,找出目标应用和Overlay应用中都存在的资源文件(资源名称相同的),这一对资源会组成std::pair,以资源类型为Key存入一个Map中,在后面的创建Idmap时直接遍历,为每一对std::pair创建映射。

但是在创建std::pair之前需要经过两种情况的判断,这两种情况就是我们开发者需要关心的了,看到留下来的代码的注释了吗,这两种情况就是目标应用与Overlay应用的签名比对目标应用与Overlay应用的同名资源查找,所以我们在使用RRO时如果发现Overlay包的状态为STATE_NO_IDMAP,多半都是这两种情况二选一,请多注意。

好了继续回到OverlayManagerServiceImpl.updateState,Idmap的创建我们已经看了,接着来看看Overlay包的状态计算:

OverlayManagerServiceImpl.calculateNewState

private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
            @Nullable final PackageInfo overlayPackage, final int userId, final int flags)
            throws OverlayManagerSettings.BadKeyException {
        //目标应用正在升级
        if ((flags & FLAG_TARGET_IS_BEING_REPLACED) != 0) {
            return STATE_TARGET_IS_BEING_REPLACED;
        }
        //Overlay应用正在升级
        if ((flags & FLAG_OVERLAY_IS_BEING_REPLACED) != 0) {
            return STATE_OVERLAY_IS_BEING_REPLACED;
        }

        // Overlay为空
        if (DEBUG && overlayPackage == null) {
            throw new IllegalArgumentException("null overlay package not compatible with no flags");
        }
       //目标为空
        if (targetPackage == null) {
            return STATE_MISSING_TARGET;
        }
        //没有为目标应用生成Idmap文件
        if (!mIdmapManager.specifiedIdmapExists(targetPackage.applicationInfo.getBaseCodePath() + overlayPackage.applicationInfo.getBaseCodePath(), userId)) {
            return STATE_NO_IDMAP;
        }
        //Overlay包是静态的
        if (overlayPackage.isStaticOverlayPackage()) {
            return STATE_ENABLED_STATIC;
        }
        //如果上面判断都通过了,则直接返回Overlay包是否Enable
        final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
        return enabled ? STATE_ENABLED : STATE_DISABLED;
    }

Overlay包的状态值全部定义在OverlayInfo中,计算过程相对简单,无需多说。

如果Overlay包最终经过updateState之后得到modified返回值为true,则表明需要告知目标应用Overlay包状态发生改变,即会调用onOverlaysChanged,这个方法要做的事情是非常多的,主要会让Overlay包生效完成目标应用的换肤,我们下一篇文章再分析。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值