Android资源加载机制

Android资源加载

使用Resource资源文件有两种方式, 以ImageView为例

  1. 在Xml文件的src中去设置资源文件 android:src = "@drawable/xxx"
  2. 通过 setImageResource(R.drawable.xxx) 去使用资源文件 接下来分析一下Resource在资源加载时起到的作用
------------------------------------1. 通过Xml文件的android:src使用资源文件----------------------------------------------
    public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initImageView();
        final TypedArray a = context.obtainStyledAttributes(
                attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
        // 我们在XML中设置的资源的是通过TypedArray拿到的, 往下跟进
        final Drawable d = a.getDrawable(R.styleable.ImageView_src);
        ...
    }
    
    /**
     * TypedArray.getDrawable
     */
    @Nullable
    public Drawable getDrawable(@StyleableRes int index) {
        final TypedValue value = mValue;
        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
            // 这里我们迅速定位到mResource这个对象, 也就说, 我们系统资源是通过Resource加载的
            // 这个方法最终会调用到 ResourceImpl.loadDrawableForCookie 中去
            return mResources.loadDrawable(value, value.resourceId, mTheme);
        }
        return null;
    }
    
-------------------------------------------2. setImageResource使用资源文件------------------------------------------------
    public void setImageResource(@DrawableRes int resId) {
        // 获取之前drawable的宽高
        final int oldWidth = mDrawableWidth;
        final int oldHeight = mDrawableHeight;
        // 因为设置了新的resId, 所以调用该方法, 将当前ImageView的drawable置空
        updateDrawable(null);
        // 更新当前ImageView的资源id
        mResource = resId;
        mUri = null;
        // 重新加载资源
        resolveUri();
        // 判断设置了新的资源之后, drawable的宽高是否有变化, 若不相等则 requestLayout
        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        // 重新绘制
        invalidate();
    }

    private void resolveUri() {
        if (mDrawable != null)  return;
        if (getResources() == null)  return;
        // 获取Drawable
        Drawable d = null;
        if (mResource != 0) {
             d = mContext.getDrawable(mResource);
        } else if (mUri != null) {
            d = getDrawableFromUri(mUri);
        } else {
            return;
        }
        // 更新当前 ImageView 的 Drawable
        updateDrawable(d);
    }

    public final Drawable getDrawable(@DrawableRes int id) {
        // 这个方法最终也会调用到 ResourceImpl.loadDrawable 中去
        // 进而调用 ResourceImpl.loadDrawableForCookie
        return getResources().getDrawable(id, getTheme());
    }
    
