【Android关键流程】资源加载

文章目录


基于 Android U

在使用一般资源时,我们通过 Context 上下文类获取 Resources 类,然后通过资源管理类(Resources)加载资源文件,在 Resources 类内部的 impl(ResourcesImpl) 进行加载资源,而 ResourcesImpl 内部则通过 AssetManager 进行资源真正的加载操作;AssetManager 内部主要是 native 方法,所以资源的加载操作主要在 native 层进行的。

而与 style 相关的资源则是通过 Theme 进行加载,Theme 同样由 Context 上下文类获取,Theme 同时也是 Resources 的内部类,Theme 内部通过 ThemeImpl 进行 style 资源的操作,而 ThemeImpl 是 ResourcesImpl 的内部类,在 ThemeImpl 内引用着 ResourcesImpl 的 mAssets(AssetManager)对象,所以通过 Theme 加载 style 资源时,同样是通过 AssetManager 进行加载,并将加载的数据通过 TypedArray 数组进行返回;

资源创建

创建 System 的 Resources

Zygote 初始化时会对资源进行预加载。

java 复制代码
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

private static void preloadResources() {
    try {
        mResources = Resources.getSystem();
        mResources.startPreloading();
        
        ... // 系统资源加载,代码省略
            
        mResources.finishPreloading();
    } catch (RuntimeException e) {
        Log.w(TAG, "Failure preloading resources", e);
    }
}

在 Zygote 初始化时 mSystem 就被赋值了。所有的应用程序都是从 Zygote 进程 fork 而来的,这意味着所有进程中的 mSystem 引用的 Resources 实例在整个 Android 系统中是共享的,这部分其实也就是 framework 的资源。

通过 Resources.getSystem().getX() 调用资源时,就是在调用 framework 的资源。

java 复制代码
frameworks/base/core/java/android/content/res/Resources.java

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

private Resources() {
    this(null);

    final DisplayMetrics metrics = new DisplayMetrics();
    metrics.setToDefaults();

    final Configuration config = new Configuration();
    config.setToDefaults();

    mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config,
                                       new DisplayAdjustments());
}

Resources.getSystem() 方法会对 mSystem 初始化,创建 ResourcesImpl 实例时传入的 AssetManager 是通过 AssetManager.getSystem() 获取的,会加载 framework-res.apk。

创建 Application Context 的 Resources
java 复制代码
frameworks/base/core/java/android/app/ContextImpl.java

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
                                    String opPackageName) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
                                          ContextParams.EMPTY, null, null, null, null, null, 0, null, opPackageName);
    context.setResources(packageInfo.getResources());  // 1
    context.mContextType = isSystemOrSystemUI(context) ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
        : CONTEXT_TYPE_NON_UI;
    return context;
}

注释1处创建 Resources 对象,并赋值给 ContextImpl。

  • 创建 Resources 对象
java 复制代码
frameworks/base/core/java/android/app/LoadedApk.java

public Resources getResources() {
    if (mResources == null) {
        ...
        mResources = ResourcesManager.getInstance().getResources(...);  // 1
    }
    return mResources;
}

注释1处调用 ResourcesManager#getResources。

java 复制代码
frameworks/base/core/java/android/app/ResourcesManager.java
    
public Resources getResources(
    @Nullable IBinder activityToken,
    @Nullable String resDir,
    @Nullable String[] splitResDirs,
    @Nullable String[] legacyOverlayDirs,
    @Nullable String[] overlayPaths,
    @Nullable String[] libDirs,
    @Nullable Integer overrideDisplayId,
    @Nullable Configuration overrideConfig,
    @NonNull CompatibilityInfo compatInfo,
    @Nullable ClassLoader classLoader,
    @Nullable List<ResourcesLoader> loaders) {
    try {
        ...
        final ResourcesKey key = new ResourcesKey(
            resDir,
            splitResDirs,
            combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
            libDirs,
            overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
            overrideConfig,
            compatInfo,
            loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));  // 1
        classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();

        // Preload the ApkAssets required by the key to prevent performing heavy I/O while the
        // ResourcesManager lock is held.
        final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);  // 2

        ...

        Resources resources;
        if (activityToken != null) {
            Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration);
            rebaseKeyForActivity(activityToken, key, overrideDisplayId != null);
            resources = createResourcesForActivity(activityToken, key, initialOverrideConfig,
                                                   overrideDisplayId, classLoader, assetsSupplier);
        } else {
            resources = createResources(key, classLoader, assetsSupplier);  // 3
        }
        return resources;
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    }
}

注释1处创建 key,用于缓存 resources 对象。

注释2处创建 APK 加载资源的内存表示,同时防止 GC 回收,用于 C++ 交互。

注释3处创建 Resources。

java 复制代码
frameworks/base/core/java/android/app/ResourcesManager.java
    
private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
                                  @Nullable ApkAssetsSupplier apkSupplier) {
    synchronized (mLock) {
        ...

        ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);  // 1
        if (resourcesImpl == null) {
            return null;
        }

        return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);  // 2
    }
}

注释1处创建 / 获取 ResourcesImpl 对象。

注释2处创建 Resources 对象。

java 复制代码
frameworks/base/core/java/android/app/ResourcesManager.java
    
// 第一部分,创建ResourcesImpl
private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
        @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
    // 1.1 从缓存加载ResourcesImpl
    ResourcesImpl impl = findResourcesImplForKeyLocked(key);
    if (impl == null) {
        // 1.2 创建ResourcesImpl
        impl = createResourcesImpl(key, apkSupplier);
        if (impl != null) {
            // 1.3 将创建的ResourcesImpl对象放入缓存
            mResourceImpls.put(key, new WeakReference<>(impl));
        }
    }
    return impl;
}

private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
        @Nullable ApkAssetsSupplier apkSupplier) {
    // 1.2.1 创建AssetManager
    final AssetManager assets = createAssetManager(key, apkSupplier);
    if (assets == null) {
        return null;
    }

    final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
    daj.setCompatibilityInfo(key.mCompatInfo);

    final Configuration config = generateConfig(key);
    final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
    // 1.2.2 创建ResourcesImpl
    final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);

    if (DEBUG) {
        Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
    }
    return impl;
}

