今天我们来聊聊那个我们加载系统资源的问题。
说的就是我们放在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的启动过程
有没映像,里面就有提到,这里我们回忆下。在ActivityThread
的performLaunchActivity()
里面
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层就会根据这个配置信息寻找最合适的资源返回给我们。
通过上面这些步骤,我们有一点需要特别说的是,我们可以靠AssetManger
的addAssetPath
的方法来添加APK路径,从而达到一些资源替换
或者换皮肤
的效果。这个就是为什么很多程序通过他来快速的换皮肤的原因!
当然他的效果远不止这个,看到有人利用他来实现插件化,动态加载!
例如主席写的DynamicLoadApk
他的原理就用是 DexClassLoader 加 Activity 代理的方案。
即在容器中注册代理的Activity,启动插件的Activity时实际启动的都是代理的 Activity,这样来绕过Activity必须注册的问题。
我相信在看我们的Activity启动的过程已经知道没注册会报什么错误了把?不过目前这个框架还不完美,还有些问题需要解决。
现在12点多了了,前段时间熬夜写得尽情,现在觉得很累!
早点休息,明天有空再写点内容!再写估计又到两点多!
好咯,晚安!