安卓资源管理机制

95 阅读3分钟

在activity中获取资源的方式是getResource()方法

activity的getresource会进入父类ContexThemeWrapper中

  @Override
    public Resources getResources() {
        return getResourcesInternal();
    }

    private Resources getResourcesInternal() {
        if (mResources == null) {
            if (mOverrideConfiguration == null) {
                mResources = super.getResources();
            } else {
                final Context resContext = createConfigurationContext(mOverrideConfiguration);
                第一步获取context
                 mResources = resContext.getResources();
                第二步resource是context中获取的
            }
        }
        return mResources;
    }

接下来

 @Override
    public Context createConfigurationContext(Configuration overrideConfiguration) {
        return mBase.createConfigurationContext(overrideConfiguration);
    }

回到mBase中,我们知道mBase是contextImpl对象

 public Context createConfigurationContext(Configuration overrideConfiguration) {
        if (overrideConfiguration == null) {
            throw new IllegalArgumentException("overrideConfiguration must not be null");
        }

        if (mForceDisplayOverrideInResources) {
            // Ensure the resources display metrics are adjusted to match the display this context
            // is based on.
            Configuration displayAdjustedConfig = new Configuration();
            displayAdjustedConfig.setTo(mDisplay.getDisplayAdjustments().getConfiguration(),
                    ActivityInfo.CONFIG_WINDOW_CONFIGURATION, 1);
            displayAdjustedConfig.updateFrom(overrideConfiguration);
            overrideConfiguration = displayAdjustedConfig;
        }

        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams,
                mAttributionSource.getAttributionTag(),
                mAttributionSource.getNext(),
                mSplitName, mToken, mUser, mFlags, mClassLoader, null);
        context.mIsConfigurationBasedContext = true;

        final int displayId = getDisplayId();
        final Integer overrideDisplayId = mForceDisplayOverrideInResources
                ? displayId : null;
        context.setResources(createResources(mToken, mPackageInfo, mSplitName, overrideDisplayId,
                overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
                mResources.getLoaders()));
此处设置activity的的resource。
setresource的对象是createResources方法创建的,接下来看看。


        return context;
    }

  private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
            @Nullable Integer overrideDisplayId, Configuration overrideConfig,
            CompatibilityInfo compatInfo, List<ResourcesLoader> resourcesLoader) {
        final String[] splitResDirs;
        final ClassLoader classLoader;
        try {
            splitResDirs = pi.getSplitPaths(splitName);
            classLoader = pi.getSplitClassLoader(splitName);
        } catch (NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        return ResourcesManager.getInstance().getResources(activityToken,
                pi.getResDir(),
                splitResDirs,
                pi.getOverlayDirs(),
                pi.getOverlayPaths(),
                pi.getApplicationInfo().sharedLibraryFiles,
                overrideDisplayId,
                overrideConfig,
                compatInfo,
                classLoader,
                resourcesLoader);
    }

通过ResourcesManager创建ResourceManger对象是单例的。在ActivityThread创建的时候执行第一次创建

 ActivityThread() {
        mResourcesManager = ResourcesManager.getInstance();
    }

由此我们知道当应用第一次创建的时候创建activityThread并创建resourceManger就加载了此应用的资源。。

接下来看下resourceManger.getResouce()

    @Nullable
    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) {
      ....

            Resources resources;
   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]));
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
            resourceKey是有资源目录,显示屏,横竖屏,等等创建的
            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);
            }
            return resources;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

以上通过createResouces方法创建

   @Nullable
    private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
            @Nullable ApkAssetsSupplier apkSupplier) {
        synchronized (mLock) {
            if (DEBUG) {
                Throwable here = new Throwable();
                here.fillInStackTrace();
                Slog.w(TAG, "!! Create resources for key=" + key, here);
            }

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

            return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
        }
    }
 private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
            @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {

此处查找或者生成resource
        ResourcesImpl impl = findResourcesImplForKeyLocked(key);
        if (impl == null) {
没有查找到是第一次用此资源
            impl = createResourcesImpl(key, apkSupplier);
            if (impl != null) {
存放到缓存里面
                mResourceImpls.put(key, new WeakReference<>(impl));
            }
        }
        return impl;
    }

 private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        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);
        final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
此处创建了resourceImpl
        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

接下来我们看获取应用的字符串方法

getResource().getText()方法,

  @NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                + Integer.toHexString(id));
    }

由此方法可知,获取字体的方法又assetManager调用。其实resourcesImpl所有的获取资源的方法都是由assetManager调用的

我们看下resourcesImpl的assetManager哪里来的

 private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        final AssetManager assets = createAssetManager(key, apkSupplier);
此处得到assetManager
        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);
        final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);

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

