目录
摘要:
本文主要学习混淆的基础知识,基础知识包括:Android Studio如何开启混淆,怎么混淆指定的包名,怎么混淆指定的类名,怎么混淆指定的方法以及通配符的使用,目的帮忙还不熟悉如何混淆的同学快速入门,如果想要更深入学习,可以继续参考《Android开发之混淆高级教程》。
其中,开启代码混淆功能(如果没有添加混淆规则)后,默认混淆项目下的所有代码
一、Android Studio如何开启混淆?
在android 2.3以前,混淆Android代码只能手动添加proguard来实现代码混淆,非常不方便。而2.3以后,Google已经将这个工具加入到了SDK的工具集里。具体路径:SDK\tools\proguard。当使用Android Studio创建一个新的module时,在module的根目录下,自动创建名为proguard-rules.pro
的配置文件。
Android Studio在打包签名的apk文件时,默认未开启混淆功能,打开module根目录下的build.gradle
文件,将minifyEnabled false
修改为minifyEnabled true
,即可开启代码混淆功能。
看到上图,混淆的文件包括:proguard-android.txt
和proguard-rules.pro
两个,前者当前读取SDK工具集Google默认提供的混淆规则,或者读取开发者自定义的混淆规则,如果开发者还不熟悉怎么书写自定义的混淆规则,proguard-rules.pro
文件不写入任何内容,然后签名打包apk文件,生成的apk文件使用了proguard-android.txt
文件的规则混淆代码:
未开启代码混淆的MainActivity.class
使用默认规则混淆的MainActivity.class
到此,完成了Android Studio如何开启代码混淆功能的学习
二、怎么混淆指定的包名、类名、方法名?
因为开启混淆后,默认混淆项目下的所有代码,所以混淆指定的包名、类名、方法名转变成保留指定的包名、类名、方法名,为什么这样设计?可能,考虑到保留的类总是比混淆的类少很多很多吧!
如果你想深入了解混淆的基础知识,可以参考SDK工具集混淆文档:sdk\tools\proguard\docs\index.html
,你会发现,广义的混淆,包括代码压缩、代码优化和代码混淆,而本文所说的仅指代码混淆,广义的混淆,如下图:
2.1 默认混淆项目下的包名、类名、方法名
如果阅读文档比较吃力,没有关系,可以继续往下看,TeachCourse将文档涉及部分知识点以例子的方式进行演示。在上面创建的module项目中,新建一个类User
类,并添加对应的属性、方法,项目结构如下图:
User
的代码结构如下图:
因为开启混淆后,默认混淆项目下的所有代码,新建的User
类、User
类方法名和User
类包名都会被混淆,因此我们不需要在proguard-rules.pro
添加任何规则,签名打包apk文件,进行反编译后查看源码,项目结构如下图:
2.2 保留指定的包名
在User
包名下新建City
类,该类包含latitude
、longitude
、cityName
三个属性,代码如下:
public class City {
private double latitude;
private double longitude;
private String cityName;
public City(long latitude, long longitude, String cityName) {
this.latitude = latitude;
this.longitude = longitude;
this.cityName = cityName;
}
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
public String getCityName() {
return cityName;
}
}
保留City
的包名,在proguard-rules.pro
文件添加如下规则:
-keeppackagenames cn.teachcourse.bean
反编译签名打包的apk,项目结构如下图:
2.3 保留指定的类名
在保留包名的前提下,继续保留City
类名,在proguard-rules.pro
文件添加如下规则:
-keepnames class cn.teachcourse.bean.City
反编译签名打包的apk,项目结构如下图:
打开这时候的City
,各个方法名已被混淆,如下图:
2.4 保留指定的方法名
在保留包名、类名的前提下,继续保留City
指定的方法,比如:保留所有的get
方法,在proguard-rules.pro
文件添加如下规则:
-keepclassmembernames class cn.teachcourse.bean.City{
public double get*(...);
}
反编译签名打包的apk,项目结构如下图:
到此,完成了混淆指定包名、类名和方法名部分的学习,虽然不算全面的,但对于理解混淆的过程还是比较有帮助的。
三、怎么混淆一组包名、类名、方法名?
一个项目通常包含不少的包名、类名以及一个类里面也包含不少的方法,如果一个个指定必然是件繁琐的工作,比如,我想要保留当前demo包名cn.teachcourse
下所有代码(包含bean
、obfuscateapplication
子目录),那保留的规则怎么写呢?如果在子目录obfuscateapplication
再添加子目录common
、main
子目录,那保留的规则又该怎么写呢?项目结构目录如下图:
下面来介绍通配符在混淆一组包名、类名、方法名的应用。
3.1 混淆一组包名
在上面目录结构的基础上,保留cn.teachcourse
目录下的所有子目录,在proguard-rules.pro
文件添加如下规则:
-keeppackagenames cn.teachcourse.**
预测的效果:保留bean
子目录和obfuscateapplication
子目录及其下一级子目录。
反编译签名打包的apk,项目结构和上图是一样的,如下图:
现在,我想要混淆common
、main
子目录,同时保留bean
、obfuscateapplication
,在proguard-rules.pro
文件添加如下规则:
-keeppackagenames cn.teachcourse.*
预期的效果:保留cn.teachcourse
的下一级子目录,比如:bean
和obfuscateapplication
子目录,混淆第二级以下的子目录,比如:common
和main
子目录。
反编译签名打包的apk,项目结构如下图:
纳尼?common
和main
子目录竟然没有被混淆,难道proguard-rules.pro
写入的混淆规则不正确?
不可能呀!为了验证什么原因,在bean
目录下添加子目录serialbean
,项目结构如下图:
重新反编译签名打包的apk,项目结构如下图:
serialbean
子目录已经混淆,但common
和main
子目录仍然保留。
其实,添加上述规则,可以混淆cn.teachcourse
包名下的第二级及其以下的目录,因为BaseActivity
和MainActivity
继承自四大组件之一的Activity
,系统默认不混淆四大组件所在目录以及组件的名称,所以会出现上述情况。
如果,我们在common
和main
子目录存放非四大组件的代码,毫无疑问,这时候的子目录会被混淆,不信,请看下图:
反编译后的目录结构:
到此,完成了一组包名混淆的两条规则:
# 保留cn.teachcourse目录下所有包名
-keeppackagenames cn.teachcourse.**
# 混淆`cn.teachcourse`包名下的第二级及其以下的目录
-keeppackagenames cn.teachcourse.*
3.2 混淆一组类名
现在,我想要保留City
和City2
或者User
和User2
,保留规则又该怎么写呢?在proguard-rules.pro
文件中添加如下规则:
-keep class cn.teachcourse.bean.City
-keep class cn.teachcourse.**.City?
反编译签名打包的apk,混淆结构如下图:
现在,CityBean
和UserBean
都实现了Serializable
接口,我想要同时保留实现序列化接口的实体,保留规则又该怎么写呢?在proguard-rules.pro
文件中添加如下规则:
-keep class ** implements java.io.Serializable
反编译apk文件,项目结构图如下:
同理,如果想要同时保留多个继承类,比如:继承View,你需要在proguard-rules.pro
添加如下规则:
-keep public class * extends android.view.View
突然,想到一个问题,如果想要保留内部类,比如:Student
类包含静态嵌套类Builder
,想要保留嵌套类,保留规则该怎么写?Student
包含的代码如下:
/**
* Created by https://www.teachcourse.cn on 2017/8/30.
*/
public class Student {
private String name;
private String age;
private String college;
public Student(Student.Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.college = builder.college;
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
public String getCollege() {
return college;
}
public static class Builder {
private String name = "钊林";
private String age = "24";
private String college = "百色学院";
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(String age) {
this.age = age;
return this;
}
public Builder setCollege(String college) {
this.college = college;
return this;
}
public Student build() {
return new Student(this);
}
}
}
在proguard-rules.pro
文件中添加如下规则:
-keep class **.Student$*
反编译签名打包的apk,保留嵌套类的代码结构如下图:
到此,完成了保留一组类名的学习,添加的规则包含以下几种:
# 保留City和City2或User和User2一组类
-keep class cn.teachcourse.bean.City
-keep class cn.teachcourse.**.City?
# 保留实现Serializable接口的类
-keep class * implements java.io.Serializable
# 保留多个继承类,比如:继承View
-keep public class * extends android.view.View
# 保留静态嵌套类,比如:Student.Builder
-keep class **.Student$*
3.3 混淆一组方法名
因为开启混淆后,默认混淆项目下的所有代码,所以混淆一组方法名,实质也是保留一组方法名,保留的方法名包括:构造方法、成员方法、类方法
在混淆一组类名中,提到了保留所有继承自View
的子类,在此我们增加保留所有子类中添加的构造方法,在proguard-rules.pro
文件中添加如下规则:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
如果想要同时保留继承自View
子类的所有set
方法,在proguard-rules.pro
文件中添加如下规则:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
为了验证,如何保留类方法,在原来City
类的基础上添加如下代码:
public class City {
...
public static String from(){
return "Chinese";
}
}
默认情况,类方法from()
会被混淆,现在想要将其保留下来,需要在proguard-rules.pro
文件中添加如下规则:
-keepclassmembers class * {
public static <methods>;
}
反编译签名打包的apk,查看City
源码结构如下图:
到此,完成了保留一组方法名的学习,添加的规则包含以下几种:
# 保留继承自View子类的构造方法、set方法
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
# 保留类方法,比如:City.from()
-keepclassmembers class * {
public static <methods>;
}
四、总结
保留一组包名、类名、方法名是在保留指定包名、类名、方法名的基础上添加一些通配符,这些通配符以及通配符的含义如下:
%
,匹配任意私有类型,比如:boolean
、int
,但不包括void
?
,匹配一个类名中的单个字符*
,匹配不包含包名分隔符(.)的多个字符**
,匹配包含包名分隔符(.)的多个字符***
,匹配任意的类型(私有的或非私有的,数组或非数组)...
,匹配多个参数的任意类型<init>
,匹配任意的构造方法<fields>
,匹配任意的字段<methods>
,匹配任意的方法
用于保留包名的关键字是-keeppackagenames
,用于保留类名和用于保留方法名的关键字及其含义总结如下:
Command | Command | Description |
---|---|---|
-keep |
-keepnames |
保留类和里面的成员 |
-keepclassmembers |
-keepclassmembernames |
仅保留类里面的成员 |
-keepclasseswithmembers |
-keepclasseswithmembernames |
保留类和里面的成员,如果类声明有成员的情况下 |
Android Studio开启混淆功能后,除了系统保留的代码外,默认混淆所有的代码,广义的混淆包括压缩、优化、混淆,没有被引用的代码开启混淆后会自动被删除,这也是我为什么在MainActivity
类引用各个类的原因。
你可能感兴趣的文章
转载请注明出处: https://www.teachcourse.cn/2473.html ,谢谢支持!