插件化

本文最后更新于:1 年前

ClassLoader 加载 dex 文件

继承关系

BootClassLoader:
加载 Android Framework 层的 class 文件。

PathClassLoader:
加载 dex 文件,包括 jar、zip、apk 中的 dex 文件。

DexClassLoader:
加载 dex 文件,包括 jar、zip、apk 中的 dex 文件。

ClassLoader 加载 Class 的流程

双亲委托机制:
首先检测这个类是否已经被加载了,如果已经加载了,直接获取并返回。如果没有被加载,当 parent 不为 null 时,调用 parent 的 loadClass() 进行加载;当 parent 为 null 时,调用 findClass() 方法,依次递归,如果找到了或者加载了就返回,如果既没找到也加载不了,才自己去加载。

BootClassLoader#findClass() 是直接调用了 Class.classForName() 方法;
DexClassLoader#findClass() 是加载传入的 APK 文件中的所有 dexFile 到 dexElements 数组,然后再调用 Element#findClass() 查找 dexFile 中是否包含指定的 class。


执行插件中的方法

原理

1、创建插件的 DexClassLoader 类加载器,然后通过反射获取插件的 dexElements 值;

1
2
DexClassLoader
----dexElements[]

2、获取宿主的 PathClassLoader 类加载器,然后通过反射获取宿主的 dexElements 值;

1
2
3
BaseDexClassLoader
----pathList
--------dexElements[]

3、合并宿主的 dexElements 与插件的 dexElements,生成新的 Element[];
4、最后通过反射将新的 Element[] 赋值给宿主的 dexElements。

核心代码

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
private boolean loadPluginDex(String apkPath) {
try {
// 1.获取 pathList 的字段
Class baseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = baseDexClassLoader.getDeclaredField("pathList");
pathListField.setAccessible(true);

// 2.获取 DexClassLoader 类中的属性 pathList 的值
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, getCacheDir().getAbsolutePath(), null, getClassLoader());
Object pluginPathList = pathListField.get(dexClassLoader);

// 3.获取 pathList 中的属性 dexElements[] 的值--- 插件的 dexElements[]
Class pluginPathListClass = pluginPathList.getClass();
Field pluginDexElementsField = pluginPathListClass.getDeclaredField("dexElements");
pluginDexElementsField.setAccessible(true);
Object[] pluginDexElements = (Object[]) pluginDexElementsField.get(pluginPathList);

// 4.获取 PathClassLoader 类中的属性 pathList 的值
PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();
Object hostPathList = pathListField.get(pathClassLoader);

// 5.获取 pathList 中的属性 dexElements[] 的值--- 宿主的 dexElements[]
Class hostPathListClass = hostPathList.getClass();
Field hostDexElementsField = hostPathListClass.getDeclaredField("dexElements");
hostDexElementsField.setAccessible(true);
Object[] hostDexElements = (Object[]) hostDexElementsField.get(hostPathList);

// 6.创建一个新的空数组,第一个参数是数组的类型,第二个参数是数组的长度
Object[] dexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), pluginDexElements.length + hostDexElements.length);

// 7.将插件和宿主的 dexElements[] 的值放入新的数组中
System.arraycopy(pluginDexElements, 0, dexElements, 0, pluginDexElements.length);
System.arraycopy(hostDexElements, 0, dexElements, pluginDexElements.length, hostDexElements.length);

hostDexElementsField.set(hostPathList, dexElements);

return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

