@TryLoveCatch
2021-07-08T17:34:55.000000Z
字数 9106
阅读 1199
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
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 了。
下面看个例子:
ModelA.java {
int testA = 2;
public void modelA(int age) {
int a = 1 + age;
int b = testA + age;
System.out.println("print " + b);
}
public void modelB(String name) {
System.out.println("print " + name);
}
}
ModelB.java {
public void modelA(String name) {
System.out.println("print " + name);
}
public void modelB(String name) {
System.out.println("print " + name);
}
}
UtilsA.java {
private static final String UtilA = "utila";
public static void printA() {
System.out.println("print " + UtilA);
}
public static void printB() {
System.out.println("print B");
}
}
Main.java {
public static Main sMain = null;
public static void main(String[] args) {
sMain = new Main();
sMain.run();
}
private void run() {
ModelA modelA = new ModelA();
modelA.modelA(5);
UtilsA.printA();
}
}
可以看出来,这个main函数里面只用到了ModelA和UtilsA,我们先不添加任何混淆参数,混淆之后的结果:
a.java {
private int a = 2;
public final void a(int i) {
System.out.println("print " + (this.a + 5));
}
}
Main.java {
private static Main a = null;
public static void main(String[] strArr) {
a = new Main();
new a().a(5);
System.out.println("print utila");
}
}
a.java是ModelA,对比一下混淆前和混淆后的 Jar 包内容:
UtilsA.printA();
合并了,直接调用了打印语句为什么 Main 这个类以及 main 方法没有被混淆呢?
在 ProGuard 默认生成的配置文件下有个条匹配规则(这是javar的不是android的):
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
匹配每个类里面的 main 方法为代码入口点
Class Specification是指一个类和类成员的模板。它一般跟在各种-keep配置或者assumenosideeffects配置之后,只有匹配到的类和类成员会受到影响。
上面这几个功能都是默认打开的,要关闭他们只需配置规则:
官网
#关闭压缩
-dontshrink
#关闭优化
-dontoptimize
#迭代优化,n表示proguard对代码进行迭代优化的次数,Android一般为5
-optimizationpasses n
#关闭混淆 默认开启,保持默认即可。
-dontobfuscate
# 不做预校验,preverify是proguard的4个步骤之一
# Android不需要preverify,去掉这一步可加快混淆速度
-dontpreverify
主要有两个功能
举个例子来验证一下这两点:
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 "请求成功";
}
}
增加keep语句:
-keep class DownloadClient {
public java.lang.String url;
public <init>(java.lang.String);
public void start();
}
混淆效果如下:
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();
}
}
我们对比一下:
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 个通配符一致的内容。 |
-keep class DownloadClient {
<fields>;
}
-keep class DownloadClient {
<methods>;
}
符号 | 功能 | 示例 |
---|---|---|
% | 匹配所有基本类型(int, boolean, long, float,double等) | |
? | 匹配类名中的任何单个字符 | |
* | 匹配不包含包分隔符的类名的任何部分。 | |
** | 匹配类名的任何部分,可能包含任意数量的包分隔符。 | |
匹配所有任何类型,包括初始类型和非初始类型,数组和非数组。 | ||
... | 匹配任何类型任何数量的参数 | |
<n> | 在同一匹配规则中匹配和第 n 个通配符一致的内容。 |
?,* 和 ** 通配符不可以匹配原始类型和数组,比如:int,float。包装类和普通类是可以匹配的,比如:Integer,String。
符号 | 功能 | 示例 |
---|
所有 Event 结尾的类里面的所有内容都不能混淆。
-keep class **Event { *; }
所有 XXX 子类中的 x() 方法不混淆。
-keep class ** extends XXX {
void x();
}
但是 XXX 类被混淆了,如果不想 XXX 被混淆,那就在另写一条匹配规则。
https://www.jianshu.com/p/1b76e4c10495
ANDROID_SDK\tools\proguard\proguard-android.txt
#混淆时不生成大小写混合的类名
-dontusemixedcaseclassnames
#不忽略非公共的类库
-dontskipnonpubliclibraryclasses
#混淆过程中打印详细信息
-verbose
#关闭优化
-dontoptimize
#不预校验
-dontpreverify
# Annotation注释不能混淆
-keepattributes *Annotation*
#对于NDK开发 本地的native方法不能被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#保持View的子类里面的set、get方法不被混淆(*代替任意字符)
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
#保持Activity子类里面的参数类型为View的方法不被混淆,如被XML里面应用的onClick方法
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#保持枚举类型values()、以及valueOf(java.lang.String)成员不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保持实现Parcelable接口的类里面的Creator成员不被混淆
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
#保持R类静态成员不被混淆
-keepclassmembers class **.R$* {
public static <fields>;
}
#不警告support包中不使用的引用
-dontwarn android.support.**
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
#保持使用了Keep注解的方法以及类不被混淆
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
#保持使用了Keep注解的成员域以及类不被混淆
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
你想进一步压缩代码就可以改使用 getDefaultProguardFile("proguard-android-optimize.txt") ,但是会更加耗时。
ANDROID_SDK\tools\proguard\proguard-android-optimize.txt
// 删除了关闭优化指令
# -dontoptimize
// 添加以下规则
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
-dontpreverify
https://www.jianshu.com/p/1b76e4c10495
https://www.jianshu.com/p/86ee6ef970ef
sdk里面如果minifyEnabled为true的话,app使用的时候 会混淆吗?
应该有两种方式,可以把sdk的给带到app里面
https://www.jianshu.com/p/9dacabd351e3
因为默认的\sdk\tools\proguard\proguard-android.txt里面就已经对Keep的混淆,而且默认的在类上添加Keep会不混淆整个类
https://www.jianshu.com/p/a8614ff60647
一般情况,我们都是使用proguardFiles
,如下所示:
defaultConfig {
minSdkVersion 16
targetSdkVersion 26
versionCode 2
versionName "2.0"
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
当然,也可以放到具体的buildTypes下面:
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
但是这些对sdk来说,其实都是不起作用的,这个时候,我们需要使用gradle的consumerProguardFiles,来实现sdk的混淆。
consumerProguardFiles的使用位置和proguardFiles是一样的
consumerProguardFiles 'proguard.pro'
consumerProguardFiles 'proguard-a.pro','proguard-b.pro'
consumerProguardFiles fileTree(dir: projectDir, include: 'proguard*')
sdk里面如果minifyEnabled为true的话,app使用的时候 会混淆吗?
现在我们让我们来回答这个的问题:
我们假设有三个module,如下
注意:我们把consumerProguardFiles没有放到了defaultConfig
好了,我们来看一下结论:
所以说,结论如下: