2BAB's Engineering Blog

Android插件化笔记-4-LoadPluginResource

Demo: https://github.com/2BAB/Android-Plugin-Dev-Notes

插件化的资源加载大体上也分两种:

  • 每个插件构造单一的 Resource 对象,各个插件的资源互不影响
  • 所有插件的资源都加载到一个 AssetManager,全局可用,但是会出现资源 ID 冲突的现象,必须在打包流程中做修改

本节以构造单一对象为基础讲解,资源冲突的问题和方案下节讲。

资源的寻找过程

在Activity中的getResources()方法会走到ContextWrapper的实现上,而ContextWrapper顾名思义它只是一个包装类,最终的调用是ContextWrapper的实际类ContextImpl中的方法。

ContextImpl中getResources()方法返回了它的成员变量mResource,我们看一下ContextImpl的构造函数,其中mResources被第一次赋值是通过下面的函数调用:

1
Resources resources = packageInfo.getResources(mainThread);

packageInfo是一个LoadedApk类型的参数,mainThread是ActivityThread类型的参数,mainThread就是当前Apk运行的主进程类,我们继续看LoadedApk中的方法:

1
2
3
4
5
6
7
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
}
1
2
3
4
5
6
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(), null);
}

mResourceManager是一个ResourceManager类型的成员变量,当我们戳开ResourceManager的代码时,惊喜的发现这个类是一个单例,然后定位到getTopLevelResources方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
Resources r;
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;
}
}
r = new Resources(assets, dm, config, compatInfo, token);
WeakReference<Resources> wr = mActiveResources.get(key);
Resources existing = wr != null ? wr.get() : null;
if (existing != null && existing.getAssets().isUpToDate()) {
r.getAssets().close();
return existing;
}
mActiveResources.put(key, new WeakReference<Resources>(r));
return r;

至此,我们知道了资源的真正载入和管理是由 AssetManager 来实现的,那么 Hook 点也知道了——修改 ContextImpl 的 Resource 对象所持有的 AssetManager。

Hook

在 Plugin 的 Activity 里实现资源加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class MainActivity extends AppCompatActivity {
private Resources pluginR;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 如果不 Hook mResource,也可以直接 getPluginR 来获取 values 的资源,但是无法装载 Layout
// getPluginR().getString(R.string.plugin_string_res); //
// Fragment 或者 自定义 View 等需要自己 Inflate 的也支持
/*int bundleLayoutId = R.layout.activity_main;
View bundleView = LayoutInflater.from(this).inflate(bundleLayoutId, null);
setContentView(bundleView);*/
setContentView(R.layout.activity_main);
}
@Override
protected void attachBaseContext(Context newBase) {
try {
Field field = newBase.getClass().getDeclaredField("mResources");
field.setAccessible(true);
field.set(newBase, getPluginR(newBase));
} catch (Exception e) {
e.printStackTrace();
}
super.attachBaseContext(newBase);
}
public Resources getPluginR(Context context) {
if (pluginR != null) {
return pluginR;
}
try {
String dexPath = "/system/dex/" + "4-Plugin.apk";
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath);
pluginR = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
//独立使用Resource时(不hook mResource)
//Resources origin = super.getResources();
//pluginR = new Resources(assetManager, origin.getDisplayMetrics(), origin.getConfiguration());
return pluginR;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

参考资料

本系列为笔记文,文中有大量的源码解析都是引用的其他作者的成果,详见下方参考资料。

讨论请发邮件到 xx2bab@gmail.com
自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0