尾声
在我的博客上很多朋友都在给我留言,需要一些系统的面试高频题目。之前说过我的复习范围无非是个人技术博客还有整理的笔记,考虑到笔记是手写版不利于保存,所以打算重新整理并放到网上,时间原因这里先列出面试问题,题解详见:
展示学习笔记
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
正常情况下,资源压缩器可准确判定系统是否使用了资源。不过,如果您的代码(包含库)调用 Resources.getIdentifier(),这就表示您的代码将根据动态生成的字符串查询资源名称。
这时,资源压缩器会采取防御性行为,将所有具有匹配名称格式的资源标记为可能已使用,无法移除。例如,以下代码会使所有带 img_ 前缀的资源标记为已使用:
String name = String.format(“img_%1d”, angle + 1);
res = getResources().getIdentifier(name, “drawable”, getPackageName());
这时,我可以开启资源的严格审查模式,只会保留确定已使用的资源。
移除备用资源
Gradle 资源压缩器只会移除未被应用引用的资源,这意味着它不会移除用于不同设备配置的备用资源。必要时,我们可以使用 Android Gradle 插件的 resConfigs 属性来移除您的应用不需要的备用资源文件(常见的有用于国际化支持的 strings.xml,适配用的 layout.xml 等):
android {
defaultConfig {
…
//保留中文和英文国际化支持
resConfigs “en”, “zh”
}
}
品尝完了以上"配菜",下面让我们来品味一下本文的"主菜":自定义混淆规则。首先,我们来了解一下常见的混淆命令。
keep 命令
这里说的 keep 命令指的是一系列以 -keep 开头的命令,它主要用来保留 Java 中不需要进行混淆的元素。以下是常见的 -keep 命令:
-keep
作用:保留指定的类和成员,防止被混淆处理。例如:
保留包:com.moos.media.entity 下面的类以及类成员
-keep public class com.moos.media.entity.**
保留类:NumberProgressBar
-keep public class com.moos.media.widget.NumberProgressBar {*;}
-keepclassmembers
作用:保留指定的类的成员(变量/方法),它们将不会被混淆。如:
保留类的成员:MediaUtils类中的特定成员方法
-keepclassmembers class com.moos.media.MediaUtils {
public static *** getLocalVideos(android.content.Context);
public static *** getLocalPictures(android.content.Context);
}
-keepclasseswithmembers
作用:保留指定的类和其成员(变量/方法),前提是它们在压缩阶段没有被删除。与-keep 使用方式类似:
保留类:BaseMediaEntity 的子类
-keepclasseswithmembers public class * extends com.moos.media.entity.BaseMediaEntity{*;}
保留类:OnProgressBarListener接口的实现类
-keep public class * implements com.moos.media.widget.OnProgressBarListener {*;}
@Keep
除了以上方式,你也可以选择使用 @Keep 注解来保留期望代码,防止它们被混淆处理。比如,我们通过 @Keep 修饰一个类来保留它不被混淆:
@Keep
data class CloudMusicBean(var createDate: String,
var id: Long,
var name: String,
var url: String,
val imgUrl: String)
同样地,我们也可以让 @Keep 来修饰方法或者字段进而保留它们。
其他命令
dontwarn
-keep class twitter4j.** { *; }
-dontwarn twitter4j.**
-dontwarn 命令一般在我们引入新的 library 时会使用到,常用于处理 library 中无法解决的警告。如:
其他的命令用法可参考 Android 系统提供的默认混淆规则:
#混淆时不生成大小写混合的类名
-dontusemixedcaseclassnames
#不跳过非公共的库的类
-dontskipnonpubliclibraryclasses
#混淆过程中记录日志
-verbose
#关闭预校验
-dontpreverify
#关闭优化
-dontoptimize
#保留注解
-keepattributes Annotation
#保留所有拥有本地方法的类名及本地方法名
-keepclasseswithmembernames class * {
native ;
}
#保留自定义View的get和set方法
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
#保留Activity中View及其子类入参的方法,如: onClick(android.view.View)
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#保留枚举
-keepclassmembers enum * {
**[] $VALUES;
public *;
}
#保留序列化的类
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
#保留R文件的静态成员
-keepclassmembers class *.R$ {
public static ;
}
-dontwarn android.support.**
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep ;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep ;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep (…);
}
更多混淆命令可以参考文章:Proguard 最全混淆规则说明 ,这里就不做详细讲解了。
https://juejin.im/entry/58f6d2a10ce463006bc9e6af
我们在了解了混淆的基本命令之后,很多人应该还是一头雾水:到底哪些内容该混淆?
其实,我们在使用代码混淆时,ProGuard 对我们项目中大部分代码进行了混淆操作,为了防止编译时出错,我们应该通过 keep 命令保留一些元素不被混淆。所以,我们只需要知道哪些元素不应该被混淆:
枚举
项目中难免可能会用到枚举类型,然而它不能参与到混淆当中去。原因是:枚举类内部存在 values 方法,混淆后该方法会被重新命名,并抛出 NoSuchMethodException。
庆幸的是,Android 系统默认的混淆规则中已经添加了对于枚举类的处理,我们无需再去做额外工作。想了解更多枚举内部细节可以去查看源码,篇幅有限不再细说。
被反射的元素
被反射使用的类、变量、方法、包名等不应该被混淆处理。原因在于:代码混淆过程中,被反射使用的元素会被重命名,然而反射依旧是按照先前的名称去寻找元素,所以会经常发生 NoSuchMethodException 和 NoSuchFiledException 问题。
实体类
实体类即我们常说的"数据类",当然经常伴随着序列化与反序列化操作。很多人也应该都想到了,混淆是将原本有特定含义的"元素"转变为无意义的名称,所以,经过混淆的"洗礼"之后,序列化之后的 value 对应的 key 已然变为没有意义的字段,这肯定是我们不希望的。
同时,反序列化的过程创建对象从根本上来说还是借助于反射,混淆之后 key 会被改变,所以也会违背我们预期的效果。
四大组件
Android 中的四大组件同样不应该被混淆。
原因在于:
-
四大组件使用前都需要在 AndroidManifest.xml 文件中进行注册声明,然而混淆处理之后,四大组件的类名就会被篡改,实际使用的类与 manifest 中注册的类并不匹配,故而出错。
-
其他应用程序访问组件时可能会用到类的包名加类名,如果经过混淆,可能会无法找到对应组件或者产生异常。
JNI 调用的Java 方法
当 JNI 调用的 Java 方法被混淆后,方法名会变成无意义的名称,这就与 C++ 中原本的 Java 方法名不匹配,因而会无法找到所调用的方法。
其他不应该被混淆的
-
自定义控件不需要被混淆
-
JavaScript 调用 Java 的方法不应混淆
-
Java 的 native 方法不应该被混淆
-
项目中引用的第三方库也不建议混淆
代码经过 ProGuard 混淆处理后,想要读取 StackTrace(堆栈追踪)信息就会变得很困难。由于方法名称和类的名称都经过混淆处理,即使程序发生崩溃问题,也很难定位问题所在。幸运的是,ProGuard 为我们提供了补救的措施,在着手进行之前,我们先来看一下 ProGuard 每次构建后生成了哪些内容。
混淆输出结果
混淆构建完成之后,会在 /build/outputs/mapping/release/ 目录下生成以下文件:
dump.txt
说明 APK 内所有类文件的内部结构。
mapping.txt
提供混淆前后的内容对照表,内容主要包含类、方法和类的成员变量。
seeds.txt
罗列出未进行混淆处理的类和成员。
usage.txt
罗列出从 APK 中移除的代码。
恢复堆栈跟踪
了解完混淆构建完毕后输出的内容之后,我们现在就来看一下之前的问题:混淆处理后,StackTrace 定位困难。如何来恢复 StackTrace 的定位能力呢?
系统为我们提供了 retrace 工具,结合上文提到的 mapping.txt 文件,就可以将混淆后的崩溃堆栈追踪信息还原成正常情况下的 StackTrace 信息。主要有两种方式来恢复 StackTrace,为了方便理解,我们以下面这段崩溃信息为例,借助两种方式分别来还原:
java.lang.RuntimeException: Unable to start activity
Caused by: kotlin.KotlinNullPointerException
at com.moos.media.ui.ImageSelectActivity.k(ImageSelectActivity.kt:71)
学习宝典
对我们开发者来说,一定要打好基础,随时准备战斗。不论寒冬是否到来,都要把自己的技术做精做深。虽然目前移动端的招聘量确实变少了,但中高端的职位还是很多的,这说明行业只是变得成熟规范起来了。竞争越激烈,产品质量与留存就变得更加重要,我们进入了技术赋能业务的时代。
不论遇到什么困难,都不应该成为我们放弃的理由!
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我针对Android程序员,我这边给大家整理了一套学习宝典!包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【算法合集】
【延伸Android必备知识点】
【Android部分高级架构视频学习资源】
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
[外链图片转存中…(img-KRtYtWqG-1715883832086)]
【算法合集】
[外链图片转存中…(img-Tk7a5Eim-1715883832086)]
【延伸Android必备知识点】
[外链图片转存中…(img-QDgC1FYZ-1715883832087)]
【Android部分高级架构视频学习资源】
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!