您的位置:首页 > 党团工作党团工作

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

2025-08-29人已围观

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

  addAssetPath 方法:

  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

  ", 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"

  ", 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

  ", 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

  ", 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

  ", 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"

  ", 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

  ", 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

  ", 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

  ", 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

  ", 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 冲突。

  相关热词搜索:

  修复

  区别

  原因

随机图文