本文已授权微信公众号「玉刚说」独家发布。
这篇文章是「Java 混淆那些事」的第三篇,我们来真枪真刀的干一下子,用实际行动验证了解一下 ProGuard 的 Keep 语法,这篇代码偏多,希望大家好好理解。
阅读提示:上半部分纯属个人总结,不明白请看下半部分的例子,读完了根据自己的理解实践一下。
那么 keep 语法有什么用呢?如果我们对外提供了一套 Library ,如果不指定代码入口点恐怕是所有代码都要被删掉了,所以我们要指定「代码入口点」,并且告诉 ProGuard 那些类名绝对不能变动,那些方法名不能变动等等。
其中 modifier 为可选配置,可以指定一个或多个。 class_specification 是类和成员的模板
这部分是实践大家也可以跳过,根据自己的理解亲自动手操作即可,如果我有什么重要的或错误结论欢迎指正。 在 ProGuard GUI 把混淆的配置文件保存,然后使用文件编辑器直接在配置文件下面添加即可。然后在 ProGuard 读取并执行。
/* * 测试代码结构 * src * -> DownloadClient.java * -> DownloadManager.java * -> http * -> HttpDownload.java * -> HttpRequest.java */ // 各个文件的具体代码 // DownloadClient.java public int status = 0; public String url; private HttpDownload httpDownload; public DownloadClient(String url) { this.url = url; httpDownload = new HttpDownload(); } public void start() { status = 1; httpDownload.start(); } public void stop() { status = 2; httpDownload.stop(); } // DownloadManager.java HttpRequest httpRequest = new HttpRequest(); public HttpRequest getDownloadUrl() { System.out.println(httpRequest.get()); return httpRequest; } // HttpDownload.java private int i=0; public void start(){ System.out.println("开始下载"); i++; } public void stop(){ System.out.println("停止下载"); i--; } // HttpRequest.java public String get() { return "请求成功"; } 复制代码
这个例子我们不使用 main 方法,只把这个小例子当做一个 SDK。
//混淆脚本 -keep class DownloadClient { public java.lang.String url; public <init>(java.lang.String); public void start(); } 复制代码
处理效果
/* * 代码结构 * a * -> a.java * defpackage * -> DownloadClient.java */ // 各个文件的具体代码 // a.java private int a = 0; public final void a() { System.out.println("开始下载"); this.a++; } // DownloadClient.java private int a = 0; private a b; public String url; public DownloadClient(String str) { this.url = str; this.b = new a(); } public void start() { this.a = 1; this.b.a(); } 复制代码
这个效果很明显:
还有几种情况,大家自己试一下。
这个效果和 keep 完全一样,但是稍微有点不同,大家也可以自己试一试。
// 混淆脚本 -keep class DownloadClient -keepclassmembers class DownloadClient { private http.HttpDownload httpDownload; public <init>(java.lang.String); public void start(); } 复制代码
处理效果
/* * 代码结构 * a * -> a.java * defpackage * -> DownloadManager.java */ // 各个文件的具体代码 // a.java null // DownloadManager.java a httpRequest = new a(); public a getDownloadUrl() { System.out.println("请求成功"); return this.httpRequest; } 复制代码
大家看到我这次写了一条 -keep 混淆规则,为什么呢?
因为在压缩阶段能留下来的类上 -keepclassmembers 才能有效果,否则没有效果。所以要先把相关类留下来。
我们总结一下 -keepclassmembers 的效果
还是几种情况,大家自己试一下。
//混淆脚本 -keep class DownloadClient{ public <init>(java.lang.String); public void start(); } -keep,includecode class DownloadClient { private http.HttpDownload httpDownload; } 复制代码
混淆效果
/* * 代码结构 * a * -> a.java * defpackage * -> DownloadClient.java */ // 各个文件的具体代码 // a.java private int a = 0; public final void a() { System.out.println("开始下载"); this.a++; } // DownloadClient.java private int a = 0; private String b; private a httpDownload; public DownloadClient(String str) { this.b = str; this.httpDownload = new a(); } public void start() { this.a = 1; this.httpDownload.a(); } 复制代码
实际效果
动手试一试
//混淆脚本 -keep class DownloadClient { public <init>(java.lang.String); public void start(); } -keep,includedescriptorclasses class DownloadManager { public http.HttpRequest getDownloadUrl(); } -keep,includedescriptorclasses class DownloadClient { private http.HttpDownload httpDownload; } 复制代码
混淆效果
/* * 代码结构 * http * -> HttpRequest.java * -> HttpDownload.java * defpackage * -> DownloadClient.java * -> DownloadManager.java */ // 各个文件的具体代码 // HttpRequest.java public static String a() { return "请求成功"; } // HttpDownload.java private int a = 0; public final void a() { System.out.println("开始下载"); this.a++; } // DownloadClient.java private int a = 0; private String b; private HttpDownload httpDownload; public DownloadClient(String str) { this.b = str; this.httpDownload = new HttpDownload(); } public void start() { this.a = 1; this.httpDownload.a(); } // DownloadManager.java private HttpRequest a = new HttpRequest(); public HttpRequest getDownloadUrl() { System.out.println(HttpRequest.a()); return this.a; } 复制代码
实际效果
//混淆脚本 -keep class DownloadClient { public <init>(java.lang.String); public void start(); } -keep,allowshrinking class DownloadManager { public http.HttpRequest getDownloadUrl(); } 复制代码
混淆效果
/* * 代码结构 * a * -> a.java * defpackage * -> DownloadClient.java */ // 各个文件的具体代码 // a.java private int a = 0; public final void a() { System.out.println("开始下载"); this.a++; } // DownloadClient.java private int a = 0; private String b; private a c; public DownloadClient(String str) { this.b = str; this.c = new a(); } public void start() { this.a = 1; this.c.a(); } 复制代码
实际效果
动手试一试
//混淆脚本 -keep,allowoptimization class DownloadClient { public <init>(java.lang.String); public void start(); } 复制代码
混淆效果
/* * 代码结构 * defpackage * -> DownloadClient.java */ // 各个文件的具体代码 // DownloadClient.java //空的 复制代码
实际效果
动手试一试
-keep class DownloadClient { public <init>(java.lang.String); public void start(); } 复制代码
//混淆脚本 -keep,allowobfuscation class DownloadClient { public <init>(java.lang.String); public void start(); } 复制代码
混淆效果
/* * 代码结构 * a * -> a.java * defpackage * -> a.java */ // 各个文件的具体代码 // a/a.java private int a = 0; public final void a() { System.out.println("开始下载"); this.a++; } // defpackage/a.java private int a = 0; private String b; private a.a c; public a(String str) { this.b = str; this.c = new a.a(); } public void a() { this.a = 1; this.c.a(); } 复制代码
实际效果
自己动手
官方描述的类规范模板,看着 Java 代码很像。
再放一张格式化过的比较好理解的图
我对官方的规则进行格式化了一下,这样看是不是就好理解多了,如果不考虑通配符,其实就是跟 Java 正常的写法是一致的。比如我们上面例子中混淆的写法,除了有个 之外,其他都是普通的 Java 语法。
再说几条上面没有体现的规则
到此为止 ProGuard 的 Keep 规则我们也简单的聊了一下,希望大家自己多尝试,然后总结一下每条命令的用处,方便日后使用。