什么是插件化动态加载apk?

支付宝是万能的,既可以淘票票看电影,又可以买车票,还可以开共享单车,这些都是支付宝的开发人员开发维护的么?显然不是,那么他是怎么做到的呢?是使用了动态加载apk的解决方案。

怎么动态加载apk呢?

支付宝作为一个宿主apk提前将要集成的apk作为一个插件(plugin)下载到本地,然后当使用该plugin(apk)的时候再去加载对应plugin(apk)的资源文件以及对应的native页面。就是不去安装plugin(apk)就可以直接运行该plugin(apk)中的页面。

动态加载plugin(apk)分析

怎么调用一个apk中的页面呢?我们可以动态加载plugin中的文件资源使其以伪宿主身份运行在宿主apk中。以加载一个Activity页面来作为例子。

要让插件中的Activity运行起来,我们可以在宿主中创建一个Activity,然后去手动创建插件中的Acitivity的实例,然后使用宿主apk中Activity的生命周期去调用插件Activity的生命周期,这样就可以让Plugin中的Activity运行起来。

  • Plugin中Activity生命周期的处理

    我们可以在宿主中使用一个特殊的Activity,这个Activity是一个空壳,没有任何页面。但是它有实际的Activity的生命周期,这样我们可以通过这个Activity的生命周期去调用我们自己创建的Plugin中的Activity中的生命周期,实现了Plugin中的Activity的伪生命周期。这个宿主Activity命名为ProxyActivity。

  • Plugin中资源文件的获取

    使用AssetManager去得到Plugin包中的资源文件。

    加载Plugin实现

    第一步 PluginInterface

    我们的宿主要提供一套标准,这套标准用来规范宿主与Plugin之间的上下文以及生命周期关系的标准。我们称之为:PluginInterface。这个标准涉及到Activity生命周期以及上下文,定义如下:

    public interface PluginInterface {

       void onCreate(Bundle saveInstance);

       void attachContext(FragmentActivity context);

       void onStart();

       void onResume();

       void onRestart();

       void onDestroy();

       void onStop();

       void onPause();

    }

    我们新建一个android依赖库plugin,依赖库中只有一个PluginInterface接口,这个interface作为一个依赖库的形式存在于宿主与Plugin中。宿主gradle与plugin gradle都引用这个库。

    compile project(':plugin')

    为了使得编译起来更方便,我这里将宿主apk,插件plugin(项目中称之为otherapk)与依赖库plugin放在同一个项目下,只不过这个项目有两个module。

    第二步 PluginManager

    宿主需要一套工具,来管理加载PluginApk以及获取PluginApk中资源文件,就叫PluginManager。

    获取PluginApk的字节码文件对象

    DexClassLoader是一个类加载器,可以用来从.jar和.apk文件中加载class。可以用来加载执行没用和应用程序一起安装的那部分代码。

    我们要拿到Plugin中的字节码文件对象,需要拿到Plugin对应的DexClassLoader可以使用DexClassLoader的DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)方法。

    dexPath:被解压的apk路径,不能为空。

    optimizedDirectory:解压后的.dex文件的存储路径,不能为空。这个路径强烈建议使用应用程序的私有路径,不要放到sdcard上,否则代码容易被注入攻击。

    libraryPath:os库的存放路径,可以为空,若有os库,必须填写。

    parent:父亲加载器,一般为context.getClassLoader(),使用当前上下文的类加载器。

    获取PluginApk中的Resource

    我们可以使用Resource提供的下面的构造:

    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {

          this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);

      }

    由于要获取PluginApk中的资源,所以这个assets对象应当是PluginApk中的资源对象;而对于一款手机的DisplayMetrics和Configuration来说,无论是宿主还是PluginApk获取的值都是一样的,所以可以使用宿主的值。

    获取AssetManager对象

    public final int addAssetPath(String path) {

           synchronized (this) {

               int res = addAssetPathNative(path);

               makeStringBlocks(mStringBlocks);

               return res;

          }

      }

    这个path也就是PluginApk包在手机中的位置,由于这个方法被hide 了,我们需要使用反射。

    AssetManager assets = AssetManager.class.newInstance();

    //方法名 参数

    Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);

    addAssetPath.invoke(assets, dexPath);

    到这里,成功拿到了PluginApk的DexClassLoader和Resources。

    PluginManager完整代码:

    public class PluginManager {

       public static PluginManager instacne;

       private Context context;

       private DexClassLoader pluginDexClassLoader;

       private Resources pluginResource;

       private PackageInfo pluginPackageArchiveInfo;

       private PluginManager() {

      }

       public static PluginManager getInstacne() {

           if (instacne == null) {

               synchronized (PluginManager.class) {

                   if (instacne == null) {

                       instacne = new PluginManager();

                  }

              }

          }

           return instacne;

      }

       public void setContext(Context context) {

           this.context = context.getApplicationContext();

      }

       public PackageInfo getPluginPackageArchiveInfo() {

           return pluginPackageArchiveInfo;

      }

       public void loadApk(String dexPath) {

           pluginDexClassLoader = new DexClassLoader(dexPath, context.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(), null, context.getClassLoader());

           //拿到别的apk包下的入口Activity

           pluginPackageArchiveInfo = context.getPackageManager().getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);

           AssetManager assets = null;

           try {

               assets = AssetManager.class.newInstance();

               Method addAssetPaht = AssetManager.class.getMethod("addAssetPath", String.class);

               addAssetPaht.invoke(assets,dexPath);

          } catch (InstantiationException e) {

               e.printStackTrace();

          } catch (IllegalAccessException e) {

               e.printStackTrace();

          } catch (NoSuchMethodException e) {

               e.printStackTrace();

          } catch (InvocationTargetException e) {

               e.printStackTrace();

          }

           pluginResource = new Resources(assets, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());

      }

       

       public DexClassLoader getPluginDexClassLoader(){

           return pluginDexClassLoader;

      }

       

       public Resources getPluginResource(){

           return pluginResource;

      }

    }

    第三步 ProxyActivity 代理Activity

    ProxyActivity是宿主的Activity,这个ProxyActivity只是一个空壳,提供一套生命周期和上下文给我们自己创建的PluginActivity的的实例用的。

    我们自己加载的PluginActivity实例只是一个对象,没有任何意义的,要给它套上生命周期,给他的上下文赋值

    具体实现思路

    启动PluginActivity时,先去启动ProxyActivity,然后再ProxyAcitivity中的oCreate方法中去创建PluginActivity的实例,然后去调用PluginActivity的onCreate方法。在ProxyActivity的onResume方法中调用PluginActivity的onResume方法等等。

    记得重写ProxyActivity的getResources,因为这个时候要拿到的getResources是Plugin的

    public class ProxyActivity extends AppCompatActivity {

       private PluginInterface pluginInterface;

       @Override

       protected void onCreate(Bundle savedInstanceState) {

           super.onCreate(savedInstanceState);

           setContentView(R.layout.activity_proxy);

           //拿到要启动的Activity

           String className=getIntent().getStringExtra("className");

           try {

               //加载该Acitivity的字节码对象

               Class aClass = PluginManager.getInstacne().getPluginDexClassLoader().loadClass(className);

               //创建该Activity的实例

               Object newInstance = aClass.newInstance();

               //程序健壮性检查

               if (newInstance instanceof  PluginInterface){

                   pluginInterface= (PluginInterface) newInstance;

                   //将代理Activity的实例传递给三方Activity

                   pluginInterface.attachContext(this);

                   //创建bundle用来与三方apk传输数据

                   Bundle bundle=new Bundle();

                   //调用三方Activity的onCreate

                   pluginInterface.onCreate(bundle);

              }

          } catch (ClassNotFoundException e) {

               e.printStackTrace();

          } catch (InstantiationException e) {

               e.printStackTrace();

          } catch (IllegalAccessException e) {

               e.printStackTrace();

          }

      }

       /**

        * 很关键

        * 三方调用拿到对应加载的三方Resource

        * @return

        */

       public Resources getResource(){

           return PluginManager.getInstacne().getPluginResource();

      }

       public void startActivity(Intent intent){

           Intent newIntent=new Intent(this,ProxyActivity.class);

           newIntent.putExtra("className",intent.getComponent().getClassName());

           super.startActivity(newIntent);

      }

       @Override

       protected void onStart() {

           pluginInterface.onStart();

           super.onStart();

      }

       @Override

       protected void onResume() {

           pluginInterface.onResume();

           super.onResume();

      }

       @Override

       protected void onPause() {

           pluginInterface.onPause();

           super.onPause();

      }

       @Override

       protected void onStop() {

           pluginInterface.onStop();

           super.onStop();

      }

       @Override

       protected void onDestroy() {

           pluginInterface.onDestory();

           super.onDestroy();

      }

       @Override

       protected void onRestart() {

           pluginInterface.onRestart();

           super.onRestart();

      }

    }

    第四步 PluginApk的BaseActivity的构建

    public class BaseActivity extends AppCompatActivity implements PluginInterface{

       //这里命名为protected 以便于子类使用

       protected AppCompatActivity thisContex;

       @Override

       public void onCreate(Bundle bundle) {

      }

       @Override

       public void setContentView(@LayoutRes int layoutResID) {

           thisContex.setContentView(layoutResID);

      }

       @Override

       public void setContentView(View view) {

           thisContex.setContentView(view);

      }

       @Override

       public void setContentView(View view, ViewGroup.LayoutParams params) {

           thisContex.setContentView(view, params);

      }

       @NonNull

       @Override

       public LayoutInflater getLayoutInflater() {

           return thisContex.getLayoutInflater();

      }

       @Override

       public Window getWindow() {

           return thisContex.getWindow();

      }

       @Override

       public View findViewById(@IdRes int id) {

           return thisContex.findViewById(id);

      }

       @Override

       public void attachContext(AppCompatActivity context) {

           thisContex=context;

      }

       @Override

       public ClassLoader getClassLoader() {

           return thisContex.getClassLoader();

      }

       @Override

       public WindowManager getWindowManager() {

           return thisContex.getWindowManager();

      }

       @Override

       public ApplicationInfo getApplicationInfo() {

           return thisContex.getApplicationInfo();

      }

       @Override

       public void finish() {

           thisContex.finish();

      }

       @Override

       public void onStart() {

      }

       @Override

       public void onResume() {

      }

       @Override

       public void onPause() {

      }

       @Override

       public void onStop() {

      }

       @Override

       public void onDestory() {

      }

       @Override

       public void onRestart() {

      }

       @Override

       protected void onSaveInstanceState(Bundle outState) {

           

      }

       @Override

       public boolean onTouchEvent(MotionEvent event) {

           return false;

      }

       @Override

       public void onBackPressed() {

           thisContex.onBackPressed();

      }

       @Override

       public void startActivity(Intent intent) {

           thisContex.startActivity(intent);

      }

    }

    PluginMainActivity

    public class PluginMainActivity extends BaseActivity implements View.OnClickListener {

       @Override

       public void onCreate(Bundle savedInstanceState) {

           super.onCreate(savedInstanceState);

           setContentView(R.layout.activity_plugin_main);

           findViewById(R.id.btn).setOnClickListener(this);

      }

       @Override

       public void onClick(View view) {

           startActivity(new Intent(thisContext,SecondActivity.class));

      }

    }

    在宿主中启动PluginMainActivity

    public class MainActivity extends AppCompatActivity {

       @Override

       protected void onCreate(Bundle savedInstanceState) {

           super.onCreate(savedInstanceState);

           setContentView(R.layout.activity_main);

      }

       public void loadPlugin(View view){

             ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE},100);

      }

       public void startPlugin(View view){

           Intent intent=new Intent(this,ProxyActivity.class);

           String otherapkName=PluginManager.getInstance().getPluginPackageAricheInfo().activities[0].name;

           intent.putExtra("className",otherapkName);

           startActivity(intent);

      }

       @Override

       public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

           super.onRequestPermissionsResult(requestCode, permissions, grantResults);

           PluginManager.getInstance().setContext(this);

           PluginManager.getInstance().loadApk(Environment.getExternalStorageDirectory().getAbsolutePath()+"/pluginapk-debug.apk");

      }

    }

    最后

    加权限

    把pluginapk-debug.apk放在sd卡中,实际是下载完放在sd 某个位置,先加载插件,后运行。搞定。就能打开插件中的secondActivity。

    github地址:GitHub - walkingCoder/PluginDemo: 插件化apk,小试

    精彩内容

    评论可见,请评论后查看内容,谢谢!!!
     您阅读本篇文章共花了: