Skip to content

Repository files navigation

titleAndroid 动态调用外部jar/dex
date2018-07-25 13:42:54 -0700
tags
Android
jar
dex
categoriesAndroid
commentstrue
toctrue

Android 动态调用外部jar/dex

Codacy Badge

需求分析

现有需求,需要做一个生成外部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

里面只有一个方法:

/** * 获取Md5值 * @param content 原字符串 * @return */StringgetMd5(Stringcontent);

实现接口类方法

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

lib_md5build.gradle多了句:

 implementation project(':lib_interface')

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

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

@OverridepublicStringgetMd5(Stringcontent) {
returnMD5.MD5(content);
}

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

依赖测试

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

简单修改activity_main.xml:

MainActivity.java添加下面代码:

TextViewtxtResult = findViewById(R.id.txt_result);
txtResult.setText(newMd5Utils().getMd5("123456"));

编译运行测试:

测试正常。

混淆打包jar

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

混淆模板参考这里

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

-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:

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:

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,后面的版本根据你自己的版本:

执行下面命令:

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中代码实现:

FilecacheFile = FileUtils.getCacheDir(getApplicationContext());
FilelibFile = newFile(cacheFile, "lib");
if (!libFile.exists()) libFile.mkdirs();
StringinternalPath = cacheFile.getAbsolutePath() + File.separator + "lib" + File.separator + jarName;
FiledesFile = newFile(internalPath);
try {
if (!desFile.exists()) {
desFile.createNewFile();
FileUtils.copyFiles(this, jarName, desFile);
}
} catch (IOExceptione) {
e.printStackTrace();
}
//以上是将jar拷贝到sd卡,是为测试方便,实际应用中应该是下载保存到sd卡.//下面开始加载dex classDexClassLoaderdexClassLoader = newDexClassLoader(internalPath, libFile.getAbsolutePath(), null, getClassLoader());
try {
//加载的类名为jar文件里面完整类名,写错会找不到此类hhClasslibClazz = dexClassLoader.loadClass(className);
finalMd5JarInterfacemd5JarInterface = (Md5JarInterface) libClazz.newInstance();
if (md5JarInterface != null) {
txtResult.post(newRunnable() {
@Overridepublicvoidrun() {
txtResult.setText(md5JarInterface.getMd5("123456"));
}
});
}
} catch (Exceptione) {
e.printStackTrace();
}

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

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

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

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

测试之:

项目地址:github

About

android 动态加载外部jar/dex

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages