前言:
- 支付宝中有各种应用例如各种共享单车等,他们并不是直接集成在支付宝app中(也不可能)
- 那他们是怎么加载如此之多的应用的呢?
- 当然可以嵌入h5网页实现,但是h5的用户体验和兼容性当然没有原生的好,经过观察这些第三方应用都不是h5页面
- 那么问题来了,这些应用是如何加载到支付宝上的呢?
- 答案是:支付宝动态加载了第三方apk应用
原理:
- 如果我们能访问到第三方apk文件的类、资源文件,是不是就可以直接在主app加载出来,实现第三方app的功能呢?
- 答案是肯定的,但是在第三方app中,呈现的是一个个Activity,他又通过生命周期进行各种管理
- 那么主app能直接调用第三方自带生命周期的Activity类吗?很可惜是不能的
- 主app的生命周期是android系统赋予的,而我们使用主app直接加载第三方类的时候并不存在生命周期,所以我们需要自己赋予他生命周期
目标:
- 加载第三方apk文件的类、资源
- 赋予Activity类生命周期
步骤:
-
首先我们创建个PluginManager libs模块 主工程和插件工程都需要依赖他(主要实现资源和类的加载和管理)他包含一下几个类:
public class PluginApk {
//插件实体对象
//包信息
private PackageInfo mPackageInfo;
//资源对象
private Resources mResources;
//asset对象
private AssetManager mAssetManager;
//类加载器
private ClassLoader mClassLoader;
public PluginApk(PackageInfo mPackageInfo, Resources mResources, ClassLoader mClassLoader) {
this.mPackageInfo = mPackageInfo;
this.mResources = mResources;
this.mAssetManager = mResources.getAssets();
this.mClassLoader = mClassLoader;
}
public PackageInfo getmPackageInfo() {
return mPackageInfo;
}
public Resources getmResources() {
return mResources;
}
public AssetManager getmAssetManager() {
return mAssetManager;
}
public ClassLoader getmClassLoader() {
return mClassLoader;
}
}
public class PluginManager {
private final static PluginManager mInstance=new PluginManager();
private PluginManager(){
}
public static PluginManager getmInstance() {
return mInstance;
}
private PluginApk pluginApk;
private Context mContext;
public void init(Context context){
//初始化上下文
mContext=context.getApplicationContext();
}
public void loadApk(String apkPath){
//读取第三方apk的包信息
PackageInfo packageInfo= mContext.getPackageManager().getPackageArchiveInfo(apkPath,
PackageManager.GET_ACTIVITIES|PackageManager.GET_SERVICES);
if (packageInfo==null){
return;
}
//加载dex二进制文件,读取里面的类
DexClassLoader classLoader=createDexClassLoader(apkPath);
//加载资源文件
Resources resources=createResources(createAssetManager(apkPath));
//生成插件对象
pluginApk=new PluginApk(packageInfo,resources,classLoader);
}
public PluginApk getPluginApk() {
return pluginApk;
}
private Resources createResources(AssetManager assetManager) {
return new Resources(assetManager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration());
}
private AssetManager createAssetManager(String apkPath) {
try {
AssetManager assetManager=AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, apkPath);
return assetManager;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private DexClassLoader createDexClassLoader(String apkPath) {
return new DexClassLoader(apkPath,
mContext.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(),
null, mContext.getClassLoader());
}
}
/**
* Created by wudh on 2019/3/22.
* 宿主和插件都需要依赖的共同标准
**/
public interface PluginInterface {
int FROM_INTERNAL=0;
int FROM_EXTERNAL=1;
void onCreate(Bundle saveBundleInstance);
void attach(Activity activity);
void onStart();
void onRestart();
void onResume();
void onPause();
void onStop();
void onDestroy();
}
实现接口 接管生命周期
public class PluginActivity extends FragmentActivity implements PluginInterface{
private int FROM=FROM_INTERNAL;
private Activity mPluginActivity;
@Override
public void onCreate(Bundle saveBundleInstance) {
//判断启动该Activity来自本app还是主app
if (saveBundleInstance!=null){
FROM=saveBundleInstance.getInt("FROM");
}
if (FROM==FROM_INTERNAL){
super.onCreate(saveBundleInstance);
mPluginActivity=this;
}
}
//布局文件也需要判断 1.是主app加载apk资源文件传给他的 2.还是插件app自己运行时自带的
@Override
public void setContentView(int layoutResID) {
if (FROM==FROM_INTERNAL){
super.setContentView(layoutResID);
}else {
mPluginActivity.setContentView(layoutResID);
}
}
@Override
public Resources getResources() {
if (FROM==FROM_INTERNAL){
return super.getResources();
}else {
return mPluginActivity.getResources();
}
}
//赋予主app代理Activity的上下文
@Override
public void attach(Activity activity) {
this.mPluginActivity=activity;
}
@Override
public void onStart() {
if (FROM==FROM_INTERNAL){
super.onStart();
}
}
@Override
public void onRestart() {
if (FROM==FROM_INTERNAL){
super.onRestart();
}
}
@Override
public void onResume() {
if (FROM==FROM_INTERNAL){
super.onResume();
}
}
@Override
public void onPause() {
if (FROM==FROM_INTERNAL){
super.onPause();
}
}
@Override
public void onStop() {
if (FROM==FROM_INTERNAL){
super.onStop();
}
}
@Override
public void onDestroy() {
if (FROM==FROM_INTERNAL){
super.onDestroy();
}
}
}
2.主app的类
代理ProxyActivity,赋予第三方apk activity生命周期
public class MainActivity extends FragmentActivity {
private String apkPath=Environment.getExternalStorageDirectory().getAbsolutePath()+"/plugin.apk";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_load_apk).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//注意:使用运行时权限
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
}
});
findViewById(R.id.btn_goto_plugin).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startApk();
}
});
}
public void startApk() {
//初始化插件
PluginManager.getmInstance().init(MainActivity.this);
//获取第三方apk的类和资源
PluginManager.getmInstance().loadApk(apkPath);
//获取第三方apk中的Activity的完成名称 com.wudh.study.pluginapp.OneActivity
String otherApkMainActivityName = PluginManager.getmInstance().getPluginApk().getmPackageInfo().activities[0].name;
//跳转到代理ProxyActivity 他将帮我们实现第三方Activity的功能
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", otherApkMainActivityName);
startActivity(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//将assets的资源文件拷贝到android设备上
try {
InputStream inputStream=getAssets().open("plugin.apk");
File file = new File(apkPath);
if (file.exists()) file.delete();
FileOutputStream out=new FileOutputStream(file);
byte[] buffer=new byte[1024];
int len=-1;
while ((len=inputStream.read(buffer))!=-1){
out.write(buffer,0,len);
}
out.flush();//刷新缓存区
inputStream.close();
out.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
MainActivity.java
public class MainActivity extends FragmentActivity {
private String apkPath=Environment.getExternalStorageDirectory().getAbsolutePath()+"/plugin.apk";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_load_apk).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//注意:使用运行时权限
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
}
});
findViewById(R.id.btn_goto_plugin).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startApk();
}
});
}
public void startApk() {
//初始化插件
PluginManager.getmInstance().init(MainActivity.this);
//获取第三方apk的类和资源
PluginManager.getmInstance().loadApk(apkPath);
//获取第三方apk中的Activity的完成名称 com.wudh.study.pluginapp.OneActivity
String otherApkMainActivityName = PluginManager.getmInstance().getPluginApk().getmPackageInfo().activities[0].name;
//跳转到代理ProxyActivity 他将帮我们实现第三方Activity的功能
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", otherApkMainActivityName);
startActivity(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//将assets的资源文件拷贝到android设备上
try {
InputStream inputStream=getAssets().open("plugin.apk");
File file = new File(apkPath);
if (file.exists()) file.delete();
FileOutputStream out=new FileOutputStream(file);
byte[] buffer=new byte[1024];
int len=-1;
while ((len=inputStream.read(buffer))!=-1){
out.write(buffer,0,len);
}
out.flush();//刷新缓存区
inputStream.close();
out.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
3.插件工程依赖pluginlib模块 只需要一个继承PluginActivity的Activity
public class OneActivity extends PluginActivity {
@Override
public void onCreate(Bundle saveBundleInstance) {
super.onCreate(saveBundleInstance);
setContentView(R.layout.activity_one);
}
}
最后将在插件工程中编译得到apk,放入主app工程的assets下

~~~最后附上github地址:demo