内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

系统资源的预加载过程

2017-01-09 14:36 出处:清屏网 人气: 评论(0

在Zygote进程那篇文章中,提到过,在初始化的时候会预加载系统资源,这样,应用进程在fork了之后,不需要通过加载过程,就可以直接使用这些资源,那么,今天就来看下,这个过程是怎么样的。

ZygoteInit#preloadResources

private static void preloadResources() {
    final VMRuntime runtime = VMRuntime.getRuntime();

    try {
        mResources = Resources.getSystem();
        mResources.startPreloading();
        if (PRELOAD_RESOURCES) {
            Log.i(TAG, "Preloading resources...");

            long startTime = SystemClock.uptimeMillis();
            TypedArray ar = mResources.obtainTypedArray(
                    com.android.internal.R.array.preloaded_drawables);
            int N = preloadDrawables(runtime, ar);
            ar.recycle();
            Log.i(TAG, "...preloaded " + N + " resources in "
                    + (SystemClock.uptimeMillis()-startTime) + "ms.");

            startTime = SystemClock.uptimeMillis();
            ar = mResources.obtainTypedArray(
                    com.android.internal.R.array.preloaded_color_state_lists);
            N = preloadColorStateLists(runtime, ar);
            ar.recycle();
            Log.i(TAG, "...preloaded " + N + " resources in "
                    + (SystemClock.uptimeMillis()-startTime) + "ms.");
        }
        mResources.finishPreloading();
    } catch (RuntimeException e) {
        Log.w(TAG, "Failure preloading resources", e);
    }
}

上面的过程分为三步

  • 得到Resources对象
  • 得到TypedArray对象
  • preloadDrawables,preloadColorStateLists 加载资源

现在,按照上面的三部分来学习下。

Resources#getSystem

Resources代码资源,提供了许多方法让我们获取,这里获取的是经过映射的资源,resources.arsc。

public static Resources getSystem() {
    synchronized (sSync) {
        Resources ret = mSystem;
        if (ret == null) {
            ret = new Resources();
            mSystem = ret;
        }

        return ret;
    }
}

在这里初始化了一个Resources对象,并且赋值给mSystem变量,那么我们现在看下Resources的构造方法。

private Resources() {
    mAssets = AssetManager.getSystem();
    // NOTE: Intentionally leaving this uninitialized (all values set
    // to zero), so that anyone who tries to do something that requires
    // metrics will get a very wrong value.
    mConfiguration.setToDefaults();
    mMetrics.setToDefaults();
    updateConfiguration(null, null);
    mAssets.ensureStringBlocks();
}

在初始化方法中,通过AssetManager.getSystem获取一个AssetManager对象,这个是用来访问原始资源的(assets目录)。并且,将Configuration和DisplayMetrics都设置默认,更新配置,初始化StringBlocks。那么,我们来看下AssetManager.getSystem。这个方法中调用ensureSystemAssets,ensureSystemAssets代码如下:

private static void ensureSystemAssets() {
    synchronized (sSync) {
        if (sSystem == null) {
            AssetManager system = new AssetManager(true);
            system.makeStringBlocks(null);
            sSystem = system;
        }
    }
}

可以看到,这里初始化了一个AssetManager,好吧,接着看AssetManager的构造函数。

private AssetManager(boolean isSystem) {
    if (DEBUG_REFS) {
        synchronized (this) {
            mNumRefs = 0;
            incRefsLocked(this.hashCode());
        }
    }
    init(true);
    if (localLOGV) Log.v(TAG, "New asset manager: " + this);
}

这里调用init去初始化,这是个native函数,实现在android_util_AssetManager.cpp中,对应的方法为android_content_AssetManager_init

android_content_AssetManager_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));
}

从代码中来看,分为四步

  • 验证idmaps
  • 生成c++的AssetManager对象
  • 添加默认的assets
  • 设置java层AssetManager的mObject为c++的AssetManager地址。

我们看添加默认的assets那一步。

bool AssetManager::addDefaultAssets()
{
    const char* root = getenv("ANDROID_ROOT");
    LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");

    String8 path(root);
    path.appendPath(kSystemAssets);

    return addAssetPath(path, NULL);
}
  • ANDROID_ROOT为system
  • static const char* kSystemAssets = “framework/framework-res.apk”;

然后调用addAssetPath两个参数的方法,将framework-res.apk加进去。

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) {
        appendPathToResTable(ap);
    }

    return true;
}
  • asset_path结构体存储文件路径、文件类型等,结构体定义如下

    struct asset_path
    {
        asset_path() : path(""), type(kFileTypeRegular), idmap(""), isSystemOverlay(false) {}
        String8 path;
        FileType type;
        String8 idmap;
        bool isSystemOverlay;
    };
    
  • kAppZipName一般为null,static const char* kAppZipName = NULL; //“classes.jar”;

  • 如果已经在mAssetPaths,就反悔
  • 如果路径下没有AndroidManifest.xml文件,返回false
  • 添加到mAssetPaths中
  • appendPathToResTable将资源进行解析添加

AssetManager::appendPathToResTable

bool AssetManager::appendPathToResTable(const asset_path& ap) const {
    // skip those ap's that correspond to system overlays
    if (ap.isSystemOverlay) {
        return true;
    }

    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;
}
  • 通过openIdmapLocked解析资源包中的idmap表
  • ap.type != kFileTypeDirectory,不是目录,就是资源包,如果nextEntryIdx为0,则为framework.apk资源包,解析并保存在sharedRes中,这个是共享的资源。
  • sharedRes == NULL 表示为应用资源包,这解析保存在ass中,
  • nextEntryIdx == 0 && ass != NULL,在zygote进程第一次调用才成立,也就是预加载资源,这个时候把framework.apk的资源索引表创建起来,并添加到mZipSet中
  • 如果是目录,就创建resources.arsc的asset对象,这个不是共享的。
  • 如果存在sharedRes,就保存到mResources中,否则,这是应用的资源,则把应用的资源加入到mResources中。

上面的比较乱,总结一下。简单来说就是这样的,就是对资源包进行解析,并加入到mResources中,如果是framework.apk,就是共享的,否则就是应用的资源包,不共享。

TypedArray

TypedArray ar = mResources.obtainTypedArray(
        com.android.internal.R.array.preloaded_drawables);

mResources.obtainTypedArray的代码如下:

public TypedArray obtainTypedArray(@ArrayRes int id)
        throws NotFoundException {
    int len = mAssets.getArraySize(id);
    if (len < 0) {
        throw new NotFoundException("Array resource ID #0x"
                                    + Integer.toHexString(id));
    }
    
    TypedArray array = TypedArray.obtain(this, len);
    array.mLength = mAssets.retrieveArray(id, array.mData);
    array.mIndices[0] = 0;
    
    return array;
}
  • 先获取id对应的数组长度
  • 然后根据长度生成TypedArray
  • 最后mAssets.retrieveArray赋值,将资源对应的id写入到java层array.mData中。写入部分代码如下

    dest[STYLE_TYPE] = value.dataType;
       dest[STYLE_DATA] = value.data;
       dest[STYLE_ASSET_COOKIE] = block != kXmlBlock ?
           static_cast<jint>(res.getTableCookie(block)) : -1;
       dest[STYLE_RESOURCE_ID] = resid;
       dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
       dest[STYLE_DENSITY] = config.density;
    

getArraySize获取资源id对应的数组长度

这是个native方法,对应的实现在android_util_AssetManager.cpp的android_content_AssetManager_getArraySize方法中,代码如下:

static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz,
                                                       jint id)
{
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }
    const ResTable& res(am->getResources());

    res.lock();
    const ResTable::bag_entry* defStyleEnt = NULL;
    ssize_t bagOff = res.getBagLocked(id, &defStyleEnt);
    res.unlock();

    return static_cast<jint>(bagOff);
}
  • 首先将java对象转换为对应的c++对象,上面有说到过,AssetManager中的mobject字段存的是对应c++中,AssetManager对象的地址,因此,我们很容易做到转换
  • 通过ResTable的getBagLocked方法获取id,对应的数组长度(该方法的实现在ResourceTypes.cpp中),实现太长,看不懂,略过。

preloadDrawables来说明资源的预加载过程

private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
    int N = ar.length();
    for (int i=0; i<N; i++) {
        int id = ar.getResourceId(i, 0);
        if (false) {
            Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
        }
        if (id != 0) {
            if (mResources.getDrawable(id, null) == null) {
                throw new IllegalArgumentException(
                        "Unable to find preloaded drawable resource #0x"
                        + Integer.toHexString(id)
                        + " (" + ar.getString(i) + ")");
            }
        }
    }
    return N;
}
  • 首先通过getResourceId获取资源id
  • getDrawable获取资源

getResourceId 获取资源id

public int getResourceId(int index, int defValue) {
    if (mRecycled) {
        throw new RuntimeException("Cannot make calls to a recycled instance!");
    }

    index *= AssetManager.STYLE_NUM_ENTRIES;
    final int[] data = mData;
    if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
        final int resid = data[index+AssetManager.STYLE_RESOURCE_ID];
        if (resid != 0) {
            return resid;
        }
    }
    return defValue;
}
  • 其中STYLE_NUM_ENTRIES为6,
  • 从赋值id的代码中,知道第四个为id值,因此STYLE_RESOURCE_ID为3

getDrawable

public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
    TypedValue value;
    synchronized (mAccessLock) {
        value = mTmpValue;
        if (value == null) {
            value = new TypedValue();
        } else {
            mTmpValue = null;
        }
        getValue(id, value, true);
    }
    final Drawable res = loadDrawable(value, id, theme);
    synchronized (mAccessLock) {
        if (mTmpValue == null) {
            mTmpValue = value;
        }
    }
    return res;
}
  • getValue查找与id对应的资源,没找到对应的就抛出NotFoundException异常
  • loadDrawable 去加载

在getValue中,会调用AssetManager的getResourceValue方法,这个是native方法,实现在android_uitl_AssetManager.cpp的android_content_AssetManager_loadResourceValue中

android_content_AssetManager_loadResourceValue

static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
                                                           jint ident,
                                                           jshort density,
                                                           jobject outValue,
                                                           jboolean resolve)
{
    if (outValue == NULL) {
         jniThrowNullPointerException(env, "outValue");
         return 0;
    }
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }
    const ResTable& res(am->getResources());

    Res_value value;
    ResTable_config config;
    uint32_t typeSpecFlags;
    ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
    if (kThrowOnBadId) {
        if (block == BAD_INDEX) {
            jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
            return 0;
        }
    }
    uint32_t ref = ident;
    if (resolve) {
        block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
        if (kThrowOnBadId) {
            if (block == BAD_INDEX) {
                jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
                return 0;
            }
        }
    }
    if (block >= 0) {
        return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
    }

    return static_cast<jint>(block);
}
  • 得到ResTable对象
  • resolveReference查找是否有匹配的资源
  • 如果有,就将数据复制给copyValue这个java层对象

具体的代码就不往下追了。

loadDrawable

经过上面的步骤,资源的信息就保存在了TypedValue中,解析来就是通过loadDrawable去加载了。

Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
    if (TRACE_FOR_PRELOAD) {
        // Log only framework resources
        if ((id >>> 24) == 0x1) {
            final String name = getResourceName(id);
            if (name != null) {
                Log.d("PreloadDrawable", name);
            }
        }
    }

    final boolean isColorDrawable;
    final DrawableCache caches;
    final long key;
    if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
            && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
        isColorDrawable = true;
        caches = mColorDrawableCache;
        key = value.data;
    } else {
        isColorDrawable = false;
        caches = mDrawableCache;
        key = (((long) value.assetCookie) << 32) | value.data;
    }

    // First, check whether we have a cached version of this drawable
    // that was inflated against the specified theme.
    if (!mPreloading) {
        final Drawable cachedDrawable = caches.getInstance(key, theme);
        if (cachedDrawable != null) {
            return cachedDrawable;
        }
    }

    // Next, check preloaded drawables. These may contain unresolved theme
    // attributes.
    final ConstantState cs;
    if (isColorDrawable) {
        cs = sPreloadedColorDrawables.get(key);
    } else {
        cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
    }

    Drawable dr;
    if (cs != null) {
        dr = cs.newDrawable(this);
    } else if (isColorDrawable) {
        dr = new ColorDrawable(value.data);
    } else {
        dr = loadDrawableForCookie(value, id, null);
    }

    // Determine if the drawable has unresolved theme attributes. If it
    // does, we'll need to apply a theme and store it in a theme-specific
    // cache.
    final boolean canApplyTheme = dr != null && dr.canApplyTheme();
    if (canApplyTheme && theme != null) {
        dr = dr.mutate();
        dr.applyTheme(theme);
        dr.clearMutated();
    }

    // If we were able to obtain a drawable, store it in the appropriate
    // cache: preload, not themed, null theme, or theme-specific.
    if (dr != null) {
        dr.setChangingConfigurations(value.changingConfigurations);
        cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
    }

    return dr;
}
  • 根据判断是否是ColorDrawable,赋值不同的cache和key
  • 如果不是预加载,就从cache中找,找到返回
  • 从预加载数组中得到key对应的ConstantState
  • 根据cs是否为null以及是不是ColorDrawable,来得到的Drawable
  • 设置主题相关的
  • 进行缓存

而我们预加载资源的主要过程是loadDrawableForCookie。这个方法是从xml或者流里面加载Drawable。

核心代码如下:

if (file.endsWith(".xml")) {
    final XmlResourceParser rp = loadXmlResourceParser(
            file, id, value.assetCookie, "drawable");
    dr = Drawable.createFromXml(this, rp, theme);
    rp.close();
} else {
    final InputStream is = mAssets.openNonAsset(
            value.assetCookie, file, AssetManager.ACCESS_STREAMING);
    dr = Drawable.createFromResourceStream(this, value, is, file, null);
    is.close();
}

关于Drawable的生成过程,这里就不说了。

占坑

  • aapt资源打包过程
  • idmap解析过程openIdmapLocked
  • 其他

分享给小伙伴们:
本文标签: JavaC++安卓开发

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号