private String loadPluginMethod() {
try {
Class<?> threadClazz = Class.forName("io.weichao.plugin.util.PluginUtil");
Method method = threadClazz.getMethod("getMessage");
return (String) method.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

启动插件中的 Activity

关键难点——无法通过 AMS 检测

插件的 Activity 没有在宿主的清单文件中注册。

启动 Activity 流程

绕过 AMS 检测——hook

hook:
劫持,改变代码的正常执行流程。

1、在宿主中创建一个 ProxyActivity 继承自 Activity,并且在 manifest 中注册;
2、当启动插件的 Activity 时,在 AMS 检测前,找到一个 hook 点,然后通过 hook 将启动插件 Activity 的 Intent 替换成启动 ProxyActivity 的 Intent;

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
// API 26、28
Activity#startActivity()
----startActivityForResult()
Instrumentation#execStartActivity()
----ActivityManager.getService().startActivity(..., intent, ...) // ActivityManager.getService() 是 AMS 检测前的最后的 hook 机会,ActivityManager.getService() 的返回值是 IActivityManager,所以需要对 IActivityManager 进行动态代理,hook IActivityManager#startActivity()

// 1. IActivityManager 可使用 IActivityManagerSingleton 获取
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}

// 2. IActivityManagerSingleton 在 ActivityManager 中是 static,反射时容易获取
private static final Singleton<IActivityManager> IActivityManagerSingleton = new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};

// 3. IActivityManager 在 Singleton 中以变量 mInstance 存在
public abstract class Singleton<T> {
private T mInstance;

protected abstract T create();

public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}

// 总结:反射获取 ActivityManager 中的 IActivityManagerSingleton,再获取 IActivityManagerSingleton 中的 mInstance,这个 mInstance 本质是 IActivityManager,对其进行动态代理



// API 23、24、25
Activity#startActivity()
----startActivityForResult()
Instrumentation#execStartActivity()
----ActivityManagerNative.getDefault().startActivity(..., intent, ...) // API 23 不同点,ActivityManagerNative.getDefault() 返回值是 IActivityManager,所以还是动态代理 IActivityManager,但是获取 IActivityManager 的方法不一样

// 1. IActivityManager 可使用 gDefault 获取:
static public IActivityManager getDefault() {
return gDefault.get();
}

// 2. gDefault 在 ActivityManagerNative 中是 static,反射时容易获取
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};

// 3. IActivityManager 在 Singleton 中以变量 mInstance 存在,和前面一样

// 总结:反射获取 ActivityManagerNative 中的 gDefault,再获取 gDefault 中的 mInstance,这个 mInstance 本质是 IActivityManager,对其进行动态代理

3、在 AMS 检测完后,找到一个 hook 点,然后通过 hook 将启动 ProxyActivity 的 Intent 替换成启动插件 Activity 的 Intent;

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
// API 26 及以前
ActivityStackSupervisor#realStartActivityLocked()
ActivityThread#scheduleLaunchActivity(intent, ...)
----sendMessage(H.LAUNCH_ACTIVITY, r) // r 含 Intent
Handler#dispatchMessage(msg) // msg.obj = r
----callback(msg)
Callback
----mCallback // 劫持 mCallback,修改 Intent

// 1. Handler 在 ActivityThread 中以 mH 存在,所以需要先拿到 ActivityThread
final H mH = new H();

// 2. ActivityThread 中存在 sCurrentActivityThread,且是 static,反射时容易获取
private static volatile ActivityThread sCurrentActivityThread;

// 3. 拿到 mH 以后,给 mH 设置新的 Callback,重写其 handleMessage(),将 msg.obj 中的 Intent 改为插件的 Intent



// API 28
ActivityStackSupervisor#realStartActivityLocked()
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent), ...) // API 28 不同点
ClientTransaction
----List<ClientTransactionItem> // 包含 Intent
mService.getLifecycleManager().scheduleTransaction(clientTransaction)
ActivityThread#scheduleLaunchActivity(clientTransaction)
----sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, clientTransaction) // API 28 不同点
Handler#dispatchMessage(msg) // msg.obj = clientTransaction
----callback(msg)
Callback
----mCallback // 劫持 mCallback,修改 Intent

// 1. 和上面一样,Handler 在 ActivityThread 中以 mH 存在,所以需要先拿到 ActivityThread

// 2. 和上面一样,ActivityThread 中存在 sCurrentActivityThread,且是 static,反射时容易获取