// 第二部分,创建Resources
private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
        @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
    cleanupReferences(mResourceReferences, mResourcesReferencesQueue);

    // 2.1 创建Resources对象
    Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
        : new Resources(classLoader);
    // 2.2 将impl设置到Resources内
    resources.setImpl(impl);
    resources.setCallbacks(mUpdateCallbacks);
    // 2.3 将resources放入缓存内
    mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
    ...
    return resources;
}
  • 将 Resources 赋值给 ContextImpl 的 mResources 对象
java 复制代码
frameworks/base/core/java/android/app/ContextImpl.java

void setResources(Resources r) {
    if (r instanceof CompatResources) {
        ((CompatResources) r).setContext(this);
    }
    mResources = r;  // 1
}

注释1处将 resources 赋值给 ContextImpl 的 mResources 对象。

  • 总结

    Resources 资源管理对象的创建,主要分为7个步骤:

    1. 创建 ContextImpl 时,同步会创建相关的 Resources 对象,并将 Resources 赋值给 mResources 对象;

    2. 通过 ResourcesManager 的 getResources 方法创建 Resources 对象;

    3. 创建 ResourcesKey 对象,作为缓存 key 和记录 Resource 相关的信息;

    4. 创建 ApkAssetsSupplier 对象,内部维护了一个 Map 集合,用于缓存不同目录下的资源数据,Key 为 Appkey(目录path生成),Value 为 ApkAssets 的 Map;

    5. 创建 AssetManager,用于和 native 层进行交互;

    6. 创建 ResourceImpl,作为 Resources 的实现类;

    7. 创建 Resources,并将 resources 设置到 ContextImpl;

    非 Activity 类型的 Resources 最终会通过 ResourcesManager#createResourcesLocked 创建。

创建 Activity Context 的 Resources
前置信息
  • ActivityResources

    java 复制代码
    private static class ActivityResources {
        
        public final Configuration overrideConfig = new Configuration();
    
        public final ArrayList<ActivityResource> activityResources = new ArrayList<>();
        ...
    }

    包含了与一个 Activity 或者 WindowContext 相关联的基础 Configuration 和 Resources 集合。

    这也就是说,每一个 Activity 或者 WindowContext,都可能对应多个 Resources。在创建一个 Activity 或者 WindowContext 的时候,系统都会为其创建一个 ActivityResources 对象,这个 ActivityResources 的作用之一就是通过 activityResources 变量为 Activity 或者 WindowContext 保存与之联系的所有 Resources 对象。overrideConfig 成员变量会被应用到 ActivityResources.activityResources 队列中的所有 Resources 的 Configuration。

  • ActivityResource

    java 复制代码
    private static class ActivityResource {
        
        public final Configuration overrideConfig = new Configuration();
    
        @Nullable
        public WeakReference<Resources> resources;
        ...
    }

    ActivityResource 就是对 Resources 的一层封装。resources,即当前 ActivityResource 对象保存的那个 Resources 对象的弱引用。overrideConfig 保存了一个 Activity 或者 WindowContext 对象创建的时候的初始 Configuration,这个变量在 Configuration 更新的时候不会被新传入的 Configuration 覆盖掉。

  • ResourceManager

    用于管理 App 所有的 resources,内部有一个 mActivityResourceReferences map 保存着所有 Activity或者 WindowToken 对应的 Resources 对象。

    java 复制代码
    private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
                new WeakHashMap<>();
创建流程
java 复制代码
frameworks/base/core/java/android/app/ContextImpl.java
    
static ContextImpl createActivityContext(ActivityThread mainThread,
                                         LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
                                         Configuration overrideConfiguration) {
    ...
    String[] splitDirs = packageInfo.getSplitResDirs();  // 1
    ClassLoader classLoader = packageInfo.getClassLoader();  // 2
    ...
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, ContextParams.EMPTY,
                                          attributionTag, null, activityInfo.splitName, activityToken, null, 0, classLoader,
                                          null);  // 3
    ...   
    final ResourcesManager resourcesManager = ResourcesManager.getInstance();  // 4

    // Create the base resources for which all configuration contexts for this Activity
    // will be rebased upon.
    context.setResources(resourcesManager.createBaseTokenResources(activityToken,
                                                                   packageInfo.getResDir(),
                                                                   splitDirs,
                                                                   packageInfo.getOverlayDirs(),
                                                                   packageInfo.getOverlayPaths(),
                                                                   packageInfo.getApplicationInfo().sharedLibraryFiles,
                                                                   displayId,
                                                                   overrideConfiguration,
                                                                   compatInfo,
                                                                   classLoader,
                                                                   packageInfo.getApplication() == null ? null
                                                                   : packageInfo.getApplication().getResources().getLoaders()));  // 5
    context.setDisplay(resourcesManager.getAdjustedDisplay(
        displayId, context.getResources()));
    return context;
}

注释1、2处获取当前的分辨率文件、ClassLoader。

注释3处初始化 ContextImpl。

注释4、5处获取 ResourcesManager 单例对象,然后使用其 createBaseTokenResources() 去创建最终的 Resources。接着将 Resources 对象保存到 Context 中,即赋值给 ContextImpl.mResources。

java 复制代码
frameworks/base/core/java/android/app/ResourcesManager.java
    