--------------------------------------------------------------------------------------------------------------------------
    /**
     * ResourceImpl.loadDrawableForCookie
     */
    private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
            Resources.Theme theme) {
        final String file = value.string.toString();
        final Drawable dr;
        try {
            // 判断文件类型并且加载
            if (file.endsWith(".xml")) {
                final XmlResourceParser rp = loadXmlResourceParser(
                        file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXml(wrapper, rp, theme);
                rp.close();
            } else {
                // 通过AsstsManager去加载系统资源流文件
                final InputStream is = mAssets.openNonAsset(
                        value.assetCookie, file, AssetManager.ACCESS_STREAMING);
                // 这里就将我们的文件流加载为Bitmap转为Drawable
                dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
                is.close();
            }
        } 
        return dr;
    }
复制代码

从上面的源码分析来看, 无论是通过Xml文件的 android:src 使用资源文件还是通过 setImageResource 使用资源文件, 最终都会交由Resource去加载, 可见我们使用的资源文件, 最终都与 mResources 这个对象相关, 接下来我们看看 Resource 实例化的走向

Resource 实例化走向

我们都知道在App开发中, 通过context.getResource() 可以拿到 Resource 对象, 接下来我们就来分析一下, Resource 是如何实例化的

    /** 
     * ContextImpl.getResources
     * 在应用中通过 getResource() 方法最后会来到 ContextImpl 的 getResource() 中
     */
    @Override
    public Resources getResources() {
        // 可见返回的是 ContextImpl 的内部的一个对象
        // 接下来我们就要追溯一下, mResource的创建
        return mResources;
    }
   /**
    * ContextImpl.createResources
    * 1. 该方法在以下方法创建 Context 实例时均会调用, 可见它是实例化 mResources 的入口
    * ContextImpl.createApplicationContext()
    * ContextImpl.createPackageContextAsUser()
    * ContextImpl.createConfigurationContext()
    * ContextImpl.createSystemUiContext()
    */
   private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
        final String[] splitResDirs;
        final ClassLoader classLoader;
        try {
            splitResDirs = pi.getSplitPaths(splitName);
            classLoader = pi.getSplitClassLoader(splitName);
        } catch (NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        // 2. 将创建Resources的任务交给了ResourcesManager, 从 ResourcesManager.getInstance() 可见进程单例的
        return ResourcesManager.getInstance().getResources(activityToken,
                pi.getResDir(),
                splitResDirs,
                pi.getOverlayDirs(),
                pi.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfig,
                compatInfo,
                classLoader);
    }
    /**
     * ResourcesManager.getResource
     */
    public Resources getResources(IBinder activityToken,
            @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");
            // 3.1 构造Resource的key, 通过key可以从缓存中查找Resource对象
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    overlayDirs,
                    libDirs,
                    displayId,
                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                    compatInfo);
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
            // 3.2 调用了 getOrCreateResources 方法去获取 Resource 实例
            return getOrCreateResources(activityToken, key, classLoader);
        }
    }
    /**
     * ResourcesManager.getOrCreateResources
     */
    private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
        synchronized (this) {
            // 4.1 通过 findResourcesImplForKeyLocked() 方法获取 resourcesImpl
            // 若 key -> resourcesImpl  能够获取到, 则直接通过 getOrCreateResourcesForActivityLocked / getOrCreateResourcesLocked 获取实例
            if (activityToken != null) {
                // ResourcesImpl 是 Resource 中方法的具体执行者
                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
                if (resourcesImpl != null) {
                    return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                            resourcesImpl, key.mCompatInfo);
                }
            } else {
                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
                if (resourcesImpl != null) {
                    return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
                }
            }
        }
        // 4.2 通过 createResourcesImpl 创建 ResourcesImpl 对象
        ResourcesImpl resourcesImpl = createResourcesImpl(key);
        if (resourcesImpl == null) {
            return null;
        }
        synchronized (this) {
            // 用于二次检验, 防止往 mResourceImpls Map 中 put 多次, 类似于双重锁机制
            ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
            if (existingResourcesImpl != null) {
                resourcesImpl.getAssets().close();
                resourcesImpl = existingResourcesImpl;
            } else {
                // 将我们创建好的 resourcesImpl 实例加入缓存中
                mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
            }
            final Resources resources;
            if (activityToken != null) {
                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                        resourcesImpl, key.mCompatInfo);
            } else {
                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
            }
            return resources;
        }
    }
复制代码
  1. ContextImpl.createResources 这个方法是App创建资源文件的入口, 在构建 ContextImpl 创建 Context 实例的时候均会调用
  2. ContextImpl.createResources 通过 ResourcesManager.getInstance().getResources() 来获取 Resource 对象
    • ResourcesManager 从名字就能看的出来, 这个类主要负责 Resource 的管理
    • ResourcesManager.getInstance() 可见 ResourcesManager 是进程间单例的
  3. ResourcesManager.getResources 这个方法将构建了 ResourcesKey , 并交由 ResourcesManager.getOrCreateResources 方法去获取 Resource 对象
  4. ResourcesManager.getOrCreateResources
    • 先通过 findResourcesImplForKeyLocked() 方法去获取当前 ResourcesKey 对应的 resourcesImpl 对象的缓存
    • 若有缓存: 直接通过 ResourcesManager.getOrCreateResourcesForActivityLocked(activityToken != null) / ResourcesManager.getOrCreateResourcesLocked(activityToken == null) 获取实例
    • 若无缓存
      • 通过 ResourcesManager.createResourcesImpl() 来创建 resourcesImpl 实例, 添加进 mResourceImpls 的 Map 集合中缓存
      • 通过 ResourcesManager.getOrCreateResourcesForActivityLocked(activityToken != null) / ResourcesManager.getOrCreateResourcesLocked(activityToken == null) 获取实例

