Android代码混淆知识点

一、前言
    Java代码时非常容易反编译的,但是代码被反编译有可能泄漏核心技术,所以一个安全性高的程序最起码要做到的一件事就是:对代码混淆。
 
 
二、混淆的概念和作用
    解释:对程序员来说,Utils类的isEmpty()方法可以更好的理解含义,但是对于计算机来说叫A类的b()方法效果是一样的
    作用:1、增加APK反编译之后代码泄漏的困难性
                2、缩小生成APK的体积
 
 
三、混淆工具
     Gradle 插件 3.4.0以下:Android Studio默认都是用 ProGuard来执行代码优化和混淆
     Gradle 插件 3.4.0以上:默认使用R8进行混淆和压缩,可使用 android.enableR8=false 关闭R8 
    
    ProGuard工作原理简介:
    ProGuard能够对Java类中的代码进行压缩(Shrink),优化(Optimize),混淆(Obfuscate),预检(Preveirfy)
  1. 压缩(Shrink):在压缩处理这一步中,用于检测和删除没有使用的类,字段,方法和属性
  2. 优化(Optimize):在优化处理这一步中,对字节码进行优化,并且移除无用指令
  3. 混淆(Obfuscate):在混淆处理这一步中,使用a,b,c等无意义的名称,对类,字段和方法进行重命名
  4. 预检(Preveirfy):在预检这一步中,主要是在Java平台上对处理后的代码进行预检
  对于ProGuard执行流程图如下图所示
 
 
四、混淆实践
1、混淆配置 
  •     Android Studio新建项目minifyEnabled默认值为false,将其置为true即可在release包时开启混淆。
  •     这里Proguard工具是SDK中自带的,proguard-android-optimize.txt文件为默认混淆规则文件,proguard-rules.pro文件则是自动为我们生成的自定义规则文件,用于开发根据 需求添加自定义混淆规则。
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}

 

2、一个简单的混淆事例
代码文件
混淆后的目录
  • 首先可看到那个未被引用的类和MainActivity中未调用的方法并未出现在反编译的后的文件中,这是因为ProGuard在混淆前先会进行压缩,主要是移除无效的类、类成员和方法等
  • 其次看到MainActivity、MyService、MyTextView的类名均未被混淆。这是因为MainActivity、MyService均是系统组件,事实上凡事在Manifest中注册的所有类名都不会被混淆。而MyTextView作为自定义View,也是没有被混淆的,
  • …………
  • 上述的那几个混淆的规则都是在哪里定义的,其实都是写在默认规则proguard-android-optimize.txt文件中的,这个文件存放于<Android SDK>/tools/proguard/proguard-android.txt路径,可以打开看下
3、ProGuard默认混淆文件
    
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.


-dontusemixedcaseclassnames    #表示混淆时不使用大小写混合的类名,混淆后均为小写类名
-dontskipnonpubliclibraryclasses   #指定不去忽略非公共库的类
-verbose        #表示打印混淆的详细信息,即产生混淆前后的映射文件


# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
#默认optimize和preverify选项是关闭的,因为Android的dex并不像Java虚拟机需要optimize(优化)和previrify(预检)两个步骤
-dontoptimize  
-dontpreverify    #不做预校验,Android不需要preverify,去掉这一步能够加快混淆速度
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.


-keepattributes *Annotation*    #表示对注解中的参数进行保留
-keep public class com.google.vending.licensing.ILicensingService    #不混淆这俩类,这俩类基本是接入Google原生的一些服务时使用的
-keep public class com.android.vending.licensing.ILicensingService


# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
#不混淆任何包含native方法的类的类名以及native方法名。因为C、C++会调用Java代码
-keepclasseswithmembernames class * {
    native <methods>;
}

#不混淆任何一个View中的setXxx()和getXxx()方法,因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

#表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick="buttonClick"属性,当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
# 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()方法
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#Parcelable实现类中的CREATOR字段是绝对不能改变的,因为反序列化时底层是会获取静态CREATOR字段的,然后再调用createFromParcel()恢复数据
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

#不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了
-keepclassmembers class **.R$* {
    public static <fields>;
}


#忽略support包因为版本兼容产生的警告
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**


# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep


-keep @android.support.annotation.Keep class * {*;}


-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}


-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}


-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

 

4、自定义ProGuard规则
ProGuard关键字
关键字                      描述
keep                        保留类和类中的成员,防止被重命名或移除
keepnames                   保留类和类中的成员,防止被重命名
keepclassmembers            只保留类中的成员,防止被重命名或移除
keepclassmembernames        只保留类中的成员,防止被重命名
keepclasseswithmembers      保留类和类中的成员,防止被重命名或移除,针对特定成员
keepclasseswithmembernames  保留类和类中的成员,防止被重命名,针对特定成员

保留

防止被移除或者被重命名

防止被重命名

类和类成员

-keep

-keepnames

仅类成员

-keepclassmembers

-keepclassmembernames

如果拥有某成员,保留类和类成员

-keepclasseswithmembers

-keepclasseswithmembernames

ProGuard通配符

通配符      描述
<field>     匹配类中的所有字段
<method>    匹配类中所有的方法
<init>      匹配类中所有的构造函数
*           匹配任意长度字符,不包含包名分隔符(.)
**          匹配任意长度字符,包含包名分隔符(.)
***         匹配任意参数类型
...

可以看如下两个常用命令,很多人可能会迷惑以下两者的区别:

-keep class com.xiaoming.test.**
-keep class com.xiaoming.test.*
-keep class com.xiaoming.test.* {*;}

一颗星表示只保留该包下的类名,而子包下的类名还是会被混淆;两颗星表示把本包和子包下所有的类名都保持,即包含包名分隔符.;如果要保留类名和方法变量名,则可以使用-keep class com.xiaoming.test.* {*;}

 
除此之外,可以使用java的基本规则来保护特点的类不被混淆,比如我们可以用extend,implement等这些Java规则。如下例子就避免所有继承Activity的类被混淆。
-keep public class * extends android.app.Activity

如果只想保证一个类中特定内容不被混淆,可以使用通配符<field>、<method>、<init> ,并且可以在前面加上权限修饰符来进一步指定不被混淆的内容。如:表示One类下的所有public 方法都不会被混淆

-keep class com.xiaoming.test.One {
    public <methods>;
}

还可以加上参数,如以下用JSONObject作为入参的构造函数不会被混淆

-keep class cn.hadcn.test.One {
   public <init>(org.json.JSONObject);
}

如果我们要保留一个类中的内部类不被混淆则需要用$符号,如下例子表示保持MyClass内部类JavaScriptInterface中的所有public内容

-keepclassmembers class com.xy.myapp.MyClass$JavaScriptInterface { public *; }

 

 
最后看下下面这两个规则的区别
-keepclasseswithmember class * {
    native <methods>;
}

保留所有含有native方法的类的类名和native方法名,而如果某个类中没有含有native方法,那就还是会被混淆;

但是如果改成keep关键字,结果会完全不一样
-keep class * {
    native <methods>;
}
用keep关键字后,你会发现代码中所有类的类名都不会被混淆了,因为keep关键字看到class *就认为应该将所有类名进行保留,而不会关心该类中是否含有native方法。
 
 
5、注意事项,通常哪些项是不能混淆的
1.在AndroidManifest中配置的类,比如四大组件。这些在Android Studio中都是默认的规则
2.JNI调用的方法,因为要和native方法保持一样
3.反射用到的类
4.WebView中JavaScript调用的方法
5. 使用第三方开源库或者引用其他第三方的SDK包时,如果有特别要求,也需要在混淆文件中加入对应的混淆规则。此项需格外注意

 

6、ProGuard模板代码
一个项目的混淆规则一般都要配置以下几个方面
#—————------一些基本指令添加---------------
......
#-----------Android开发中需要保留的公共部分---------------
......
#-----------处理反射类---------------
......
#-----------处理js交互---------------
......
#-----------处理实体类---------------
......
#-----------处理第三方依赖库---------
......

 

 
 
附:当使用Android Gradle插件3.4.0或更高版本时,插件不再使用ProGuard执行优化而是R8

R8和Proguard 相比,R8 可以更快地缩减代码,同时改善输出大小

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值