public @Nullable Resources createBaseTokenResources(@NonNull IBinder token,
                                                        @Nullable String resDir,
                                                        @Nullable String[] splitResDirs,
                                                        @Nullable String[] legacyOverlayDirs,
                                                        @Nullable String[] overlayPaths,
                                                        @Nullable String[] libDirs,
                                                        int displayId,
                                                        @Nullable Configuration overrideConfig,
                                                        @NonNull CompatibilityInfo compatInfo,
                                                        @Nullable ClassLoader classLoader,
                                                        @Nullable List<ResourcesLoader> loaders) {
    try {
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                         "ResourcesManager#createBaseActivityResources");
        final ResourcesKey key = new ResourcesKey(
            resDir,
            splitResDirs,
            combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
            libDirs,
            displayId,
            overrideConfig,
            compatInfo,
            loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
        classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
        ...
        synchronized (mLock) {
            // Force the creation of an ActivityResourcesStruct.
            getOrCreateActivityResourcesStructLocked(token);  // 1
        }

        // Update any existing Activity Resources references.
        updateResourcesForActivity(token, overrideConfig, displayId);  // 2

        synchronized (mLock) {
            Resources resources = findResourcesForActivityLocked(token, key,
                                                                 classLoader);  // 3
            if (resources != null) {
                return resources;
            }
        }

        // Now request an actual Resources object.
        return createResourcesForActivity(token, key,
                                          /* initialOverrideConfig */ Configuration.EMPTY, /* overrideDisplayId */ null,
                                          classLoader, /* apkSupplier */ null);  // 4
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    }
}

注释1处创建对应的 ActivityResources 对象。

注释2处更新该 token 对应的 resources。

注释3处查找当前 Activity 对应的 resources 对象,存在则复用。如果不存在,注释4处创建。

该方法内部会经历如下步骤:

  1. 先查找或创建当前 token(activity) 对应的 resources。

    找到了,什么都不做;

    没找到,创建一个 ActivityResources 对象,并将其添加到 mActivityResourceReferences map 中。

  2. 更新该 activity 对应的 resources(内部会再执行第一步)。

  3. 再次查找当前 activity 对应的 resources,如果找到,则直接返回。

  4. 如果没找到,则重新创建一个 resources(内部会再执行第一步)。

  • getOrCreateActivityResourcesStructLocked

    java 复制代码
    frameworks/base/core/java/android/app/ResourcesManager.java
        
    private ActivityResources getOrCreateActivityResourcesStructLocked(
        @NonNull IBinder activityToken) {
        ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
        if (activityResources == null) {
            activityResources = new ActivityResources();
            mActivityResourceReferences.put(activityToken, activityResources);
        }
        return activityResources;
    }
  • updateResourcesForActivity

    java 复制代码
    frameworks/base/core/java/android/app/ResourcesManager.java
        
    public void updateResourcesForActivity(@NonNull IBinder activityToken,
                                               @Nullable Configuration overrideConfig, int displayId) {
        try {
            ...
            synchronized (mLock) {
                final ActivityResources activityResources =
                    getOrCreateActivityResourcesStructLocked(activityToken);
    
                boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId;
                if (Objects.equals(activityResources.overrideConfig, overrideConfig)
                    && !movedToDifferentDisplay) {  // 1
                    // They are the same and no change of display id, no work to do.
                    return;
                }
    
                // Grab a copy of the old configuration so we can create the delta's of each
                // Resources object associated with this Activity.
                final Configuration oldConfig = new Configuration(activityResources.overrideConfig);  // 2
    
                // Update the Activity's base override.
                if (overrideConfig != null) {
                    activityResources.overrideConfig.setTo(overrideConfig);
                } else {
                    activityResources.overrideConfig.unset();
                }
    
                // Update the Activity's override display id.
                activityResources.overrideDisplayId = displayId;
    
                // If a application info update was scheduled to occur in this process but has not
                // occurred yet, apply it now so the resources objects will have updated paths if
                // the assets sequence changed.
                applyAllPendingAppInfoUpdates();
                ...
                // Rebase each Resources associated with this Activity.
                // 3
                final int refCount = activityResources.activityResources.size();
                for (int i = 0; i < refCount; i++) {
                    final ActivityResource activityResource =
                        activityResources.activityResources.get(i);
    
                    final Resources resources = activityResource.resources.get();
                    if (resources == null) {
                        continue;
                    }
    
                    final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource,
                                                                             overrideConfig, displayId);  // 4
                    if (newKey == null) {
                        continue;
                    }
    
                    // TODO(b/173090263): Improve the performance of AssetManager & ResourcesImpl
                    // constructions.
                    final ResourcesImpl resourcesImpl =
                        findOrCreateResourcesImplForKeyLocked(newKey);  // 5
                    if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
                        // Set the ResourcesImpl, updating it for all users of this Resources
                        // object.
                        resources.setImpl(resourcesImpl);  // 6
                    }
                }
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

    内部会先获取当前 activity 对应的 resources(如果不存在,则创建),如果当前传入的配置与之前一致,则直接返回(注释1);否则先使用当前 activity 对应的配置创建一个旧的 Configuration 对象(注释2),接着去更新该 activity 所有的 resourcesImpl(注释3)。每次更新时会先与当前的配置进行差异更新并返回并返回新的 ResourcesKey(注释4),并使用这个 key 获取其对应的 impl,如果没有则创建(注释5)。获取到的 impl 如果与当前的不一致,则更新当前 resources 的 impl。

  • findResourcesForActivityLocked

    java 复制代码
    frameworks/base/core/java/android/app/ResourcesManager.java
        
    private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
                                                         @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
        ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
            targetActivityToken);  // 1
    
        final int size = activityResources.activityResources.size();
        for (int index = 0; index < size; index++) {
            ActivityResource activityResource = activityResources.activityResources.get(index);
            Resources resources = activityResource.resources.get();
            ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
                resources.getImpl());
    
            if (key != null
                && Objects.equals(resources.getClassLoader(), targetClassLoader)
                && Objects.equals(key, targetKey)) {  // 2
                return resources;
            }
        }
    
        return null;  // 3
    }

    通过 findResourcesForActivityLocked 获取指定的 resources 时,内部会先获取当前 token 对应的 activityResources(注释1),从而拿到其所有的 resources;然后遍历所有 resources,如果某个 resources 对应的 key(ResourcesKey)与当前查找的一致并且符合其他规则(注释2),则直接返回;否则返回 null(注释3)。

  • createResourcesForActivity

    java 复制代码
    frameworks/base/core/java/android/app/ResourcesManager.java
        
    private Resources createResourcesForActivity(@NonNull IBinder activityToken,
                                                     @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig,
                                                     @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader,
                                                     @Nullable ApkAssetsSupplier apkSupplier) {
        synchronized (mLock) {
            ...
            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);  // 1
            if (resourcesImpl == null) {
                return null;
            }
    
            return createResourcesForActivityLocked(activityToken, initialOverrideConfig,
                                                    overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo);  // 2
        }
    }
    
    private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
                                                       @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId,
                                                       @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
                                                       @NonNull CompatibilityInfo compatInfo) {
        final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
            activityToken);
        cleanupReferences(activityResources.activityResources,
                          activityResources.activityResourcesQueue,
                          (r) -> r.resources);
    
        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
            : new Resources(classLoader);
        resources.setImpl(impl);
        resources.setCallbacks(mUpdateCallbacks);
    
        ActivityResource activityResource = new ActivityResource();
        activityResource.resources = new WeakReference<>(resources,
                                                         activityResources.activityResourcesQueue);
        activityResource.overrideConfig.setTo(initialOverrideConfig);
        activityResource.overrideDisplayId = overrideDisplayId;
        activityResources.activityResources.add(activityResource);
        ...
        return resources;
    }

    创建 Resources 时,内部会先使用 key 查找或创建相应的 ResourcesImpl(注释1),然后创建新的 Resources。

  • 总结

    Activity 类型的 Resources 最终会通过 ResourcesManager#createResourcesForActivityLocked 创建。

