Android 动态调用外部jar/dex

需求分析

现有需求,需要做一个生成外部jar,去验证已发布App有效性,这个外部jar可更新,而App不用重新发布之需要重新发布这个jar包即可。此次记录这种需求开发,jar包中以MD5加密为例。

创建项目

跟往常一样,创建android studio 项目,其中包含两个app Module和两个library Module,目前都是空项目。如下图:

其中:

  • app:发布App,需要验证的App项目。
  • app2:用于直接依赖测试打包jar项目。
  • lib_interface:这个项目里只有一个接口interface,提供了一个或多个可供调用的方法,所有用到验证jar包的项目包括发布jar项目本身都要依赖于它,比如此项目中另外三个项目都要依赖于此library Module。
  • lib_md5:用于发包jar包的项目。

创建接口类

首先处理lib_interface,在lib_interface中新建一个interface接口类Md5JarInterface.java

里面只有一个方法:

1
2
3
4
5
6
/**
* 获取Md5值
* @param content 原字符串
* @return
*/
String getMd5(String content);

实现接口类方法

要实现上面接口类方法,转到lib_md5 module,首先需要先依赖lib_interface:

lib_md5build.gradle多了句:

1
implementation project(':lib_interface')

依赖好之后,在lib_md5新建一个Md5Utils.java实现Md5JarInterface接口:

下面引进MD5.java(md5算法网上多得是),并实现getMd5()方法:

1
2
3
4
@Override
public String getMd5(String content) {
return MD5.MD5(content);
}

此时一个简单的库项目功能基本完成,测试通过后就能发包jar包了。

依赖测试

完成了库项目功能开发,先直接依赖测试下结果。让app2 module依赖于lib_interfacelib_md5

简单修改activity_main.xml:

MainActivity.java添加下面代码:

1
2
TextView txtResult = findViewById(R.id.txt_result);
txtResult.setText(new Md5Utils().getMd5("123456"));

编译运行测试:

测试正常。

混淆打包jar

经过测试lib_md5项目功能正常,下面准备混淆打包。

混淆模板参考这里

但须注意,对外调用的接口方法是不能被混淆,否则后找不到,修改proguard-rules.pro添加如下:

1
2
3
4
5
6
7
-keep public class org.sogrey.md5.impl.Md5Utils
-keepclasseswithmembers public class org.sogrey.md5.impl.Md5Utils{
public String getMd5();
}
-keep class org.sogrey.md5.impl.Md5Utils{
public <methods>;
}

编辑lib_md5build.gradle,修改buildTypes.release.minifyEnabled 为 true.

添加task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def SDK_BASENAME = "MD5" //名称
def SDK_VERSION = "_V1.0" //版本
def sdkDestinationPath = "build" //生成保存位置
def zipFile = file('build/intermediates/bundles/release/classes.jar') //打包源文件

task deleteBuild(type: Delete) {
delete sdkDestinationPath + SDK_BASENAME + SDK_VERSION + ".jar"
}
task makeJar(type: Jar) {
from zipTree(zipFile)
from fileTree(dir: 'src/main',includes: ['assets/**'])//将assets目录打入jar包
baseName = SDK_BASENAME + SDK_VERSION
destinationDir = file(sdkDestinationPath)
}

makeJar.dependsOn(deleteBuild, build)