// 3. 拿到 mH 以后,给 mH 设置新的 Callback,重写其 handleMessage(),这里 msg.what 变了,同时 msg.obj 变了,不是 r 了,而是 clientTransaction,通过反射获取 clientTransaction 中的 mActivityCallbacks,修改 Intent

4、启动插件的 Activity。

核心代码

将启动插件 Activity 的 Intent 替换成启动 ProxyActivity 的 Intent

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
try {
// 获取 Singleton<T> 类的对象
Object singleton = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Class<?> clazz = Class.forName("android.app.ActivityManager");
Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
singleton = singletonField.get(null);
} else {
Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
Field singletonField = clazz.getDeclaredField("gDefault");
singletonField.setAccessible(true);
singleton = singletonField.get(null);
}

// 获取 mInstance 对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstance = mInstanceField.get(singleton);

// 创建 IActivityManager 的代理对象
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* int result = ActivityManager.getService()
* .startActivity(whoThread, who.getBasePackageName(), intent,
* intent.resolveTypeIfNeeded(who.getContentResolver()),
* token, target != null ? target.mEmbeddedID : null,
* requestCode, 0, null, options);
*/
// 当执行的方法是 startActivity 时作处理
if ("startActivity".equals(method.getName())) {
int index = 0;

// 获取插件的 intent
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
Intent intent = (Intent) args[index];

// 创建代理的 intent
Intent proxyIntent = new Intent();
proxyIntent.setClassName("io.weichao.plugin_demo", ProxyActivity.class.getName());
// 保存插件的 intent 到代理的 intent 中
proxyIntent.putExtra(TARGET_INTENT, intent);

// 将插件的 intent 替换为代理的 intent
args[index] = proxyIntent;
}

// IActivityManager 对象 --- 通过反射
return method.invoke(mInstance, args);
}
});

// 使用代理对象替换原有的 mInstance 对象
mInstanceField.set(singleton, proxyInstance);
} catch (Exception e) {
e.printStackTrace();
Log.e("aaaaa", "aaaaa" + e.getMessage());
}

将启动 ProxyActivity 的 Intent 替换成启动插件 Activity 的 Intent

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
try {
// 获取 ActivityThread 对象
Class<?> clazz = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object activityThread = sCurrentActivityThreadField.get(null);

// 获取 Handler 对象
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(activityThread);

// 创建一个 Callback 替换系统的 Callback 对象
Class<?> handlerClass = Class.forName("android.os.Handler");
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 100:
try {
// 获取保存在代理的 intent 中的插件的 intent
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(msg.obj);
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
// 判断调用的是否是插件的,如果不是插件的,intent 就会为空
if (intent != null) {
intentField.set(msg.obj, intent);
}
} catch (Exception e) {
e.printStackTrace();
Log.e("aaaaa", "aaaaa" + e.getMessage());
}
break;
case 159:
try {
Class<?> clazz = Class.forName("android.app.servertransaction.ClientTransaction");
Field mActivityCallbacksField = clazz.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);

List activityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < activityCallbacks.size(); i++) {
if (activityCallbacks.get(i).getClass().getName() .equals("android.app.servertransaction.LaunchActivityItem")) {
Object launchActivityItem = activityCallbacks.get(i);
Field mIntentField = launchActivityItem.getClass().getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);
// 判断调用的是否是插件的,如果不是插件的,intent 就会为空
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
mIntentField.set(launchActivityItem, intent);
}
break;
}
}
} catch (Exception e) {
e.printStackTrace();
Log.e("aaaaa", "aaaaa" + e.getMessage());
}
break;
}
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
Log.e("aaaaa", "aaaaa" + e.getMessage());
}

启动插件的 Activity

1
2
3
Intent intent = new Intent();
intent.setComponent(new ComponentName("io.weichao.plugin", "io.weichao.plugin.activity.MainActivity"));
startActivity(intent);

让插件中的 Activity 加载插件中的资源

前期铺垫

1、加载 res 中的资源是通过 Resources,但是最后也是调用 AssetManager,只是会先通过 id 查找。AssetManager 可以加载未编译过的资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getResources().getString(R.string.app_name);

