需求分析 现有需求,需要做一个生成外部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 String getMd5 (String content) ;
实现接口类方法 要实现上面接口类方法,转到lib_md5
module,首先需要先依赖lib_interface
:
在lib_md5
的build.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_interface
和lib_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_md5
的build.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:makeJar
和proguardJar
,直接双击执行,我们需要混淆的直接双击proguardJar
task,等待编译完成,会在build
里生成了两个jar包:MD5_V1.0.jar
和proguard-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(); } DexClassLoader dexClassLoader = new DexClassLoader (internalPath, libFile.getAbsolutePath(), null , getClassLoader());try { 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