美文网首页Android知识Android开发Android技术知识
【Android源码】资源加载AssetManager源码分析

【Android源码】资源加载AssetManager源码分析

作者: 指间沙似流年 | 来源:发表于2017-06-02 09:57 被阅读1061次

    通常情况下,当我们需要使用资源的时候,都是通过api直接调用:

    getResources().getDrawable(R.mipmap.ic_launcher);
    

    通过getResources()的众多方法可以获取到整个apk包里面的资源,那么我们是如何获取到资源的?这些资源又是如何被加载到内存中的?
    今天我们来一起分析一下app是如何加载资源文件的。

    资源加载过程

    首先我们通过getResources()来看一下Resource是在哪里被创建出来的。

    // AppCompatActivity.java
    @Override
    public Resources getResources() {
       if (mResources == null && VectorEnabledTintResources.shouldBeUsed()) {
           mResources = new VectorEnabledTintResources(this, super.getResources());
       }
       return mResources == null ? super.getResources() : mResources;
    }
    
    // Context.java
    public abstract Resources getResources();
    

    一路点击过去发现,最终又到了Context这个上下文中,而Context是一个抽象类,所以我们需要找到其实现类ContextImpl.java

    // ContextImpl.java
    @Override
    public Resources getResources() {
       return mResources;
    }
    
    private ContextImpl(ContextImpl container, ActivityThread mainThread,
           LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
           Display display, Configuration overrideConfiguration, int createDisplayWithId) {
                
        Resources resources = packageInfo.getResources(mainThread);
        mResources = resources; 
    }
    
    // LoadedApk.java
    public Resources getResources(ActivityThread mainThread) {
       if (mResources == null) {
           mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                   mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
       }
       return mResources;
    }
    
    // ResourcesManager.java
    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
           String[] libDirs, int displayId, LoadedApk pkgInfo) {
       return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
               displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
    }
    

    发现在ContextImpl.java中Resources是被ResourcesManager的getTopLevelResources方法返回的。ResourcesManager是资源的管理类,我们着重看一下它。

    public @NonNull Resources getResources(@Nullable IBinder activityToken,
           @Nullable String resDir,
           @Nullable String[] splitResDirs,
           @Nullable String[] overlayDirs,
           @Nullable String[] libDirs,
           int displayId,
           @Nullable Configuration overrideConfig,
           @NonNull CompatibilityInfo compatInfo,
           @Nullable ClassLoader classLoader) {
       try {
           Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
           // 生成资源key对象
           final ResourcesKey key = new ResourcesKey(
                   resDir,
                   splitResDirs,
                   overlayDirs,
                   libDirs,
                   displayId,
                   overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                   compatInfo);
           classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
           return getOrCreateResources(activityToken, key, classLoader);
       } finally {
           Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
       }
    }
    
    private @NonNull Resources getOrCreateResources(@Nullable IBinder activityToken,
           @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
       synchronized (this) {
    
                // 从缓存中获取Resources
           if (activityToken != null) {
               final ActivityResources activityResources =
                       getOrCreateActivityResourcesStructLocked(activityToken);
                    // 通过资源key对象获取缓存Resources
               ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
               if (resourcesImpl != null) {
                   if (DEBUG) {
                       Slog.d(TAG, "- using existing impl=" + resourcesImpl);
                   }
                   return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                           resourcesImpl);
               }
           } else {
                // 通过资源key对象获取缓存Resources
               ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
               if (resourcesImpl != null) {
                   if (DEBUG) {
                       Slog.d(TAG, "- using existing impl=" + resourcesImpl);
                   }
                   return getOrCreateResourcesLocked(classLoader, resourcesImpl);
               }
           }
       }
    
       // 没有缓存创建ResourcesImpl
       ResourcesImpl resourcesImpl = createResourcesImpl(key);
    
       synchronized (this) {
            // 通过资源key对象获取缓存Resources
           ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
           if (existingResourcesImpl != null) {
               resourcesImpl.getAssets().close();
               resourcesImpl = existingResourcesImpl;
           } else {
               // 如果缓存中没有,将创建好的ResourceImpl加入到缓存中
               mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
           }
    
           final Resources resources;
           if (activityToken != null) {
                // 通过资源key对象获取缓存Resources
               resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                       resourcesImpl);
           } else {
                // 通过资源key对象获取缓存Resources
               resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
           }
           return resources;
       }
    }
    

    生成资源Resources对象的步骤:

    1. 首先生成资源Key对象,用于将资源对象添加到缓存map中或者从缓存map中查找资源对象
    2. 通过activityToken判断是否关联Activity,并从缓存中通过key获取缓存的ResourcesImpl对象,如果有直接返回
    3. 如果没有缓存的ResourcesImpl对象,则直接创建新的缓存ResourcesImpl对象,通过createResourcesImpl(key)来创建。
    4. 创建完之后在将其加入缓存Map中,并最终返回Resources.setImpl(impl)方法,创建出Resources对象并返回。

    所以我们需要着重看下createResourcesImpl方法,这个方法才是创建ResourcesImpl的核心。

    private @NonNull ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
       final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
       daj.setCompatibilityInfo(key.mCompatInfo);
    
       final AssetManager assets = createAssetManager(key);
       final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
       final Configuration config = generateConfig(key, dm);
       final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
       if (DEBUG) {
           Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
       }
       return impl;
    }
    

    可以发现这儿方法创建了AssetManager资产管理类:

    @VisibleForTesting
    protected @NonNull AssetManager createAssetManager(@NonNull final ResourcesKey key) {
       AssetManager assets = new AssetManager();
       if (key.mResDir != null) {
           if (assets.addAssetPath(key.mResDir) == 0) {
               throw new Resources.NotFoundException("failed to add asset path " + key.mResDir);
           }
       }
       return assets;
    }
    
    // LoadedApk.java
    private void setApplicationInfo(ApplicationInfo aInfo) {
       final int myUid = Process.myUid();
       aInfo = adjustNativeLibraryPaths(aInfo);
       mApplicationInfo = aInfo;
       mAppDir = aInfo.sourceDir;
       mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
    }
    

    AssetManager是用来加载制定路径下的资源文件的,所以我们想要获取的资源文件都是在这个类中找到的。而key.mResDir就是app的资源路径。

    所以AssetManager才是整个资源加载的关键所在。

    public AssetManager() {
       synchronized (this) {
           if (DEBUG_REFS) {
               mNumRefs = 0;
               incRefsLocked(this.hashCode());
           }
           init(false);
           if (localLOGV) Log.v(TAG, "New asset manager: " + this);
           ensureSystemAssets();
       }
    }
    
    private native final void init(boolean isSystem);
    
    private native final int loadResourceValue(int ident, short density, TypedValue outValue,
                boolean resolve);
    

    在创建AssetManager的时候,它首先会去调用native层中的init方法

    static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
    {
        if (isSystem) {
            verifySystemIdmaps();
        }
        //  AssetManager.cpp
        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));
    }
    
    bool AssetManager::addDefaultAssets()
    {
        const char* root = getenv("ANDROID_ROOT");
        LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
    
        String8 path(root);
        // kSystemAssets -> framework/framework-res.apk  
        // 加载系统的资源如颜色、图片、文字
        path.appendPath(kSystemAssets);
    
        return addAssetPath(path, NULL);
    }
    
    bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
    {
        asset_path ap;
    
        // 判断是否已经加载过了
        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;
            }
        }
    
        // 检查路径是否有一个androidmanifest.xml
        Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
                kAndroidManifest, Asset::ACCESS_BUFFER, ap);
        if (manifestAsset == NULL) {
            // 如果不包含任何资源
            delete manifestAsset;
            return false;
        }
        delete manifestAsset;
        // 添加 
        mAssetPaths.add(ap);
        if (mResources != NULL) {
            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) {
                // mAssetPaths中存储的第一个资源包路径是系统资源的路径,
                // 即framework-res.apk的路径,它在zygote启动时已经加载了
                // 可以通过mZipSet.getZipResourceTable获得其ResTable对象
                sharedRes = const_cast<AssetManager*>(this)->
                    mZipSet.getZipResourceTable(ap.path);
                // 对于APP来说,肯定不为NULL
                if (sharedRes != NULL) {
                    // 得到系统资源包路径中resources.arsc个数
                    nextEntryIdx = sharedRes->getTableCount();
                }
            }
            // 当参数是mAssetPaths中除第一个以外的其他资源资源包路径,
            // 比如app自己的资源包路径时,走下面的逻辑
            if (sharedRes == NULL) {
                // 检查该资源包是否被其他进程加载了,这与ZipSet数据结构有关,后面在详细介绍
                ass = const_cast<AssetManager*>(this)->
                    mZipSet.getZipResourceTableAsset(ap.path);
                if (ass == NULL) {
                    ALOGV("loading resource table %s\n", ap.path.string());
                    // 创建Asset对象,就是打开resources.arsc
                    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);
                    }
                }
                // 只有在zygote启动时,才会执行下面的逻辑
                // 为系统资源创建 ResTable,并加入到mZipSet里。
                if (nextEntryIdx == 0 && ass != NULL) {
                    ALOGV("Creating shared resources for %s", ap.path.string());
                    // 创建ResTable对象,并把前面与resources.arsc关联的Asset对象,加入到这个ResTabl中
                    sharedRes = new ResTable();
                    sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
                    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 {
                // 非系统资源包时,将与resources.arsc关联的Asset对象加入到Restable中
                // 此过程会解析resources.arsc文件。
                ALOGV("Parsing resources for %s", ap.path.string());
                mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
            }
            onlyEmptyResources = false;
    
            if (!shared) {
                delete ass;
            }
        } else {
            mResources->addEmpty(nextEntryIdx + 1);
        }
    
        if (idmap != NULL) {
            delete idmap;
        }
        MY_TRACE_END();
    
        return onlyEmptyResources;
    }
    

    获取系统路径并解析resources.arsc这个文件映射表,其中存放的就是资源的索引。

    至此,系统资源加载就完成了。

    获取资源

    现在所有资源的索引已经加载到内存中了,我们就需要获取资源并加载了。

    <ImageView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:src="@drawable/image"/>
    

    我们看一下ImageView的src属性图片是如何加载的,通常情况下,src属性和我们自定义属性很想,都是自定义的属性,只不过一个是系统定义的,一个是我们定义的,所有我们直接看ImageView的构造函数:

    public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
           int defStyleRes) {
       super(context, attrs, defStyleAttr, defStyleRes);
       final TypedArray a = context.obtainStyledAttributes(
                    attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
    
       final Drawable d = a.getDrawable(R.styleable.ImageView_src);
        if (d != null) {
           setImageDrawable(d);
       }   
    }
    
    @Nullable
    public Drawable getDrawable(@StyleableRes int index) {
       final TypedValue value = mValue;
       if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
           if (value.type == TypedValue.TYPE_ATTRIBUTE) {
               throw new UnsupportedOperationException(
                       "Failed to resolve attribute at index " + index + ": " + value);
           }
           return mResources.loadDrawable(value, value.resourceId, mTheme);
       }
       return null;
    }
    

    最后也是通过Resources的loadDrawable(value, value.resourceId, mTheme)方法来获取到资源:

    // Resources.java
    @NonNull
    Drawable loadDrawable(@NonNull TypedValue value, int id, @Nullable Theme theme)
           throws NotFoundException {
       return mResourcesImpl.loadDrawable(this, value, id, theme, true);
    }
    
    // ResourcesImpl.java
    @Nullable
    Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
           boolean useCache) throws NotFoundException {
       try {
           Drawable dr;
           if (cs != null) {
               dr = cs.newDrawable(wrapper);
           } else if (isColorDrawable) {
               dr = new ColorDrawable(value.data);
           } else {
               dr = loadDrawableForCookie(wrapper, value, id, null);
           }
    
           return dr;
       } catch (Exception e) {
           throw nfe;
       }
    }
    

    加载drawable的时候,首先会现在缓存中获取,如果有就直接返回,如果没有的话,说明没有加载过这个资源文件,就需要先加载并缓存到caches中,所有我们看loadDrawableForCookie方法:

    private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
           Resources.Theme theme) {
       final String file = value.string.toString();
    
       final Drawable dr;
    
       Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
       try {
           if (file.endsWith(".xml")) {
            // 类似于shape的xml文件
               final XmlResourceParser rp = loadXmlResourceParser(
                       file, id, value.assetCookie, "drawable");
               dr = Drawable.createFromXml(wrapper, rp, theme);
               rp.close();
           } else {
            // 不是xml文件,就是图片文件,就将其加载到内存中
            // value.assetCookie这个cookie就是图片在native层中的mAssetPaths中的索引
               final InputStream is = mAssets.openNonAsset(
                       value.assetCookie, file, AssetManager.ACCESS_STREAMING);
               dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
               is.close();
           }
       } catch (Exception e) {
           Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
           final NotFoundException rnf = new NotFoundException(
                   "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
           rnf.initCause(e);
           throw rnf;
       }
       Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    
       return dr;
    }
    

    loadDrawableForCookie方法会首先判断是否是.xml文件,如果是xml文件,则直接使用xml解析并创建Drawable对象返回,如果不是xml文件,则认为是图片的资源文件,使用流的方式将资源读取到缓存中。

    至此为止,资源是如何加载的和如何从内存中获取资源都完成了。

    相关文章

      网友评论

        本文标题:【Android源码】资源加载AssetManager源码分析

        本文链接:https://www.haomeiwen.com/subject/zspsfxtx.html