Android R8代码混淆

作者: 望着天数月亮 | 来源:发表于2020-03-12 18:26 被阅读0次

     Android Gradle插件升级至3.4.0版本之后,带来一个新特性-新一代混淆工具R8,做为D8的升级版替代Proguard;在应用压缩、应用优化方面提供更极致的体验。

    R8 和 Proguard

     R8 一步到位地完成了所有的缩减(shrinking),去糖(desugaring)和 转换成 Dalvik 字节码(dexing )过程。

    缩减(shrinking)过程实现以下三个重要的功能:

    • 代码缩减:从应用及其库依赖项中检测并安全地移除未使用的类、字段、方法和属性。
    • 资源缩减:从封装应用中移除不使用的资源,包括应用库依赖项中的不使用的资源。
    • 优化:检查并重写代码,以进一步减小应用的 DEX 文件的大小。
    • 混淆:缩短类和成员的名称,从而减小 DEX 文件的大小。

     R8 和当前的代码缩减解决方案 Proguard 相比,R8 可以更快地缩减代码,同时改善输出大小。下面将通过几张数据图来对比(数据源自于 benchmark):


    shrinkingTime.png dexSize.png
    apkSize.png

    R8混淆使用

     R8的使用非常简单,使用方式与Proguard并无差异,Android Studio创建项目时默认是关闭的,因为这会加长工程打包时间,所以开发阶段不建议开启。开启方式如下:

    buildTypes {
            release {
                // 启用代码收缩、混淆和优化。
                minifyEnabled true
                // 启用资源缩减
                shrinkResources true
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    

     可以看到gradle中加载了两个混淆配置文件, 其中 proguard-rules.pro 供开发者自定义混淆规则;proguard-android-optimize.txt 这是默认的配置文件,包含一些通用的混淆规则,在sdk/tools/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.
    
    # Optimizations: If you don't want to optimize, use the
    # proguard-android.txt configuration file instead of this one, which
    # turns off the optimization flags.  Adding optimization introduces
    # certain risks, since for example not all optimizations performed by
    # ProGuard works on all versions of Dalvik.  The following flags turn
    # off various optimizations known to have issues, but the list may not
    # be complete or up to date. (The "arithmetic" optimization can be
    # used if you are only targeting Android 2.0 or later.)  Make sure you
    # test thoroughly if you go this route.
    -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
    -optimizationpasses 5
    -allowaccessmodification
    -dontpreverify
    
    # The remainder of this file is identical to the non-optimized version
    # of the Proguard configuration file (except that the other file has
    # flags to turn off optimization).
    
    -dontusemixedcaseclassnames
    -dontskipnonpubliclibraryclasses
    -verbose
    
    -keepattributes *Annotation*
    -keep public class com.google.vending.licensing.ILicensingService
    -keep public class com.android.vending.licensing.ILicensingService
    
    # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
    -keepclasseswithmembernames class * {
        native <methods>;
    }
    
    # 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*();
    }
    
    # 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);
    }
    
    # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    }
    
    -keepclassmembers class * implements android.os.Parcelable {
      public static final android.os.Parcelable$Creator CREATOR;
    }
    
    -keepclassmembers class **.R$* {
        public static <fields>;
    }
    
    # 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>(...);
    }
    

    ProGuard常用规则

    关闭压缩

    -dontshrink
    

    关闭代码优化

    -dontoptimize
    

    关闭混淆

    -dontobfuscate
    

    指定代码优化级别,值在0-7之间,默认为5

    -optimizationpasses 5
    

    混淆时不使用大小写混合类名

    -dontusemixedcaseclassnames
    

    不忽略库中的非public的类

    -dontskipnonpubliclibraryclasses
    

    不忽略库中的非public的类成员

    -dontskipnonpubliclibraryclassmembers
    

    输出详细信息

    -verbose
    

    不做预校验,预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度

    -dontpreverify
    

    保持指定包下的类名,不包括子包下的类名

    -keep class com.xy.myapp*
    

    保持指定包下的类名,包括子包下的类名

    -keep class com.xy.myapp**
    

    保持指定包下的类名以及类里面的内容

    -keep class com.xy.myapp.* {*;}
    

    保持所有继承于指定类的类

    -keep public class * extends android.app.Activity
    

    其它keep方法:

    保留 防止被移除或者被混淆 防止被混淆
    类和类成员 -keep -keepnames
    仅类成员 -keepclassmembers -keepclassmembernames
    如果拥有某成员,保留类和类成员 -keepclasseswithmembers -keepclasseswithmembernames

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

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

    保持指定类的所有方法

    -keep class com.xy.myapp.MyClass {
        public <methods>;
    }
    

    保持指定类的所有字段

    -keep class com.xy.myapp.MyClass {
        public <fields>;
    }
    

    保持指定类的所有构造器

    -keep class com.xy.myapp.MyClass {
        public <init>;
    }
    

    保持用指定参数作为形参的方法

    -keep class com.xy.myapp.MyClass {
        public <methods>(java.lang.String);
    }
    

    类文件除了定义类,字段,方法外,还为它们附加了一些属性,例如注解,异常,行号等,优化操作会删除不必要的属性,使用-keepattributes可以保留指定的属性

    -keepattributes Exceptions,InnerClasses,Signature,Deprecated,
                    SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
    

    使指定的类不输出警告信息

    -dontwarn com.squareup.okhttp.**
    

    常用混淆模版

    # 指定代码的压缩级别
    -optimizationpasses 5     
    
    # 不忽略库中的非public的类成员
    -dontskipnonpubliclibraryclassmembers 
    
    # google推荐算法
    -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
    
    # 避免混淆Annotation、内部类、泛型、匿名类
    -keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
    
    # 抛出异常时保留代码行号
    -keepattributes SourceFile,LineNumberTable
    
    # 保持四大组件
    -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.content.ContentProvider
    -keep public class * extends android.app.backup.BackupAgentHelper
    -keep public class * extends android.preference.Preference
    -keep public class * extends android.view.View
    -keep public class com.android.vending.licensing.ILicensingService
    
    # 保持support下的所有类及其内部类
    -keep class android.support.** {*;}
    
    # 保留继承的
    -keep public class * extends android.support.v4.**
    -keep public class * extends android.support.v7.**
    -keep public class * extends android.support.annotation.**
    
    # 保持自定义控件
    -keep public class * extends android.view.View{
        *** get*();
        void set*(***);
        public <init>(android.content.Context);
        public <init>(android.content.Context, android.util.AttributeSet);
        public <init>(android.content.Context, android.util.AttributeSet, int);
    }
    
    # 保持所有实现 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();
    }
    
    
    # webView处理
    -keepclassmembers class fqcn.of.javascript.interface.for.webview {
        public *;
    }
    -keepclassmembers class * extends android.webkit.webViewClient {
        public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
        public boolean *(android.webkit.WebView, java.lang.String);
    }
    -keepclassmembers class * extends android.webkit.webViewClient {
        public void *(android.webkit.webView, jav.lang.String);
    }
    

    输出文件

     启用R8构建项目后会在模块下的build\outputs\mapping\release文件夹下输出下列文件:

    • dump.txt:说明 APK 中所有类文件的内部结构。
    • mapping.txt:提供原始与混淆过的类、方法和字段名称之间的转换。
    • seeds.txt:列出未进行混淆的类和成员。
    • usage.txt:列出从 APK 移除的代码。

    必须保持的代码

    • AndroidManifest.xml引用的类。
    • JNI调用的方法。
    • 反射用到的类。
    • WebView中JavaScript使用的类。
    • Layout文件引用的自定义View。

    混淆心得

     我们在开发过程中,可以先记录下必须保持的类及方法,后面在做混淆时,维度可以不用做的那么细致,当然如果有安全、规范要求,那就还是一步一步的走吧。只要细心一点,混淆并不复杂。

    参考文章

    混淆压缩官方指导文档
    Android代码压缩工具R8详解
    Android使用R8压缩,混淆,优化App

    相关文章

      网友评论

        本文标题:Android R8代码混淆

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