ProGuard混淆及R8优化

news/2024/11/30 0:39:00/

前言:使用java编写的源代码编译后生成了对于的class文件,市面上很多软件都可以对class文件进行反编译,况且Android开发的应用程序是用Java代码写的,为了很好的保护Java源代码,我们需要对编译好后的class文件进行混淆。另外随着apk的版本迭代,功能需求越多,apk发布的包的体积也会越来越大,y因此人们更倾向于安装并保留较小和安装占用空间更小的应用,所以谷歌提供了R8 编译器,您可以通过压缩、混淆和优化,更全面的缩小应用体积。Android构建中,在AGP3.4.0之前也是使用的ProGuard 进行代码优化混淆,但是在3.4.0之后,谷歌将这一工作赋予给了性能更佳的R8编译器。虽然摒弃了ProGuard,但是R8编译器还是兼容ProGuard的配置规则

ProGuard混淆

ProGuard是一个混淆代码的开源项目,它的主要作用是混淆代码,但也包括压缩(Shrink)、优化(Optimize)、混淆(Obfuscate)、预检(Preveirfy),来自官网权威的解释:Proguard是一个Java类文件压缩器、优化器、混淆器、预校验器。压缩环节会检测以及移除没有用到的类、字段、方法以及属性。优化环节会分析以及优化方法的字节码。混淆环节会用无意义的短变量去重命名类、变量、方法。这些步骤让代码更精简,更高效,也更难被逆向。因为R8取代了ProGuard的压缩、优化及预检,保留了ProGuard的混淆配置,所以本文只讲下ProGuard的混淆的一些注意事项。

 混淆

定义:简而言之就是使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名,从而提高反编译后的阅读成本。

使用:主项目的 build.gradle 设置 minifyEnabled trueproguard-rules.pro 加入混淆规则;

android {buildTypes {release {...minifyEnabled trueproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}...
}

proguard-android-optimize.txt就是默认的基本的混淆文件,包含一些基本的混淆规则。具体在SDK目录下,给图有真相。sdk安装路径因人而异look下我的

混淆的一些常用规则:

  • 一颗星:表示保留当前包下的类名,如果有子包,子包中的类名也会被混淆
-keep class com.csj.test.*

 也就是com.csj.test.ui包下的所有类(MyView)都会被混淆,但MainActivity类不会被混淆

  • 两颗星:表示保留当前包下的类名,如果有子包,子包中的类名也会被保留。
-keep class com.csj.test.**
  • 上面的方式虽然保留了类名,但是内容还是会被混淆,使用下面方式保留内容:  
-keep class com.csj.test.* {*;}

这样的话com.csj.test包下的所有类名及内容都不会被混淆,但实际开发并不提倡这么使用,因为这样就失去了混淆的意义,所以我们可以针对特定的内容进行保留不被混淆。

在此基础上,我们也可以使用Java的基本规则来保护特定类不被混淆,比如我们可以用extends,implements等这些Java规则

例如:

-keep public class * extends android.app.Activity

以上代码的意思就是所有继承android.app.Activity这个类的子类都不会被混淆。同理

-keep public class * implements java.io.Serializable

保留Serializable序列化的所实现的类不被混淆

以上是针对整个类不被混淆,但如果还是觉得混淆的范围太大,就是一个类中你不希望保持全部内容不被混淆,而只是希望保护类下的特定内容,就可以使用

<init>;     //匹配所有构造器
<fields>;   //匹配所有域
<methods>;  //匹配所有方法方法

在或前面加上private 、public等来进一步指定不被混淆的内容,如

-keep class com.csj.test.MainActivity{public <methods>;
}
  • 当然你还可以加入参数,比如以下表示用String作为入参的构造函数不会被混淆:
-keep class com.csj.test.MainActivity{public <init>(String);
}

也可以直接指定具体哪个方法不被混淆

-keep class com.csj.test.ui.MyView{public void test();
}

还有一种就是不需要保持类名,只需要把该类下的特定方法保持不被混淆就好,那你就不能用keep方法了,keep方法会保持类名,而需要用keepclassmembers ,如此类名就不会被保持,为了便于对这些规则进行理解,官网给出了以下表格:

# -keep关键字
# keep:包留类和类中的成员,防止他们被混淆
# keepnames:保留类和类中的成员防止被混淆,但成员如果没有被引用将被删除
# keepclassmembers :只保留类中的成员,防止被混淆和移除。
# keepclassmembernames:只保留类中的成员,但如果成员没有被引用将被删除。
# keepclasseswithmembers:如果当前类中包含指定的方法,则保留类和类成员,否则将被混淆。
# keepclasseswithmembernames:如果当前类中包含指定的方法,则保留类和类成员,如果类成员没有被引用,则会被移除。

到这基本上开发常用的混淆就差不多够用了,接下来就是区分实际开发中,需要注意哪些内容不应该被混淆的。

首先是混淆的一些基本规则,任何APP都要使用,可以作为模板使用。如下:

# 代码混淆压缩比,在0和7之间,默认为5,一般不需要改
-optimizationpasses 5# 混淆时不使用大小写混合,混淆后的类名为小写
-dontusemixedcaseclassnames# 指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclasses# 指定不去忽略非公共的库的类的成员
-dontskipnonpubliclibraryclassmembers# 不做预校验,preverify是proguard的4个步骤之一
# Android不需要preverify,去掉这一步可加快混淆速度
-dontpreverify# 有了verbose这句话,混淆后就会生成映射文件
# 包含有类名->混淆后类名的映射关系
# 然后使用printmapping指定映射文件的名称
-verbose
-printmapping proguardMapping.txt# 指定混淆时采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不改变
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*# 保护代码中的Annotation不被混淆,这在JSON实体映射时非常重要,比如fastJson
-keepattributes *Annotation*# 避免混淆泛型,这在JSON实体映射时非常重要,比如fastJson
-keepattributes Signature//抛出异常时保留代码行号,在异常分析中可以方便定位
-keepattributes SourceFile,LineNumberTable-dontskipnonpubliclibraryclasses用于告诉ProGuard,不要跳过对非公开类的处理。默认情况下是跳过的,因为程序中不会引用它们,有些情况下人们编写的代码与类库中的类在同一个包下,并且对包中内容加以引用,此时需要加入此条声明。-dontusemixedcaseclassnames,这个是给Microsoft Windows用户的,因为ProGuard假定使用的操作系统是能区分两个只是大小写不同的文件名,但是Microsoft Windows不是这样的操作系统,所以必须为ProGuard指定-dontusemixedcaseclassnames选项

再来说下哪些是需要保留不被混淆的

1、保留所有的本地native方法不被混淆

-keepclasseswithmembernames class * {native <methods>;
}

因为R8(ProGuard)并未对反射以及JNI等情况进行检测,如果配置文件中未处理,则这部分代码就会被丢弃,会出现NoClassFindException的异常,

2、反射用到的类不混淆

原因同上

3、保留了继承自Activity、Application这些类的子类

-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

因为这些子类,都有可能被外部调用,第一行就保证了所有Activity的子类不要被混淆。

4、使用enum类型时需要注意避免以下两个方法混淆,因为enum类的特殊性,以下两个方法会被反射调用

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

5、保留Parcelable、Serializable序列化的类不被混淆,包括自定义的一些和服务器交互的bean类

# 保留Parcelable序列化的类不被混淆
-keep class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator *;
}# 保留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();
}

使用GSON、fastjson等框架解析服务端数据时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象;这一点也是开发中经常忽略的问题

6、使用第三方开源库或者引用其他第三方的SDK包时,如果有特别要求,也需要在混淆文件中加入对应的混淆规则;这个一般三方官网上会有文档说明、例如高德地图SDK

 7、有用到WebView的JS调用也需要保证写的接口方法不混淆,

# 保留JS方法不被混淆
-keepclassmembers class com.example.xxx.MainActivity$JSInterface1 {<methods>;
}

其中JSInterface是MainActivity的子类

8、对WebView的处理

# 对WebView的处理
-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, java.lang.String)
}

9、内嵌类不被混淆

# 保留内嵌类不被混淆
-keep class com.example.xxx.MainActivity$* { *; }

这个$符号就是用来分割内嵌类与其母体的标志。

也可以在具体点,比如保持ScriptFragment内部类JavaScriptInterface中的所有public内容不被混淆。

-keepclassmembers class cc.csj.test.ScriptFragment$JavaScriptInterface {public *;
}

10、保留自定义控件(继承自View)不被混淆

# 保留自定义控件(继承自View)不被混淆
-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);
}

ok,到这里,基本proGuard的混淆规则就大致结束了。不用死记规则,但得知道有这么个东西,打包release包时,能定位出混淆所带来的问题就行了

什么是R8?

R8 是一个将我们的 java 字节码转换为优化的 dex 码的工具。它遍历整个应用程序,然后对其进行优化,例如删除未使用的类、方法等。它在编译时运行。它可以帮助我们减少构建的大小并使我们的应用程序更加安全。R8 使用 Proguard 规则来修改其默认行为。

R8 收缩是如何工作的?

