本文正在参与「金石计划」

前言

运用java编写的源代码编译后生成了关于的class文件,可是class文件是一个非常规范的文件,市面上许多软件都能够对class文件进行反编译,为了咱们app的安全性,就需求运用到Android代码混杂这一功用。

针对 Java 的混杂,ProGuard 便是常用的混杂东西,且他不仅仅是混杂东西,它同时能够对代码进行 紧缩优化混杂

下面咱们来简略介绍下ProGuard作业流程。

ProGuard 作业流程

ProGuard作业过程包括四个过程:shrinkoptimizeobfuscatepreverigy。这四个过程都是可选,可是次序都是不变的

【Android性能优化】:ProGuard,混淆,R8优化

  • shrink:检测并删去项目中未运用到的类,字段,办法以及特点
  • optimize:优化字节码,移除无用指令,或许进行指令优化。
  • obfuscate:代码混杂,将代码中的类,字段,办法和特点等名称运用无意义的名称进行表明,减少代码反编译后的可读性。
  • preverify:针对 Java 1.6 及以上版别进行预校验, 校验 StackMap /StackMapTable 特点.在编译时能够关闭,加快编译速度

Android中的代码优化以及混杂

Android构建中,在AGP3.4.0之前也是运用的ProGuard 进行代码优化混杂,可是在3.4.0之后,谷歌将这一作业赋予给了功能更佳的R8编译器

虽然摒弃了ProGuard,可是R8编译器还是兼容ProGuard的装备规矩

运用R8编译器能够做以下优化:

  • 1.代码减缩

  • 2.资源减缩

  • 3.代码混杂

  • 4.代码优化

1.代码减缩

代码减缩指的是:R8编译期智能检测代码中未运用到的类、字段、办法和特点等,并移除

比方你项目中依靠了许多库,可是只运用了库里面少部分代码,为了移除这部分代码,R8会依据装备文件确认运用代码的一切入口点:包括运用启动的第一个Activity或许服务等,R8会依据入口,检测运用代码,并构建出一张图表,列出运用运转过程中或许拜访的办法,成员变量和类等,并对图中没有关联到的代码,视为可移除代码。

如下图:

【Android性能优化】:ProGuard,混淆,R8优化

图中入口方位:MainActivity,整个调用链路中,运用到了foo,bar函数以及AwesomeApi类中的faz函数,所以这部分代码会被构建到依靠图中,而OkayApi类以及其baz函数都未拜访到,则这部分代码就能够被优化。

运用办法:

android {
  buildTypes {
    release {
         ...
      minifyEnabled true
     }
   }
   ...
}

minifyEnabled 设置为true,会默许启用R8代码减缩功用。

代码优化需求留意的两种状况:

1.反射调用的状况

2.JNI调用的状况

R8并未对反射以及JNI等状况进行检测,假如装备文件中未处理,则这部分代码就会被丢弃,会呈现NoClassFindException的反常,如何处理呢?

  • 1.1:在装备文件中运用-keep对这部分类进行阐明:
-keep public class MyClass
  • 1.2:给需求保存的代码增加@keep注解

前提条件:1.声明了运用AndroidX 2.运用AGP默许的ProGuard文件

R8装备文件

R8运用ProGuard 规矩文件来决定哪部分代码需求保存,装备文件来历分为下面几个:

  • 1.AndroidStudio:

方位:/proguard-rules.pro

阐明

创立新的模块时,会在当时模块目录下创立一个默许的:proguard-rules.pro 文件

  • 2.AGP插件

方位:由AGP在编译时生成的proguard-android-optimize.txt

阐明

Android Gradle 插件会生成 proguard-android-optimize.txt(其中包括了对大多数 Android 项目都有用的规矩),并启用 @Keep* 注解。

编译后在\build\intermediates\proguard-files\目录下会生成3个文件:

【Android性能优化】:ProGuard,混淆,R8优化

其中proguard-android-optimize.txt-4.1.1是需求进行optimize代码优化的ProGuard装备文件,而proguard-android.txt-4.1.1表明不需求进行optimize代码优化的ProGuard文件。

4.1.1表明当时模块的AGP插件版别

  • 3.库依靠项

方位

AAR 库:<library-dir>/proguard.txt

JAR 库:<library-dir>/META-INF/proguard/

阐明

引进的aar或许jar包的库中,默许包括proguard优化规矩,则在编译过程中也会被归入R8装备项中,所以特别留意aar中引进的proguad和原项目规矩抵触的状况。

  • 4.AAPT2(Android资源打包东西)

方位:运用 minifyEnabled true 构建项目后:<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt

阐明

AAPT2 会依据对运用清单中的类、布局及其他运用资源的引证,生成保存规矩。例如,AAPT2 会为您在运用清单中注册为入口点的每个 activity 增加一个保存规矩。

  • 5.自定义装备文件

方位:默许状况下,当您运用 Android Studio 创立新模块时,IDE 会创立 <module-dir>/proguard-rules.pro,以便您增加自己的规矩。

阐明

您能够增加其他装备,R8 会在编译时运用这些装备。假如您将 minifyEnabled 特点设为 true,R8 会将来自上述一切可用来历的规矩组合在一起,可是需求留意依靠库引进导致的规矩抵触问题

假如需求输出 R8 在构建项目时运用的一切规矩的完好陈述:能够增加下面句子到proguard-rules.pro中

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

假如需求增加额定的proguad文件:能够经过将相应文件增加到模块的 build.gradle 文件的 proguardFiles 特点中:

如分别给每个productflavor增加规矩能够运用下面这种办法:

android {
   ...
   buildTypes {
     release {
       minifyEnabled true
       proguardFiles
         getDefaultProguardFile('proguard-android-optimize.txt'),
         // List additional ProGuard rules for the given build type here. By default,
         // Android Studio creates and includes an empty rules file for you (located
         // at the root directory of each module).
         'proguard-rules.pro'
     }
   }
   flavorDimensions "version"
   productFlavors {
     flavor1 {
       ...
     }
     flavor2 {
       proguardFile 'flavor2-rules.pro'
     }
   }
}

2.资源减缩

资源减缩是在代码减缩之后进行的,只要去除了不需求的代码后, 才能够知道哪些代码里面的资源也是不被引进,能够移除的。

资源减缩只要在模块gradle下面增加shrinkResources特点即可:

android {
   ...
   buildTypes {
     release {
       shrinkResources true
       minifyEnabled true
       ...
     }
   }
}

留意:资源减缩需求提前敞开代码减缩minifyEnabled

当然你也能够对资源文件增加白名单

<?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" />

运用tools:keep指定需求保存的资源文件,运用tools:discard指定能够放弃的资源文件

3.代码混杂

混杂指的是将类名,办法名,特点名运用无意义的字符来表明:看下图:

【Android性能优化】:ProGuard,混淆,R8优化

如何进行混杂?

混杂的一些根本规矩

  • 一颗星:表明保存当时包下的类名,假如有子包,子包中的类会被混杂
-keep class cn.hadcn.test.*
  • 两颗星:表明保存当时包下的类名,假如有子包,子包中的类名也会被保存。
-keep class cn.hadcn.test.**
  • 上面的办法虽然保存了类名,可是内容还是会被混杂,运用下面办法保存内容
-keep class cn.hadcn.test.* {*;}
  • 在此基础上,咱们也能够运用Java的根本规矩来维护特定类不被混杂,比方咱们能够用extends,implements等这些Java规矩。如下比方就防止一切承继Activity的类被混杂:
-keep public class * extends android.app.Activity
  • 假如咱们要保存一个类中的内部类不被混杂则需求用$符号,如下比方表明坚持ScriptFragment内部类JavaScriptInterface中的一切public内容不被混杂。
-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
  public *;
}
  • 再者,假如一个类中你不期望坚持全部内容不被混杂,而仅仅期望维护类下的特定内容,就能够运用:
<init>;   //匹配一切构造器
<fields>;  //匹配一切域
<methods>;  //匹配一切办法办法
  • 你还能够在或前面加上private 、public、native等来进一步指定不被混杂的内容,如
