读懂-Android-中的代码混淆

MainActivity中反射获取的属性名称依然是BOOK_NAME,而对应的类已经没有了这个属性名,所以会抛出NoSuchFieldException.

注意,如果上面的filedName使用字面量或者字符串常量,即使混淆也不会出现NoSuchFieldException异常。因为这两种情况下,混淆可以感知外界对filed的引用,已经在调用出替换成了混淆后的名称。

GSON的序列化与反序列化

GSON是一个很好的工具,使用它我们可以轻松的实现序列化和反序列化.但是当它一旦遇到混淆,就需要我们注意了.

一个简单的类Item,用来处理序列化和反序列化

public class Item {
public String name;
public int id;
}

序列化的代码

Item toSerializeItem = new Item();
toSerializeItem.id = 2;
toSerializeItem.name = “Apple”;
String serializedText = gson.toJson(toSerializeItem);
Log.i(LOGTAG, “testGson serializedText=” + serializedText);

开启混淆之后的日志输出结果

属性名已经改变了,变成了没有意思的名称,对我们后续的某些处理是很麻烦的.

反序列化的代码

Gson gson = new Gson();
Item item = gson.fromJson(“{“id”:1, “name”:“Orange”}”, Item.class);
Log.i(LOGTAG, “testGson item.id=” + item.id + “;item.name=” + item.name);

对应的日志结果是

I/MainActivity: testGson item.id=0;item.name=null

可见,混淆之后,反序列化的属性值设置都失败了.

为什么呢?
  • 因为反序列化创建对象本质还是利用反射,会根据json字符串的key作为属性名称,value则对应属性值.
如何解决
  • 将序列化和反序列化的类排除混淆
  • 使用@SerializedName注解字段

@SerializedName(parameter)通过注解属性实现了

  • 序列化的结果中,指定该属性key为parameter的值.
  • 反序列化生成的对象中,用来匹配key与parameter并赋予属性值.

一个简单的用法为

public class Item {
@SerializedName(“name”)
public String name;
@SerializedName(“id”)
public int id;

|

枚举也不要混淆

枚举是Java 5 中引入的一个很便利的特性,可以很好的替代之前的常量形式.

枚举使用起来很简单,如下

public enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}

这里我们这样使用枚举

Day day = Day.valueOf(“monday”);
Log.i(LOGTAG, “testEnum day=” + day);

运行上面的的代码,通常情况下是没有问题的,是否说明枚举就可以混淆呢?

其实不是.

为什么没有问题呢,因为默认的Proguard配置已经处理了枚举相关的keep操作.

For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations

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

如果我们手动去掉这条keep配置,再次运行,一个这样的异常会从天而降.

