实现动态加载dex
最近在做的项目是把一个dex的jar文件放置在assets下,程序启动时,从assets目录拷贝一份文件到sd以及app的data\data区,并在程序初次运行时检测是否是服务器上的最新版本,若不是则从服务器下载最新版本替换掉sd卡以及data\data去的jar,就可实现随时自更新的得动态加载,本文在这里讲述如何加载存放在sd卡的插件;
第一步首先生成一个用于启动的接口jar
这么做是为引用用以及项目打包方便,这里例如生成PlugPlatform.jar,关于如何生成只含此接口的jar的方法如下(这里说的是Android Studio的方法,Eclipse可直接选择单个文件)
在build.gradle文件中自定义makeJar ,点击Sync Now,然后在左侧的gradle的任务列表中找到并点击运行即可生成
task makeJar(type: Jar, dependsOn: “build”) {
archiveName = ‘PlugPlatform’ + “.jar”//生成需要的jar名称
from(‘build/intermediates/classes/release/’)//所有class文件的位置
from fileTree(dir: ‘src/’, includes: [‘com/adcube/resources/’])//图片资源的位置
exclude(‘com/manny/tent/’)//不需要的类
exclude(‘com/manny/utils/’)//不需要的类
exclude(‘*R.class’)
exclude(‘R$.class’)
destinationDir = file(‘build’) //生成的jar路径
第二步生成dex文件
在插件apk中引用PlugPlatform.jar但最后不参与编译,在插件的启动类中实现PlugPlatform这个接口并实现init和deinit方法,对应的效果分别是启动和退出,项目中的相关功能可在init中实现,项目需要生成dex文件,利用Android Studio 快速打包apk Build—>Build Apk(s)生成apk格式的文件其中就包含需要的的dex文件(在打包时在build.gradle文件中依赖 compileOnly files(‘libs/PlugPlatform.jar’));
第三步在宿主应用中实现动态加载插件
在宿主项目中依赖PlugPlatform.jar
implementation files(‘libs/PlugPlatform.jar’)
首先我们需要创建一个ClassLoader,ClassLoader是抽象类,一般使用DexClassLoader或者PathClassLoader加载,他们的区别是
* DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk
* PathClassLoader只能加载系统中已经安装过的apk
这里我们需要加载SD位置的jar,创建ClassLoader 如下:
File optimizedDirectory = getDir( “appdex”, MODE_PRIVATE );//解压出的dex文件的存放路径
String dexPath = Environment.getExternalStorageDirectory().getAbsolutePath() + “/PlugPlatform.jar”;//指目标类所在的jar/apk文件路径
BaseDexClassLoader baseDexClassLoader = new DexClassLoader( dexPath, optimizedDirectory.getAbsolutePath(), null, MainActivity.class.getClassLoader() );
下面是封装的获取classLoader的方法
ClassLoader classLoader = createClassLoader( this, baseDexClassLoader );
public static ClassLoader createClassLoader(
Context mContext,
BaseDexClassLoader paramBaseDexClassLoader )
{
ClassLoader localClassLoader = mContext.getClassLoader();
try
{
Field localField1 = BaseDexClassLoader.class.getDeclaredField( “pathList” );
localField1.setAccessible( true );
Object localObject1 = localField1.get( paramBaseDexClassLoader );
Object localObject2 = localField1.get( localClassLoader );
Field localField2 = localObject1.getClass().getDeclaredField( “dexElements” );
localField2.setAccessible( true );
Object[] arrayOfObject1 = ( Object[] )localField2.get( localObject2 );
Object[] arrayOfObject2 = ( Object[] )localField2.get( localObject1 );
Object[] arrayOfObject3 = ( Object[] )Array.newInstance( arrayOfObject1.getClass().getComponentType(), arrayOfObject1.length + arrayOfObject2.length );
System.arraycopy( arrayOfObject2, 0, arrayOfObject3, 0, arrayOfObject2.length );
System.arraycopy( arrayOfObject1, 0, arrayOfObject3, arrayOfObject2.length, arrayOfObject1.length );
localField2.set( localObject2, arrayOfObject3 );
return localClassLoader;
}
catch( Exception localException )
{
return null;
}
}然后就可以利用反射来获取插件中的启动类的实例
PlugPlatform plugPlatform= ( PlugPlatform )classLoader.loadClass( “com.plug.test.AppPlug” ).newInstance();
然后通过plugPlatform.init( this )就启动了插件功能