0%

VirtualAPK加载插件资源

解决插件资源生效问题,首先需要看看系统创建资源

1.资源创建流程

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, ...) {
//...
//省略了部分源码
Resources resources = packageInfo.getResources(mainThread);
mResources = resources;
//...
//省略了部分源码
}


public Resources getResources() {
return mResources;
}
```


继续跟进
```java
public final class LoadedApk {
private final String mResDir;

public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
CompatibilityInfo compatInfo, ClassLoader baseLoader,
boolean securityViolation, boolean includeCode, boolean registerPackage) {
final int myUid = Process.myUid();
aInfo = adjustNativeLibraryPaths(aInfo);

mActivityThread = activityThread;
mApplicationInfo = aInfo;
mPackageName = aInfo.packageName;
mAppDir = aInfo.sourceDir;
mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
// 注意一下这个sourceDir,这个是我们宿主的APK包在手机中的路径,宿主的资源通过此地址加载。
// 该值的生成涉及到PMS,暂时不进行分析。
// Full path to the base APK for this application.
//....
}



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

```java
public final class ActivityThread {
Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
//我们暂时只关注下面这一段代码

AssetManager assets = new AssetManager();
if (assets.addAssetPath(resDir) == 0) { //此处将上面的mResDir,也就是宿主的APK在手机中的路径当做资源包添加到AssetManager里,则Resources对象可以通过AssetManager查找资源,此处见(老罗博客:Android应用程序资源的查找过程分析)
return null;
}
// 创建Resources对象,此处依赖AssetManager类来实现资源查找功能。
r = new Resources(assets, metrics, getConfiguration(), compInfo);

}
}
```

2. 上面分析完Resources的创建流程,现在继续看如何使加载插件资源
```java
private static Resources createResources(Context context, File apk) {
if (Constants.COMBINE_RESOURCES) {
//如果插件资源合并到宿主里面去的情况,插件可以访问宿主的资源
Resources resources = new ResourcesManager().createResources(context, apk.getAbsolutePath());
ResourcesManager.hookResources(context, resources);
return resources;
} else {
//插件使用独立的Resources,不与宿主有关系,无法访问到宿主的资源
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
}

(1)如果是独立的资源, 只需要重新创建新的资源,不用管宿主资源
(2)如果是插件资源需要使用宿主资源,就需要添加宿主资源和所有的插件资源

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
class ResourcesManager {

public static synchronized Resources createResources(Context hostContext, String apk) {
Resources hostResources = hostContext.getResources();
Resources newResources = null;
AssetManager assetManager;
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
assetManager = AssetManager.class.newInstance();
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", hostContext.getApplicationInfo().sourceDir);
} else {
assetManager = hostResources.getAssets();
}
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk);
List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
for (LoadedPlugin plugin : pluginList) {
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", plugin.getLocation());
}
if (isMiUi(hostResources)) {
newResources = MiUiResourcesCompat.createResources(hostResources, assetManager);
} else if (isVivo(hostResources)) {
newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager);
} else if (isNubia(hostResources)) {
newResources = NubiaResourcesCompat.createResources(hostResources, assetManager);
} else if (isNotRawResources(hostResources)) {
newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager);
} else {
// is raw android resources
newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
} catch (Exception e) {
e.printStackTrace();
}

return newResources;

}

(1)这里针对21一下版本和21以上版本做了区分,问题在于21以下版本在addPath之前,AssetManager中的资源路径已经初始化,再次获取的时候将不会改变。
(2)这里还有个疑问,针对api21以上,AssetManager使用的同一个引用,为什么需要每次重新添加之前的所有插件资源?像官方提过问题,一直没回复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ResTable* AssetManager::getResTable(bool required) const
{
ResTable* rt = mResources;
if (rt) {
return rt;
}

// Iterate through all asset packages, collecting resources from each.

AutoMutex _l(mLock);

if (mResources != NULL) {
return mResources;
}

//下面是为null的时候重新全部初始化addPath进来的文件资源
}

mResources指向的是一个ResTable对象,如果它的值不等于NULL,那么就说明当前应用程序已经解析过它使用的资源包里面的resources.arsc文件,因此,这时候AssetManager类的成员函数getResources就可以直接将该ResTable对象返回给调用者。如果还没有初始化 mResources 则按照一定步骤遍历当前应用所使用的每个资源包进而生成 mResources 。 所以在 Android L 之前是需要想办法构造一个新的AssetManager里的 mResources 才行,这里有两种方案,VirtualAPK 用的是类似 InstantRun 的那种方案,构造一个新的AssetManager,将宿主和加载过的插件的所有 apk 全都添加一遍,然后再调用hookResources方法将新的 Resources 替换回原来的,这样会引起两个问题,一个是每次加载新的插件都会重新构造一个 AssetManger 和 Resources,然后重新添加所有资源,这样涉及到很多机型的兼容(因为部分厂商自己修改了 Resources 的类名),一个是需要有一个替换原来Resources的过程,这样就需要涉及到很多地方,从hookResources的实现里看,替换了四处地方,在尽量少的 hook 原则下这样的情况还是尽量避免的。
另外还有一种方案,可以看下VirtualAPK 资源篇

回到上面createResources, 解决完AssetManager的问题然后创建Resources, 然后针对国产手机做兼容性处理

3.使资源生效,通俗点就是替换Context中的Resource

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
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
cl.loadClass(className);
} catch (ClassNotFoundException e) {
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
String targetClassName = PluginUtil.getTargetActivity(intent);

Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName));

if (targetClassName != null) {
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);

try {
// for 4.1+
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());//注意此处的代码
} catch (Exception ignored) {
// ignored.
}

return activity;
}
}

return mBase.newActivity(cl, className, intent);
}

这部分的处理在很多插件框架中都有存在,也就是之前对于21版本以下。这里对资源做了一次赋值的缘由需要探讨一下。
跟踪到新建Activity对象的地方,也就是出现问题的地方,这里通过4.4的代码解释一下,后面版本的代码虽然变化大但也会出现问题,追溯到底的原因是一样的。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//.....
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}

try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);

if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());

if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);//出现问题的地点
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);

if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}

activity.mCalled = false;
mInstrumentation.callActivityOnCreate(activity, r.state);//此处调用了onCreate方法,如果不进行资源处理则在此出现找不到资源的crash
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
if (!r.activity.mFinished) {
activity.mCalled = false;
mInstrumentation.callActivityOnPostCreate(activity, r.state);
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPostCreate()");
}
}
}
r.paused = true;

mActivities.put(r.token, r);

} catch (SuperNotCalledException e) {
throw e;

} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}

return activity;
}


private Context createBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
appContext.setOuterContext(activity);

// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
Context baseContext = appContext;

return baseContext;
}
```
系统在创建完Activity对象后,紧接着创建Activity所附着的Context,从最上面的创建Resources 一部分内容可知,在createBaseContextForActivity 方法中创建出来的ContextImpl appContext 使用的是宿主的Resources,如果不进行处理紧接着Activity会走入
onCreate的生命周期中,此时插件加载资源的时候还是使用的宿主的资源,而不是我们特意为插件所创建出来的Resources对象,则会发生找不到资源的问题,这里用了一个很机智的方式解决这个问题。

