源码探索系列18---关于资源的加载与匹配的资源机制

enter image description here

今天我们来聊聊那个我们加载系统资源的问题。

说的就是我们放在res目录下的内容,系统是如何获取的。

getResources().getString(R.string.app_name);

起航

API:23

我们先从这个getResource()函数开始说起

@Override
public Resources getResources() {
    if (mResources != null) {
        return mResources;
    }
    if (mOverrideConfiguration == null) {
        mResources = super.getResources();
        return mResources;
    } else {
        Context resc = createConfigurationContext(mOverrideConfiguration);
        mResources = resc.getResources();
        return mResources;
    }
}

这里一般情况下是返回mResource的,因为这个变量在创建我们的Activity的时候会配套一个Context,在这个context里面,mResource会跟着被初始化。
如果没有下面的也会去创建一个createConfigurationContext(mOverrideConfiguration);,两者最后到会调用到同一个函数去。
不过,不知道你对前面的文章介绍Activity的启动过程有没映像,里面就有提到,这里我们回忆下。在ActivityThreadperformLaunchActivity()里面

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        if (activity != null) {
               //下面这句重要
            Context appContext = createBaseContextForActivity(r, activity);
          ...
    return activity;
}

压缩到上面重点的一句

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
        int displayId = Display.DEFAULT_DISPLAY;
        try {
            displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
        } catch (RemoteException e) {
        }
        //下面这句重要
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;

        ...
        return baseContext;
    }


static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {

        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

        return new ContextImpl(null, mainThread, packageInfo, null, null, false,
                null, overrideConfiguration, displayId);
    }

重要内容来了,他去new一个context,前面我们已经知道这个Context的具体实现是contextImpl

private ContextImpl(ContextImpl container, ActivityThread mainThread,
        LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
        Display display, Configuration overrideConfiguration, int createDisplayWithId) {
    mOuterContext = this;

    mMainThread = mainThread;
    ...
    mPackageInfo = packageInfo;
    mResourcesManager = ResourcesManager.getInstance();

    ...
    //   重要的一句
    Resources resources = packageInfo.getResources(mainThread);
    if (resources != null) {
        if (displayId != Display.DEFAULT_DISPLAY
                || overrideConfiguration != null
                || (compatInfo != null && compatInfo.applicationScale
                        != resources.getCompatibilityInfo().applicationScale)) {
            resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
                    packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
                    packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
                    overrideConfiguration, compatInfo);
        }
    }
    mResources = resources;

    ...
}

是吧,我们看到了。

我们继续看生成的具体内容

public Resources getResources(ActivityThread mainThread) {
    if (mResources == null) {

        mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);            
    }
    return mResources;
}

跑到了那个ActivitThread来了,但通过这里我们可以看到一些信息,他会配置一些资源位置,包信息,屏幕配置信息等。所以我们的resource可以根据配置的信息获取特定的内容。

Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
        String[] libDirs, int displayId, Configuration overrideConfiguration,
        LoadedApk pkgInfo) {
    return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
            displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
}

然后我们去ResourcesManager里面看看。

Resources getTopLevelResources(String resDir, String[] splitResDirs,
        String[] overlayDirs, String[] libDirs, int displayId,
        Configuration overrideConfiguration, CompatibilityInfo compatInfo) {

    final float scale = compatInfo.applicationScale;
    Configuration overrideConfigCopy = (overrideConfiguration != null)
            ? new Configuration(overrideConfiguration) : null;
    ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);


    Resources r;        
     //判断是否存在了
    synchronized (this) { 
        ...
        WeakReference<Resources> wr = mActiveResources.get(key);
        r = wr != null ? wr.get() : null;
        ...
        if (r != null && r.getAssets().isUpToDate()) {                
            return r;
        }
    }
    ...
    AssetManager assets = new AssetManager();

    //添加了我们的资源的路径啦!
    if (resDir != null) {
        if (assets.addAssetPath(resDir) == 0) {  
            return null;
        }
    }

    //添加我们的Asset路径
    if (splitResDirs != null) {
        for (String splitResDir : splitResDirs) {
            if (assets.addAssetPath(splitResDir) == 0) { 
                return null;
            }
        }
    }

     //这个是关于系统资源的         
    /**
     * Add a set of assets to overlay an already added set of assets. 
     * This is only intended for application resources. System wide resources
     * are handled before any Java code is executed. 
     */
    if (overlayDirs != null) {
        for (String idmapPath : overlayDirs) {
            assets.addOverlayPath(idmapPath);
        }
    }
    //这个是我们的库文件
    if (libDirs != null) {
        for (String libDir : libDirs) {
            if (libDir.endsWith(".apk")) {
                // Avoid opening files we know do not have resources,
                // like code-only .jar files.
                if (assets.addAssetPath(libDir) == 0) {
                    Log.w(TAG, "Asset path '" + libDir +
                            "' does not exist or contains no resources.");
                }
            }
        }
    }
     //哎呀,这个就贼熟悉了,每次获取屏幕大小都靠他了。
    DisplayMetrics dm = getDisplayMetricsLocked(displayId);
    Configuration config; //这个负责保存我们的设备配置信息
    final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    final boolean hasOverrideConfig = key.hasOverrideConfiguration();
    if (!isDefaultDisplay || hasOverrideConfig) {
        config = new Configuration(getConfiguration());
        if (!isDefaultDisplay) {
            applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
        }
        if (hasOverrideConfig) {
            config.updateFrom(key.mOverrideConfiguration);
            if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
        }
    } else {
        config = getConfiguration();
    }
        //最压轴的一句来了
    r = new Resources(assets, dm, config, compatInfo);

     // 保存下,配合我们开头的获取,为何是用弱引用呢?
    synchronized (this) {
        WeakReference<Resources> wr = mActiveResources.get(key);
        Resources existing = wr != null ? wr.get() : null;
        if (existing != null && existing.getAssets().isUpToDate()) { 
            ...
            r.getAssets().close();
            return existing;
        }

        // XXX need to remove entries when weak references go away
        mActiveResources.put(key, new WeakReference<>(r));
        ...          
        return r;
    }
   }