E AndroidRuntime: Process: com.example.admin.proguardsample, PID: 17246
E AndroidRuntime: java.lang.AssertionError: impossible
E AndroidRuntime: at java.lang.Enum$1.create(Enum.java:45)
E AndroidRuntime: at java.lang.Enum 1. c r e a t e ( E n u m . j a v a : 36 ) E A n d r o i d R u n t i m e : a t l i b c o r e . u t i l . B a s i c L r u C a c h e . g e t ( B a s i c L r u C a c h e . j a v a : 54 ) E A n d r o i d R u n t i m e : a t j a v a . l a n g . E n u m . g e t S h a r e d C o n s t a n t s ( E n u m . j a v a : 211 ) E A n d r o i d R u n t i m e : a t j a v a . l a n g . E n u m . v a l u e O f ( E n u m . j a v a : 191 ) E A n d r o i d R u n t i m e : a t c o m . e x a m p l e . a d m i n . p r o g u a r d s a m p l e . a . a ( U n k n o w n S o u r c e ) E A n d r o i d R u n t i m e : a t c o m . e x a m p l e . a d m i n . p r o g u a r d s a m p l e . M a i n A c t i v i t y . j ( U n k n o w n S o u r c e ) E A n d r o i d R u n t i m e : a t c o m . e x a m p l e . a d m i n . p r o g u a r d s a m p l e . M a i n A c t i v i t y . o n C r e a t e ( U n k n o w n S o u r c e ) E A n d r o i d R u n t i m e : a t a n d r o i d . a p p . A c t i v i t y . p e r f o r m C r e a t e ( A c t i v i t y . j a v a : 6237 ) E A n d r o i d R u n t i m e : a t a n d r o i d . a p p . I n s t r u m e n t a t i o n . c a l l A c t i v i t y O n C r e a t e ( I n s t r u m e n t a t i o n . j a v a : 1107 ) E A n d r o i d R u n t i m e : a t a n d r o i d . a p p . A c t i v i t y T h r e a d . p e r f o r m L a u n c h A c t i v i t y ( A c t i v i t y T h r e a d . j a v a : 2369 ) E A n d r o i d R u n t i m e : a t a n d r o i d . a p p . A c t i v i t y T h r e a d . h a n d l e L a u n c h A c t i v i t y ( A c t i v i t y T h r e a d . j a v a : 2476 ) E A n d r o i d R u n t i m e : a t a n d r o i d . a p p . A c t i v i t y T h r e a d . − w r a p 11 ( A c t i v i t y T h r e a d . j a v a ) E A n d r o i d R u n t i m e : a t a n d r o i d . a p p . A c t i v i t y T h r e a d 1.create(Enum.java:36) E AndroidRuntime: at libcore.util.BasicLruCache.get(BasicLruCache.java:54) E AndroidRuntime: at java.lang.Enum.getSharedConstants(Enum.java:211) E AndroidRuntime: at java.lang.Enum.valueOf(Enum.java:191) E AndroidRuntime: at com.example.admin.proguardsample.a.a(Unknown Source) E AndroidRuntime: at com.example.admin.proguardsample.MainActivity.j(Unknown Source) E AndroidRuntime: at com.example.admin.proguardsample.MainActivity.onCreate(Unknown Source) E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:6237) E AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107) E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369) E AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) E AndroidRuntime: at android.app.ActivityThread.-wrap11(ActivityThread.java) E AndroidRuntime: at android.app.ActivityThread 1.create(Enum.java:36)EAndroidRuntime:atlibcore.util.BasicLruCache.get(BasicLruCache.java:54)EAndroidRuntime:atjava.lang.Enum.getSharedConstants(Enum.java:211)EAndroidRuntime:atjava.lang.Enum.valueOf(Enum.java:191)EAndroidRuntime:atcom.example.admin.proguardsample.a.a(UnknownSource)EAndroidRuntime:atcom.example.admin.proguardsample.MainActivity.j(UnknownSource)EAndroidRuntime:atcom.example.admin.proguardsample.MainActivity.onCreate(UnknownSource)EAndroidRuntime:atandroid.app.Activity.performCreate(Activity.java:6237)EAndroidRuntime:atandroid.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)EAndroidRuntime:atandroid.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)EAndroidRuntime:atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)EAndroidRuntime:atandroid.app.ActivityThread.wrap11(ActivityThread.java)EAndroidRuntime:atandroid.app.ActivityThreadH.handleMessage(ActivityThread.java:1344)
E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:102)
E AndroidRuntime: at android.os.Looper.loop(Looper.java:148)
E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:5417)
E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
E AndroidRuntime: Caused by: java.lang.NoSuchMethodException: values []
E AndroidRuntime: at java.lang.Class.getMethod(Class.java:624)
E AndroidRuntime: at java.lang.Class.getDeclaredMethod(Class.java:586)
E AndroidRuntime: at java.lang.Enum$1.create(Enum.java:41)
E AndroidRuntime: … 19 more

好玩的事情来了,我们看一看为什么会抛出这个异常

1.首先,一个枚举类会生成一个对应的类文件,这里是Day.class. 这里类里面包含什么呢,看一下反编译的结果

➜ proguardsample javap Day
Warning: Binary file Day contains com.example.admin.proguardsample.Day
Compiled from “Day.java”
public final class com.example.admin.proguardsample.Day extends java.lang.Enum<com.example.admin.proguardsample.Day> {
public static final com.example.admin.proguardsample.Day MONDAY;
public static final com.example.admin.proguardsample.Day TUESDAY;
public static final com.example.admin.proguardsample.Day WEDNESDAY;
public static final com.example.admin.proguardsample.Day THURSDAY;
public static final com.example.admin.proguardsample.Day FRIDAY;
public static final com.example.admin.proguardsample.Day SATURDAY;
public static final com.example.admin.proguardsample.Day SUNDAY;
public static com.example.admin.proguardsample.Day[] values();
public static com.example.admin.proguardsample.Day valueOf(java.lang.String);
static {};
}

  • 枚举实际是创建了一个继承自java.lang.Enum的类
  • java代码中的枚举类型最后转换成类中的static final属性
  • 多出了两个方法,values()和valueOf().
  • values方法返回定义的枚举类型的数组集合,即从MONDAY到SUNDAY这7个类型.

