AssetManager打开及关闭文件研究

来源:互联网 发布:海马电动车数据 编辑:程序博客网 时间:2024/05/18 17:01

AssetManager总结

在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<jlong>(am));

}

 

生成了AssetManager对象

同时,env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));

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.<init>(ContextImpl.java:1884)

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.<init>(ActivityRecord.java:564)

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<jint>(cookie) : 0;

}

 

注意,这里AssetManager* am = assetManagerForJavaObject(env, clazz);获取到java对象对应的nativeAssetManager对象的方法就是根据上面介绍到的由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<AssetManager*>(amHandle);

    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<AssetManager*>(this)->openNonAssetInPathLocked(

            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<SharedZip> zip = mZipFile[idx];

    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<String8, wp<SharedZip> > gOpen;

 

另外一个需要重点关注的是,zip sp<SharedZip> 类型的强指针,指向SharedZip对象,使其引用计数增加了,并存放到了ZipSet 类的mZipFile数组中,当ZipSet析构的时候,再释放这个引用,减小引用计数

/*
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<Asset*>(assetHandle);

 

    //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<Long, RuntimeException>();

            }

            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'


0 0
原创粉丝点击