赞
踩
上篇文章说到目标应用的Overlay包路径被更新到了目标应用ApplicationInfo
之后,就会将更新之后的ApplicationInfo
传给APP进程,本篇继续来看APP进程的处理。
public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai);
sendMessage(H.APPLICATION_INFO_CHANGED, ai);
}
@VisibleForTesting(visibility = PACKAGE) public void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) { //LoadedApk用来描述APP进程中一个package的详细信息 LoadedApk apk; LoadedApk resApk; //一个APP的LoadedApk通常是放在mPackages中,mResourcePackages通常存放为其他资源包应用创建的LoadedApk synchronized (mResourcesManager) { WeakReference<LoadedApk> ref = mPackages.get(ai.packageName); apk = ref != null ? ref.get() : null; 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<>(); LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths); //将AMS传过来的,已经更新了的ApplicationInfo再更新到APP进程的LoadedApk 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 mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs); } ApplicationPackageManager.configurationChanged(); Configuration newConfig = new Configuration(); newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1; //回调onConfigurationChanged handleConfigurationChanged(newConfig, null); // 重启Activity使得Overlay资源生效,这里的重启不是整个应用被杀掉那种重启,preserveWindows代表是否保留窗口 relaunchAllActivities(true /* preserveWindows */); }
APP这边拿到新的ApplicationInfo
之后会将其更新到自己的LoadedApk
中,之后会回调onConfigurationChanged
方法,最后是重启所有Activity
,使Overlay资源能够生效,我们看到这里只是针对Activity
可以重启生效,但如果是对SystemUI这种只有window的应用是不行的,那SystemUI是怎么生效的呢? 依靠的就是onConfigurationChanged
的回调,其回调入口就在SystemUIApplication
中,接着重点看下ApplicationInfo
的更新过程:
public void updateApplicationInfo(@NonNull ApplicationInfo aInfo, @Nullable List<String> oldPaths) { //将ApplicationInfo保存到LoadedApk,主要是更新了mOverlayDirs //mOverlayDirs = aInfo.resourceDirs setApplicationInfo(aInfo); ...... synchronized (this) { ...... //重建目标应用的ResourcesManager mResources = ResourcesManager.getInstance().getResources(null, mResDir, splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), getClassLoader()); } } .. }
只关注上面代码两部分,一部分是将新的ApplicationInfo
更新到LoadedApk
,第二部分就是新ApplicationInfo
中的数据然后重建ResourcesManager,对于RRO来说主要更新就是mOverlayDirs
这个变量,它代表目标应用的Overlay包的路径,详见上一篇:
private void setApplicationInfo(ApplicationInfo aInfo) {
......
mOverlayDirs = aInfo.resourceDirs;
......
}
接着我们需要看的是ResourcesManager
的重建:
public @Nullable Resources getResources(@Nullable IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] overlayDirs, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader) { try { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources"); final ResourcesKey key = new ResourcesKey( resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy compatInfo); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); return getOrCreateResources(activityToken, key, classLoader); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } }
上面的变量很多,我们关心的就两个,resDir
代表目标应用的安装路径,overlayDirs
代表Overlay资源包的安装路径,可以有多个Overlay,ResourcesKey
用来保存这些信息。
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { synchronized (this) { //activityToken为空 if (activityToken != null) { .... } else { // 从缓存中去拿ResourcesImpl,这里面有条件的,并不是说缓存中有就一定会取,还会判断当前的ResourcesImpl //是不是最新的 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); if (resourcesImpl != null) { if (DEBUG) { Slog.d(TAG, "- using existing impl=" + resourcesImpl); } return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); } } // 创建新的ResourcesImpl ResourcesImpl resourcesImpl = createResourcesImpl(key); if (resourcesImpl == null) { return null; } // 缓存下来 mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); final Resources resources; if (activityToken != null) { resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl, key.mCompatInfo); } else { resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); } return resources; } }
这里直接来看ResourcesImpl
的创建流程:
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
....
final AssetManager assets = createAssetManager(key);
....
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
这里我们只关注AssetManager
的创建:
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { final AssetManager.Builder builder = new AssetManager.Builder(); //应用自身的资源包 if (key.mResDir != null) { try { builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null; } } //拆分应用的资源包 if (key.mSplitResDirs != null) { for (final String splitResDir : key.mSplitResDirs) { try { builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { Log.e(TAG, "failed to add split asset path " + splitResDir); return null; } } } //Overlay资源包 if (key.mOverlayDirs != null) { for (final String idmapPath : key.mOverlayDirs) { try { builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/)); } catch (IOException e) { Log.w(TAG, "failed to add overlay path " + idmapPath); // continue. } } } //共享资源包 if (key.mLibDirs != null) { for (final String libDir : key.mLibDirs) { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. try { builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/)); } catch (IOException e) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); // continue. } } } } return builder.build(); }
每个应用在创建createAssetManager
时都会加载四种类型的资源包(如果有的话),第一个是应用自身,第二个是拆分的资源包(Android5.0之后,支持将一个应用拆分为多个包),第三个是Overlay资源包,第四个是共享资源包(其他应用共享给当前应用的资源,这个和Overlay有什么区别呢?主要区别就是共享资源包需要目标应用自己在代码中引入,并且明确表明需要使用,如引用某个共享资源包的资源需要加其包名前缀)。
上面四种资源包的加载流程都一样的,我们以Overlay包为例,loadApkAssets
根据资源包的路径构造ApkAssets
对象,并通过addApkAssets
添加到AssetManager
中:
private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) throws IOException { final ApkKey newKey = new ApkKey(path, sharedLib, overlay); ApkAssets apkAssets = null; //省略缓存相关的代码 ..... // We must load this from disk. if (overlay) { apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), false /*system*/); } else { apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib); } if (mLoadedApkAssets != null) { mLoadedApkAssets.put(newKey, apkAssets); } mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); return apkAssets; }
loadApkAssets
三个参数分别为资源包的安装路径,是否共享,是否为Overlay包,我们这里主要看的是目标应用对Overlay包的加载,所以后面都沿着Overlay流程去分析,overlayPathToIdmapPath
这个方法相当重要,它会将Overlay包的安装路径进行转换:
private static String overlayPathToIdmapPath(String path) {
return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
}
比如我们Overlay包安装路径为"product/overlay/RROResource/RROResource.apk"
,转换之后的路径就为"/data/resource-cache/product@overlay@RROResource@RROResource.apk@idmap"
,这是个什么路径呢?前面
AndroidQ RRO(Runtime Resource Overlay)机制(2)
说过,这个路径就是Overlay包的Idmap文件的生成路径,这个文件中包含了目标应用与Overlay应用相同资源名称间的映射关系,通过命令adb shell idmap2 dump --idmap-path [file]
可以查看:
[target res id] - > [overlay res id] [resource name]
0x01040151 -> 0x01050001 string/config_dozeComponent
0x01040152 -> 0x01050002 string/config_dozeDoubleTapSensorType
0x01040153 -> 0x01050003 string/config_dozeLongPressSensorType
继续看加载:
public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
throws IOException {
return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
}
private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
throws IOException {
Preconditions.checkNotNull(path, "path");
mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
可以发现最终的加载都在native层实现,Java层ApkAssets
会保存native层ApkAssets
的引用,参数system
表示是否为framework-res.apk,显然这里为false。
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system, jboolean force_shared_lib, jboolean overlay) { ScopedUtfChars path(env, java_path); if (path.c_str() == nullptr) { return 0; } .. std::unique_ptr<const ApkAssets> apk_assets; //不同类型的资源包有不同的加载方式 if (overlay) { apk_assets = ApkAssets::LoadOverlay(path.c_str(), system); } else if (force_shared_lib) { apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system); } else { apk_assets = ApkAssets::Load(path.c_str(), system); } ... return reinterpret_cast<jlong>(apk_assets.release()); }
我们关注的是Overlay:
std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, bool system) { std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path); if (idmap_asset == nullptr) { return {}; } const StringPiece idmap_data( reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)), static_cast<size_t>(idmap_asset->getLength())); std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data); if (loaded_idmap == nullptr) { LOG(ERROR) << "failed to load IDMAP " << idmap_path; return {}; } return LoadImpl({} /*fd*/, loaded_idmap->OverlayApkPath(), std::move(idmap_asset), std::move(loaded_idmap), system, false /*load_as_shared_library*/); }
这个方法里面全是对文件流的操作,CreateAssetFromFile
会打开idmap_path,构造Asset
,idmap_data
代表从Idmap文件读取到的数据,这些数据又被构造成LoadedIdmap
对象,loaded_idmap->OverlayApkPath
返回的是Overlay包的安装路径。
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset, std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) { ::ZipArchiveHandle unmanaged_handle; int32_t result; //打开Overlay apk压缩包 if (fd >= 0) { result = ::OpenArchiveFd(fd.release(), path.c_str(), &unmanaged_handle, true /*assume_ownership*/); } else { result = ::OpenArchive(path.c_str(), &unmanaged_handle); } ..... //构造native层ApkAssets std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time)); // kResourcesArsc = "resources.arsc" ::ZipString entry_name(kResourcesArsc.c_str()); ::ZipEntry entry; //找到"resources.arsc" result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry); ..... // 打开"resources.arsc" loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER); ... loaded_apk->idmap_asset_ = std::move(idmap_asset); const StringPiece data( reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)), loaded_apk->resources_asset_->getLength()); //加载"resources.arsc"的数据 loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library); ..... return std::move(loaded_apk); }
LoadImpl
会通过OpenArchiveFd
解压缩Overlay包(压缩算法为zip),接着构造native层ApkAssets
对象,然后通过FindEntry
找到Overlay包的resources.arsc
,最后打开resources.arsc
并读取其中的数据。
resources.arsc
是一个二进制文件,是由Android的aapt打包工具生成的,它里面包含了当前应用的资源数据,其结构非常复杂,要理解resources.arsc
首先必须熟悉其中的数据结构,关于resources.arsc
解析不在本篇讨论范围中。
resources.arsc
的加载由LoadedArsc
来完成,我们来简单看看它是如何实现的:
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data, const LoadedIdmap* loaded_idmap, bool system, bool load_as_shared_library) { std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc()); loaded_arsc->system_ = system; ChunkIterator iter(data.data(), data.size()); while (iter.HasNext()) { const Chunk chunk = iter.Next(); //根据Chunk类型解析 switch (chunk.type()) { case RES_TABLE_TYPE: if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) { return {}; } break; default: LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type()); break; } } .... return std::move(loaded_arsc); }
一个resources.arsc
文件就是类型为RES_TABLE_TYPE
的Chunk,所以核心代码在LoadTable
中:
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library) { .... ChunkIterator iter(chunk.data_ptr(), chunk.data_size()); while (iter.HasNext()) { const Chunk child_chunk = iter.Next(); switch (child_chunk.type()) { case RES_STRING_POOL_TYPE: if (global_string_pool_.getError() == NO_INIT) { status_t err = global_string_pool_.setTo(child_chunk.header<ResStringPool_header>(), child_chunk.size()); } .... break; case RES_TABLE_PACKAGE_TYPE: { .... std::unique_ptr<const LoadedPackage> loaded_package = LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library); if (!loaded_package) { return false; } packages_.push_back(std::move(loaded_package)); } break; default: LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type()); break; } } .... return true; }
resources.arsc
文件中存放数据的Chunk大的类型分为两种,类型为RES_STRING_POOL_TYPE
的全局字符串池,类型为RES_TABLE_PACKAGE_TYPE
的包相关信息,RES_TABLE_PACKAGE_TYPE
里面还有子Chunk,所以RES_TABLE_PACKAGE_TYPE
还需要通过LoadedPackage
进一步解析,LoadedPackage
的Load
函数同样又对Chunk分类型解析,它的内部的Chunk类型比较多,解析更为复杂,由于resources.arsc
解析很复杂且不是本篇重点,感兴趣的可以自己去看看代码。
到此我们就知道了Overlay资源包的加载其核心就是解析apk的resources.arsc
文件,将其中的资源数据加载进内存,方法目标应用获取。不仅是Overlay包的加载是这样,其他资源包的加载也是同样的流程。
对APP层的处理我们主要关注的就是这部分重建AssetManager
的逻辑,接着回到ActivityThread
的handleApplicationInfoChanged
方法中,资源包加载之后为了让Overlay的资源能够生效就会relaunchAllActivities
,这里的重启Activity为了给用户好的体验会保留窗口的重启,重启之后应用加载资源时,Android资源管理框架就会去Overlay包中加载那些已经被Overlay的资源到达换肤目的。
最后附上一张流程图:
资源管理框架的分析推荐博客:资源管理框架
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。