Android 6.0 (API23) Resources管理源码分析

这篇文章写的时候,Android已经迭代到10.0了 那为什么还要去看6.0的源码呢

因为从6.0以后Google对Resource这一块的代码进行了一次比较大的重构迭代

因此如果要想了解这一块的知识,或者说利用这些知识开发一些功能,那么6.0的源码还是要了解一下的

众所周知 在Anrdoid中获取Resource一般使用的context.getResource()方法

而无论是Application还是Activity,其Context的最终实现类都是ContextImpl

于是我们从ContextImpl开始看起

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        return new ContextImpl(null, mainThread,
                packageInfo, null, null, false, null, null, Display.INVALID_DISPLAY);
    }

    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);
    }

以上两个方法完成了Applicaiton和Activity的Context创建

在Context创建之初,Resource就已经创建完成

来看代码

    private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
        mOuterContext = this;
        mMainThread = mainThread;
        mActivityToken = activityToken;
        mRestricted = restricted;
        if (user == null) {
            user = Process.myUserHandle();
        }
        mUser = user;
        mPackageInfo = packageInfo;
        mResourcesManager = ResourcesManager.getInstance();
        final int displayId = (createDisplayWithId != Display.INVALID_DISPLAY)? createDisplayWithId
                : (display != null) ? display.getDisplayId() : Display.DEFAULT_DISPLAY;
        CompatibilityInfo compatInfo = null;
        if (container != null) {
            compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();
        }
        if (compatInfo == null) {
            compatInfo = (displayId == Display.DEFAULT_DISPLAY)
                    ? packageInfo.getCompatibilityInfo()
                    : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
        }
        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
        mDisplayAdjustments.setConfiguration(overrideConfiguration);
        mDisplay = (createDisplayWithId == Display.INVALID_DISPLAY) ? display
                : ResourcesManager.getInstance().getAdjustedDisplay(displayId, mDisplayAdjustments);
        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;
        if (container != null) {
            mBasePackageName = container.mBasePackageName;
            mOpPackageName = container.mOpPackageName;
        } else {
            mBasePackageName = packageInfo.mPackageName;
            ApplicationInfo ainfo = packageInfo.getApplicationInfo();
            if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) {
                mOpPackageName = ActivityThread.currentPackageName();
            } else {
                mOpPackageName = mBasePackageName;
            }
        }

        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
    }

主要是代码中标红加粗的部分

现从packageInfo.getResources(mainThread); 说起

packageInfo 其实就是LoadedApk

public Resources getResources(ActivityThread mainThread) {
    if (mResources == null) {
        mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
    }
    return mResources;
}

再看ActivityThread

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());
}

其实调用的都是mResourcesManager.getTopLevelResources 

Android M 的ResourcesManager写的比较简单

其内部有一个Resource缓存

getTopLevelResource 方法会使用传入的参数 组装一个key

ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);

使用这个key去缓存里面找,找到了就拿出来用,

WeakReference<Resources> wr = mActiveResources.get(key);
找不到就新创建一个assets 来生成一个Resource实例

AssetManager assets = new AssetManager();
if (resDir != null) {
    if (assets.addAssetPath(resDir) == 0) {
        return null;
    }
}
if (splitResDirs != null) {
    for (String splitResDir : splitResDirs) {
        if (assets.addAssetPath(splitResDir) == 0) {
            return null;
        }
    }
}
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.");
            }
        }
    }
}

缓存的另一个作用就是configuration变化的时候 可以从缓存里面找到所有当前正在激活状态的Resource

并且调用这些Resource的

public void updateConfiguration(Configuration config,
                                DisplayMetrics metrics, CompatibilityInfo compat) {

方法,最终生效的是对Resource中的mAssets的configuration

再来看一下Resource.java

其核心包含两个部分

1:封装Assets,讲所有资源调用最终都是调用到mAssets的方法

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

2:提供缓存

private static final LongSparseArray<ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<ConstantState> sPreloadedColorDrawables = new LongSparseArray<>();
private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>> sPreloadedColorStateLists = new LongSparseArray<>();
private final DrawableCache mDrawableCache = new DrawableCache(this);
private final DrawableCache mColorDrawableCache = new DrawableCache(this);
private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache = new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache = new ConfigurationBoundResourceCache<>(this);

将从mAsserts中取出的大资源进行缓存,避免读取耗时和内存占用

思考:

在传统的换肤教程中,对系统资源的hook主要是替换Activity或者Application的mResource

而生成这个用于替换的Resource的方式是直接new Resource(asserts)

从源码上面来看这种方式肯定是有问题的,如果直接这样不通过ResourceManger来创建Resource,

那么后续的config变化,都无法通知到Resource

发布了24 篇原创文章 · 获赞 3 · 访问量 6133

猜你喜欢

转载自blog.csdn.net/binghelonglong123/article/details/99179450