@Override
```java
public Resources getResources() {
if (mResources != null) {//我们提前设置了mResources 所以不会走到下面去
return mResources;
}
if (mOverrideConfiguration == null) {
mResources = super.getResources();
return mResources;
} else {
Context resc = createConfigurationContext(mOverrideConfiguration);
mResources = resc.getResources();
return mResources;
}
}

其实在于Resource中是通过AssetManager获取资源的,对于Api21以下,每次添加插件资源的时候都会创建一个新的AssetManager,而且保存在一个新的Resource中。在API21以上,AssetManager使用的是同一个引用,在添加插件资源的时候,是往同一个AssetManager中添加,所以并不会有这个问题。

4.Service中的资源处理
LocalService.onStartCommand(Intent intent, int flags, int startId)中(为什么在这里后面分析四大组件的时候会讲)

1
2
3
4
5
6
7
8
	 Application app = plugin.getApplication();
IBinder token = appThread.asBinder();
Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);
IActivityManager am = mPluginManager.getActivityManager();

IActivityManager am = mPluginManager.getActivityManager();

attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);

这里将之前创建插件Context放置到Service中,下面继续看看插件Context的创建

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class PluginContext extends ContextWrapper {

private final LoadedPlugin mPlugin;

public PluginContext(LoadedPlugin plugin) {
super(plugin.getPluginManager().getHostContext());
this.mPlugin = plugin;
}

@Override
public Context getApplicationContext() {
return this.mPlugin.getApplication();
}

@Override
public ApplicationInfo getApplicationInfo() {
return this.mPlugin.getApplicationInfo();
}

private Context getHostContext() {
return getBaseContext();
}

@Override
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}

@Override
public ClassLoader getClassLoader() {
return this.mPlugin.getClassLoader();
}

@Override
public String getPackageName() {
return this.mPlugin.getPackageName();
}

@Override
public String getPackageResourcePath() {
return this.mPlugin.getPackageResourcePath();
}

@Override
public String getPackageCodePath() {
return this.mPlugin.getCodePath();
}

@Override
public PackageManager getPackageManager() {
return this.mPlugin.getPackageManager();
}

@Override
public Object getSystemService(String name) {
// intercept CLIPBOARD_SERVICE,NOTIFICATION_SERVICE
if (name.equals(Context.CLIPBOARD_SERVICE)) {
return getHostContext().getSystemService(name);
} else if (name.equals(Context.NOTIFICATION_SERVICE)) {
return getHostContext().getSystemService(name);
}

return super.getSystemService(name);
}

@Override
public Resources getResources() {
return this.mPlugin.getResources();
}

@Override
public AssetManager getAssets() {
return this.mPlugin.getAssets();
}

@Override
public Resources.Theme getTheme() {
return this.mPlugin.getTheme();
}

@Override
public void startActivity(Intent intent) {
ComponentsHandler componentsHandler = mPlugin.getPluginManager().getComponentsHandler();
componentsHandler.transformIntentToExplicitAsNeeded(intent);
super.startActivity(intent);
}
}

其实就是创建了一个代理Context, 获取的资源等其他都是从插件Resource中获取。

5.BroadcastReceiver
这一点比较奇怪,BroadcastReceiver中的资源并未替换,那在Api24以下是否存在问题,这个我已经向官方反馈。

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
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity receiver : this.mPackage.receivers) {
receivers.put(receiver.getComponentName(), receiver.info);

try {
BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
this.mHostContext.registerReceiver(br, aii);
}
} catch (Exception e) {
e.printStackTrace();
}
}

//改成下面这样 应该就没问题

Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity receiver : this.mPackage.receivers) {
receivers.put(receiver.getComponentName(), receiver.info);
try {
final BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
br.onReceive(getPluginContext(), intent);
}
};
for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
this.mHostContext.registerReceiver(broadcastReceiver, aii);
}
} catch (Exception e) {
e.printStackTrace();
}
}