接下来我们先对 ==getOrCreateResourcesForActivityLocked、getOrCreateResourcesLocked(activityToken == null)== 进行分析

    /**
     * ResourcesManager.getOrCreateResourcesForActivityLocked
     */
    private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
            @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
        // 1.1 遍历 activityResources 集合, 在缓存中找到当前传入 classLoader/impl 对应的 resources
        final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                activityToken);
        final int refCount = activityResources.activityResources.size();
        for (int i = 0; i < refCount; i++) {
            WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
            Resources resources = weakResourceRef.get();
            if (resources != null
                    && Objects.equals(resources.getClassLoader(), classLoader)
                    && resources.getImpl() == impl) {
                return resources;
            }
        }
        // 1.2 若缓存中没有, 则创建新的对象
        Resources resources = new Resources(classLoader);
        // 1.3 将 resource 与 impl 绑定
        resources.setImpl(impl);
        // 1.4 添加到缓存集合中
        activityResources.activityResources.add(new WeakReference<>(resources));
        return resources;
    }
    
    /**
     * ResourcesManager.getOrCreateResourcesLocked
     */
    private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
            @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
        // 2.1 遍历 mResourceReferences 集合, 在缓存中找到当前传入 classLoader/impl 对应的 resources
        final int refCount = mResourceReferences.size();
        for (int i = 0; i < refCount; i++) {
            WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
            Resources resources = weakResourceRef.get();
            if (resources != null &&
                    Objects.equals(resources.getClassLoader(), classLoader) &&
                    resources.getImpl() == impl) {
                return resources;
            }
        }

        // 2.2 若缓存中没有, 则创建新的对象
        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
        // 2.3 将 resource 与 impl 绑定
        resources.setImpl(impl);
        // 2.4 添加到缓存集合中
        mResourceReferences.add(new WeakReference<>(resources));
        return resources;
    }
    /**
     * 这是一个过时的构造方法, 我们在插件式换肤框架的搭建中, 常用这个过时的方法去实例化 Resources 资源对象
     */
    @Deprecated
    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
        this(null);
        mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
    }

复制代码

可以看到 getOrCreateResourcesForActivityLocked/getOrCreateResourcesLocked 处理的事情十分相似

  1. 从各自对应的缓存中查找与当前传入 classLoader/impl 相对应的 resources
  2. 若存在则直接返回
  3. 若不存在
    • Resources resources = new Resources(classLoader); 创建对象
    • resources.setImpl(impl); 绑定 ResourceImpl
    • 添加到各自对应的缓存集合中

还记得我们上面有一个 ResourcesManager.createResourcesImpl() 没有分析吗, 这个方法非常重要, 所以我们接着往下走

ResourcesImpl 与 AssetManager

    /**
     * ResourcesManager.createResourcesImpl
     * 创建 ResourcesImpl 的实例
     */
   private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        // 配置相关构建 ResourcesImpl 对象的参数
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        // 可以看到这里有一个非常非常重要的方法 createAssetManager, 通过这个方法, 我们构建了 AssetManager 对象
        final AssetManager assets = createAssetManager(key);
        if (assets == null) { return null; }
        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        // 这里直接 new 了一个 ResourcesImpl 对象
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
        return impl;
    }
    /**
     * ResourcesManager.createAssetManager
     */
    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        // 1. new 了一个 AssetManager 
        AssetManager assets = new AssetManager();
        if (key.mResDir != null) {
            // 2. 将当前 App 的资源(resource.arsc)加载到 AssetManager 中
            if (assets.addAssetPath(key.mResDir) == 0) {
                return null;
            }
        }
        if (key.mSplitResDirs != null) {
            for (final String splitResDir : key.mSplitResDirs) {
                if (assets.addAssetPath(splitResDir) == 0) {
                    return null;
                }
            }
        }
        if (key.mOverlayDirs != null) {
            for (final String idmapPath : key.mOverlayDirs) {
                assets.addOverlayPath(idmapPath);
            }
        }
        return assets;
    }
    /**
     * AssetManager.Constructor
     */
    public AssetManager() {
        synchronized (this) {
            // 重点!, 这里调用了init方法
            init(false);
            if (localLOGV) Log.v(TAG, "New asset manager: " + this);
            ensureSystemAssets();
        }
    }
    /**
     * AssetManager.init
     * 1.1 调用了一个 native 层的 init 方法
     */
    private native final void init(boolean isSystem);
   
   public final int addAssetPath(String path) {
       return addAssetPathInternal(path, false);
    }
    
   private final int addAssetPathInternal(String path, boolean appAsLib) {
        synchronized (this) {
            // 调用了addAssetPathNative
            int res = addAssetPathNative(path, appAsLib);
            makeStringBlocks(mStringBlocks);
            return res;
        }
    }
    /**
     * AssetManager.init
     *  2.1 调用了 native 层的 addAssetPathNative 方法
     */
    private native final int addAssetPathNative(String path, boolean appAsLib);
复制代码
  1. ResourcesManager.createResourcesImpl 构造 ResourcesImpl 实例的时候, 通过 ResourcesManager.createAssetManager 去创建了 AssetManager 对象
  2. ResourcesManager.createAssetManager
    • AssetManager assets = new AssetManager(); 创建了 AssetManager 对象
      • 在构造方法 AssetManager() 中 init(false); 最终调用了一个 native 层的 init 方法
    • assets.addAssetPath(key.mResDir); 最终调用了一个 native 层的 addAssetPathNative 方法