资源加载

Drawable 资源加载流程

Context#getDrawable -> Resources#getDrawable -> Resources#getDrawableForDensity -> Resources#loadDrawable -> ResourcesImpl#loadDrawable

java 复制代码
frameworks/base/core/java/android/content/res/ResourcesImpl.java

Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
                      int density, @Nullable Resources.Theme theme)
    throws NotFoundException {
    ...
    try {
        ...
        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;
        }

        int cacheGeneration = caches.getGeneration();
        // First, check whether we have a cached version of this drawable
        // that was inflated against the specified theme. Skip the cache if
        // we're currently preloading or we're not using the cache.
        if (!mPreloading && useCache) {
            Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);  // 1
            if (cachedDrawable != null) {
                cachedDrawable.setChangingConfigurations(value.changingConfigurations);
                return cachedDrawable;
            }
        }

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

        Drawable dr;
        boolean needsNewDrawableAfterCache = false;
        if (cs != null) {
            dr = cs.newDrawable(wrapper);
        } else if (isColorDrawable) {
            dr = new ColorDrawable(value.data);  // 3.1
        } else {
            dr = loadDrawableForCookie(wrapper, value, id, density);  // 3.2
        }
        ...
        if (dr != null) {
            dr.setChangingConfigurations(value.changingConfigurations);
            if (useCache) {
                cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr,
                              cacheGeneration);  // 4
                if (needsNewDrawableAfterCache) {
                    Drawable.ConstantState state = dr.getConstantState();
                    if (state != null) {
                        dr = state.newDrawable(wrapper);
                    }
                }
            }
        }

        return dr;
    } catch (Exception e) {
        ...
    }
}

注释1处尝试从缓存中获取资源。

注释2处检查预加载资源中是否存在当前需要的 drawable,并将其状态与参数保存。

注释3.1处,如果是颜色 drawable,直接创建;注释3.2处,如果不是颜色 drawable,调用 loadDrawableForCookie() 创建。

注释4处,将资源写入缓存中。

java 复制代码
frameworks/base/core/java/android/content/res/ResourcesImpl.java
    
private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
                                       int id, int density) {
    ...
    final Drawable dr;
    ...
    try {
        ...
        try {
            if (file.endsWith(".xml")) {
                final String typeName = getResourceTypeName(id);
                if (typeName != null && typeName.equals("color")) {
                    dr = loadColorOrXmlDrawable(wrapper, value, id, density, file);  // 1
                } else {
                    dr = loadXmlDrawable(wrapper, value, id, density, file);  // 2
                }
            } else if (file.startsWith("frro://")) {
                ...
                dr = decodeImageDrawable(is, wrapper);
            } else {
                final InputStream is = mAssets.openNonAsset(
                    value.assetCookie, file, AssetManager.ACCESS_STREAMING);  // 3
                final AssetInputStream ais = (AssetInputStream) is;
                dr = decodeImageDrawable(ais, wrapper, value);
            }

        } finally {
            stack.pop();
        }
    } catch (Exception | StackOverflowError e) {
        ...
    }
    Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

    return dr;
}

根据资源文件类型分别处理。

如果是 xml 类型,颜色资源解析为 ColorStateListDrawable,其他资源调用 loadXmlDrawable 解析为 Drawable。

如果不是xml类型,调用 AssetManager.openNonAsset() 打开文件。调用 decodeImageDrawable(),这里一般对应 png 等二进制图片资源,一般解析成 BitmapDrawable、NinePatchDrawable 或 AnimatedImageDrawable。

  1. 是 color 类型的 xml 文件

    java 复制代码
    frameworks/base/core/java/android/content/res/ResourcesImpl.java
        
    private Drawable loadColorOrXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
                                            int id, int density, String file) {
        try {
            ColorStateList csl = loadColorStateList(wrapper, value, id, null);
            return new ColorStateListDrawable(csl);
        } catch (NotFoundException originalException) {
            // If we fail to load as color, try as normal XML drawable
            try {
                return loadXmlDrawable(wrapper, value, id, density, file);
            } catch (Exception ignored) {
                // If fallback also fails, throw the original exception
                throw originalException;
            }
        }
    }
    
    ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
                                      Resources.Theme theme)
        throws NotFoundException {
        if (TRACE_FOR_PRELOAD) {
            // Log only framework resources
            if ((id >>> 24) == 0x1) {
                final String name = getResourceName(id);
                if (name != null) android.util.Log.d("PreloadColorStateList", name);
            }
        }
    
        final long key = (((long) value.assetCookie) << 32) | value.data;
        // Handle inline color definitions.
        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
            && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
            return getColorStateListFromInt(value, key);  // 1
        }
    
        ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);
        if (complexColor != null && complexColor instanceof ColorStateList) {
            return (ColorStateList) complexColor;
        }
    
        throw new NotFoundException(
            "Can't find ColorStateList from drawable resource ID #0x"
            + Integer.toHexString(id));
    }
  2. 不是 color 类型的 xml 文件

    java 复制代码
    frameworks/base/core/java/android/content/res/ResourcesImpl.java
    
    private Drawable loadXmlDrawable(@NonNull Resources wrapper, @NonNull TypedValue value,
                                     int id, int density, String file)
        throws IOException, XmlPullParserException {
        try (
            XmlResourceParser rp =
            loadXmlResourceParser(file, id, value.assetCookie, "drawable")
        ) {
            return Drawable.createFromXmlForDensity(wrapper, rp, density, null);
        }
    }
    
    XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
                                            @NonNull String type)
        throws NotFoundException {
        if (id != 0) {
            try {
                synchronized (mCachedXmlBlocks) {
                    ...
                    final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
                    ...
                    return block.newParser(id); 
                }
            } catch (Exception e) {
                ...
            }
        }
    
        throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
                                    + Integer.toHexString(id));
    }

    最终调用了 AssetManager.openXmlBlockAsset()。

  3. 不是 xml 文件

    调用 Assets.openNonAsset()。

