Android代码混淆

作为Android开发者,如果你不想开源你的应用,那么在应用发布前,就需要对代码进行混淆处理,从而让我们代码即使被反编译,也难以阅读。混淆概念虽然容易,但很多初学者也只是网上搜一些成型的混淆规则粘贴进自己项目,并没有对混淆有个深入的理解。本篇文章的目的就是让一个初学者在看完后,能在不进行任何帮助的情况下,独立写出适合自己代码的混淆规则。

1. 什么是混淆

混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。

用一个词概括什么是混淆:重命名

2. 混淆的好处

(1)降低代码阅读性,保护源码

(2)精简编译后的程序大小

缺点:重命名可能导致运行出错,若测试不充分,可能会影响部分功能。

3. 打开混淆(app/build.gradle)

这里我们直接用Android Studio来说明如何进行混淆,Android Studio自身集成Java语言的ProGuard作为压缩,优化和混淆工具,配合Gradle构建工具使用很简单,只需要在工程应用目录的gradle文件中设置minifyEnabled为true即可。然后我们就可以到proguard-rules.pro文件中加入我们的混淆规则了。

android {
    ...
    buildTypes {
        release {
            minifyEnabled true //打开混淆
            zipAlignEnabled true //排列压缩apk
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

发布一款应用除了设minifyEnabled为ture,建议你也应该设置zipAlignEnabled为true,像Google Play强制要求开发者上传的应用必须是经过zipAlign的,zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗。

4. 配置自定义的混淆文件(app/proguard-rules.pro)

(1)Android自带的混淆规则文件
Androidsdk目录\tools\proguard\proguard-android.txt
一般Android自带的混淆规则文件是不够用的,它只定义了部分混淆规则,例如组件不混淆、View不混淆等,完全不够实际项目的使用,所以我们还是需要自己编辑自定义的混淆文件:app/proguard-rules.pro。

(2)混淆基本语法:
#:代表行注释符
-:表示一条规则的开始
keep 保留,例如-keepattributes Signature :表示保留泛型不混淆;例如-keep class :表示保留类不混淆
dont 不要,例如-dontwarn retrofit2.**:表示不要提示retrofit2包下的所有警告,-dontwarn命令主要用在第三方包编译时候的警告报错

(3)保留不混淆的命令(举例说明):

    -keep class com.appname.test.*:只保持该包下的类名不会被混淆,子包下的类名还是会被混淆(类的内容还是会混淆)
    -keep class com.appname.test.**:只保持该包下的类名和子包下的类名不会被混淆(类的内容还是会混淆)
    -keep class com.appname.test.* {*;}:既保持类名,也保持类里面的内容不会被混淆

再者,如果一个类中你不希望保持全部内容不被混淆,而只是希望保护类下的特定内容,就可以使用

<init>;     //匹配所有构造器
<fields>;   //匹配所有域
<methods>;  //匹配所有方法方法

你还可以在<fields>或<methods>前面加上private 、public、native等来进一步指定不被混淆的内容,如

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

表示One类下的所有public方法都不会被混淆,当然你还可以加入参数,比如以下表示用JSONObject作为入参的构造函数不会被混淆

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

有时候你是不是还想着,我不需要保持类名,我只需要把该类下的特定方法保持不被混淆就好,那你就不能用keep方法了,keep方法会保持类名,而需要用keepclassmembers ,如此类名就不会被保持,为了便于对这些规则进行理解,官网给出了以下表格

注:具体的语法请阅读ProGuard手册来了解更多关于混淆配置文件的信息

5. 混淆注意事项

  • jni方法不可混淆,方法需要和native方法保持一致
  • 反射用到的类不混淆(否则反射可能出现问题)
  • 四大组件和Application的类不混淆(会导致manifest文件找不到对应的类)
  • View及其子类不能被混淆(xml布局文件解析时可能会出错)
  • 保留R类不混淆
  • 注解相关的类不混淆
  • GSON、fastjson等解析的bean数据不可混淆
  • WebView中JS调用写的接口方法不混淆,方法名需要与JS中的方法保持一致
  • 枚举enum类中的values和valuesof这2个方法不能混淆(会被发射调用)
  • 继承Parceable和Serializable等可序列化的类不能被混淆
  • 第三方包的混淆:请参考第三方提供的混淆规则(若第三方没有提供混淆规则,建议第三方包全部不混淆)

6. mapping文件

(1)mapping文件目录:
工程目录\app\build\outputs\mapping\发布渠道\release\

(2)mapping目录下的文件:
dump.txt:描述.apk文件中所有类文件间的内部结构
seeds.txt:列出了未被混淆的类和成员
usage.txt:列出了从.apk中删除的代码
mapping.txt:列出了原始的类,方法和字段名与混淆后代码间的映射(即重命名前后的映射表)。这个文件很重要,当你从release版本中收到一个bug报告时,可以用它来翻译被混淆的代码,将混淆的日志转化成未混淆的日志。

(3)mapping还原工具: <Android-sdk>/tools/proguard/bin/proguardgui.bat

注意:每次发布版本的时候最好都保留mapping文件,与apk版本文件一同保留。

7. 导入混淆的时机

工程代码加入混淆的时机越早越好,越早测试就能越早地发现问题,测试不充分可能导致某些功能不能使用。假设第N个版本为release的正式版本,导入混淆的版本建议在:第1~(N-4)个版本(至少预留4个版本来保证测试)。

一般都是在Release模式才加入混淆, 之所以不在Debug模式下加,是因为混淆后的代码会使得调试变得很累赘。

8. 最后附上项目中最终版本的混淆文件(proguard-rules.pro)

备注:该项目的主包名是com.appname.test,直接使用的话请修改文件内的主包名
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

#指定压缩级别
-optimizationpasses 5

# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames

# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses

#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers

# 包含有类名->混淆后类名的映射关系
-verbose

#不做预校验,加快混淆速度
-dontpreverify

#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#把混淆类中的方法名也混淆了
-useuniqueclassmembernames

#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification

#将文件来源重命名为“SourceFile”字符串
#-renamesourcefileattribute SourceFile

#不混淆泛型
-keepattributes Signature

#保留注解
-keepattributes *Annotation*

#保留行号
-keepattributes SourceFile,LineNumberTable

#保持所有实现 Serializable 接口的类成员
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}


# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**

# 通常不混肴的类
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.preference.Preference
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.annotation.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService


#保留R类
-keep class **.R$* {
   *;
}

#保持native方法不混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

#保持自定义控件类不混淆
-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

#保持枚举enum类不被混淆
-keepclasseswithmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#保持parcelable不混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

#保持serializable不混淆
-keep class * implements java.io.Serializable{
*;
}

#保持自定义的类不混淆
-keep class com.appname.test.view.** { *; }
-keep class com.appname.test.beans.** { *; }
-keep class com.appname.test.modules.data.remote.beans.** { *; }
-dontwarn com.appname.**
-dontwarn org.joda.time**

-keep public class com.appname.test.R$*{
public static final int *;
}

####################################第三方库 Start####################################
# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions

# butterknife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
    @butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
    @butterknife.* <methods>;
}

# rxjava
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
   long producerIndex;
   long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
-dontnote rx.internal.util.PlatformDependent

# gson
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }

# okhttp3
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault

# Okio
-dontwarn com.squareup.**
-dontwarn okio.**
-keep public class org.codehaus.* { *; }
-keep public class java.nio.* { *; }

# glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}
# for DexGuard only
#-keepresourcexmlelements manifest/application/meta-data@value=GlideModule

#不混淆cordova插件
#-repackageclasses ''
#-allowaccessmodification
#-optimizations !code/simplification/arithmetic
#-keepattributes *Annotation*
-keep public class * extends org.apache.cordova.CordovaPlugin
-keep public class org.apache.cordova.**{*;}

#greendao
-keep class org.greenrobot.greendao.**{*;}
-keep public interface org.greenrobot.greendao.**
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
-keep class net.sqlcipher.database.**{*;}
-keep public interface net.sqlcipher.database.**
-dontwarn net.sqlcipher.database.**
-dontwarn org.greenrobot.greendao.**
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use RxJava:
-dontwarn rx.**

#EventBus
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(Java.lang.Throwable);
}

#Umeng App analytics
-keepclassmembers class * {
   public <init> (org.json.JSONObject);
}
-keep public class com.appname.test.R$*{
public static final int *;
}
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
####################################第三方库 End####################################
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容