并不能满足于此, 我们要看看, AssetManager 初始化的时候做了些什么

AssetManager 的初始化过程(AssetManager.cpp 进入 Native 层)

// init 方法
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
    if (isSystem) {
        verifySystemIdmaps();
    }
    //  1. 构建对象
    AssetManager* am = new AssetManager();
    if (am == NULL) {
        jniThrowException(env, "java/lang/OutOfMemoryError", "");
        return;
    }
    // 2. 添加 Android 系统默认的资源
    am->addDefaultAssets();

    ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
    env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}

// 添加 Android 默认的资源文件
bool AssetManager::addDefaultAssets()
{
    // 2.1 获取 Android 系统资源路径
    const char* root = getenv("ANDROID_ROOT");
    String8 path(root);// 初始化的时候会去加载系统的 framework-res.apk 资源
    path.appendPath(kSystemAssets);// 也就是说我们为什么能加载系统的资源如颜色、图片、文字等等
    // 2.2 通过系统资源路径去添加系统资源
    return addAssetPath(path, NULL);
}

// 通过资源路径去添加资源
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
    asset_path ap;
    ...// 省略一些校验代码
    // 2.3 遍历 native 中维护的资源路径数组 mAssetPaths, 判断传入的 path 是否已经存在
    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;
        }
    }
    // 2.4 检查传入的 path 是否有 AndroidManifest.xml 文件
    Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
            kAndroidManifest, Asset::ACCESS_BUFFER, ap);
    // 2.4.1 可以看到若是没有 AndroidManifest.xml , 直接返回加载失败
    if (manifestAsset == NULL) {
        delete manifestAsset;
        return false;
    }
    delete manifestAsset;
    // 2.5 若是新路径, 且通过了重重校验, 则添加到这个资源路径数组 mAssetPaths 中
    mAssetPaths.add(ap);
    // 2.5.1 新路径总是补充到最后
    if (cookie) {
        *cookie = static_cast<int32_t>(mAssetPaths.size());
    }
    // 2.6 添加到Resource资源表中
    if (mResources != NULL) {
        appendPathToResTable(ap);
    }
    return true;
}
复制代码

可以看到Native层的AssetManager.init()方法做了两个非常重要的事情

  1. AssetManager* am = new AssetManager(); 创建了一个 Native 层的 AssetManager
  2. am->addDefaultAssets(); 调用了 addDefaultAssets() 方法
    • 获取 Android 系统资源路径: kSystemAssets(framework-res.apk)
    • 调用 addAssetPath(path, NULL, false , true); 通过系统资源路径去添加系统资源
    • 遍历 native 中维护的资源路径数组 mAssetPaths, 判断传入的资源路径 path 是否已经存在
      • 存在直接返回加载成功
    • 检查传入的 path 是否有 AndroidManifest.xml 文件
      • 不存在直接返回加载失败, 代表当前资源不符合 AssetManager 加载的要求
    • 若是新路径, 且通过了重重校验, 则添加到这个资源路径数组 mAssetPaths 中
    • ** appendPathToResTable(ap) 添加到Resource资源表中**

接下来看看 native 层的 appendPathToResTable() 具体是如何将资源添加到表中的

bool AssetManager::appendPathToResTable(const asset_path& ap) const {
    // 判断是否为系统资源覆盖
    if (ap.isSystemOverlay) {
        return true;
    }
    Asset* ass = NULL;// 用于存储我们App中的资源, 其中只有一个"resources.arsc"
    ResTable* sharedRes = NULL; // 用于存储 framework-res.apk 中的资源, 其中有多个"resources.arsc"
    bool shared = true;
    bool onlyEmptyResources = true;
    // 资源覆盖机制,暂不考虑
    Asset* idmap = openIdmapLocked(ap);
    size_t nextEntryIdx = mResources->getTableCount();// 当前资源表的数量
    if (ap.type != kFileTypeDirectory) { // 1. 资源路径是一个apk文件 (资源包路径不是一个文件夹,那就是一个apk文件了)
        // 1.1 加载路径为 Android 系统资源包(framework-res.apk)时会走该分支
        // 因为在App启动的时候, 肯定会先调用 AssetManager 的 init 方法
        if (nextEntryIdx == 0) {
            // 通过 mZipSet 获取 ap.path 目录下所有资源, 即: "resources.arsc"文件集合 -> ResTable*
            sharedRes = const_cast<AssetManager*>(this)->mZipSet.getZipResourceTable(ap.path);
            // 肯定不为NULL, 因为framework-res.apk一定存在 "resources.arsc" 文件
            if (sharedRes != NULL) {
                nextEntryIdx = sharedRes->getTableCount();// 得到资源包路径中 "resources.arsc" 文件的个数
            }
        }
        // 1.2 加载路径非 framework-res.apk 时会走该分支
        if (sharedRes == NULL) {
            // 通过尝试从缓存中直接获取 ap.path 对应的资源
            ass = const_cast<AssetManager*>(this)->mZipSet.getZipResourceTableAsset(ap.path);
            if (ass == NULL) {
                // 从 ap 这个文件夹中查找 "resources.arsc", 即: "resources.arsc" -> Asset
                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) {
                // 创建 ResTable 对象,并把前面与 resources.arsc 关联的 Asset 对象,加入到这个 ResTab l中
                sharedRes = new ResTable();
                sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
                sharedRes = const_cast<AssetManager*>(this)->
                    mZipSet.setZipResourceTable(ap.path, sharedRes);
            }
        }
    } else { // 2. 资源包路径是一个文件夹
        // 从 ap 这个文件夹中查找 "resources.arsc", 从中获取 Asset 资源
        ass = const_cast<AssetManager*>(this)->
            openNonAssetInPathLocked("resources.arsc",Asset::ACCESS_BUFFER, ap);
        shared = false;
    }
    // 3. 将解析到的资源添加到 mResources 中维护
    // mResources = new ResTable();
    if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
        if (sharedRes != NULL) {// Android framework-res.apk 资源包时
            mResources->add(sharedRes);
        } else { // 非Android framework-res.apk 资源包时
            mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
        }
        onlyEmptyResources = false;
        if (!shared) {
            delete ass;
        }
    } else {
        mResources->addEmpty(nextEntryIdx + 1);
    }
    if (idmap != NULL) {
        delete idmap;
    }
    return onlyEmptyResources;
}
复制代码

mResources(ResTable): 为当前应用在 Native 层维护资源的资源表, 与 Framework 层的 mResource(Resource) 同名, 注意区分

  1. 若传入的路径对应的为一个apk
    • 优先加载 framework-res.apk 中的资源, 因为我们 Framework 层的 AssetManager 创建的时候一定会先调用 init() 加载系统资源, 再调用 addAssetPath() 加载当前应用资源
    • 加载当前应用资源
  2. 若传入的为一个文件, 则直接解析其中的 "resources.arsc" 文件
  3. 将解析到的所有资源("resources.arsc" convert2 Asset)添加到 mResources(ResTable) 中集中缓存
  4. 至此我们就可以搞懂为什么 R.drawable.xxx 就能唯一的确定一个资源文件了, 因为它具体的路径映射在了 mResources(ResTable) 资源表中

总结

1. App启动的时会创建 ContextImpl 对象

2. 在 ContextImpl 创建 Context 实例的同时会创建 mResource(Resource) 对象

  • 在 mResource(Resource) 对象构造的过程中, 会绑定 impl(ResourceImpl) 对象
  • ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj); 在构建 impl(ResourceImpl) 对象时会传入 AssetManager 对象
    • AssetManager 对象构建时内部会调用 init() 方法, 通过 native 层的 init() 将 Android 系统资源(framework-res.apk)加载进 Native 层维护的资源表(mResources(ResTable))中
    • AssetManager.addAssetPath() -> addAssetPathNative() 将当前App的资源(resources.arsc)加载进 Native 层维护的资源表(mResources(ResTable))中

3. 之后通过 Context.getResource() 即可拿到这个全局单例的 mResource(Resource) 对象, 从这时起我们便可以随心所欲的通过 R.drawable.XXX 使用资源文件了

4. Simple: ImageView.setImageResource(R.drawable.logo)

  • ContextImpl.java: getDrawable(resId) -> mResources.loadDrawable(value, value.resourceId, mTheme);
  • ResourceImpl.java: loadDrawable() -> loadDrawableForCookie()(Resources wrapper, TypedValue value, int id, Resources.Theme theme)
  • loadDrawableForCookie()
    • 若有缓存, 直接从缓存中获取Drawable
    • 若无缓存, 会通过 AssetManager 去 resource.arsc 文件中查找对应的资源路径加载并放入缓存
  • loadDrawableForCookie() 方法执行完成我们就拿到了 Drawable 对象了, 进而触发 ImageView
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值