Color 资源加载流程

ContextCompat#getColor -> ContextCompatApi23#getColor -> Context#getColor -> Resources#getColor

java 复制代码
ContextCompat.java
public static final int getColor(Context context, int id) {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 23) {
        return ContextCompatApi23.getColor(context, id);
    } else {
        return context.getResources().getColor(id);
    }
}

ContextCompatApi23.java
public static int getColor(Context context, int id) {
    return context.getColor(id);
}

frameworks/base/core/java/android/content/Context.java
public final int getColor(@ColorRes int id) {
    return getResources().getColor(id, getTheme());
}

调用到了 Resources#getColor

java 复制代码
frameworks/base/core/java/android/content/res/Resources.java

    public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
    final TypedValue value = obtainTempTypedValue();
    try {
        final ResourcesImpl impl = mResourcesImpl;
        impl.getValue(id, value, true);  // 1
        if (value.type >= TypedValue.TYPE_FIRST_INT
            && value.type <= TypedValue.TYPE_LAST_INT) {
            return value.data;
        } else if (value.type != TypedValue.TYPE_STRING) {
            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                                        + " type #0x" + Integer.toHexString(value.type) + " is not valid");
        }

        final ColorStateList csl = impl.loadColorStateList(this, value, id, theme);  // 2
        return csl.getDefaultColor();
    } finally {
        releaseTempTypedValue(value);
    }
}

通过 getValue() 获取相应的 color 资源,并将其保存到 TypedValue 中(注释1)。然后通过 ResourcesImpl.loadColorStateList() 加载,返回颜色状态列表的默认显示颜色(注释2)。

java 复制代码
frameworks/base/core/java/android/content/res/ResourcesImpl.java
    
ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
                                      Resources.Theme theme)
    throws NotFoundException {
    ...
    final long key = (((long) value.assetCookie) << 32) | value.data;
    // Handle inline color definitions.
    if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
        && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
        return getColorStateListFromInt(value, key);  // 1
    }

    ComplexColor complexColor = loadComplexColorFromName(wrapper, theme, value, id);  // 2
    if (complexColor != null && complexColor instanceof ColorStateList) {
        return (ColorStateList) complexColor;
    }
    ...
}

private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
    ColorStateList csl;
    final android.content.res.ConstantState<ComplexColor> factory =
        sPreloadedComplexColors.get(key);
    if (factory != null) {
        return (ColorStateList) factory.newInstance();  // 1.1
    }

    csl = ColorStateList.valueOf(value.data);  // 1.2

    if (mPreloading) {
        if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
                                "color")) {
            sPreloadedComplexColors.put(key, csl.getConstantState());  // 1.3
        }
    }
    return csl;
}

private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
                                              TypedValue value, int id) {
    final long key = (((long) value.assetCookie) << 32) | value.data;
    final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
    ComplexColor complexColor = cache.getInstance(key, wrapper, theme);  // 2.1
    if (complexColor != null) {
        return complexColor;
    }
    int cacheGeneration = cache.getGeneration();

    final android.content.res.ConstantState<ComplexColor> factory =
        sPreloadedComplexColors.get(key);

    if (factory != null) {
        complexColor = factory.newInstance(wrapper, theme);  // 2.2
    }
    if (complexColor == null) {
        complexColor = loadComplexColorForCookie(wrapper, value, id, theme);  // 2.3
    }

    if (complexColor != null) {
        complexColor.setBaseChangingConfigurations(value.changingConfigurations);

        if (mPreloading) {
            if (verifyPreloadConfig(complexColor.getChangingConfigurations(),
                                    0, value.resourceId, "color")) {
                sPreloadedComplexColors.put(key, complexColor.getConstantState());  // 2.4
            }
        } else {
            cache.put(key, theme, complexColor.getConstantState(), cacheGeneration);  // 2.5
        }
    }
    return complexColor;
}

如果当前要获取的颜色类型是 RGB 颜色值(#XXX),则先从预加载数组中取(注释1);如果此时没有加载,则创建新的 ColorStateList(注释1.1),并将其存到预加载数组中(注释1.3)。

如果当前要获取的颜色类型是引用类型,则要从 xml 中取。内部先从缓存数组中取(注释2.1)。如果不存在,从预加载数组中取(注释2.2)。如果仍不存在,则调用 loadComplexColorForCookie() 重新初始化(注释2.3)。加载完成后,如果此时正在预加载(注释2.4),将其添加到预加载数组中,否则添加到缓存中(注释2.5)。

java 复制代码
frameworks/base/core/java/android/content/res/ResourcesImpl.java
    
private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
                                                   Resources.Theme theme) {
    ...
    ComplexColor complexColor = null;
    ...
    if (file.endsWith(".xml")) {
        try {
            final XmlResourceParser parser = loadXmlResourceParser(
                file, id, value.assetCookie, "ComplexColor");  // 1

            final AttributeSet attrs = Xml.asAttributeSet(parser);
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG
                   && type != XmlPullParser.END_DOCUMENT) {
                // Seek parser to start tag.
            }
            if (type != XmlPullParser.START_TAG) {
                throw new XmlPullParserException("No start tag found");
            }

            final String name = parser.getName();  // 2
            if (name.equals("gradient")) {
                complexColor = GradientColor.createFromXmlInner(wrapper, parser, attrs, theme);  // 渐变颜色
            } else if (name.equals("selector")) {
                complexColor = ColorStateList.createFromXmlInner(wrapper, parser, attrs, theme);  // 带状态颜色
            }
            parser.close();
        } catch (Exception e) {
            ...
        }
    } else {
        ...
    }
    ...
    return complexColor;
}

