巧用createPackageContext,实现插件化

2,181 阅读2分钟

XWalk 浏览器内核实现的插件化,只需安装一份,就可为其他APP提供兼容性浏览器控件,可在安卓4.4版本、或其他版本的定制机上,运行版本较高的webview。相比x5内核,这种插件化才是我想要的,而不要像x5那样,每个调用x5内核的APP都会复制多达一百多兆的文件、用动态加载apk/dex的方式实现谷歌所不允许的插件化(也只有国内规矩多多多的市场们才会允许)。

我曾观摩过xwalk的源码。其实,观看源码并非什么高大上的事情,Java层多为透明,IDEA中直接可以用鼠标点进去,即使是编译好的jar文件,也可以查看反编译后的源码、查找引用、进行包内的全文搜索等等。看到XWalk是用 createPackageContext 方法实现的插件化,早就想着学习一番,而今得空,就来试它一试。

编写、安装插件APK

项目文件github地址

链接的是之前文章里编写的黑暗模式切换按钮,实现了月亮变成太阳、太阳变成月亮的切换动画,直接拿来插件化,作为本文演示的插件项目。

简记项目构建过程:用 Android Studio 新建工程,模板用空Activity,包名是com.knziha.plugin101。将上一篇文章中的代码粘贴至目录树,然后在主界面xml中引用此按钮。

调用插件APK

参考   项目文件github地址

在onCreate中调用插件包,动态创建黑暗模式切换按钮(DarkToggleButton),并嵌入至主界面:

反射创建插件中的视图

findViewById(android.R.id.content).postDelayed(() -> {
   TextView txvA = findViewById(R.id.textview_first);
   try {
      String pluginPkg = "com.knziha.plugin101";
      Context context = createPackageContext(pluginPkg, Context.CONTEXT_INCLUDE_CODE
            | Context.CONTEXT_IGNORE_SECURITY);
      Class<?> cls = context.getClassLoader().loadClass(pluginPkg+".R"); // 获得目标apk的R类
      txvA.setText(context.getResources().getText(getResourseIdByName(cls, "string", "message")));
      
      Class<?> darkToggleClass = context.getClassLoader().loadClass(pluginPkg+".DarkToggleButton");
      Constructor<?> cons = darkToggleClass.getConstructor(Context.class, AttributeSet.class);
      View darkToggleBtn = (View) cons.newInstance(this, null);
      ((ViewGroup)txvA.getParent()).addView(darkToggleBtn);
      
   } catch (Exception e) {
      e.printStackTrace();
   }
}, 200);

简记项目构建过程:用 Android Studio 新建工程,模板选基础活动。在主活动的onCreate末尾粘贴以上代码。

反射调用插件类的方法

接口:

public interface DarkToggleInterface {
   void toggle();
}
public class DarkToggle implements DarkToggleInterface{
   final Object mDelegate;
   final Method mToggle;
   
   public DarkToggle(Object mDelegate) throws NoSuchMethodException {
      this.mDelegate = mDelegate;
      mToggle = mDelegate.getClass().getMethod("toggle");
   }
   
   public void toggle() {
      try {
         mToggle.invoke(mDelegate);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

调用:

try {
   DarkToggle darkToggle = new DarkToggle(darkToggleBtn);
   darkToggle.toggle();
} catch (Exception e) {
   throw new RuntimeException(e);
}