// Resources
public String getString(@StringRes int id) throws NotFoundException {
return getText(id).toString();
}

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

2、启动 Activity 时会调用 ActivityThread#performLaunchActivity(),此时建立了 Resources、AssetManager 和 Context 的绑定关系,并且指定了资源目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ContextImpl appContext = createBaseContextForActivity(r);
...
activity.attach(appContext, ...);
...
return activity;
}

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
...
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
...
return appContext;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ContextImpl
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
...
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, activityToken, null, 0, classLoader);
...
// Context 绑定 Resources
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
...
return 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
// ResourcesManager
public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
...
return getOrCreateResources(activityToken, key, classLoader);
}

private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
...
ResourcesImpl resourcesImpl = createResourcesImpl(key);
...
}

private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
...
final AssetManager assets = createAssetManager(key);
...
// Resources 绑定 AssetManager
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
...
return impl;
}

protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
AssetManager assets = new AssetManager();
if (key.mResDir != null) {
// 调用 AssetManager#addAssetPath() 添加资源目录
if (assets.addAssetPath(key.mResDir) == 0) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
}
...
}

原理

  1. 创建 AssetManager,反射 addAssetPath 方法设置插件的资源目录;
  2. 创建 Resources,绑定该 AssetManager;
  3. 在 Application 中重写 getResources(),使用该 Resources;
  4. 在插件的 Activity 中重写 getResources(),使用 Application 中的 Resources。

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// LoadUtil.java
public static Resources loadResource(Context context) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.setAccessible(true);
// 参数就是插件的资源路径
addAssetPathMethod.invoke(assetManager, apkPath);
Resources resources = context.getResources();
return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyApplication extends Application {
private Resources resources;

@Override
public void onCreate() {
super.onCreate();
resources = LoadUtil.loadResource(this);
}

@Override
public Resources getResources() {
return resources == null ? super.getResources() : resources;
}
}
1
2
3
4
5
6
7
8
9
10
public class MainActivity extends Activity {
@Override
public Resources getResources() {
if (getApplication() != null && getApplication().getResources() != null) {
return getApplication().getResources();
}

return super.getResources();
}
}

当宿主的 Activity 和插件的 Activity 都继承于 AppCompatActivity 时会报错

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
java.lang.RuntimeException: Unable to start activity ComponentInfo{io.weichao.plugin/io.weichao.plugin.activity.MainActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'void androidx.appcompat.widget.DecorContentParent.setWindowCallback(android.view.Window$Callback)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void androidx.appcompat.widget.DecorContentParent.setWindowCallback(android.view.Window$Callback)' on a null object reference
at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:753)
at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:659)
at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:552)
at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
at io.weichao.plugin.activity.MainActivity.onCreate(MainActivity.java:15)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

报错的原因

当宿主和插件都使用 AppCompatActivity 时,由于编译时会将资源建立映射关系,在宿主和插件中分别建立了 1 次映射关系,且这 2 次映射关系基本上不会相同。在宿主中 key1 -> value1,在插件中 key2 -> value2。
但是 AppCompatDelegateImpl 类只会在宿主中被加载一次,也就是宿主和插件都使用 key1 查找资源。

解决办法

让插件使用自己的 Resource。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MainActivity extends AppCompatActivity {
protected Context mContext;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Resources resource = LoadUtil.getResource(getApplication());
mContext = new ContextThemeWrapper(getBaseContext(), 0);
Class<? extends Context> clazz = mContext.getClass();
try {
Field mResourcesField = clazz.getDeclaredField("mResources");
mResourcesField.setAccessible(true);
mResourcesField.set(mContext, resource);
} catch (Exception e) {
e.printStackTrace();
}

View view = LayoutInflater.from(mContext).inflate(R.layout.activity_main, null);
setContentView(view);
}
}


插件化
https://weichao.io/0fe2eafd78be/
作者
魏超
发布于
2020年4月9日
更新于
2022年12月4日
许可协议