-keep class cn.hadcn.test.One {
   public <methods>;
}
  • 当然你还能够参加参数,比方以下表明用JSONObject作为入参的构造函数不会被混杂:
-keep class cn.hadcn.test.One {
  public <init>(org.json.JSONObject);
}
  • 有时候你是不是还想着:我不需求坚持类名,我只需求把该类下的特定办法坚持不被混杂就好,那你就不能用keep办法了,keep办法会坚持类名,而需求用keepclassmembers ,如此类名就不会被坚持,为了便于对这些规矩进行了解,官网给出了以下表格:

【Android性能优化】:ProGuard,混淆,R8优化

# -keep关键字
# keep:包留类和类中的成员,防止他们被混杂
# keepnames:保存类和类中的成员防止被混杂,但成员假如没有被引证将被删去
# keepclassmembers :只保存类中的成员,防止被混杂和移除。
# keepclassmembernames:只保存类中的成员,但假如成员没有被引证将被删去。
# keepclasseswithmembers:假如当时类中包括指定的办法,则保存类和类成员,不然将被混杂。
# keepclasseswithmembernames:假如当时类中包括指定的办法,则保存类和类成员,假如类成员没有被引证,则会被移除。
留意事项
  • 1.运用AS4.1.1版别进行混杂编译后,在 /build/outputs/mapping/release/下面会生成以下四个文件:

    • configuration:一切ProGuard文件整合后的规矩文件:
    • mapping:混杂前后的映射文件,这个文件在运用反混杂的时候有用。
    • seeds:未进行混杂的类和成员。
    • usage:未运用的文件,也便是移除后的文件。

      【Android性能优化】:ProGuard,混淆,R8优化

  • 2.有运用jni的状况下,jni办法不行混杂
-keepclasseswithmembernames class * {  
   native <methods>;
}
  • 3.反射用到的类不混杂。
  • 4.AndroidMainfest中的类不混杂,一切四大组件和Application的子类和Framework层下一切的类默许不会进行混杂,自定义的View默许也不会被混杂。所以像网上贴的许多扫除自定义View,或四大组件被混杂的规矩在Android Studio中是无需参加的。
  • 5.与服务端交互时,运用GSON、fastjson等框架解析服务端数据时,所写的JSON目标类不混杂,不然无法将JSON解析成对应的目标;
  • 6.运用第三方开源库或许引证其他第三方的SDK包时,假如有特别要求,也需求在混杂文件中参加对应的混杂规矩;
  • 7.有用到WebView的JS调用也需求确保写的接口办法不混杂,原因和第一条一样;
  • 8.Parcelable的子类和Creator静态成员变量不混杂,不然会产生Android.os.BadParcelableException反常;
# 坚持Parcelable不被混杂  
-keep class * implements Android.os.Parcelable {     
   public static final Android.os.Parcelable$Creator *;
}
  • 9.运用enum类型时需求留意防止以下两个办法混杂,因为enum类的特殊性,以下两个办法会被反射调用,见第二条规矩。
-keepclassmembers enum * { 
   public static **[] values(); 
   public static ** valueOf(java.lang.String); 
}
混杂模板

下面是一个混杂模板:可依据自身需求进行增加和删去

#--------------------------1.实体类---------------------------------
# 假如运用了Gson之类的东西要使被它解析的JavaBean类即实体类不被混杂。(这儿填写自己项目中存放bean目标的详细路径)
-keep class com.php.soldout.bean.**{*;}
​
#--------------------------2.第三方包-------------------------------
​
#Gson
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keep class com.google.gson.* { *;}
-dontwarn com.google.gson.**
​
#butterknife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
​
#-------------------------3.与js相互调用的类------------------------
​
​
#-------------------------4.反射相关的类和办法----------------------
​
​
#-------------------------5.根本不用动区域--------------------------
#指定代码的紧缩等级
-optimizationpasses 5
​
#包明不混合大小写
-dontusemixedcaseclassnames
​
#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
​
#混杂时是否记载日志
-verbose
​
#优化  不优化输入的类文件
-dontoptimize
​
#预校验
-dontpreverify
​
# 保存sdk系统自带的一些内容 【例如:-keepattributes *Annotation* 会保存Activity的被@override注释的onCreate、onDestroy办法等】
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
​
# 记载生成的日志数据,gradle build时在本项根目录输出
# apk 包内一切 class 的内部结构
-dump proguard/class_files.txt
# 未混杂的类和成员
-printseeds proguard/seeds.txt
# 列出从 apk 中删去的代码
-printusage proguard/unused.txt
# 混杂前后的映射
-printmapping proguard/mapping.txt
​
​
# 防止混杂泛型
-keepattributes Signature
# 抛出反常时保存代码行号,坚持源文件以及行号
-keepattributes SourceFile,LineNumberTable
​
#-----------------------------6.默许保存区-----------------------
# 坚持 native 办法不被混杂
-keepclasseswithmembernames class * {
   native <methods>;
}
​
-keepclassmembers public class * extends android.view.View {
 public <init>(android.content.Context);
 public <init>(android.content.Context, android.util.AttributeSet);
 public <init>(android.content.Context, android.util.AttributeSet, int);
 public void set*(***);
}
​
#坚持 Serializable 不被混杂
-keepclassmembers class * implements java.io.Serializable {
   static final long serialVersionUID;
   private static final java.io.ObjectStreamField[] serialPersistentFields;
   !static !transient <fields>;
   !private <fields>;
   !private <methods>;
   private void writeObject(java.io.ObjectOutputStream);
   private void readObject(java.io.ObjectInputStream);
   java.lang.Object writeReplace();
   java.lang.Object readResolve();
}
​
# 坚持自定义控件类不被混杂
-keepclasseswithmembers class * {
   public <init>(android.content.Context,android.util.AttributeSet);
}
# 坚持自定义控件类不被混杂
-keepclasseswithmembers class * {
   public <init>(android.content.Context,android.util.AttributeSet,int);
}
# 坚持自定义控件类不被混杂
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}
​
# 坚持枚举 enum 类不被混杂
-keepclassmembers enum * {
   public static **[] values();
   public static ** valueOf(java.lang.String);
}
​
# 坚持 Parcelable 不被混杂
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
​
# 不混杂R文件中的一切静态字段,咱们都知道R文件是经过字段来记载每个资源的id的,字段名要是被混杂了,id也就找不着了。
-keepclassmembers class **.R$* {
   public static <fields>;
}
​
#假如引证了v4或许v7包
-dontwarn android.support.**
​
# 坚持哪些类不被混杂
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-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.preference.Preference
​
-keep class com.zhy.http.okhttp.**{*;}
-keep class com.wiwide.util.** {*;}
​
# ============忽略警告,不然打包或许会不成功=============
-ignorewarnings

4.代码优化

为了进一步减缩运用,R8 会在更深的层次上检查代码,以移除更多不运用的代码,或许在或许的状况下重写代码,以使其更简洁。下面是此类优化的几个示例:

  • 假如您的代码从未选用过给定 if/else 句子的 else {} 分支,R8 或许会移除 else {} 分支的代码
  • 假如您的代码只在一个方位调用某个办法,R8 或许会移除该办法并将其内嵌在这一个调用点
  • 假如 R8 确认某个类只要一个仅有子类且该类自身未实例化(例如,一个仅由一个详细实现类运用的抽象基类),它就能够将这两个类组合在一起并从运用中移除一个类。

更多优化点能够查看:Jake Wharton 写的关于 R8 优化的博文。

总结

本篇文章首要讲解了关于R8编译器在整个编译过程中对apk代码以及资源的一些优化操作,首要会集在代码减缩,资源减缩,代码混杂,代码优化这几部分,其中对代码混杂做了一个比较全面的分析,后期还会讲解一些关于代码反混杂的知识,这篇文章就讲到这儿了,我是小余,咱们下期见。

参阅:

Android官方R8优化

guardsquare

Android编译优化:D8和R8

android打包混杂及语法规矩详解

深化 Android 混杂实践:ProGuard 通关秘籍