Android使用R8压缩,混淆,优化App

作者: 土豆吞噬者 | 来源:发表于2019-08-02 19:04 被阅读0次

    在Android开发中,为了使App尽可能小,可以使用R8来压缩,混淆,优化App,当使用Android Gradle插件3.4.0或更高版本时,插件不再使用ProGuard执行优化而是R8。

    R8的功能

    • 代码压缩:安全地从App及其库依赖项中删除未使用的类,字段,方法和属性。
    • 资源压缩:从打包的App中删除未使用的资源,包括应用程序库依赖项中未使用的资源。它与代码压缩一起使用,这样一旦删除了未使用的代码,也可以安全地删除不再引用的资源。
    • 代码混淆:使用简短无意义的名称重命名代码里的类,字段和方法,从而减少DEX文件大小。
    • 代码优化:删除未使用的代码或重写代码使其更简洁。

    R8 和 Proguard

    R8和Proguard 相比,R8 可以更快地缩减代码,同时改善输出大小,R8 默认处于启用状态,你可将以下代码添加到项目的 gradle.properties 文件以停用 R8:

    android.enableR8=false
    

    R8 普通模式是兼容 Proguard的,R8 完全模式与会启用一些额外的优化,这个时候可能需要一些其它ProGuard规则以避免运行时问题,可以在 项目的gradle.properties 文件中设置以下内容启用完全模式。

    android.enableR8.fullMode=true
    

    启用压缩,混淆,优化

    使用Android Studio创建新项目时,默认情况下不启用压缩,混淆,优化,因为这会增加项目的构建时间,而且有些代码混淆后会出错。要想启用这些功能,需要在项目的build.gradle包含下面的内容。

    getDefaultProguardFile('proguard-android.txt') 方法可从 Android SDK tools/proguard/ 文件夹获取默认的 ProGuard规则文件。

    proguard-rules.pro文件用于添加自定义 ProGuard 规则。默认情况下,该文件位于模块根目录。

    android {
        buildTypes {
            release {
                //启用代码压缩,混淆,优化
                minifyEnabled true
    
                //启用资源压缩
                shrinkResources true
    
                //ProGuard规则文件
                proguardFiles getDefaultProguardFile(
                        'proguard-android-optimize.txt'),
                        'proguard-rules.pro'
            }
        }
        ...
    }
    

    自定义要保留的资源

    如果您有想要保留或舍弃的特定资源,请在您的项目中创建一个包含 <resources> 标记的 XML 文件,并在 tools:keep 属性中指定每个要保留的资源,在 tools:discard 属性中指定每个要舍弃的资源。这两个属性都接受逗号分隔的资源名称列表。您可以使用星号字符作为通配符。

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
        tools:discard="@layout/unused2" />
    

    严格压缩模式

    如果你的代码或库代码(例如AppCompat)调用了Resources.getIdentifier(),这就表示你的代码将根据动态生成的字符串查询资源名称,默认情况下资源压缩器会采取防御性行为,将所有具有匹配名称格式的资源标记为可能已使用,无法移除。例如,以下代码会使所有带 img_ 前缀的资源标记为已使用。

    String name = String.format("img_%1d", angle + 1);
    res = getResources().getIdentifier(name, "drawable", getPackageName());
    

    在 keep.xml 文件中将 shrinkMode 设置为 strict可以停用该防御性行为,这个时候你必须用tools:keep 属性手动保留这些资源。

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:shrinkMode="strict" />
    

    合并重复资源

    默认情况下,Gradle 还会合并同名资源,例如可能位于不同资源文件夹中的同名可绘制对象。这一行为不受 shrinkResources 属性控制,也无法停用,因为在有多个资源匹配代码查询的名称时,有必要利用这一行为来避免错误。

    只有在两个或更多个文件具有完全相同的资源名称类型限定符时,才会进行资源合并。

    自定义要保持的代码

    在ProGuard规则文件中可以使用-keep保持特定代码不被移除或混淆,或者向你想保持的代码添加 @Keep 注解,在类上添加 @Keep 可原样保持整个类,在方法或字段上添加它可完整保持方法/字段(及其名称)以及类名称。

    -keep public class MyClass
    

    必须保持的代码

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

    常用ProGuard规则

    关闭压缩

    -dontshrink
    

    关闭代码优化,默认Proguard规则文件已包含

    -dontoptimize
    

    关闭混淆

    -dontobfuscate
    

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

    -optimizationpasses 5
    

    混淆时不使用大小写混合类名,默认Proguard规则文件已包含

    -dontusemixedcaseclassnames
    

    不忽略库中的非public的类,默认Proguard规则文件已包含

    -dontskipnonpubliclibraryclasses
    

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

    -dontskipnonpubliclibraryclassmembers
    

    输出详细信息,默认Proguard规则文件已包含

    -verbose
    

    不做预校验,预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度,默认Proguard规则文件已包含

    -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.**
    

    特殊ProGuard规则

    由于enum类的特殊性,下面两个方法会被反射调用,默认Proguard规则文件已经处理。

    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    }
    

    Parcelable的子类和Creator静态成员变量要保持,否则会产生Android.os.BadParcelableException异常,默认Proguard规则文件已经处理。

    -keepclassmembers class * implements android.os.Parcelable {
      public static final android.os.Parcelable$Creator CREATOR;
    }
    

    常用混淆模板

    # 指定代码的压缩级别
    -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或ProGuard构建项目后会在模块下的build\outputs\mapping\release文件夹下输出下列文件:

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

    相关文章

      网友评论

        本文标题:Android使用R8压缩,混淆,优化App

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