2024年安卓最全读懂-Android-中的代码混淆(1),海尔面试一般都会问什么

面试复习笔记

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

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

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

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

-dontwarn com.twitter.sdk.**

其他混淆相关的介绍,都可以通过访问官方文档获取.

哪些不应该混淆

反射中使用的元素

如果一些被混淆使用的元素(属性,方法,类,包名等)进行了混淆,可能会出现问题,如NoSuchFiledException或者NoSuchMethodException等.

比如下面的示例源码

//Constants.java
public class Constants {
public static String BOOK_NAME = “book_name”;
}

//MainActivity.java
Field bookNameField = null;
try {
String fieldName = “BOOK_NAME”;
bookNameField = Constants.class.getField(fieldName);
Log.i(LOGTAG, “bookNameField=” + bookNameField);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

如果上面的Constants类进行了混淆,那么上面的语句就可能抛出NoSuchFieldException.

想要验证,我们需要看一看混淆的映射文件,文件名为mapping.txt,该文件保存着混淆前后的映射关系.

com.example.admin.proguardsample.Constants -> com.example.admin.proguardsample.a:
java.lang.String BOOK_NAME -> a
void () ->
void () ->
com.example.admin.proguardsample.MainActivity -> com.example.admin.proguardsample.MainActivity:
void () ->
void onCreate(android.os.Bundle) -> onCreate

从映射文件中,我们可以看到

  • Constants类被重命名为a.
  • Constants类的BOOK_NAME重命名了a

然后,我们对APK文件进行反编译一探究竟.推荐一下这个在线反编译工具 http://www.javadecompilers.com/apk

注意,使用jadx decompiler后,会重新命名,正如下面注释/* renamed from: com.example.admin.proguardsample.a */所示.

package com.example.admin.proguardsample;

/* renamed from: com.example.admin.proguardsample.a */
public class C0314a {
public static String f1712a;

static {
f1712a = “book_name”;
}
}

而MainActivity的翻译后的对应的源码为

try {
Log.i(“MainActivity”, “bookNameField=” + C0314a.class.getField(“BOOK_NAME”));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

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.很多场景下注解被用作在运行时反射确定一些元素的特征.

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

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

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

Android组件注册机制的.

  • 外部程序可能使用组件的字符串类名,如果类名混淆,可能导致出现异常

注解不能混淆

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

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

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

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

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值