如果资源文件不是 xml 类型,抛出异常;否则调用 loadXmlResourceParser() 拿到该资源文件的 xml 解析器(注释1)。再由解析器的 name 判断具体的资源类型,从而初始化具体的颜色类(注释2)。

  • 总结

    当我们调用 getColor() 获取某个颜色资源时,内部会先通过 AssetManager 加载该资源,并将其保存到 TypedValue 中,如果没有读到,则抛出异常;否则调用 ResoucesImpl.loadColorStateList() 获取颜色资源。如果该资源在缓存中存在,则直接取出并返回新的实例,否则根据当前要加载的类型,如果是 "#xxx" ,则直接初始化并添加到缓存,否则判断 TypedValue 中保存的资源信息后缀是否为 xml ,如果不是则直接抛出异常,证明此时非 .xml 文件,文件无法读取,否则通过 AssetManager 获取该资源对应的xml解析器 ,并判断解析器的名字,从而决定创建 GradientColor 还是 ColorStateList,然后将结果缓存到 ResourcesImpl 中并返回。

应用图标资源加载流程

应用图标加载方式:

  • 方式1
java 复制代码
PackageManager pm = getPackageManager();
PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
ApplicationInfo appInfo = packageInfo.applicationInfo;
Drawable icon = pm.getDrawable(appInfo.packageName, appInfo.icon, null);
  • 方式2
java 复制代码
PackageManager pm = getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
Drawable icon = pm.getApplicationIcon(appInfo);

PackageManager#getApplicationIcon(实现在 ApplicationPackageManager#getApplicationIcon)

-> ApplicationInfo#loadIcon (实现在 PackageItemInfo.loadIcon)

-> PackageManager#loadItemIcon(实现在 ApplicationPackageManager#loadItemIcon)

-> ApplicationPackageManager#loadUnbadgedItemIcon(先获取图标)

-> ApplicationPackageManager#getDrawable

-> ApplicationPackageManager#getUserBadgedIcon(再根据 UserHandle 设置分身图标)

  • 方式3
    Android13 + 时:
java 复制代码
PackageManager pm = getPackageManager();
Drawable icon = pm.getApplicationIcon(packageName);

和方式2类似:

java 复制代码
@Override public Drawable getApplicationIcon(ApplicationInfo info) {
    return info.loadIcon(this);
}

@Override public Drawable getApplicationIcon(String packageName)
    throws NameNotFoundException {
    return getApplicationIcon(getApplicationInfo(packageName, sDefaultFlags));
}
  • 方式4

获取 Activity 图标:

java 复制代码
ComponentName component = new ComponentName(packageName, className);
ActivityInfo activityInfo = context.getPackageManager()
    .getActivityInfo(component, PackageManager.GET_META_DATA);
Drawable icon = activityInfo.loadIcon(context.getPackageManager());

ActivityInfo#loadIcon (实现在 ComponentInfo.loadIcon)

-> ApplicationInfo#loadIcon (实现在 PackageItemInfo.loadIcon)

-> PackageManager#loadItemIcon(实现在 ApplicationPackageManager#loadItemIcon)

-> ApplicationPackageManager#loadUnbadgedItemIcon(先获取图标)

-> ApplicationPackageManager#getDrawable

-> ApplicationPackageManager#getUserBadgedIcon(再根据 UserHandle 设置分身图标)

  • 共同逻辑

四种方式都会调用到 ApplicationPackageManager#getDrawable。先尝试获取缓存,没有缓存再通过 Resources 加载资源。

java 复制代码
public Drawable getDrawable(String packageName, @DrawableRes int resId,
            @Nullable ApplicationInfo appInfo) {
        final ResourceName name = new ResourceName(packageName, resId);
        final Drawable cachedIcon = getCachedIcon(name);
        if (cachedIcon != null) {
            return cachedIcon;
        }
    ...

    if (resId != 0) {
        try {
            final Resources r = getResourcesForApplication(appInfo);
            final Drawable dr = r.getDrawable(resId, null);
            if (dr != null) {
                putCachedIcon(name, dr);
            }
            ...
            return dr;
        }...
    }

    return null;
}

ApplicationPackageManager#getDrawable -> Resources#getDrawable,后续流程同"Drawable 资源加载流程"。

AssetManager

AssetManager 类是 Android 中实际管理资源的模块,在 Java 层和 Native 层都有代码。

java 复制代码
frameworks/base/core/java/android/content/res/AssetManager.java

public final class AssetManager implements AutoCloseable {
    
    @UnsupportedAppUsage
    @GuardedBy("sSync") static AssetManager sSystem = null;

