当前位置:   article > 正文

Android插件化动态加载apk_android 动态加载apk

android 动态加载apk
 

什么是插件化动态加载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运行起来。

<li>Plugin中Activity生命周期的处理

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

<li>Plugin中资源文件的获取

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

加载Plugin实现

第一步 PluginInterface

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

  1. public interface PluginInterface {
  2.    void onCreate(Bundle saveInstance);
  3.    void attachContext(FragmentActivity context);
  4.    void onStart();
  5.    void onResume();
  6.    void onRestart();
  7.    void onDestroy();
  8.    void onStop();
  9.    void onPause();
  10. }

我们新建一个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可以使用DexClassLoaderDexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)方法。

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

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

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

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

获取PluginApk中的Resource

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

  1. public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
  2.       this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
  3.   }

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

获取AssetManager对象

  1. public final int addAssetPath(String path) {
  2.        synchronized (this) {
  3.            int res = addAssetPathNative(path);
  4.            makeStringBlocks(mStringBlocks);
  5.            return res;
  6.       }
  7.   }

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

  1. AssetManager assets = AssetManager.class.newInstance();
  2. //方法名 参数
  3. Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
  4. addAssetPath.invoke(assets, dexPath);

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

PluginManager完整代码:

  1. public class PluginManager {
  2.    public static PluginManager instacne;
  3.    private Context context;
  4.    private DexClassLoader pluginDexClassLoader;
  5.    private Resources pluginResource;
  6.    private PackageInfo pluginPackageArchiveInfo;
  7.    private PluginManager() {
  8.   }
  9.    public static PluginManager getInstacne() {
  10.        if (instacne == null) {
  11.            synchronized (PluginManager.class) {
  12.                if (instacne == null) {
  13.                    instacne = new PluginManager();
  14.               }
  15.           }
  16.       }
  17.        return instacne;
  18.   }
  19.    public void setContext(Context context) {
  20.        this.context = context.getApplicationContext();
  21.   }
  22.    public PackageInfo getPluginPackageArchiveInfo() {
  23.        return pluginPackageArchiveInfo;
  24.   }
  25.    public void loadApk(String dexPath) {
  26.        pluginDexClassLoader = new DexClassLoader(dexPath, context.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(), null, context.getClassLoader());
  27.        //拿到别的apk包下的入口Activity
  28.        pluginPackageArchiveInfo = context.getPackageManager().getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
  29.        AssetManager assets = null;
  30.        try {
  31.            assets = AssetManager.class.newInstance();
  32.            Method addAssetPaht = AssetManager.class.getMethod("addAssetPath", String.class);
  33.            addAssetPaht.invoke(assets,dexPath);
  34.       } catch (InstantiationException e) {
  35.            e.printStackTrace();
  36.       } catch (IllegalAccessException e) {
  37.            e.printStackTrace();
  38.       } catch (NoSuchMethodException e) {
  39.            e.printStackTrace();
  40.       } catch (InvocationTargetException e) {
  41.            e.printStackTrace();
  42.       }
  43.        pluginResource = new Resources(assets, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
  44.   }
  45.    
  46.    public DexClassLoader getPluginDexClassLoader(){
  47.        return pluginDexClassLoader;
  48.   }
  49.    
  50.    public Resources getPluginResource(){
  51.        return pluginResource;
  52.   }
  53. }

第三步 ProxyActivity 代理Activity

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

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

具体实现思路

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

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

  1. public class ProxyActivity extends AppCompatActivity {
  2.    private PluginInterface pluginInterface;
  3.    @Override
  4.    protected void onCreate(Bundle savedInstanceState) {
  5.        super.onCreate(savedInstanceState);
  6.        setContentView(R.layout.activity_proxy);
  7.        //拿到要启动的Activity
  8.        String className=getIntent().getStringExtra("className");
  9.        try {
  10.            //加载该Acitivity的字节码对象
  11.            Class<?> aClass = PluginManager.getInstacne().getPluginDexClassLoader().loadClass(className);
  12.            //创建该Activity的实例
  13.            Object newInstance = aClass.newInstance();
  14.            //程序健壮性检查
  15.            if (newInstance instanceof  PluginInterface){
  16.                pluginInterface= (PluginInterface) newInstance;
  17.                //将代理Activity的实例传递给三方Activity
  18.                pluginInterface.attachContext(this);
  19.                //创建bundle用来与三方apk传输数据
  20.                Bundle bundle=new Bundle();
  21.                //调用三方Activity的onCreate
  22.                pluginInterface.onCreate(bundle);
  23.           }
  24.       } catch (ClassNotFoundException e) {
  25.            e.printStackTrace();
  26.       } catch (InstantiationException e) {
  27.            e.printStackTrace();
  28.       } catch (IllegalAccessException e) {
  29.            e.printStackTrace();
  30.       }
  31.   }
  32.    /**
  33.     * 很关键
  34.     * 三方调用拿到对应加载的三方Resource
  35.     * @return
  36.     */
  37.    public Resources getResource(){
  38.        return PluginManager.getInstacne().getPluginResource();
  39.   }
  40.    public void startActivity(Intent intent){
  41.        Intent newIntent=new Intent(this,ProxyActivity.class);
  42.        newIntent.putExtra("className",intent.getComponent().getClassName());
  43.        super.startActivity(newIntent);
  44.   }
  45.    @Override
  46.    protected void onStart() {
  47.        pluginInterface.onStart();
  48.        super.onStart();
  49.   }
  50.    @Override
  51.    protected void onResume() {
  52.        pluginInterface.onResume();
  53.        super.onResume();
  54.   }
  55.    @Override
  56.    protected void onPause() {
  57.        pluginInterface.onPause();
  58.        super.onPause();
  59.   }
  60.    @Override
  61.    protected void onStop() {
  62.        pluginInterface.onStop();
  63.        super.onStop();
  64.   }
  65.    @Override
  66.    protected void onDestroy() {
  67.        pluginInterface.onDestory();
  68.        super.onDestroy();
  69.   }
  70.    @Override
  71.    protected void onRestart() {
  72.        pluginInterface.onRestart();
  73.        super.onRestart();
  74.   }
  75. }

第四步 PluginApk的BaseActivity的构建

  1. public class BaseActivity extends AppCompatActivity implements PluginInterface{
  2.    //这里命名为protected 以便于子类使用
  3.    protected AppCompatActivity thisContex;
  4.    @Override
  5.    public void onCreate(Bundle bundle) {
  6.   }
  7.    @Override
  8.    public void setContentView(@LayoutRes int layoutResID) {
  9.        thisContex.setContentView(layoutResID);
  10.   }
  11.    @Override
  12.    public void setContentView(View view) {
  13.        thisContex.setContentView(view);
  14.   }
  15.    @Override
  16.    public void setContentView(View view, ViewGroup.LayoutParams params) {
  17.        thisContex.setContentView(view, params);
  18.   }
  19.    @NonNull
  20.    @Override
  21.    public LayoutInflater getLayoutInflater() {
  22.        return thisContex.getLayoutInflater();
  23.   }
  24.    @Override
  25.    public Window getWindow() {
  26.        return thisContex.getWindow();
  27.   }
  28.    @Override
  29.    public View findViewById(@IdRes int id) {
  30.        return thisContex.findViewById(id);
  31.   }
  32.    @Override
  33.    public void attachContext(AppCompatActivity context) {
  34.        thisContex=context;
  35.   }
  36.    @Override
  37.    public ClassLoader getClassLoader() {
  38.        return thisContex.getClassLoader();
  39.   }
  40.    @Override
  41.    public WindowManager getWindowManager() {
  42.        return thisContex.getWindowManager();
  43.   }
  44.    @Override
  45.    public ApplicationInfo getApplicationInfo() {
  46.        return thisContex.getApplicationInfo();
  47.   }
  48.    @Override
  49.    public void finish() {
  50.        thisContex.finish();
  51.   }
  52.    @Override
  53.    public void onStart() {
  54.   }
  55.    @Override
  56.    public void onResume() {
  57.   }
  58.    @Override
  59.    public void onPause() {
  60.   }
  61.    @Override
  62.    public void onStop() {
  63.   }
  64.    @Override
  65.    public void onDestory() {
  66.   }
  67.    @Override
  68.    public void onRestart() {
  69.   }
  70.    @Override
  71.    protected void onSaveInstanceState(Bundle outState) {
  72.        
  73.   }
  74.    @Override
  75.    public boolean onTouchEvent(MotionEvent event) {
  76.        return false;
  77.   }
  78.    @Override
  79.    public void onBackPressed() {
  80.        thisContex.onBackPressed();
  81.   }
  82.    @Override
  83.    public void startActivity(Intent intent) {
  84.        thisContex.startActivity(intent);
  85.   }
  86. }

PluginMainActivity

  1. public class PluginMainActivity extends BaseActivity implements View.OnClickListener {
  2.    @Override
  3.    public void onCreate(Bundle savedInstanceState) {
  4.        super.onCreate(savedInstanceState);
  5.        setContentView(R.layout.activity_plugin_main);
  6.        findViewById(R.id.btn).setOnClickListener(this);
  7.   }
  8.    @Override
  9.    public void onClick(View view) {
  10.        startActivity(new Intent(thisContext,SecondActivity.class));
  11.   }
  12. }
  13. 在宿主中启动PluginMainActivity
  14. public class MainActivity extends AppCompatActivity {
  15.    @Override
  16.    protected void onCreate(Bundle savedInstanceState) {
  17.        super.onCreate(savedInstanceState);
  18.        setContentView(R.layout.activity_main);
  19.   }
  20.    public void loadPlugin(View view){
  21.          ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE},100);
  22.   }
  23.    public void startPlugin(View view){
  24.        Intent intent=new Intent(this,ProxyActivity.class);
  25.        String otherapkName=PluginManager.getInstance().getPluginPackageAricheInfo().activities[0].name;
  26.        intent.putExtra("className",otherapkName);
  27.        startActivity(intent);
  28.   }
  29.    @Override
  30.    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  31.        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  32.        PluginManager.getInstance().setContext(this);
  33.        PluginManager.getInstance().loadApk(Environment.getExternalStorageDirectory().getAbsolutePath()+"/pluginapk-debug.apk");
  34.   }
  35. }

最后

加权限

  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  2. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/861160
推荐阅读
相关标签
  

闽ICP备14008679号