Android开发之混淆基础教程

2017-08-30 16:43 阅读 759 次 评论 0 条
版权声明:本文著作权归TeachCourse所有,未经许可禁止转载,谢谢支持!
转载请注明出处:http://teachcourse.cn/2473.html

摘要:

本文主要学习混淆的基础知识,基础知识包括: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开启代码混淆

Android Studio在打包签名的apk文件时,默认未开启混淆功能,打开module根目录下的build.gradle文件,将minifyEnabled false修改为minifyEnabled true,即可开启代码混淆功能。

Android Studio代码混淆

看到上图,混淆的文件包括:proguard-android.txtproguard-rules.pro两个,前者当前读取SDK工具集Google默认提供的混淆规则,或者读取开发者自定义的混淆规则,如果开发者还不熟悉怎么书写自定义的混淆规则,proguard-rules.pro文件不写入任何内容,然后签名打包apk文件,生成的apk文件使用了proguard-android.txt文件的规则混淆代码:

未开启代码混淆的MainActivity.class
Android Studio代码混淆

使用默认规则混淆的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类,该类包含latitudelongitudecityName三个属性,代码如下:

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下所有代码(包含beanobfuscateapplication子目录),那保留的规则怎么写呢?如果在子目录obfuscateapplication再添加子目录commonmain子目录,那保留的规则又该怎么写呢?项目结构目录如下图:

混淆通配符

下面来介绍通配符在混淆一组包名、类名、方法名的应用。

3.1 混淆一组包名

在上面目录结构的基础上,保留cn.teachcourse目录下的所有子目录,在proguard-rules.pro文件添加如下规则:

-keeppackagenames cn.teachcourse.**

预测的效果:保留bean子目录和obfuscateapplication子目录及其下一级子目录。

反编译签名打包的apk,项目结构和上图是一样的,如下图:

混淆指定包名

现在,我想要混淆commonmain子目录,同时保留beanobfuscateapplication,在proguard-rules.pro文件添加如下规则:

-keeppackagenames cn.teachcourse.*

预期的效果:保留cn.teachcourse的下一级子目录,比如:beanobfuscateapplication子目录,混淆第二级以下的子目录,比如:commonmain子目录。

反编译签名打包的apk,项目结构如下图:

混淆多级包名

纳尼?commonmain子目录竟然没有被混淆,难道proguard-rules.pro写入的混淆规则不正确?

不可能呀!为了验证什么原因,在bean目录下添加子目录serialbean,项目结构如下图:

混淆多级包名

重新反编译签名打包的apk,项目结构如下图:

混淆多级包名

serialbean子目录已经混淆,但commonmain子目录仍然保留。

其实,添加上述规则,可以混淆cn.teachcourse包名下的第二级及其以下的目录,因为BaseActivityMainActivity继承自四大组件之一的Activity,系统默认不混淆四大组件所在目录以及组件的名称,所以会出现上述情况。

如果,我们在commonmain子目录存放非四大组件的代码,毫无疑问,这时候的子目录会被混淆,不信,请看下图:

混淆多级包名

反编译后的目录结构:

混淆指定包名

到此,完成了一组包名混淆的两条规则:

# 保留cn.teachcourse目录下所有包名
-keeppackagenames cn.teachcourse.**
# 混淆`cn.teachcourse`包名下的第二级及其以下的目录
-keeppackagenames cn.teachcourse.*

3.2 混淆一组类名

现在,我想要保留CityCity2或者UserUser2,保留规则又该怎么写呢?在proguard-rules.pro文件中添加如下规则:

-keep class cn.teachcourse.bean.City
-keep class cn.teachcourse.**.City?

反编译签名打包的apk,混淆结构如下图:

混淆多个类名

现在,CityBeanUserBean都实现了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 http://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>;
}

四、总结

保留一组包名、类名、方法名是在保留指定包名、类名、方法名的基础上添加一些通配符,这些通配符以及通配符的含义如下:

  • %,匹配任意私有类型,比如:booleanint,但不包括void
  • ,匹配一个类名中的单个字符
  • *,匹配不包含包名分隔符(.)的多个字符
  • **,匹配包含包名分隔符(.)的多个字符
  • ***,匹配任意的类型(私有的或非私有的,数组或非数组)
  • ...,匹配多个参数的任意类型
  • <init>,匹配任意的构造方法
  • <fields>,匹配任意的字段
  • <methods>,匹配任意的方法

用于保留包名的关键字是-keeppackagenames,用于保留类名和用于保留方法名的关键字及其含义总结如下:

Command Command Description
-keep -keepnames 保留类和里面的成员
-keepclassmembers -keepclassmembernames 仅保留类里面的成员
-keepclasseswithmembers -keepclasseswithmembernames 保留类和里面的成员,如果类声明有成员的情况下

Android Studio开启混淆功能后,除了系统保留的代码外,默认混淆所有的代码,广义的混淆包括压缩、优化、混淆,没有被引用的代码开启混淆后会自动被删除,这也是我为什么在MainActivity类引用各个类的原因。

关注公众号 扫一扫二维码,加我QQ

如果文章对你有帮助,欢迎点击上方按钮关注作者

来源:TeachCourse每周一次,深入学习Android教程,关注(QQ1589359239或公众号TeachCourse)
转载请注明出处:http://teachcourse.cn/2473.html

资源分享

混淆规则源码学习
Eclipse卸载已安装的Genymotion插件 Eclipse卸载已安装的Genymotio
浅谈AnalogClock和DigitalClock 浅谈AnalogClock和DigitalCl
修改猎豹浏览器主页与IE浏览器之间的区别 修改猎豹浏览器主页与IE浏览器
Android获取apk安装包的版本及包名等信息 Android获取apk安装包的版本及包

发表评论

呲牙 憨笑 坏笑 偷笑 色 微笑 抓狂 睡觉 酷 流汗 鼓掌 大哭 可怜 疑问 晕 惊讶 得意 尴尬 发怒 奋斗 衰 骷髅 啤酒 吃饭 礼物 强 弱 握手 OK NO 勾引 拳头 差劲 爱你

表情