赞
踩
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相关的信息或资源。
新建一个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中某个类的方法或者静态方法,比较容易实现,这里省略不说。
可能存在异步业务处理逻辑,考虑面向接口方式,回调方式获取。
新建一个宿主和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.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(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。