在优化代码的同时,R8 减少了我们应用程序的代码,进而减小了 APK 的大小。

为了减小 APK 大小,我们采用了三种不同的技术:

  1. 收缩或摇树:收缩是从我们的 Android 项目中删除无法访问的代码的过程。R8 执行一些静态分析以摆脱无法访问的代码并删除未实例化的对象。

  2. 优化:这用于优化代码的大小。它涉及删除死代码、删除未使用的参数、选择性内联、类合并等。

  3. 标识符重命名:在这个过程中,我们混淆了类名和其他变量名。例如,如果类的名称是“ MainActivity ”,那么它将被混淆为“ a ”或其他名称,但大小更小。

/*** @Author shengjie.chen* @Date 2023-06-23 19:19*/
public class Chen {private void unused() {System.out.println("没吊用的代码");}private static void greeting() {System.out.println("hello,端午安康!");}public static void main(String[] args) {greeting();}}

程序的入口是 static void main 方法,我们使用以下 keep 规则 指定该方法:

-keep class com.csj.test.Chen{ public static void main(java.lang.String[]); }

R8 缩减算法的运作方式如下:

  • 首先,它从程序常见的入口点跟踪所有可访问的代码。这些入口点由 R8 keep 规则定义。例如,在此 Java 代码示例中,R8 会在 main 方法处开始运行。
  • 在该示例中,R8 从 main 方法跟踪到 greeting 方法。greeting 方法是在运行时被调用的,因此跟踪在此处停止。
  • 跟踪完成后,R8 使用摇树优化来删除未使用的代码。在此示例中,摇树删除了未使用的方法(unused),因为 R8 的跟踪过程检测到从任何已知的入口都无法到达该方法。
  • 接下来,R8 将标识重命名为较短的名称,这些名称在 DEX 文件中占用较少的空间。在示例中,R8 可能会将 greeting 方法重命名为短名称 a:
package com.csj.test;/*** @Author shengjie.chen* @Date 2023-06-23 19:19*/
public class Chen {private static void a() {System.out.println("hello,端午安康!");}public static void main(String[] args) {a();}}
  • 最后,应用代码优化。缩减代码大小的内联是其一。在此示例中,将方法 a 的主体直接迁移到 main 中,代码会显得更简洁:
  • public class Chen {public static void main(String[] args) {System.out.println("hello,端午安康!");}}

    简而言之就是比如你项目中依赖了很多库,但是只使用了库里面少部分代码,为了移除这部分代码,R8会根据配置文件确定应用代码的所有入口点:包括应用启动的第一个Activity或者服务等,R8会根据入口,检测应用代码,并构建出一张图表,列出应用运行过程中可能访问的方法,成员变量和类等,并对图中没有关联到的代码,视为可移除代码。盗个图

图中入口位置:MainActivity,整个调用链路中,使用到了foo,bar函数以及AwesomeApi类中的faz函数,所以这部分代码会被构建到依赖图中,而OkayApi类以及其baz函数都未访问到,则这部分代码就可以被优化。图上所表示的内内容和我上面那个例子是差不多的意思,但人家的直观点。嘎嘎。。。

那么R8狠在哪里呢?好比你的项目引入个三方依赖,但只用了其中一小部分代码,而R8会在你打包的时候,把三方依赖里未关联的代码都会移除掉再打包你的apk中。

在说下R8的代码优化。

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

1、如果您的代码从未采用过给定 if/else 语句的 else {} 分支,R8 可能会移除 else {} 分支的代码

package com.csj.testimport android.util.Log/*** @Author shengjie.chen* @Date 2023-06-23 19:34*/
class Demo {fun test() {if (true) {Log.e("TAG", "test: 端午安康")} else {Log.e("TAG", "test: 端午放假,但下大雨了")}}
}

2、如果您的代码只在一个位置调用某个方法,R8 可能会移除该方法并将其内嵌在这一个调用点

这个上面举过例子了,不举了!

3、如果 R8 确定某个类只有一个唯一子类且该类本身未实例化(例如,一个仅由一个具体实现类使用的抽象基类),它就可以将这两个类组合在一起并从应用中移除一个类。

class Father{}
class Son extends Father{}

这种情况,Son就被干掉。

用 ProGuard 还是 R8?

如果没有历史包袱,直接R8,毕竟兼容绝大部分的ProGuard规则,更快的编译速度,对Kotlin更友好。

还是简单描述下两者吧:

  • ProGuard → 压缩、优化和混淆Java字节码文件的免费工具,开源仓库地址:proguard
  • R8 → ProGuard的替代工具,支持现有ProGuard规则,更快更强AGP 3.4.0或更高版本,默认使用R8混淆编译器。

