Android代码混淆

2017-10-20  本文已影响0人  小七_Rolllin

作为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. 混淆注意事项

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####################################
上一篇下一篇

猜你喜欢

热点阅读