Android 插件化学习之路(六)之动态创建 Activity
静态代理 Activity 模式的限制
在之前的代理 Activity 模式文章中,我们讨论了启动插件 APK 中 Activity 的两个核心难题。由于插件中的 Activity 未在主项目的 Manifest 中注册,无法经历系统 Framework 层级的一系列初始化过程,最终导致获得的 Activity 实例没有生命周期且无法使用资源文件。
虽然使用代理 Activity 能够解决这两个问题,但它存在一些明显的限制:
- 实际运行的 Activity 实例都是 ProxyActivity,并非真正想要启动的 Activity;
- ProxyActivity 只能指定一种 LaunchMode,因此插件里的 Activity 无法自定义 LaunchMode;
- 不支持静态注册的 BroadcastReceiver;
- 往往不是所有的 APK 都可作为插件被加载,插件项目需要依赖特定的框架,并且需要遵循一定的'开发规范'。
特别是最后一点,无法直接把一个普通的 APK 作为插件使用。插件的 Activity 不是标准的 Activity 对象才会有这些限制,使其成为标准的 Activity 是解决问题的关键。而要使其成为标准的 Activity,则需要在主项目里注册这些 Activity。
想到代理模式需要注册一个代理的 ProxyActivity,那么能不能在主项目里注册一个通用的 Activity(比如 TargetActivity)给插件里所有的 Activity 用呢?解决对策就是,在需要启动插件的某一个 Activity(比如 PlugActivity)的时候,动态创建一个 TargetActivity。新创建的 TargetActivity 会继承 PlugActivity 的所有共有行为,而这个 TargetActivity 的包名与类名刚好与我们事先注册的 TargetActivity 一致,我们就能以标准的方式启动这个 Activity。
动态创建 Activity 模式
运行时动态创建并编译一个 Activity 类,这种想法并非天方夜谭。动态创建类的工具包括 DexMaker 等,二者均能实现动态字节码操作。最大的区别在于前者是创建 dex 文件,而后者通常涉及 class 文件的生成。这里我们主要探讨使用 DexMaker 在运行时创建一个编译好并能运行的类,这被称为'动态字节码操作(runtime bytecode manipulation)'。
使用 DexMaker 动态创建一个类
我们可以使用 DexMaker 工具创建一个 dex 文件,之后反编译这个 dex 看看创建出来的类是什么样子。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onMakeDex(View view) {
try {
DexMaker dexMaker = new DexMaker();
TypeId<?> helloWorld = TypeId.get();
dexMaker.declare(helloWorld, , Modifier.PUBLIC, TypeId.OBJECT);
generateHelloMethod(dexMaker, helloWorld);
(Environment.getExternalStorageDirectory() + File.separator + );
(!outputDir.exists()) outputDir.mkdir();
dexMaker.generateAndLoad(.getClassLoader(), outputDir);
Class<?> helloWorldClass = loader.loadClass();
helloWorldClass.getMethod().invoke();
} (Exception e) {
Log.e(, , e);
}
}
{
TypeId<System> systemType = TypeId.get(System.class);
TypeId<PrintStream> printStreamType = TypeId.get(PrintStream.class);
declaringType.getMethod(TypeId.VOID, );
dexMaker.declare(hello, Modifier.STATIC | Modifier.PUBLIC);
Local<Integer> a = code.newLocal(TypeId.INT);
Local<Integer> b = code.newLocal(TypeId.INT);
Local<Integer> c = code.newLocal(TypeId.INT);
Local<String> s = code.newLocal(TypeId.STRING);
Local<PrintStream> localSystemOut = code.newLocal(printStreamType);
code.loadConstant(a, );
code.loadConstant(b, );
code.op(BinaryOp.SUBTRACT, c, a, b);
MethodId<Integer, String> toHexString
= TypeId.get(Integer.class).getMethod(TypeId.STRING, , TypeId.INT);
code.invokeStatic(toHexString, s, c);
FieldId<System, PrintStream> systemOutField = systemType.getField(printStreamType, );
code.sget(systemOutField, localSystemOut);
MethodId<PrintStream, Void> printlnMethod = printStreamType.getMethod(
TypeId.VOID, , TypeId.STRING);
code.invokeVirtual(printlnMethod, , localSystemOut, s);
code.returnVoid();
}
}