    @GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0];
    @GuardedBy("sSync") private static ArraySet<ApkAssets> sSystemApkAssetsSet;
    
    @UnsupportedAppUsage
    @GuardedBy("this") private long mObject;

    // The loaded asset paths.
    @GuardedBy("this") private ApkAssets[] mApkAssets;
    ...
}
  • sSystem 是和 Resources 类中 mSystem 对应的 AssetManager 对象,用来管理系统 framework 的资源。

  • sSystemApkAssets、sSystemApkAssetsSet 都是和系统资源相关的 ApkAssets 的集合,在 Zygote 初始化时就已经创建完成。

    java 复制代码
    frameworks/base/core/java/android/content/res/ApkAssets.java
        
    public final class ApkAssets {
        @GuardedBy("this")
        private long mNativePtr;
    
        @Nullable
        @GuardedBy("this")
        private final StringBlock mStringBlock; 
        ...
        
        private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
                @Nullable AssetsProvider assets) throws IOException {
            Objects.requireNonNull(path, "path");
            mFlags = flags;
            mNativePtr = nativeLoad(format, path, flags, assets);
            mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
            mAssets = assets;
        }    
    }

    通常一个 ApkAssets 管理的是一个资源包中的资源。ApkAssets 主要是与 native 层进行联系,通过各种 native 调用加载相关资源。另一个功能是把 native 层加载字符串资源保存到 mStringBlock 中。

  • mObject 用来存储在 native 层创建的 AssetManager 对象指针。

  • mApkAssets 记录所有已经加载的 assets 资源,也包括 sSystemApkAssets。

创建 Application Context 的 Resources 部分提到:创建 ResourcesImpl 实例时传入的 AssetManager 是通过 AssetManager.getSystem() 获取的,会加载 framework-res.apk。具体逻辑如下:

java 复制代码
frameworks/base/core/java/android/content/res/AssetManager.java

public static AssetManager getSystem() {
    synchronized (sSync) {
        createSystemAssetsInZygoteLocked(false, FRAMEWORK_APK_PATH);
        return sSystem;
    }
}

public static void createSystemAssetsInZygoteLocked(boolean reinitialize,
            String frameworkPath) {
    if (sSystem != null && !reinitialize) {
        return;
    }

    try {
        final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
        apkAssets.add(ApkAssets.loadFromPath(frameworkPath, ApkAssets.PROPERTY_SYSTEM));  // 1

        final String[] systemIdmapPaths =
            OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote();
        for (String idmapPath : systemIdmapPaths) {
            apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, ApkAssets.PROPERTY_SYSTEM));
        }

        sSystemApkAssetsSet = new ArraySet<>(apkAssets);  // 2
        sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);  // 3
        if (sSystem == null) {
            sSystem = new AssetManager(true /*sentinel*/);  // 4
        }
        sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);  // 5
    } catch (IOException e) {
        throw new IllegalStateException("Failed to create system AssetManager", e);
    }
}

注释1处通过 loadFromPath() 来加载 framework 资源,最终是通过 nativeLoad() native 函数来完成。

注释2、3处分别对 sSystemApkAssetsSet 和 sSystemApkAssets 赋值。

注释4处创建系统专用的 AssetManager 实例,最终调用的是 nativeCreate 函数。

注释5处设置资源到已加载集合,并赋值给 mApkAssets 变量。

  • 加载 framework-res.apk 的入口是 ApkAssets.loadFromPath();

  • 设置资源的是 AssetManager.setApkAssets()。

ApkAssets.loadFromPath()

ApkAssets.loadFromPath() -> new ApkAssets() -> nativeLoad()

cpp 复制代码
frameworks/base/core/jni/android_content_res_ApkAssets.cpp

static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
                        jstring java_path, const jint property_flags, jobject assets_provider) {
    
  ...

  auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);  // 1
  AssetManager2::ApkAssetsPtr apk_assets;  // 2
  switch (format) {
    case FORMAT_APK: {
        auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
                                                  ZipAssetsProvider::Create(path.c_str(),
                                                                            property_flags));
        apk_assets = ApkAssets::Load(std::move(assets), property_flags);
        break;
    }
    case FORMAT_IDMAP:
      apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
      break;
    case FORMAT_ARSC:
      apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
                                        std::move(loader_assets),
                                        property_flags);
      break;
    case FORMAT_DIRECTORY: {
      auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
                                                DirectoryAssetsProvider::Create(path.c_str()));
      apk_assets = ApkAssets::Load(std::move(assets), property_flags);
      break;
    }
    default:
      const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
      jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
      return 0;
  }

  ...
      
  return CreateGuardedApkAssets(std::move(apk_assets));  // 3
}

注释1处使用提供的 assets_provider 创建资源加载器,这个加载器处理资源的加载过程。

注释2处声明一个指针,用于存储加载的 Apk 资源。

接下来根据不同的格式来选择相应的资源加载方式的分支结构。

  • FORMAT_APK:创建一个多资源提供者,它将 loader_assets 与一个 zip 提供者结合,zip 提供者负责从 apk 文件中提取资源,然后使用提供的属性标志加载 apk 资源。
  • FORMAT_IDMAP:将 apk 资源作为覆盖资源加载。
  • FORMAT_ARSC:将 apk 资源作为表格加载。
  • FORMAT_DIRECTORY:创建一个多资源提供者,将 loader_assets 与目录资源提供者结合,然后使用提供的属性标志加载 apk 资源。
  • 默认:如果传入的格式不支持,抛出一个异常,并返回错误信息。

注释3处成功加载资源,返回结果。CreateGuardedApkAssets() 将加载的 apk 资源包装成一个受保护的对象。

以 FORMAT_APK 为例:

cpp 复制代码
frameworks/base/libs/androidfw/ApkAssets.cpp
    
ApkAssetsPtr ApkAssets::Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags) {
  return LoadImpl(std::move(assets), flags, nullptr /* idmap_asset */, nullptr /* loaded_idmap */);
}

调用 LoadImpl() 进行实际的资源加载。

cpp 复制代码
frameworks/base/libs/androidfw/ApkAssets.cpp

ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
                                 package_property_t property_flags,
                                 std::unique_ptr<Asset> idmap_asset,
                                 std::unique_ptr<LoadedIdmap> loaded_idmap) {
  ...
  auto resources_asset = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER,
                                      &resources_asset_exists);  // 1
  ...
  return LoadImpl(std::move(resources_asset), std::move(assets), property_flags,
                  std::move(idmap_asset), std::move(loaded_idmap));  // 2
}

注释1处尝试打开资源文件(kResourcesArsc)并读取资源,如果文件不存在,resources_asset 为 nullptr。

