Android插件化之DexClasssLoader动态加载apk(Java代码和资源文件) 您所在的位置:网站首页 DexClassLoader类 Android插件化之DexClasssLoader动态加载apk(Java代码和资源文件)

Android插件化之DexClasssLoader动态加载apk(Java代码和资源文件)

2023-09-03 23:29| 来源: 网络整理| 查看: 265

DexClassLoader介绍:

DexClassLoader可以载入一个含有classes.dex文件的压缩包,可以是jar,可以是apk,也可以是含有dex文件的zip。

构造器DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)

dexPath: 包含dex文件的压缩文件(jar,apk,zip)的绝对路径,不能为空。

optimizedDirectory: 解压后存储路径,建议放在程序私有路径/data/data/com.xxx.xxx下。

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

parent:父类加载器,一般为context.getClassLoader()。

接下来,开始动手,编码实战。

一个假设性的需求:

假设有一个这样的需求,宿主需要加载Plugin.apk,获取到插件Plugin相关的信息或资源。

1. 新建个名为Plugins项目

新建一个Plugin项目,该项目放入一些需要被调用的java代码和资源文件,如下图所示: 这里写图片描述

先放入一种图片资源,供宿主调用,路下图所示: 这里写图片描述

例如:宿主获取到Plugin中的某个实体的信息。

这里,先创建一个Bean实体:

public class Bean implements BeanProvider{ private String name="根根"; @Override public void setName(String name) { this.name=name; } @Override public String getName() { return name; } }

这里思考一下,宿主又该入如何获取到该实体信息呢?

因Plugin和宿主是属于不同的Module,无法直接通过new方式创建。

在Java中,跨Jar调用,一般考虑反射。

反射调用Plugin中某个类的方法或者静态方法,比较容易实现,这里省略不说。

可能存在异步业务处理逻辑,考虑面向接口方式,回调方式获取。

2. 新建一个通用CommmonLibrary项目

新建一个宿主和Plugin都依赖的通用类库,提供对应接口,如下图所示: 这里写图片描述

在实际开发中,采用面向接口编程方式来调用Plugin中的功能逻辑,会有很多个回调接口,为了方便统一管理。

先创建一个动态调用的接口,用作统一的入口:

public interface IDynamic { /** * 涉及插件中回调 * @param callBack */ void invokeCallback(CallBack callBack); }

接下来,创建一个回调接口,用于传递信息实体:

public interface CallBack { void callback(BeanProvider beanProvider); }

在提出一个Bean实体的操作接口:

public interface BeanProvider { void setName(String name); String getName(); }

又返回到Plugin项目中去,去添加具体的的Bean业务逻辑。

在实际开发中,该bean对象可能是从数据库中读取,或者从网络上获取,也可能需要经过一系列的逻辑操作才产生。

创建一个IDynamic接口的实现类。

public class Dynamic implements IDynamic { @Override public void invokeCallback(CallBack callBack) { //具体的业务逻辑操作 BeanProvider provider=new Bean(); provider.setName("根根"); callBack.callback(provider); } }

注意点:Plugins项目和宿主App项都需要依赖该通用库。

3. 宿主加载Plugins项目生成的apk

3.1 准备步骤:

通过AndroidStudio中Build-->Build APK(s)生成对应的apk。

将Plugin项目产生apk拷贝到宿主中assets文件夹下,如下图所示: 这里写图片描述

接下来,编写Assets文件的Utils工具类:

public class Utils { public static String copyFiles(Context context, String fileName) { File dir = getCacheDir(context); String filePath = dir.getAbsolutePath() + File.separator + fileName; try { File desFile = new File(filePath); if (!desFile.exists()) { desFile.createNewFile(); copyFiles(context, fileName, desFile); } } catch (Exception e) { e.printStackTrace(); } return filePath; } public static void copyFiles(Context context, String fileName, File desFile) { InputStream in = null; OutputStream out = null; try { in = context.getApplicationContext().getAssets().open(fileName); out = new FileOutputStream(desFile.getAbsolutePath()); byte[] bytes = new byte[1024]; int i; while ((i = in.read(bytes)) != -1) out.write(bytes, 0, i); } catch (IOException e) { e.printStackTrace(); } finally { try { if (in != null) in.close(); if (out != null) out.close(); } catch (IOException e) { e.printStackTrace(); } } } public static boolean hasExternalStorage() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); } /** * 获取缓存路径 * * @param context * @return 返回缓存文件路径 */ public static File getCacheDir(Context context) { File cache; if (hasExternalStorage()) { cache = context.getExternalCacheDir(); } else { cache = context.getCacheDir(); } if (!cache.exists()) cache.mkdirs(); return cache; } }

将assets中plugin.apk拷贝到手机磁盘中,方便DexClassLoader加载:

public class MainActivity extends AppCompatActivity implements View.OnClickListener { private String dexPath; private String fileName = "plugin-debug.apk"; private String cacheDir; @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); this.dexPath = Utils.copyFiles(newBase, fileName); this.cacheDir = Utils.getCacheDir(newBase).getAbsolutePath(); } }

3.2 插件中代码接口回调:

在onCreate()中创建DexClassLoader对象:

public class MainActivity extends AppCompatActivity implements View.OnClickListener { private DexClassLoader dexClassLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dexClassLoader = new DexClassLoader(dexPath, cacheDir, null, getClassLoader()); findViewById(R.id.main_test).setOnClickListener(this); } }

然后按钮点击调用,插件中回调返回Bean实体信息:

@Override public void onClick(View v) { try { //通过dexClassLoader加载指定包名的类 Class mClass = dexClassLoader.loadClass("com.xingen.plugin.Dynamic"); IDynamic iDynamic = (IDynamic) mClass.newInstance(); iDynamic.invokeCallback(new CallBack() { @Override public void callback(BeanProvider beanProvider) { //插件回调宿主 Toast.makeText(getApplicationContext()," 插件回调宿主 ,获取的Bean实体的字段是 "+beanProvider.getName(),Toast.LENGTH_LONG).show(); } }); } catch (Exception e) { e.printStackTrace(); } }

3.3加载插件中图片资源:

思路:先反射方式获取apk的AssetManager,在创建apk的Resource对象,通过Resource获取到资源文件。

先创建一个工具类:

public class AssertsDexLoader { /** * 获取指定apk的AssetManager * * 例如:获取插件的AssetManager * * @param apkPath * @return */ public static AssetManager createAssetManager(String apkPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke( assetManager, apkPath); return assetManager; } catch (Throwable th) { th.printStackTrace(); } return null; } /** * 获取到插件中的Resource * @param context * @param apkPath * @return */ public static Resources getResource(Context context, String apkPath){ AssetManager assetManager = createAssetManager(apkPath); return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration()); } }

接下来,加载显示到ImageView中:

/** * 加载插件中的resource资源 */ private void loadImage(){ ImageView imageView=findViewById(R.id.main_iv); File file = new File(dexPath); Log.d("xingen", "file exist " + file.exists()+" "+dexPath); Resources resources=AssertsDexLoader.getResource(getApplicationContext(),dexPath); try { //加载Drawable下的bd_logo1图片 Drawable drawable=resources.getDrawable(resources.getIdentifier("bd_logo1","drawable","com.xingen.plugin")); imageView.setImageDrawable(drawable); }catch (Exception e){ e.printStackTrace(); } } 4. 运行效果如下:

这里写图片描述

项目代码地址:https://github.com/13767004362/HookDemo


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有