什么是插件化动态加载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生命周期以及上下文,定义如下:

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可以使用DexClassLoaderDexClassLoader(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包下的入口ActivitypluginPackageArchiveInfo = 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;
​@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_proxy);
​//拿到要启动的ActivityString 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的实例传递给三方ActivitypluginInterface.attachContext(this);//创建bundle用来与三方apk传输数据Bundle bundle=new Bundle();//调用三方Activity的onCreatepluginInterface.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);}
​@Overrideprotected void onStart() {pluginInterface.onStart();super.onStart();}
​@Overrideprotected void onResume() {pluginInterface.onResume();super.onResume();}
​@Overrideprotected void onPause() {pluginInterface.onPause();super.onPause();}
​@Overrideprotected void onStop() {pluginInterface.onStop();super.onStop();}
​@Overrideprotected void onDestroy() {pluginInterface.onDestory();super.onDestroy();}
​@Overrideprotected void onRestart() {pluginInterface.onRestart();super.onRestart();}
}

第四步 PluginApk的BaseActivity的构建

public class BaseActivity extends AppCompatActivity implements PluginInterface{
​//这里命名为protected 以便于子类使用protected AppCompatActivity thisContex;
​@Overridepublic void onCreate(Bundle bundle) {
​}
​@Overridepublic void setContentView(@LayoutRes int layoutResID) {thisContex.setContentView(layoutResID);}
​@Overridepublic void setContentView(View view) {thisContex.setContentView(view);}
​@Overridepublic void setContentView(View view, ViewGroup.LayoutParams params) {thisContex.setContentView(view, params);}
​@NonNull@Overridepublic LayoutInflater getLayoutInflater() {return thisContex.getLayoutInflater();}
​@Overridepublic Window getWindow() {return thisContex.getWindow();}
​@Overridepublic View findViewById(@IdRes int id) {return thisContex.findViewById(id);}
​@Overridepublic void attachContext(AppCompatActivity context) {thisContex=context;}
​@Overridepublic ClassLoader getClassLoader() {return thisContex.getClassLoader();}
​@Overridepublic WindowManager getWindowManager() {return thisContex.getWindowManager();}
​@Overridepublic ApplicationInfo getApplicationInfo() {return thisContex.getApplicationInfo();}
​@Overridepublic void finish() {thisContex.finish();}
​@Overridepublic void onStart() {
​}
​@Overridepublic void onResume() {
​}
​@Overridepublic void onPause() {
​}
​@Overridepublic void onStop() {
​}
​@Overridepublic void onDestory() {
​}
​@Overridepublic void onRestart() {
​}
​@Overrideprotected void onSaveInstanceState(Bundle outState) {}
​@Overridepublic boolean onTouchEvent(MotionEvent event) {return false;}
​@Overridepublic void onBackPressed() {thisContex.onBackPressed();}
​@Overridepublic void startActivity(Intent intent) {thisContex.startActivity(intent);}
}

PluginMainActivity

public class PluginMainActivity extends BaseActivity implements View.OnClickListener {
​@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_plugin_main);
​findViewById(R.id.btn).setOnClickListener(this);}
​@Overridepublic void onClick(View view) {startActivity(new Intent(thisContext,SecondActivity.class));}
}
在宿主中启动PluginMainActivitypublic class MainActivity extends AppCompatActivity {
​@Overrideprotected 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);}
​
​@Overridepublic 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");}
}

最后

加权限

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

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

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

Android插件化动态加载apk相关推荐

  1. Android 插件化学习 加载apk并调用类的函数

    项目地址 https://github.com/979451341/PlugStudy/tree/master/LoadSimpleClass 知识点在于通过加载apk,复制成一个个FIle,然后通过 ...

  2. Android 插件化之—— 加载插件中的资源

    Android 资源分类: res目录下的资源 res目录下的资源可以通过Resource对象进行访问,通过分析Resource源码可知,Resource访问res目录下的资源其实还是调用的Asset ...

  3. osgi框架 android,基于OSGi的Android应用模块动态加载框架设计与实现

    摘要: 伴随着移动互联网科技水平向4G的飞跃,移动终端的使用日趋常态化,移动智能设备的普及率越来越高,得到了大量使用者的追捧.与此同时,各手机操作系统下应用商店里正充斥着琳琅满目的移动应用产品,用户对 ...

  4. android listview动态加载网络图片不显示,Android Listview异步动态加载网络图片

    Android Listview异步动态加载网络图片 详见: http://blog.sina.com.cn/s/blog_62186b460100zsvb.html 标签: Android SDK ...

  5. Android动态加载APK插件类

    前言 插件化开发目前是非常热门的Android技术,它主要通过将不同的业务对象封装到插件中,这样不同的业务可以独立开发和调试,提高项目的开发效率.APK文件就是常见的插件文件格式,它包含了Androi ...

  6. 【Android 插件化】Hook 插件化框架 ( 加载插件包资源 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  7. App系列之Android Apk分析---付宝android客户端的动态加载

    工具: 工欲善其事,必先利其器.因为平时拆包少,对某些好工具也了解不多,基本用了手工的方法来处理的.大家可以用什么APK改之理之类的工具. apktool:这个大家都知道,反编译利器,我下的是apkt ...

  8. Android开发之动态加载,运行未安装apk

    Android运行未安装apk可以使用Android的DexClassLoader类 这个也可以再Android的官方文档中看到 A class loader that loads classes f ...

  9. Android 插件化 动态升级

    1.作用 大多数朋友开始接触这个问题是因为 App 爆棚了,方法数超过了一个 Dex 最大方法数 65535 的上限,因而便有了插件化的概念,将一个 App 划分为多个插件(Apk 或相关格式) 常用 ...

最新文章

  1. 第二届Bio-protocol生物实验短视频大赛作品征集开始 | 奖品丰厚,等你来拿
  2. 【leetcode】923. 3Sum With Multiplicity
  3. ud分区删除工具_如何用DiskGenius对硬盘进行分区
  4. 运行launch文件报错Roslaunch got a ‘No such file or directory‘ error while attempting to run:
  5. zcmu1716(思维)
  6. Linux正则和grep命令
  7. 【转】[Java] HashMap使用的示例
  8. [蓝桥杯][算法提高VIP]数的划分(记忆化搜索)
  9. Linux——安全权限(禁止添加新用户+umask值+修改默认密码最长有效期)
  10. Shell-删除误解压的文件
  11. centos6升级glibc-2.14没有报错,但是验证没有升级成功的问题解决
  12. GridView模板问题
  13. 103.及时清除缓存
  14. python经典实例-终于明了python入门经典实例
  15. GBin1专题之Web热点#10
  16. newifi3 高恪魔改_原地升值?newifi 3 路由器刷入高恪固件教程
  17. 摄影测量之空间后方交会程序
  18. 华为防火墙忘记密码,使用console口更改密码
  19. juqery addClass方法失效问题
  20. 开机强制进入安全模式的三种方法

热门文章

  1. lamp 架构的搭建
  2. 上溯造型和下溯造性(解耦合)
  3. Mac Elasticsearch 7.9.3 安装指南
  4. SurFS:共享式和分布式集群各取所长
  5. 第十一届蓝桥杯——REPEAT程序
  6. 超详细Redis入门教程——Redis命令(上)
  7. Python open函数用法
  8. datagrip对Oracle支持不好,datagrip 连接oracle
  9. CRF as RNN 代码解读
  10. 将pem证书转换为crt/key