[关闭]
@TryLoveCatch 2021-07-08T09:34:55.000000Z 字数 9106 阅读 993

Android基础之Proguard

android android基础 Proguard


https://www.jianshu.com/p/86ee6ef970ef
https://www.jianshu.com/p/b471db6a01af
https://cloud.tencent.com/developer/article/1334693
https://juejin.im/post/5aeae5b6f265da0ba469a265
https://www.cnblogs.com/cr330326/p/5534915.html
https://sivanliu.github.io/2017/10/21/%E6%B7%B7%E6%B7%86/
https://www.jianshu.com/p/1b76e4c10495
https://juejin.im/post/5c97485f6fb9a070f30b0af7
https://www.jianshu.com/p/c54d7b35806c

ProGuard

https://juejin.im/post/5c97485f6fb9a070f30b0af7
https://www.cnblogs.com/cr330326/p/5534915.html

Android SDK 自带了混淆工具Proguard。它位于SDK根目录\tools\proguard下面。如果开启了混淆,Proguard默认情况下会对所有代码,包括第三方包都进行混淆,可是有些代码或者第三方包是不能混淆的,这就需要我们手动编写混淆规则来保持不能被混淆的部分。

混淆有4个作用:

「代码入口点」

我们刚才应该也看到了 ProGuard 压缩阶段是从代码入口点开始递归查找用到的代码的。

ProGuard 怎么知道哪里是代码入口点的呢? 没错这个代码入口点如果我们不告诉 ProGuard,他是不会知道的。那么怎么告诉他呢?我们通过 keep 规则就可以告诉 ProGuard 了。

下面看个例子:

  1. ModelA.java {
  2. int testA = 2;
  3. public void modelA(int age) {
  4. int a = 1 + age;
  5. int b = testA + age;
  6. System.out.println("print " + b);
  7. }
  8. public void modelB(String name) {
  9. System.out.println("print " + name);
  10. }
  11. }
  12. ModelB.java {
  13. public void modelA(String name) {
  14. System.out.println("print " + name);
  15. }
  16. public void modelB(String name) {
  17. System.out.println("print " + name);
  18. }
  19. }
  20. UtilsA.java {
  21. private static final String UtilA = "utila";
  22. public static void printA() {
  23. System.out.println("print " + UtilA);
  24. }
  25. public static void printB() {
  26. System.out.println("print B");
  27. }
  28. }
  29. Main.java {
  30. public static Main sMain = null;
  31. public static void main(String[] args) {
  32. sMain = new Main();
  33. sMain.run();
  34. }
  35. private void run() {
  36. ModelA modelA = new ModelA();
  37. modelA.modelA(5);
  38. UtilsA.printA();
  39. }
  40. }

可以看出来,这个main函数里面只用到了ModelA和UtilsA,我们先不添加任何混淆参数,混淆之后的结果:

  1. a.java {
  2. private int a = 2;
  3. public final void a(int i) {
  4. System.out.println("print " + (this.a + 5));
  5. }
  6. }
  7. Main.java {
  8. private static Main a = null;
  9. public static void main(String[] strArr) {
  10. a = new Main();
  11. new a().a(5);
  12. System.out.println("print utila");
  13. }
  14. }

a.java是ModelA,对比一下混淆前和混淆后的 Jar 包内容:

为什么 Main 这个类以及 main 方法没有被混淆呢?

在 ProGuard 默认生成的配置文件下有个条匹配规则(这是javar的不是android的):

  1. -keepclasseswithmembers public class * {
  2. public static void main(java.lang.String[]);
  3. }

匹配每个类里面的 main 方法为代码入口点

语法

Class Specification

Class Specification是指一个类和类成员的模板。它一般跟在各种-keep配置或者assumenosideeffects配置之后,只有匹配到的类和类成员会受到影响。

压缩

上面这几个功能都是默认打开的,要关闭他们只需配置规则:
官网

  1. #关闭压缩
  2. -dontshrink
  3. #关闭优化
  4. -dontoptimize
  5. #迭代优化,n表示proguard对代码进行迭代优化的次数,Android一般为5
  6. -optimizationpasses n
  7. #关闭混淆 默认开启,保持默认即可。
  8. -dontobfuscate
  9. # 不做预校验,preverifyproguard4个步骤之一
  10. # Android不需要preverify,去掉这一步可加快混淆速度
  11. -dontpreverify

