当前位置:党团工作 > 【安卓笔记】热修复_addAssetPath不同版本区别原因x

【安卓笔记】热修复_addAssetPath不同版本区别原因x

时间:2025-08-01 02:52:23 浏览次数:

 【安卓笔记】热修复_addAssetPath 不同版本区别原因 在做热修复功能时 Java 层通过反射调用 addAssetPath 在 Android5.0 及以上系统没有问题,在 Android 4.x 版本找不到资源。

 addAssetPath 方法: /**

  * Add an additional set of assets to the asset manager.

 This can be

  * either a directory or ZIP file.

 Not for use by applications.

 Returns

  * the cookie of the added asset, or 0 on failure.

  * {@hide}

  */

 public final int addAssetPath(String path) {

 int res = addAssetPathNative(path);

 return res;

 }

 private native final int addAssetPathNative(String path);

 addAssetPath 具体的实现方法是 native 层的。

 AssetManager.cpp 源码 4.4 及以前的版本调用 addAssetPath 方法时,只是把补丁包的路径添加到mAssetPath 中,不会去重新解析,真正解析的代码是在 app 第一次执行AssetManager.getResTable()`方法的时候。

 一旦解析完一次后,mResource 对象就不为 nil,以后就会直接 return 掉,不会重新解析。

 bool AssetManager::addAssetPath(const String8& path, void** cookie)

 {

 AutoMutex _l(mLock);

 asset_path ap;

 String8 realPath(path);

 if (kAppZipName) {

 realPath.appendPath(kAppZipName);

 }

 ap.type = ::getFileType(realPath.string());

 if (ap.type == kFileTypeRegular) {

 ap.path = realPath;

 } else {

 ap.path = path;

 ap.type = ::getFileType(path.string());

 if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {

 ALOGW("Asset path %s is neither a directory nor file (type=%

 d).",

  path.string(), (int)ap.type);

 return false;

 }

 }

 // Skip if we have it already.

 for (size_t i=0; i<mAssetPaths.size(); i++) {

 if (mAssetPaths[i].path == ap.path) {

 if (cookie) {

 *cookie = (void*)(i+1);

 }

 return true;

 }

 }

 ALOGV("In %p Asset %s path: %s", this,

  ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());

 mAssetPaths.add(ap);

 // new paths are always added at the end

 if (cookie) {

 *cookie = (void*)mAssetPaths.size();

 }

 // add overlay packages for /system/framework; apps are handled by the

 // (Java) package manager

 if (strncmp(path.string(), "/system/framework/", 18) == 0) {

 // When there is an environment variable for /vendor, this

 // should be changed to something similar to how ANDROID_ROOT

 // and ANDROID_DATA are used in this file.

 String8 overlayPath("/vendor/overlay/framework/");

 overlayPath.append(path.getPathLeaf());

 if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {

 asset_path oap;

 oap.path = overlayPath;

 oap.type = ::getFileType(overlayPath.string());

 bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay

 if (addOverlay) {

 oap.idmap = idmapPathForPackagePath(overlayPath);

 if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {

 addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);

 }

 }

 if (addOverlay) {

 mAssetPaths.add(oap);

 } else {

 ALOGW("failed to add overlay package %s\n", overlayPath.string());

 }

 }

 }

 return true;

 }

 const ResTable* AssetManager::getResTable(bool required) const

 {

 // 执行该方法第一次之后,就都会 return

 ResTable* rt = mResources;

 if (rt) {

 return rt;

 }

 // Iterate through all asset packages, collecting resources from each.

 AutoMutex _l(mLock);

 if (mResources != NULL) {

 return mResources;

 }

 if (required) {

 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");

 }

 if (mCacheMode != CACHE_OFF && !mCacheValid)

 const_cast<AssetManager*>(this)->loadFileNameCacheLocked();

 const size_t N = mAssetPaths.size();

 // 真正解析 package 的地方

 for (size_t i=0; i<N; i++) {

 Asset* ass = NULL;

 ResTable* sharedRes = NULL;

 bool shared = true;

 const asset_path& ap = mAssetPaths.itemAt(i);

 MY_TRACE_BEGIN(ap.path.string());

 Asset* idmap = openIdmapLocked(ap);

 ALOGV("Looking for resource asset in "%s"\n", ap.path.string());

 if (ap.type != kFileTypeDirectory) {

 if (i == 0) {

 // The first item is typically the framework resources,

 // which we want to avoid parsing every time.

 sharedRes = const_cast<AssetManager*>(this)->

 mZipSet.getZipResourceTable(ap.path);

 }

 if (sharedRes == NULL) {

 ass = const_cast<AssetManager*>(this)->

 mZipSet.getZipResourceTableAsset(ap.path);

 if (ass == NULL) {

 ALOGV("loading resource table %s\n", ap.path.string());

 ass = const_cast<AssetManager*>(this)->

 openNonAssetInPathLocked("resources.arsc",

  Asset::ACCESS_BUFFER,

  ap);

 if (ass != NULL && ass != kExcludedAsset) {

 ass = const_cast<AssetManager*>(this)->

 mZipSet.setZipResourceTableAsset(ap.path, ass);

 }

 }

  if (i == 0 && ass != NULL) {

 // If this is the first resource table in the asset

 // manager, then we are going to cache it so that we

 // can quickly copy it out for others.

 ALOGV("Creating shared resources for %s", ap.path.string());

 sharedRes = new ResTable();

 sharedRes->add(ass, (void*)(i+1), false, idmap);

 sharedRes = const_cast<AssetManager*>(this)->

 mZipSet.setZipResourceTable(ap.path, sharedRes);

 }

 }

 } else {

 ALOGV("loading resource table %s\n", ap.path.string());

 Asset* ass = const_cast<AssetManager*>(this)->

 openNonAssetInPathLocked("resources.arsc",

  Asset::ACCESS_BUFFER,

  ap);

 shared = false;

 }

 if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {

 if (rt == NULL) {

 mResources = rt = new ResTable();

 updateResourceParamsLocked();

 }

 ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);

 if (sharedRes != NULL) {

 ALOGV("Copying existing resources for %s", ap.path.string());

 rt->add(sharedRes);

 } else {

 ALOGV("Parsing resources for %s", ap.path.string());

 rt->add(ass, (void*)(i+1), !shared, idmap);

 }

 if (!shared) {

 delete ass;

 }

 }

 if (idmap != NULL) {

 delete idmap;

 }

 MY_TRACE_END();

 }

 if (required && !rt) ALOGW("Unable to find resources file resources.arsc");

 if (!rt) {

 mResources = rt = new ResTable();

 }

 return rt;

 }

 const ResTable& AssetManager::getResources(bool required) const

 {

 const ResTable* rt = getResTable(required);

 return *rt;

 }

 而当我们执行加载补丁的代码的时候,getResTable 已经执行过多次了,Android Framework 里面的代码会多次调用该方法。即使是使用 addAssetPath,也只是添加到了 mAssetPath,并不会发生解析,所以补丁包里面的资源就是完全不生效的。

 而在 android 5.0 及以上的代码中: Android 5.0 AssetManager.cpp 源码 bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)

 {

 AutoMutex _l(mLock);

 asset_path ap;

 String8 realPath(path);

 if (kAppZipName) {

 realPath.appendPath(kAppZipName);

 }

 ap.type = ::getFileType(realPath.string());

 if (ap.type == kFileTypeRegular) {

 ap.path = realPath;

 } else {

 ap.path = path;

 ap.type = ::getFileType(path.string());

 if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {

 ALOGW("Asset path %s is neither a directory nor file (type=%d).",

  path.string(), (int)ap.type);

 return false;

 }

 }

 // Skip if we have it already.

 for (size_t i=0; i<mAssetPaths.size(); i++) {

 if (mAssetPaths[i].path == ap.path) {

 if (cookie) {

 *cookie = static_cast<int32_t>(i+1);

 }

 return true;

 }

 }

 ALOGV("In %p Asset %s path: %s", this,

  ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());

 // Check that the path has an AndroidManifest.xml

 Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(

 kAndroidManifest, Asset::ACCESS_BUFFER, ap);

 if (manifestAsset == NULL) {

 // This asset path does not contain any resources.

 delete manifestAsset;

 return false;

 }

 delete manifestAsset;

 mAssetPaths.add(ap);

 // new paths are always added at the end

 if (cookie) {

 *cookie = static_cast<int32_t>(mAssetPaths.size());

 }

 #ifdef HAVE_ANDROID_OS

 // Load overlays, if any

 asset_path oap;

 for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {

 mAssetPaths.add(oap);

 }

 #endif

 if (mResources != NULL) {

 // 重新调用该方法去解析 package

 appendPathToResTable(ap);

 }

 return true;

 }

 bool AssetManager::appendPathToResTable(const asset_path& ap) const {

 Asset* ass = NULL;

 ResTable* sharedRes = NULL;

 bool shared = true;

 bool onlyEmptyResources = true;

 MY_TRACE_BEGIN(ap.path.string());

 Asset* idmap = openIdmapLocked(ap);

 size_t nextEntryIdx = mResources->getTableCount();

 ALOGV("Looking for resource asset in "%s"\n", ap.path.string());

 if (ap.type != kFileTypeDirectory) {

 if (nextEntryIdx == 0) {

 // The first item is typically the framework resources,

 // which we want to avoid parsing every time.

 sharedRes = const_cast<AssetManager*>(this)->

 mZipSet.getZipResourceTable(ap.path);

 if (sharedRes != NULL) {

 // skip ahead the number of system overlay packages preloaded

 nextEntryIdx = sharedRes->getTableCount();

 }

 }

 if (sharedRes == NULL) {

 ass = const_cast<AssetManager*>(this)->

 mZipSet.getZipResourceTableAsset(ap.path);

 if (ass == NULL) {

 ALOGV("loading resource table %s\n", ap.path.string());

 ass = const_cast<AssetManager*>(this)->

 openNonAssetInPathLocked("resources.arsc",

  Asset::ACCESS_BUFFER,

  ap);

 if (ass != NULL && ass != kExcludedAsset) {

 ass = const_cast<AssetManager*>(this)->

 mZipSet.setZipResourceTableAsset(ap.path, ass);

 }

 }

  if (nextEntryIdx == 0 && ass != NULL) {

 // If this is the first resource table in the asset

 // manager, then we are going to cache it so that we

 // can quickly copy it out for others.

 ALOGV("Creating shared resources for %s", ap.path.string());

 sharedRes = new ResTable();

 sharedRes->add(ass, idmap, nextEntryIdx + 1, false);

 #ifdef HAVE_ANDROID_OS

 const char* data = getenv("ANDROID_DATA");

 LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");

 String8 overlaysListPath(data);

 overlaysListPath.appendPath(kResourceCache);

 overlaysListPath.appendPath("overlays.list");

 addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);

 #endif

 sharedRes = const_cast<AssetManager*>(this)->

 mZipSet.setZipResourceTable(ap.path, sharedRes);

 }

 }

 } else {

 ALOGV("loading resource table %s\n", ap.path.string());

 ass = const_cast<AssetManager*>(this)->

 openNonAssetInPathLocked("resources.arsc",

  Asset::ACCESS_BUFFER,

  ap);

 shared = false;

 }

 if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {

 ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);

 if (sharedRes != NULL) {

 ALOGV("Copying existing resources for %s", ap.path.string());

 mResources->add(sharedRes);

 } else {

 ALOGV("Parsing resources for %s", ap.path.string());

 mResources->add(ass, idmap, nextEntryIdx + 1, !shared);

 }

 onlyEmptyResources = false;

 if (!shared) {

 delete ass;

 }

 } else {

 ALOGV("Installing empty resources in to table %p\n", mResources);

 mResources->addEmpty(nextEntryIdx + 1);

 }

 if (idmap != NULL) {

 delete idmap;

 }

 MY_TRACE_END();

 return onlyEmptyResources;

 }

 const ResTable* AssetManager::getResTable(bool required) const

 {

 ResTable* rt = mResources;

 if (rt) {

 return rt;

 }

 // Iterate through all asset packages, collecting resources from each.

 AutoMutex _l(mLock);

 if (mResources != NULL) {

 return mResources;

 }

 if (required) {

 LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");

 }

 if (mCacheMode != CACHE_OFF && !mCacheValid) {

 const_cast<AssetManager*>(this)->loadFileNameCacheLocked();

 }

 mResources = new ResTable();

 updateResourceParamsLocked();

 bool onlyEmptyResources = true;

 const size_t N = mAssetPaths.size();

 // 也是调用 appendPathToResTable 去解析

 for (size_t i=0; i<N; i++) {

 bool empty = appendPathToResTable(mAssetPaths.itemAt(i));

 onlyEmptyResources = onlyEmptyResources && empty;

 }

 if (required && onlyEmptyResources) {

 ALOGW("Unable to find resources file resources.arsc");

 delete mResources;

 mResources = NULL;

 }

 return mResources;

 }

 也就是说在 Android5.0 及以上的系统中 native 层的 addAssetPath 方法会再调用appendPathToResTable 去解析,所以在 5.0 及以上系统通过反射调用addAssetPath 方法就不会有问题。

 解决方案就是根据 Google Instant Run 实现的原理:创建一个新的 AssetManager,然后加入完整的新资源包,替换掉原有的 AssetManager。

 具体可参考:深度理解 Android InstantRun 原理以及源码分析中的 2.2 monkeyPatchExistingResources 部分 通过 aapt 工具编译 apk 包,package id 是 0x7f,系统的资源包(framework-res.jar),package id 为 0x01,如果 addAssetPath 补丁包中的 package id 也是 0x7f,就会使得同一个 pakcage id 的包被加载两次,在 Android L 后,会把后来的包添加到之前的包的同一个 PackageGoup 下,但是在 get 资源时,会从前往后便利,也就是说先得到原有安装包里的资源,补丁中的资源就永远无法生效。可以构建一个package id 为 0x66 的资源包。这样就不会与已经加载的 0x7f 冲突。

相关热词搜索: 修复 区别 原因