注释2处如果打开资源文件成功,继续调用 LoadImpl 处理后续逻辑,传递资源文件、资源提供者和其他参数。

cpp 复制代码
frameworks/base/libs/androidfw/ApkAssets.cpp

ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
                                 std::unique_ptr<AssetsProvider> assets,
                                 package_property_t property_flags,
                                 std::unique_ptr<Asset> idmap_asset,
                                 std::unique_ptr<LoadedIdmap> loaded_idmap) {
    // 如果assets为nullptr,表示资源加载失败,返回空指针
    if (assets == nullptr ) {
    return {};
  }

  std::unique_ptr<LoadedArsc> loaded_arsc;  // 用于存储加载后的ARSC资源表
  // 如果资源文件(resources_asset)不为空,表示需要加载ARSC资源表
  if (resources_asset != nullptr) {
    // 获取资源文件的数据缓冲区,并确保数据对齐
    const auto data = resources_asset->getIncFsBuffer(true /* aligned */);
    const size_t length = resources_asset->getLength();
    // 如果资源文件读取失败或者数据长度为零,输出错误日志并返回空指针
    if (!data || length == 0) {
      LOG(ERROR) << "Failed to read resources table in APK '" << assets->GetDebugName() << "'.";
      return {};
    }
    // 调用LoadedArsc::Load()加载资源表
    loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
  } else if (loaded_idmap != nullptr &&
      IsFabricatedOverlay(std::string(loaded_idmap->OverlayApkPath()))) {
    // 如果没有资源文件但有已加载的IDMAP,并且该IDMAP是一个FabricatedOverlay(即时生成的运行时资源覆盖),则加载ARSC数据
    loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
  } else {
    // 如果没有资源文件且没有有效的IDMAP,创建一个空的ARSC数据
    loaded_arsc = LoadedArsc::CreateEmpty();
  }

  // 如果加载ARSC资源表失败,输出错误日志并返回空指针
  if (loaded_arsc == nullptr) {
    LOG(ERROR) << "Failed to load resources table in APK '" << assets->GetDebugName() << "'.";
    return {};
  }

  // 最终构造并返回一个ApkAssets对象
  return ApkAssetsPtr::make(PrivateConstructorUtil{}, std::move(resources_asset),
                            std::move(loaded_arsc), std::move(assets), property_flags,
                            std::move(idmap_asset), std::move(loaded_idmap));
}
  • 总结

    ApkAssets 类是 Android 系统中用于表示 Apk 文件资源的底层实现类。它提供了 Apk 文件的不可变内存表示,并与 AssetManager 配合使用,允许高效地访问和共享 Apk 内的资源。其实现主要是本地 C++ 代码,目的是为了提高性能和减少资源消耗。

AssetManager.setApkAssets()
java 复制代码
frameworks/base/core/java/android/content/res/AssetManager.java

public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {
    Objects.requireNonNull(apkAssets, "apkAssets");

    ApkAssets[] newApkAssets = new ApkAssets[sSystemApkAssets.length + apkAssets.length];

    // Copy the system assets first.
    System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length);  // 1

    // Copy the given ApkAssets if they are not already in the system list.
    int newLength = sSystemApkAssets.length;
    for (ApkAssets apkAsset : apkAssets) {  // 2
        if (!sSystemApkAssetsSet.contains(apkAsset)) {
            newApkAssets[newLength++] = apkAsset;
        }
    }

    // Truncate if necessary.
    if (newLength != newApkAssets.length) {
        newApkAssets = Arrays.copyOf(newApkAssets, newLength);
    }

    synchronized (this) {
        ensureOpenLocked();
        mApkAssets = newApkAssets;  // 3
        nativeSetApkAssets(mObject, mApkAssets, invalidateCaches);  // 4
        if (invalidateCaches) {
            // Invalidate all caches.
            invalidateCachesLocked(-1);
        }
    }
}

注释1处把 sSystemApkAssets 中的资源拷贝到一个全新数组 newApkAssets 中。

注释2处检查 sSystemApkAssetsSet 没有的资源,并添加到数组 newApkAssets。

注释3处更新已加载资源集合 mApkAssets。

注释4处调用 nativeSetApkAssets() 处理 native 层的资源。

cpp 复制代码
frameworks/base/core/jni/android_util_AssetManager.cpp

static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
                               jobjectArray apk_assets_array, jboolean invalidate_caches) {
  ...
  auto assetmanager = LockAndStartAssetManager(ptr);
  assetmanager->SetApkAssets(apk_assets, invalidate_caches);
}

NativeSetApkAssets() 函数对 Java 层的数据进行简单处理后,调用了 AssetManager2 的 SetApkAssets() 函数。

cpp 复制代码
frameworks/base/libs/androidfw/AssetManager2.cpp
    
bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
  BuildDynamicRefTable(apk_assets);  // 1
  RebuildFilterList();  // 2
  if (invalidate_caches) {
    InvalidateCaches(static_cast<uint32_t>(-1));
  }
  return true;
}

注释1处构建动态资源引用表。这个函数会把 LoadedArsc 中的 package 提取出来,填充到 package_group 中。

注释2处重新构建符合当前设备的资源,即 filtered_configs_。

相关推荐
2501_915918412 小时前
常见 iOS 抓包工具的使用,从代理抓包、设备抓包到数据流抓包
android·ios·小程序·https·uni-app·iphone·webview
墨月白3 小时前
[QT]QProcess的相关使用
android·开发语言·qt
enbug4 小时前
编译安卓内核:以坚果R1、魔趣MK100(Android 10)系统为例
android·linux
、BeYourself4 小时前
应用专属文件与应用偏好设置(SharedPreferences)
android
2501_948120154 小时前
基于模糊数学的风险评估模型
android
MengFly_4 小时前
Compose 脚手架 Scaffold 完全指南
android·java·数据库
·云扬·5 小时前
MySQL Binlog三种记录格式详解
android·数据库·mysql
月明泉清5 小时前
Android中对于点击事件的深度梳理(二)
android
遇见火星6 小时前
Linux 服务可用性监控实战:端口、进程、接口怎么监控?
android·linux·运维