在做热修复功能时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冲突。