接下来

   private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        final AssetManager.Builder builder = new AssetManager.Builder();
此处生成了用了builder模式生成了builder
        final ArrayList<ApkKey> apkKeys = extractApkKeys(key);

遍历apkkey添加到builderfor (int i = 0, n = apkKeys.size(); i < n; i++) {
            final ApkKey apkKey = apkKeys.get(i);
            try {
添加apk的资源
                builder.addApkAssets(
                        (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
            } catch (IOException e) {
                if (apkKey.overlay) {
                    Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
                } else if (apkKey.sharedLib) {
                    Log.w(TAG, String.format(
                            "asset path '%s' does not exist or contains no resources",
                            apkKey.path), e);
                } else {
                    Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
                    return null;
                }
            }
        }

        if (key.mLoaders != null) {
            for (final ResourcesLoader loader : key.mLoaders) {
                builder.addLoader(loader);
            }
        }

        return builder.build();
    }

接下来我们看下apk的资源都放在哪吧

  private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
        final ArrayList<ApkKey> apkKeys = new ArrayList<>();

        // resDir can be null if the 'android' package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the 'android' package
        // already.
        if (key.mResDir != null) {
            apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
        }
第一项 res目录
        if (key.mSplitResDirs != null) {
第二项 splitRes目录
            for (final String splitResDir : key.mSplitResDirs) {

                apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
            }
        }

        if (key.mLibDirs != null) {
第三项 lib的资源目录,比如so库,等等。
            for (final String libDir : key.mLibDirs) {
                // Avoid opening files we know do not have resources, like code-only .jar files.
                if (libDir.endsWith(".apk")) {
                    apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
                }
            }
        }

        if (key.mOverlayPaths != null) {
第四项 mOverlayPaths的资源目录            for (final String idmapPath : key.mOverlayPaths) {
                apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
            }
        }

        return apkKeys;
    }

上述方法把应用的资源包路径放在assetManger路径下,接下来看下assetmanager的build方法。

        public AssetManager build() {
            // Retrieving the system ApkAssets forces their creation as well.
            final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
系统apk的资源
            // Filter ApkAssets so that assets provided by multiple loaders are only included once
            // in the AssetManager assets. The last appearance of the ApkAssets dictates its load
            // order.
            final ArrayList<ApkAssets> loaderApkAssets = new ArrayList<>();
            final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
            for (int i = mLoaders.size() - 1; i >= 0; i--) {
                final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets();
                for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
                    final ApkAssets apkAssets = currentLoaderApkAssets.get(j);
                    if (uniqueLoaderApkAssets.add(apkAssets)) {
                        loaderApkAssets.add(0, apkAssets);
                    }
                }
            }

            final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size()
                    + loaderApkAssets.size();
            final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];

            System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);

            // Append user ApkAssets after system ApkAssets.
            for (int i = 0, n = mUserApkAssets.size(); i < n; i++) {
                apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
            }

            // Append ApkAssets provided by loaders to the end.
            for (int i = 0, n = loaderApkAssets.size(); i < n; i++) {
                apkAssets[i + systemApkAssets.length  + mUserApkAssets.size()] =
                        loaderApkAssets.get(i);
            }
以上讲路径加入金apkseets
            // Calling this constructor prevents creation of system ApkAssets, which we took care
            // of in this Builder.
            final AssetManager assetManager = new AssetManager(false /*sentinel*/);
            assetManager.mApkAssets = apkAssets;
            AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
                    false /*invalidateCaches*/);
            assetManager.mLoaders = mLoaders.isEmpty() ? null
                    : mLoaders.toArray(new ResourcesLoader[0]);

            return assetManager;
        }

此处就创建了assetmanager。

gettext方法。由assetmanager

 @UnsupportedAppUsage
    @Nullable CharSequence getResourceText(@StringRes int resId) {
        synchronized (this) {
            final TypedValue outValue = mValue;
            if (getResourceValue(resId, 0, outValue, true)) {
                return outValue.coerceToString();
            }
            return null;
        }
    }
 boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
            boolean resolveRefs) {
        Objects.requireNonNull(outValue, "outValue");
        synchronized (this) {
            ensureValidLocked();
            final int cookie = nativeGetResourceValue(
                    mObject, resId, (short) densityDpi, outValue, resolveRefs);
            if (cookie <= 0) {
                return false;
            }

            // Convert the changing configurations flags populated by native code.
            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                if ((outValue.string = getPooledStringForCookie(cookie, outValue.data)) == null) {
                    return false;
                }
            }
            return true;
        }
    }

此后native调用从  arsc文件中获取资源。

assetManager传入自定义包的地址可以做插件化获取apk中的资源。此处不多说了