2.找寻崩溃轨迹 其中Day.valueOf(String)内部会调用Enum.valueOf(Class,String)方法

public static com.example.admin.proguardsample.Day valueOf(java.lang.String);
Code:
0: ldc #4 // class com/example/admin/proguardsample/Day
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class com/example/admin/proguardsample/Day
9: areturn

而Enum的valueOf方法会间接调用Day.values()方法,具体步骤是

  • Enum.value调用Class.enumConstantDirectory方法获取String到枚举的映射
  • Class.enumConstantDirectory方法调用Class.getEnumConstantsShared获取当前的枚举类型
  • Class.getEnumConstantsShared方法使用反射调用values来获取枚举类型的集合.

混淆之后,values被重新命名,所以会发生NoSuchMethodException.

关于调用轨迹,感兴趣的可以自己研究一下源码,不难.

四大组件不建议混淆

Android中四大组件我们都很常用,这些组件不能被混淆的原因为

  • 四大组件声明必须在manifest中注册,如果混淆后类名更改,而混淆后的类名没有在manifest注册,是不符合Android组件注册机制的.
  • 外部程序可能使用组件的字符串类名,如果类名混淆,可能导致出现异常

注解不能混淆

注解在Android平台中使用的越来越多,常用的有ButterKnife和Otto.很多场景下注解被用作在运行时反射确定一些元素的特征.

为了保证注解正常工作,我们不应该对注解进行混淆.Android工程默认的混淆配置已经包含了下面保留注解的配置

-keepattributes Annotation

关于注解,可以阅读这篇文章了解.详解Java中的注解

其他不该混淆的

  • jni调用的java方法
  • java的native方法
  • js调用java的方法
  • 第三方库不建议混淆
  • 其他和反射相关的一些情况

stacktrace的恢复

Proguard混淆带来了很多好处,但是也会导致我们收集到的崩溃的stacktrace变得更加难以读懂,好在有补救的措施,这里就介绍一个工具,retrace,用来将混淆后的stacktrace还原成混淆之前的信息.

retrace脚本

Android 开发环境默认带着retrace脚本,一般情况下路径为./tools/proguard/bin/retrace.sh

mapping映射表

Proguard进行混淆之后,会生成一个映射表,文件名为mapping.txt,我们可以使用find工具在Project下查找

find . -name mapping.txt
./app/build/outputs/mapping/release/mapping.txt

一个崩溃stacktrace信息

一个原始的崩溃信息是这样的.

E/AndroidRuntime(24006): Caused by: java.lang.NullPointerException: Attempt to invoke virtual method ‘int android.graphics.Bitmap.getWidth()’ on a null object reference
E/AndroidRuntime(24006): at com.example.admin.proguardsample.a.a(Utils.java:10)
E/AndroidRuntime(24006): at com.example.admin.proguardsample.MainActivity.onCreate(MainActivity.java:22)
E/AndroidRuntime(24006): at android.app.Activity.performCreate(Activity.java:6106)
E/AndroidRuntime(24006): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1123)
E/AndroidRuntime(24006): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2566)
E/AndroidRuntime(24006): … 10 more

对上面的信息处理,去掉E/AndroidRuntime(24006):这些字符串retrace才能正常工作.得到的字符串是

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method ‘int android.graphics.Bitmap.getWidth()’ on a null object reference
at com.example.admin.proguardsample.a.a(Utils.java:10)
at com.example.admin.proguardsample.MainActivity.onCreate(MainActivity.java:22)
at android.app.Activity.performCreate(Activity.java:6106)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1123)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2566)
… 10 more

将上面的stacktrace保存成一个文本文件,比如名称为npe_stacktrace.txt.

开搞

./tools/proguard/bin/retrace.sh /Users/admin/Downloads/ProguardSample/app/build/outputs/mapping/release/mapping.txt /tmp/npe_stacktrace.txt

得到的易读的stacktrace是

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method ‘int android.graphics.Bitmap.getWidth()’ on a null object reference
at com.example.admin.proguardsample.Utils.int getBitmapWidth(android.graphics.Bitmap)(Utils.java:10)
at com.example.admin.proguardsample.MainActivity.void onCreate(android.os.Bundle)(MainActivity.java:22)
at android.app.Activity.performCreate(Activity.java:6106)

写在最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

)]

【算法合集】

[外链图片转存中…(img-9dQMaWvX-1714693971151)]

【延伸Android必备知识点】

[外链图片转存中…(img-s3JDcTZl-1714693971152)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值