好了,我们简单的看完了这个获取resource的过程,不过其中那个AssetManager里面是关于Native层的,就不深入看了。
我们继续去看下Resources的构造函数

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config){

    this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
}

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
        CompatibilityInfo compatInfo) {
    mAssets = assets;
    mMetrics.setToDefaults();
    if (compatInfo != null) {
        mCompatibilityInfo = compatInfo;
    }
    updateConfiguration(config, metrics);
    assets.ensureStringBlocks();
}

我们看下那个更新配置函数里面的内容

public void updateConfiguration(Configuration config,
        DisplayMetrics metrics) {
    updateConfiguration(config, metrics, null);
}


public void updateConfiguration(Configuration config,
        DisplayMetrics metrics, CompatibilityInfo compat) {
    synchronized (mAccessLock) {
        ...
        if (compat != null) {
            mCompatibilityInfo = compat;
        }
        //我们的屏幕信息在这里设置的
        if (metrics != null) {
            mMetrics.setTo(metrics); 
        }            
        mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
        ...

        //我们的dpi信息!还有当初转换格式时候用到的density。
        if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
            mMetrics.densityDpi = mConfiguration.densityDpi;
            mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
        }
        mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;

        // 我们的语言设置
        String locale = null;
        if (mConfiguration.locale != null) {
            locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag());
        }

        //计算屏幕高宽,时常用到啊,
        final int width, height;
        if (mMetrics.widthPixels >= mMetrics.heightPixels) {
            width = mMetrics.widthPixels;
            height = mMetrics.heightPixels;
        } else {
            //noinspection SuspiciousNameCombination
            width = mMetrics.heightPixels;
            //noinspection SuspiciousNameCombination
            height = mMetrics.widthPixels;
        }
        ...
        // 这个配置的参数看起来还挺多的啊...
        mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
                locale, mConfiguration.orientation,
                mConfiguration.touchscreen,
                mConfiguration.densityDpi, mConfiguration.keyboard,
                keyboardHidden, mConfiguration.navigation, width, height,
                mConfiguration.smallestScreenWidthDp,
                mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
                mConfiguration.screenLayout, mConfiguration.uiMode,
                Build.VERSION.RESOURCES_SDK_INT);

        }

        mDrawableCache.onConfigurationChange(configChanges);
        mColorDrawableCache.onConfigurationChange(configChanges);
        mColorStateListCache.onConfigurationChange(configChanges);
        mAnimatorCache.onConfigurationChange(configChanges);
        mStateListAnimatorCache.onConfigurationChange(configChanges);

    }
    ...
}

好啦,这样这个就这么结束啦!
另外需要说的是这个后面的AssetManger的一堆配置参数的作用,他会跑去和底层C++部分中去,这样我们要获取资源的时候,Native层就会根据这个配置信息寻找最合适的资源返回给我们。

通过上面这些步骤,我们有一点需要特别说的是,我们可以靠AssetMangeraddAssetPath的方法来添加APK路径,从而达到一些资源替换或者换皮肤的效果。这个就是为什么很多程序通过他来快速的换皮肤的原因!

当然他的效果远不止这个,看到有人利用他来实现插件化,动态加载

例如主席写的DynamicLoadApk

他的原理就用是 DexClassLoader 加 Activity 代理的方案。
即在容器中注册代理的Activity,启动插件的Activity时实际启动的都是代理的 Activity,这样来绕过Activity必须注册的问题。
我相信在看我们的Activity启动的过程已经知道没注册会报什么错误了把?不过目前这个框架还不完美,还有些问题需要解决。

现在12点多了了,前段时间熬夜写得尽情,现在觉得很累!
早点休息,明天有空再写点内容!再写估计又到两点多!

好咯,晚安!

热评文章