keep

主要有两个功能

举个例子来验证一下这两点:

  1. DownloadClient.java{
  2. public int status = 0;
  3. public String url;
  4. private HttpDownload httpDownload;
  5. public DownloadClient(String url) {
  6. this.url = url;
  7. httpDownload = new HttpDownload();
  8. }
  9. public void start() {
  10. status = 1;
  11. httpDownload.start();
  12. }
  13. public void stop() {
  14. status = 2;
  15. httpDownload.stop();
  16. }
  17. }
  18. DownloadManager.java{
  19. HttpRequest httpRequest = new HttpRequest();
  20. public HttpRequest getDownloadUrl() {  
  21. System.out.println(httpRequest.get());
  22. return httpRequest;
  23. }
  24. }
  25. HttpDownload.java{
  26. private int i=0;
  27. public void start(){
  28. System.out.println("开始下载");
  29. i++;
  30. }
  31. public void stop(){
  32. System.out.println("停止下载");
  33. i--;
  34. }
  35. }
  36. HttpRequest.java{
  37. public String get() {
  38. return "请求成功";
  39. }
  40. }

增加keep语句:

  1. -keep class DownloadClient {
  2. public java.lang.String url;
  3. public <init>(java.lang.String);
  4. public void start();
  5. }

混淆效果如下:

  1. a.java{
  2. private int a = 0;
  3. public final void a() {
  4. System.out.println("开始下载");
  5. this.a++;
  6. }
  7. }
  8. DownloadClient.java{
  9. private int a = 0;
  10. private a b;
  11. public String url;
  12. public DownloadClient(String str) {
  13. this.url = str;
  14. this.b = new a();
  15. }
  16. public void start() {
  17. this.a = 1;
  18. this.b.a();
  19. }
  20. }

我们对比一下:

过滤器

https://juejin.im/post/5c97485ff265da6120099458

过滤器是什么呢?就是类似正则的规则过滤器。

类相关的过滤器

类名过滤器
符号 功能 示例
? 可以匹配任意一个字符,但是 package 的分隔符(.)除外 com.example.T?st
可以匹配 com.example.Test,com.example.T2st
不能匹配com.example.T12st 、com.example.Tst 和 com.example.T.st
* 可以匹配任意一部分连续的字符,但是 package 的分隔符(.)除外 com.example.*Test
可以匹配 com.example.Test,com.example.AnyTest
com.example.xxx.Test
还有一个特例 com.example.* 只能匹配当前包下的类,不到匹配com.example.xxx.Test
** 可以匹配任意一部分连续的字符 com**
可以匹配 com.Test,com.example.Test,com.example.java.Test
<n> 在同一匹配规则中匹配和第 n 个通配符一致的内容。 *Any<1>
可以匹配TestAnyTest
不能匹配TestAnytest。
字段和方法名过滤器
符号 功能 示例
<init> 匹配所有构造方法
<fields> 匹配所有字段方法
<methods> 匹配所有方法
* 匹配所有方法和字段,包括构造方法。
? 匹配方法名称中的任何单个字符。
<n> 在同一匹配规则中匹配和第 n 个通配符一致的内容。
  1. -keep class DownloadClient {
  2. <fields>;
  3. }
  1. -keep class DownloadClient {
  2. <methods>;
  3. }
类型过滤器
符号 功能 示例
% 匹配所有基本类型(int, boolean, long, float,double等)
? 匹配类名中的任何单个字符
* 匹配不包含包分隔符的类名的任何部分。
** 匹配类名的任何部分,可能包含任意数量的包分隔符。
匹配所有任何类型,包括初始类型和非初始类型,数组和非数组。
... 匹配任何类型任何数量的参数
<n> 在同一匹配规则中匹配和第 n 个通配符一致的内容。

?,* 和 ** 通配符不可以匹配原始类型和数组,比如:int,float。包装类和普通类是可以匹配的,比如:Integer,String。

文件相关的过滤器

符号 功能 示例

实战

所有 Event 结尾的类里面的所有内容都不能混淆。

  1. -keep class **Event { *; }

所有 XXX 子类中的 x() 方法不混淆。

  1. -keep class ** extends XXX {
  2. void x();
  3. }

但是 XXX 类被混淆了,如果不想 XXX 被混淆,那就在另写一条匹配规则。

Android项目

proguard-android.txt

https://www.jianshu.com/p/1b76e4c10495

ANDROID_SDK\tools\proguard\proguard-android.txt

  1. #混淆时不生成大小写混合的类名
  2. -dontusemixedcaseclassnames
  3. #不忽略非公共的类库
  4. -dontskipnonpubliclibraryclasses
  5. #混淆过程中打印详细信息
  6. -verbose
  7. #关闭优化
  8. -dontoptimize
  9. #不预校验
  10. -dontpreverify
  11. # Annotation注释不能混淆
  12. -keepattributes *Annotation*
  13. #对于NDK开发 本地的native方法不能被混淆
  14. -keepclasseswithmembernames class * {
  15. native <methods>;
  16. }
  17. #保持View的子类里面的setget方法不被混淆(*代替任意字符)
  18. -keepclassmembers public class * extends android.view.View {
  19. void set*(***);
  20. *** get*();
  21. }
  22. #保持Activity子类里面的参数类型为View的方法不被混淆,如被XML里面应用的onClick方法
  23. # We want to keep methods in Activity that could be used in the XML attribute onClick
  24. -keepclassmembers class * extends android.app.Activity {
  25. public void *(android.view.View);
  26. }
  27. #保持枚举类型values()、以及valueOf(java.lang.String)成员不被混淆
  28. -keepclassmembers enum * {
  29. public static **[] values();
  30. public static ** valueOf(java.lang.String);
  31. }
  32. #保持实现Parcelable接口的类里面的Creator成员不被混淆
  33. -keepclassmembers class * implements android.os.Parcelable {
  34. public static final android.os.Parcelable$Creator CREATOR;
  35. }
  36. #保持R类静态成员不被混淆
  37. -keepclassmembers class **.R$* {
  38. public static <fields>;
  39. }
  40. #不警告support包中不使用的引用
  41. -dontwarn android.support.**
  42. -keep class android.support.annotation.Keep
  43. -keep @android.support.annotation.Keep class * {*;}
  44. #保持使用了Keep注解的方法以及类不被混淆
  45. -keepclasseswithmembers class * {
  46. @android.support.annotation.Keep <methods>;
  47. }
  48. #保持使用了Keep注解的成员域以及类不被混淆
  49. -keepclasseswithmembers class * {
  50. @android.support.annotation.Keep <fields>;
  51. }
  52. -keepclasseswithmembers class * {
  53. @android.support.annotation.Keep <init>(...);
  54. }

proguard-android-optimize.txt

你想进一步压缩代码就可以改使用 getDefaultProguardFile("proguard-android-optimize.txt") ,但是会更加耗时。

ANDROID_SDK\tools\proguard\proguard-android-optimize.txt

  1. // 删除了关闭优化指令
  2. # -dontoptimize
  3. // 添加以下规则
  4. -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
  5. -optimizationpasses 5
  6. -allowaccessmodification
  7. -dontpreverify

ProGuard的输出文件说明

https://www.jianshu.com/p/1b76e4c10495
https://www.jianshu.com/p/86ee6ef970ef

sdk

sdk里面如果minifyEnabled为true的话,app使用的时候 会混淆吗?

应该有两种方式,可以把sdk的给带到app里面

Keep注解

https://www.jianshu.com/p/9dacabd351e3
因为默认的\sdk\tools\proguard\proguard-android.txt里面就已经对Keep的混淆,而且默认的在类上添加Keep会不混淆整个类

consumerProguardFiles

https://www.jianshu.com/p/a8614ff60647
一般情况,我们都是使用proguardFiles,如下所示:

  1. defaultConfig {
  2. minSdkVersion 16
  3. targetSdkVersion 26
  4. versionCode 2
  5. versionName "2.0"
  6. minifyEnabled true
  7. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  8. }

当然,也可以放到具体的buildTypes下面:

  1. buildTypes {
  2. release {
  3. minifyEnabled false
  4. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  5. }
  6. }

但是这些对sdk来说,其实都是不起作用的,这个时候,我们需要使用gradle的consumerProguardFiles,来实现sdk的混淆。
consumerProguardFiles的使用位置和proguardFiles是一样的

小结

sdk里面如果minifyEnabled为true的话,app使用的时候 会混淆吗?

现在我们让我们来回答这个的问题:

我们假设有三个module,如下

注意:我们把consumerProguardFiles没有放到了defaultConfig

好了,我们来看一下结论:

所以说,结论如下:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注