赞
踩
使用java编写的源代码编译后生成了对于的class文件,但是class文件是一个非常标准的文件,市面上很多软件都可以对class文件进行反编译,为了我们app的安全性,就需要使用到Android代码混淆这一功能。
针对 Java 的混淆,ProGuard
就是常用的混淆工具,且他不仅仅是混淆工具,它同时可以对代码进行 压缩
、优化
、混淆
。
下面我们来简单介绍下ProGuard工作流程。
ProGuard工作过程包括四个步骤:shrink
,optimize
,obfuscate
,preverigy
。这四个步骤都是可选,但是顺序都是不变的。
Android构建中,在AGP3.4.0之前也是使用的ProGuard 进行代码优化混淆,但是在3.4.0之后,谷歌将这一工作赋予给了性能更佳的R8编译器。
虽然摒弃了ProGuard,但是R8编译器还是兼容ProGuard的配置规则。
使用R8编译器可以做以下优化:
代码缩减指的是:R8编译期智能检测代码中未使用到的类、字段、方法和属性等,并移除。
比如你项目中依赖了很多库,但是只使用了库里面少部分代码,为了移除这部分代码,R8会根据配置文件确定应用代码的所有入口点:包括应用启动的第一个Activity或者服务等,R8会根据入口,检测应用代码,并构建出一张图表,列出应用运行过程中可能访问的方法,成员变量和类等,并对图中没有关联到的代码,视为可移除代码。
如下图:
图中入口位置:MainActivity,整个调用链路中,使用到了foo,bar函数以及AwesomeApi类中的faz函数,所以这部分代码会被构建到依赖图中,而OkayApi类以及其baz函数都未访问到,则这部分代码就可以被优化。
使用方式:
arduino
复制代码
android {
buildTypes {
release {
...
minifyEnabled true
}
}
...
}
minifyEnabled 设置为true,会默认启用R8代码缩减功能。
代码优化需要注意的两种情况:
1.反射调用的情况
2.JNI调用的情况
R8并未对反射以及JNI等情况进行检测,如果配置文件中未处理,则这部分代码就会被丢弃,会出现NoClassFindException的异常,如何解决呢?
kotlin
复制代码
-keep public class MyClass
前提条件:1.声明了使用AndroidX 2.使用AGP默认的ProGuard文件
R8使用ProGuard 规则文件来决定哪部分代码需要保留,配置文件来源分为下面几个:
位置:/proguard-rules.pro
说明:
创建新的模块时,会在当前模块目录下创建一个默认的:
proguard-rules.pro
文件
位置:由AGP在编译时生成的proguard-android-optimize.txt
说明:
Android Gradle 插件会生成
proguard-android-optimize.txt
(其中包含了对大多数 Android 项目都有用的规则),并启用@Keep*
注解。
编译后在\build\intermediates\proguard-files\目录下会生成3个文件:
其中proguard-android-optimize.txt-4.1.1
是需要进行optimize代码优化的ProGuard配置文件,而proguard-android.txt-4.1.1
表示不需要进行optimize代码优化的ProGuard文件。
4.1.1
:表示当前模块的AGP插件版本。
位置:
AAR 库:<library-dir>/proguard.txt
JAR 库:<library-dir>/META-INF/proguard/
说明:
引入的aar或者jar包的库中,默认包含proguard优化规则,则在编译过程中也会被纳入R8配置项中,所以特别注意aar中引入的proguad和原项目规则冲突的情况。
位置:使用 minifyEnabled true
构建项目后:<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt
说明:
AAPT2 会根据对应用清单中的类、布局及其他应用资源的引用,生成保留规则。例如,AAPT2 会为您在应用清单中注册为入口点的每个 activity 添加一个保留规则。
位置:默认情况下,当您使用 Android Studio 创建新模块时,IDE 会创建 <module-dir>/proguard-rules.pro
,以便您添加自己的规则。
说明:
您可以添加其他配置,R8 会在编译时应用这些配置。如果您将
minifyEnabled
属性设为true
,R8 会将来自上述所有可用来源的规则组合在一起,但是需要注意依赖库引入导致的规则冲突问题。
如果需要输出 R8 在构建项目时应用的所有规则的完整报告:可以添加下面语句到proguard-rules.pro中
javascript
复制代码
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
如果需要添加额外的proguad文件:可以通过将相应文件添加到模块的 build.gradle
文件的 proguardFiles
属性中:
如分别给每个productflavor添加规则可以使用下面这种方式:
java 复制代码 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' } } }
资源缩减是在代码缩减之后进行的,只有去除了不需要的代码后, 才可以知道哪些代码里面的资源也是不被引入,可以移除的。
资源缩减只要在模块gradle下面添加shrinkResources
属性即可:
arduino
复制代码
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
...
}
}
}
注意:资源缩减需要提前开启代码缩减minifyEnabled。
当然你也可以对资源文件添加白名单:
xml
复制代码
<?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
指定可以舍弃的资源文件
混淆指的是将类名,方法名,属性名使用无意义的字符来表示:看下图:
混淆的一些基本规则:
kotlin
复制代码
-keep class cn.hadcn.test.*
kotlin
复制代码
-keep class cn.hadcn.test.**
kotlin
复制代码
-keep class cn.hadcn.test.* {*;}
scala
复制代码
-keep public class * extends android.app.Activity
kotlin
复制代码
-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
public *;
}
csharp
复制代码
<init>; //匹配所有构造器
<fields>; //匹配所有域
<methods>; //匹配所有方法方法
kotlin
复制代码
-keep class cn.hadcn.test.One {
public <methods>;
}
kotlin
复制代码
-keep class cn.hadcn.test.One {
public <init>(org.json.JSONObject);
}
bash
复制代码
# -keep关键字
# keep:包留类和类中的成员,防止他们被混淆
# keepnames:保留类和类中的成员防止被混淆,但成员如果没有被引用将被删除
# keepclassmembers :只保留类中的成员,防止被混淆和移除。
# keepclassmembernames:只保留类中的成员,但如果成员没有被引用将被删除。
# keepclasseswithmembers:如果当前类中包含指定的方法,则保留类和类成员,否则将被混淆。
# keepclasseswithmembernames:如果当前类中包含指定的方法,则保留类和类成员,如果类成员没有被引用,则会被移除。
1.使用AS4.1.1版本进行混淆编译后,在 /build/outputs/mapping/release/下面会生成以下四个文件:
usage:未使用的文件,也就是移除后的文件。
arduino
复制代码
-keepclasseswithmembernames class * {
native <methods>;
}
php
复制代码
# 保持Parcelable不被混淆
-keep class * implements Android.os.Parcelable {
public static final Android.os.Parcelable$Creator *;
}
arduino
复制代码
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
下面是一个混淆模板:可根据自身需要进行添加和删除:
scala 复制代码 #--------------------------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
为了进一步缩减应用,R8 会在更深的层次上检查代码,以移除更多不使用的代码,或者在可能的情况下重写代码,以使其更简洁。下面是此类优化的几个示例:
else {}
分支,R8 可能会移除 else {}
分支的代码更多优化点可以查看:Jake Wharton 写的关于 R8 优化的博文。
本篇文章主要讲解了关于R8编译器在整个编译过程中对apk代码以及资源的一些优化操作,主要集中在代码缩减,资源缩减,代码混淆,代码优化这几部分,其中对代码混淆做了一个比较全面的分析,后期还会讲解一些关于代码反混淆的知识,这篇文章就讲到这里了,我是小余,我们下期见。
作者:小余的自习室
链接:https://juejin.cn/post/7225511164120891453
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。