如果不想用R8,想用回ProGuard的话(可以但没必要),可以在 gradle.properties 文件中添加下述配置禁用R8:

android.enableR8=false
android.enableR8.libraries=false

编译APK时可能会报错:

 

proguard-rules.pro 文件中加上 -ignorewarnings 即可解决。

另外,使用ProGuard或R8构建项目会在 build\outputs\mapping\release 输出下述文件:

  • mapping.txt → 原始与混淆过的类、方法、字段名称间的转换;
  • seeds.txt → 未进行混淆的类与成员;
  • usage.txt → APK中移除的代码;
  • resources.txt → 资源优化记录文件,哪些资源引用了其他资源,哪些资源在使用,哪些资源被移除;

自动生成不用管。

总结

R8保留了Proguard 混淆规则且有效地内联容器类并删除未使用的类、字段和方法.它减小了应用程序的大小。R8 提供比 Proguard 更好的输出,并且比 Proguard 更快,从而减少了整体构建时间。


参考文章:

补齐Android技能树 - 从害怕到玩转Android代码混淆 - 掘金

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

Android 中的 R8 与 Proguard的区别 - 简书


http://www.ppmy.cn/news/535910.html

相关文章

数据猿·金猿榜丨2017中国智能语音领域最具潜力创业公司

【数据猿导读】 “2017中国智能语音领域最具潜力创业公司”盘点源于数据猿推出的“金猿榜”系列内容&#xff0c;旨在通过媒体的方式与原则&#xff0c;发掘大数据领域最具潜力的创新型企业 编辑 | sharon 官网 | www.datayuan.cn 微信公众号ID | datayuancn “2017中国智能语音…

揭秘OPhone白手起家前后:一个系统的诞生

转载说明&#xff1a;一个新生事务的诞生&#xff0c;给一些人会可能带来一些生存的威胁&#xff0c;给另外一些人可能会带来一些机会&#xff0c;最近手机操作系统大战硝烟弥漫&#xff0c;如果能掌握好这个机会&#xff0c;对于开发人员来来说可能会带来一笔小财&#xff0c;…

跨界转型 打造大数据旗舰

从厦华电子公告的信息来看&#xff0c;厦华电子结束传统的电子产品业务后&#xff0c;将转向盈利能力较强&#xff0c;发展前景广阔的大数据解决方案业务。 数联铭品主营大数据 厦华电子5月27日晚间公告称&#xff0c;根据上交所二次问询函要求&#xff0c;公司、中介机构等相关…

国产手机光辉岁月

国产手机光辉岁月 TCL手机 昔日辉煌 TCL 移动通信有限公司生产经营GSM 数字移动电话。2001 年该公司取得高速的发展&#xff0c;TCL 手机的销量、销售收入及利润增幅巨大&#xff0c;取得很好效益。全年共销售手机129 万台&#xff0c;比去年同期增长了497.22%&#xff0c; …

股票的最新和历史股息收益率查询(3)

股息收益率 最近12个月的现金分红总额 / A股总市值。 山西焦化的股息收益率走势图 华域汽车的股息收益率走势图 一汽富维的股息收益率走势图 华远地产的股息收益率走势图 华银电力的股息收益率走势图 闻泰科技的股息收益率走势图 江苏索普的股息收益率走势图 ST大控的股…

成都16条新经济优势赛道解读之5G大数据

作者 | 36氪四川 来源 | 36氪四川&#xff08;ID&#xff1a;SC36kr&#xff09; 原标题 | 成都16条新经济优势赛道解读之5G&大数据 当前成都新经济企业已高达36万家&#xff0c;7月15日&#xff0c;成都市委十三届七次全会举行&#xff0c;会上提出成都要构建以新经济为…

*ST厦华连续三年亏损 5月起将暂停上市

与“同城兄弟”夏新电子&#xff08;600057.SH&#xff09;同病相怜的是&#xff0c;*ST厦华&#xff08;600870.SH&#xff09;也将因连续三年亏损而面临暂停上市的命运。但业内专家认为&#xff0c;厦华不像夏新电子那样迫切需要重组。 厦华昨天发布公告透露&#xff0c;如果…

定增变重组 *ST厦华拟购大数据公司

*ST厦华今日发布公告称&#xff0c;因资本市场及第三方支付行业环境等诸多方面因素发生了较大变化&#xff0c;公司拟终止非公开发行股票事项&#xff0c;并启动重大资产重组程序。公司股票自3月14日起停牌不超过一个月。 对于重大资产重组所为何事&#xff0c;*ST厦华仅表示&a…