首页 > 其他分享 >Android插件化动态加载APK

Android插件化动态加载APK

时间:2022-12-09 18:35:36浏览次数:76  
标签:插件 apk void class APK Activity Override Android public

什么是插件化动态加载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");
}
}

最后

加权限

<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地址:https://github.com/walkingCoder/PluginDemo​

标签:插件,apk,void,class,APK,Activity,Override,Android,public
From: https://blog.51cto.com/u_13912063/5926348

相关文章

  • android webview设置自适应任意大小的pc网页
    1.Android:WebView如何设定支持缩放:需要对WebView和WebSettings做一下设定2.WebSettingswebSettings=view.getSettings();3.webSettings.setJavaScriptEnabled(......
  • Android截屏截图的几种方法总结
    Android截屏Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途一、Activity截屏1、截Activity界面(包含空白的状态栏)/......
  • UVCCamera && AndroidUSBCamera示例运行错误的解决办法
    Android设备USB摄像头框架,主要都是基于UVCCamera(github:​​https://github.com/saki4510t/UVCCamera​​​)在此之上,AndroidUSBCamera最新3.x版本支持了多摄像头的使用。......
  • Android13.0 PackageManagerService 的重要改变(package服务的获取)
    PackageManagerService不再extendsIPackageManager.Stub ——> 改为由抽象类IPackageManagerBaseextendsIPackageManager.Stub不再以pkms为基础类addService"p......
  • Android GestureDetector
    之前一直不知道这个类,在Android就以为只有鼠标的down和up事件,原来android为了增加用户体验,新增了GestureDetector类,也就是手势识别类,感觉就是将手指触摸屏幕的touch事件更加......
  • android调试工具DDMS
       DDMS全称DalvikDebugMonitorService.DDMS为IDE和emultor及真正的android设备架起来了一座桥梁,Android DDMS将捕捉到终端的ID,并通过adb建立调试器,从而实现发送指......
  • Android 装载器---重启装载器
    在使用initLoader()方法时,如果指定ID的装载器存在,就使用这个既存的装载器,否则会创建一个新的。但是有些时候你会想要废弃旧的数据并重启装载器。你可以使用restartLoader(......
  • android 动画xml属性总结
    XML中 alpha渐变透明度动画效果scale渐变尺寸伸缩动画效果translate画面转换位置移动动画效果rotate画面转移旋转动画效果JavaCode中 AlphaAnimation渐变透明度动画效果S......
  • [译]Android防止内存的八种方法(下)
    在上一篇​​Android内存泄漏的八种可能(上)​​中,我们讨论了八种容易发生内存泄漏的代码。其中,尤其严重的是泄漏​​Activity​​对象,因为它占用了大量系统内存。不管内存泄......
  • Android中的EditText默认时不弹出软键盘的方法
    在做项目过程中,父Activity中用ViewPager中的子Activity EditText默认弹出软键盘。这是想屏蔽软键盘应该从 父Activity中处理。处理子Activity达不到效果。......