makeJar task作用是打包生成jar,但是生成的jar是没有混淆的,再添加task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
task proguardJar(dependsOn: ['makeJar'], type: proguard.gradle.ProGuardTask) {
//Android 默认的 proguard 文件
configuration android.getDefaultProguardFile('proguard-android.txt')
//manifest 注册的组件对应的 proguard 文件
configuration 'proguard-rules.pro'
String inJar = makeJar.archivePath.getAbsolutePath()
//输入 jar
injars inJar
//输出 jar
outjars inJar.substring(0, inJar.lastIndexOf(File.separator)) + "/proguard-${makeJar.archiveName}"
//设置不删除未引用的资源(类,方法等)
dontshrink
Plugin plugin = getPlugins().hasPlugin("AppPlugin") ?
getPlugins().findPlugin("AppPlugin") :
getPlugins().findPlugin("LibraryPlugin")
if (plugin != null) {
List<String> runtimeJarList
if (plugin.getMetaClass().getMetaMethod("getRuntimeJarList")) {
runtimeJarList = plugin.getRuntimeJarList()
} else if (android.getMetaClass().getMetaMethod("getBootClasspath")) {
runtimeJarList = android.getBootClasspath()
} else {
runtimeJarList = plugin.getBootClasspath()
}
for (String runtimeJar : runtimeJarList) {
//给 proguard 添加 runtime
libraryjars(runtimeJar)
}
}
}

proguardJar task 用于混淆打包。可以看到proguardJar里调用了makeJar

执行task,点击android studio 右上角Gradle展开找到:lib_md5,在task>other里找到我们刚定义的task:makeJarproguardJar,直接双击执行,我们需要混淆的直接双击proguardJar task,等待编译完成,会在build里生成了两个jar包:MD5_V1.0.jarproguard-MD5_V1.0.jar,从文件名就能看出proguard-MD5_V1.0.jar是混淆过的。

直接zip解压可直接看到包结构可class文件:

jar包dx处理

jar包生成好之后,下面就要进行dx处理,把生成的jar拷贝到Android SDK目录下build-tools\28.0.1,后面的版本根据你自己的版本:

执行下面命令:

1
dx --dex --output=proguard-MD5-dex_V1.0.jar proguard-MD5_V1.0.jar

将会生成目标jar包:proguard-MD5-dex_V1.0.jar

同样我们zip解压后看到的是一个dex文件。

引入外部jar测试

jar包dx处理完毕后就可以使用app module加载外部jar测试了,当然首先app须依赖于lib_interface

为方便安装测试,我们把dx处理好的jar放在assets文件夹下,app安装后拷贝到sd卡再加载。

MainActivity.java中代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
File cacheFile = FileUtils.getCacheDir(getApplicationContext());
File libFile = new File(cacheFile, "lib");
if (!libFile.exists()) libFile.mkdirs();
String internalPath = cacheFile.getAbsolutePath() + File.separator + "lib" + File.separator + jarName;
File desFile = new File(internalPath);
try {
if (!desFile.exists()) {
desFile.createNewFile();
FileUtils.copyFiles(this, jarName, desFile);
}
} catch (IOException e) {
e.printStackTrace();
}
//以上是将jar拷贝到sd卡,是为测试方便,实际应用中应该是下载保存到sd卡.
//下面开始加载dex class
DexClassLoader dexClassLoader = new DexClassLoader(internalPath, libFile.getAbsolutePath(), null, getClassLoader());
try {
//加载的类名为jar文件里面完整类名,写错会找不到此类hh
Class libClazz = dexClassLoader.loadClass(className);
final Md5JarInterface md5JarInterface = (Md5JarInterface) libClazz.newInstance();
if (md5JarInterface != null) {
txtResult.post(new Runnable() {
@Override
public void run() {
txtResult.setText(md5JarInterface.getMd5("123456"));
}
});
}
} catch (Exception e) {
e.printStackTrace();
}

以上代码通过DexClassLoader类加载器找到对应的类,该类实现了Md5JarInterface接口方法,调用该方法得到结果。

最后因为有SD卡文件读写,别忘了添加权限:

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

android 6.0动态权限申请请自行百度。

测试之:

项目地址:github

文章目录
  1. 需求分析
  2. 创建项目
  3. 创建接口类
  4. 实现接口类方法
  5. 依赖测试
  6. 混淆打包jar
  7. jar包dx处理
  8. 引入外部jar测试
本站总访问量 | 本文总阅读量