在Android系统中,打开一个应用的时候,AssetManager类会去加载应用对应的base.apk,
这个过程如果处理有问题,就会导致内存泄露,现在来研究下AssetManager加载和关闭文件的处理方式。
相关文件
./frameworks/base/libs/androidfw/AssetManager.cpp
vi ./frameworks/base/core/java/android/content/res/AssetManager.java
vi ./frameworks/base/core/jni/android_util_AssetManager.cpp
vi ./frameworks/base/core/java/android/content/res/AssetManager.java
frameworks/base/core/java/android/content/res/AssetManager.java
public AssetManager() {
synchronized (this) {
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
init(false);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
}
}
构造方法里调用native方法init
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
if (isSystem) {
verifySystemIdmaps();
}
AssetManager* am = new AssetManager();
if (am == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "");
return;
}
am->addDefaultAssets();
ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast
}
生成了AssetManager对象
同时,env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast
把java对象的mObject写为新创建的AssetManager* am的地址值,确立一种对应关系,这样下次通过mObject就可以找到对应的CPP对象
查看一个实例
点击一个应用的时候,会加载其对应的base.apk,其调用堆栈为
05-02 04:21:12.863: D/AssetManager(18460): android.content.res.AssetManager.addAssetPath(AssetManager.java:653)
05-02 04:21:12.863: D/AssetManager(18460): android.app.ResourcesManager.getTopLevelResources(ResourcesManager.java:221)
05-02 04:21:12.863: D/AssetManager(18460): android.app.ActivityThread.getTopLevelResources(ActivityThread.java:1854)
05-02 04:21:12.863: D/AssetManager(18460): android.app.LoadedApk.getResources(LoadedApk.java:558)
05-02 04:21:12.863: D/AssetManager(18460): android.app.ContextImpl.
05-02 04:21:12.863: D/AssetManager(18460): android.app.ContextImpl.createPackageContextAsUser(ContextImpl.java:1733)
05-02 04:21:12.863: D/AssetManager(18460): android.app.ContextImpl.createPackageContextAsUser(ContextImpl.java:1718)
05-02 04:21:12.863: D/AssetManager(18460): com.android.server.AttributeCache.get(AttributeCache.java:114)
05-02 04:21:12.863: D/AssetManager(18460): com.android.server.am.ActivityRecord.
05-02 04:21:12.863: D/AssetManager(18460): com.android.server.am.ActivityStackSupervisor.startActivityLocked(ActivityStackSupervisor.java:1763)
05-02 04:21:12.863: D/AssetManager(18460): com.android.server.am.ActivityStackSupervisor.startActivityMayWait(ActivityStackSupervisor.java:1153)
05-02 04:21:12.863: D/AssetManager(18460): com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:4271)
05-02 04:21:12.863: D/AssetManager(18460): com.android.server.am.ActivityManagerService.startActivity(ActivityManagerService.java:4258)
05-02 04:21:12.863: D/AssetManager(18460): android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:168)
05-02 04:21:12.863: D/AssetManager(18460): com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2703)
AssetManager.addAssetPath方法在这里被调用
./frameworks/base/core/java/android/app/ResourcesManager.java
Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId, String packageName,
Configuration overrideConfiguration, CompatibilityInfo compatInfo, Context context) {
……
AssetManager assets = new AssetManager();
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
if (resDir != null) {
if (assets.addAssetPath(resDir) == 0) {
return null;
}
}
……
之后,base.apk被打开了,我们可以在/proc里查看到。
addAssetPath最终是调用了native层的方法
java里的定义
/**
* 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) {
synchronized (this) {
mstrPath = path;
int res = addAssetPathNative(path);
makeStringBlocks(mStringBlocks);
return res;
}
}
private native final int addAssetPathNative(String path);
jni定义
/*
* JNI registration.
*/
static JNINativeMethod gAssetManagerMethods[] = {
/* name, signature, funcPtr */
// Basic asset stuff.
{ "openAsset", "(Ljava/lang/String;I)J",
(void*) android_content_AssetManager_openAsset },
{ "openAssetFd", "(Ljava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
(void*) android_content_AssetManager_openAssetFd },
{ "openNonAssetNative", "(ILjava/lang/String;I)J",
(void*) android_content_AssetManager_openNonAssetNative },
{ "openNonAssetFdNative", "(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
(void*) android_content_AssetManager_openNonAssetFdNative },
{ "list", "(Ljava/lang/String;)[Ljava/lang/String;",
(void*) android_content_AssetManager_list },
{ "destroyAsset", "(J)V",
(void*) android_content_AssetManager_destroyAsset },
{ "readAssetChar", "(J)I",
(void*) android_content_AssetManager_readAssetChar },
{ "readAsset", "(J[BII)I",
(void*) android_content_AssetManager_readAsset },
{ "seekAsset", "(JJI)J",
(void*) android_content_AssetManager_seekAsset },
{ "getAssetLength", "(J)J",
(void*) android_content_AssetManager_getAssetLength },
{ "getAssetRemainingLength", "(J)J",
(void*) android_content_AssetManager_getAssetRemainingLength },
{ "addAssetPathNative", "(Ljava/lang/String;)I",
(void*) android_content_AssetManager_addAssetPath },
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
jstring path)
{
ScopedUtfChars path8(env, path);
if (path8.c_str() == NULL) {
return 0;
}
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
int32_t cookie;
bool res = am->addAssetPath(String8(path8.c_str()), &cookie);
return (res) ? static_cast
}
注意,这里AssetManager* am = assetManagerForJavaObject(env, clazz);获取到java对象对应的native层AssetManager对象的方法就是根据上面介绍到的由java对象的mObject值进行存储处理的
查看assetManagerForJavaObject的处理,正是如此
// this guy is exported to other jni routines
AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj)
{
jlong amHandle = env->GetLongField(obj, gAssetManagerOffsets.mObject);
AssetManager* am = reinterpret_cast
if (am != NULL) {
return am;
}
jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
return NULL;
}
这样就调到了am->addAssetPath
其打开文件的流程如下所示
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
……
// Check that the path has an AndroidManifest.xml
Asset* manifestAsset = const_cast
kAndroidManifest, Asset::ACCESS_BUFFER, ap);
……
再看
openNonAssetInPathLocked方法
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
const asset_path& ap, bool usePrefix) // Modified for ThemeManager
{
Asset* pAsset = NULL;
……
/* look inside the zip file */
883 } else {
884 String8path(fileName);
885
886 /* check the appropriate Zip file */
887 ZipFileRO* pZip = getZipFileLocked(ap);
888 if (pZip != NULL) {
889
……
再来看getZipFileLocked方法
ZipFileRO* AssetManager::getZipFileLocked(const String8& path)
{
ALOGV("getZipFileLocked() in %p\n", this);
return mZipSet.getZip(path);
}
这里的mZipSet是内部类ZipSet类型的成员变量
ZipSet mZipSet;
查看其getZip方法
/*
* Retrieve the appropriate Zip file from the set.
*/
ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
{
ALOGW("=====CC AssetManager::ZipSet::getZip called, path=%s",
path.string());
int idx = getIndex(path);
sp
if (zip == NULL) {
ALOGW("=====CC if (zip == NULL)");
zip = SharedZip::get(path);
mZipFile.editItemAt(idx) = zip;
}
return zip->getZip();
}
2024/*
2025 * Retrieve the appropriate Zip file from the set.
2026 */
2027ZipFileRO* AssetManager::ZipSet::getZip(constString8& path)
2028{
2029 intidx = getIndex(path);
2030 sp<SharedZip> zip = mZipFile[idx];
2031 if (zip == NULL) {
2032 zip = SharedZip::get(path);
2033 mZipFile.editItemAt(idx) = zip;
2034 }
2035 returnzip->getZip();
2036}
从数组中进行查找,第一次数组里还没有加载这个path对应的数据,肯定是没有的,所以走到了zip = SharedZip::get(path);
1891sp<AssetManager::SharedZip> AssetManager::SharedZip::get(constString8& path,
1892 boolcreateIfNotPresent)
1893{
1894 AutoMutex_l(gLock);
1895 time_tmodWhen = getFileModDate(path);
1896 sp<SharedZip> zip = gOpen.valueFor(path).promote();
1897 if (zip != NULL && zip->mModWhen == modWhen) {
1898 returnzip;
1899 }
1900 if (zip == NULL && !createIfNotPresent) {
1901 returnNULL;
1902 }
1903 zip = newSharedZip(path, modWhen);
1904 gOpen.add(path, zip);
1905 returnzip;
1906
1907}
需要注意的是,gOpen是一个静态变量,这样的话,就实现了资源共享,同一个进程里的所有AssetManager对象,对同一个path,访问得到的都是同一个数据,这样就不用重复打开,这是一种共享设计
static DefaultKeyedVector
另外一个需要重点关注的是,zip 是sp
/*
2006 * Destructor. Close any open archives.
2007 */
2008AssetManager::ZipSet::~ZipSet(void)
2009{
2010 size_t N = mZipFile.size();
2011 for (size_t i = 0; i < N; i++)
2012 closeZip(i);
2013}
2014
2015/*
2016 * Close a Zip file and reset the entry.
2017 */
2018voidAssetManager::ZipSet::closeZip(intidx)
2019{
2020 mZipFile.editItemAt(idx) = NULL;
2021}
当SharedZip的引用计数为0时,触发其析构
1975AssetManager::SharedZip::~SharedZip()
1976{
1977 if (kIsDebug) {
1978 ALOGI("Destroying SharedZip %p %s\n", this, (constchar*)mPath);
1979 }
1980 if (mResourceTable != NULL) {
1981 deletemResourceTable;
1982 }
1983 if (mResourceTableAsset != NULL) {
1984 deletemResourceTableAsset;
1985 }
1986 if (mZipFile != NULL) {
1987 deletemZipFile;
1988 ALOGV("Closed '%s'\n", mPath.string());
1989 }
1990}
之后就关闭了打开的文件
~SharedZip()是由~AssetManager(void)触发的,AssetManager析构的时候,会析构其 ZipSet类型的成员变量 mZipSet;
164AssetManager::~AssetManager(void)
165{
166 intcount = android_atomic_dec(&gCount);
167 if (kIsDebug) {
168 ALOGI("Destroying AssetManager in %p #%d\n", this, count);
169 }
170
171 deletemConfig;
172 deletemResources;
173
174 // don't have a String class yet, so make sure we clean up
175 delete[] mLocale;
176 delete[] mVendor;
177}
而AssetManager析构是由JNI里调起的
static void android_content_AssetManager_destroyAsset(JNIEnv* env, jobject clazz,
jlong assetHandle)
{
Asset* a = reinterpret_cast
//printf("Destroying Asset Stream: %p\n", a);
if (a == NULL) {
jniThrowNullPointerException(env, "asset");
return;
}
delete a;
}
这里的调用,自然是java层发起的
private native final void destroy();
private final void incRefsLocked(long id) {
if (DEBUG_REFS) {
if (mRefStacks == null) {
mRefStacks = new HashMap
}
RuntimeException ex = new RuntimeException();
ex.fillInStackTrace();
mRefStacks.put(id, ex);
}
mNumRefs++;
}
private final void decRefsLocked(long id) {
if (DEBUG_REFS && mRefStacks != null) {
mRefStacks.remove(id);
}
mNumRefs--;
//System.out.println("Dec streams: mNumRefs=" + mNumRefs
// + " mReleased=" + mReleased);
if (mNumRefs == 0) {
destroy();
}
}
protected void finalize() throws Throwable {
try {
if (DEBUG_REFS && mNumRefs != 0) {
Log.w(TAG, "AssetManager " + this
+ " finalized with non-zero refs: " + mNumRefs);
if (mRefStacks != null) {
for (RuntimeException e : mRefStacks.values()) {
Log.w(TAG, "Reference from here", e);
}
}
}
Slog.d(TAG, "=====CCAM going to call destroy()");
Slog.d(TAG, Slog.getStackMsg(new Exception()));
destroy();
} finally {
super.finalize();
}
}
倒过来看,就是AssetManager的释放过程,
如果java层的AssetManager出现了内存泄漏,就会导致native层的AssetManager出现泄漏,进而导致base.apk不会close,从而引发其他问题,比如卸载sd卡里的应用出现的重启问题
相关log示例
05-02 03:10:58.137: D/AssetManager(1099): =====assetM.java addAssetPath, path=/mnt/asec/com.UCMobile-2/base.apk
05-02 03:10:58.138: D/assetJNI(1099): =====jni android_content_AssetManager_addAssetPath, path=/mnt/asec/com.UCMobile-2/base.apk
05-02 03:10:58.138: W/assetCpp(1099): =====CC addAssetPath called, Asset path /mnt/asec/com.UCMobile-2/base.apk
05-02 03:10:58.138: W/assetCpp(1099): In 0x7f63cea440 Asset zip path: /mnt/asec/com.UCMobile-2/base.apk
05-02 03:10:58.138: W/assetCpp(1099): getZipFileLocked() in 0x7f63cea440, path=/mnt/asec/com.UCMobile-2/base.apk
05-02 03:10:58.138: W/assetCpp(1099): =====CC AssetManager::ZipSet::getZip called, path=/mnt/asec/com.UCMobile-2/base.apk
05-02 03:10:58.138: W/assetCpp(1099): =====CC AssetManager::SharedZip::get, path=/mnt/asec/com.UCMobile-2/base.apk
05-02 03:10:58.139: I/assetCpp(1099): Creating SharedZip 0x7f68ebbb20 /mnt/asec/com.UCMobile-2/base.apk
05-02 03:10:58.139: W/assetCpp(1099): +++ opening zip '/mnt/asec/com.UCMobile-2/base.apk'