面试复习笔记
这份资料我从春招开始,就会将各博客、论坛。网站上等优质的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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!