美文网首页
Android代码混淆

Android代码混淆

作者: 小七_Rolllin | 来源:发表于2017-10-20 16:55 被阅读0次

    作为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####################################
    

    相关文章

      网友评论

          本文标题:Android代码混淆

          本文链接:https://www.haomeiwen.com/subject/nguvuxtx.html