总结
Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!
上面分享的字节跳动公司2020年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
就先写到这,码字不易,写的很片面不好之处敬请指出,如果觉得有参考价值的朋友也可以关注一下我
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包阅读下载,最后觉得有帮助、有需要的朋友可以点个赞
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
data class User(
@Transient
val name: String,
val age: Int,
) : Serializable {
companion object {
val serialVersionUID = 1
}
}
反序列化后,我们就可以看到,因为name没有参与序列化,所以拿到的值为null
E/TAG: result==>User(name=null, age=25)
如果一个类中的类成员变量不支持序列化,会发生什么情况?
场景如下,UserInner没有实现序列化接口
data class UserInner(
val a: Int,
val b: String
)
data class User(
@Transient
val name: String,
val age: Int,
val inner: UserInner
) : Serializable {
companion object {
val serialVersionUID = 1
}
}
那么当进行序列化操作的时候报错,就是因为UserInner不支持序列化。
Caused by: java.io.NotSerializableException: com.lay.learn.asm.binder.UserInner
那么这里我们需要了解,序列化其实是一次深拷贝的操作。对于浅拷贝(这里不考虑基本数据类型)只是将引用地址做一次拷贝;深拷贝则是需要重新创建一个对象,并把数据拷贝过去。
所以在序列化的时候,因为还需要做一次本地化存储,所以必定是需要拿到UserInner的数据并存储下来,所以就需要把UserInner内部的数据序列化,但是UserInner又不支持序列化,所以就会报错。
如果某个类可以序列化,但是其父类不可以序列化,那么这个类可以序列化吗?
父类:
open class Person(
val des: String
)
子类:
data class User(
@Transient
val name: String,
val age: Int,
) : Serializable, Person(name) {
companion object {
val serialVersionUID = 1
}
}
此时进行序列化操作的时候,报错:没有可用的构造函数。
Caused by: java.io.InvalidClassException: com.lay.learn.asm.binder.User; no valid constructor
at com.lay.learn.asm.SerializeUtils.readObject(Unknown Source:22)
从堆栈信息中,我们看是在反序列化的时候报错了,说明在序列化存储的时候是没问题的,那么这里我们就需要看下readObject的源码了
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//......省略部分代码
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
//......省略部分代码
return obj;
}
这里我们先关注下核心代码,我们发现最终反序列化的时候,是通过反射创建一个Object对象,此时我们看直接调用了newInstance()方法。
所以这里我们这样想,在反序列化的时候类似于创建一个子类的过程,此时应该先创建父类,调用父类的构造方法,因为父类没有实现序列化接口,那么父类信息是缺失的,只能调用一个无参构造方法,那么此时父类没有空参构造方法,因此直接报错。
open class Person(
val des: String
){
constructor() : this("") {
}
}
反之,如果子类没实现序列化接口,而父类实现了,那么这种情况下是可以完成序列化的,因为继承关系,子类就能够获取父类的序列化能力。
1.2 Java处理序列化的过程
如果我们想要从源码角度去看序列化的过程,其实只需要关注两个类:ObjectInputStream和ObjectOutputStream。
ObjectOutputStream:用于将特定数据结构拆分成二进制数据,例如类的信息,并本地化存储,那么这个过程如何完成的?
ObjectInputStream:用于将二进制数据合并成想要的数据结构。
1.2.1 数据怎么拆
拆数据的核心方法就是ObjectOutputStream的writeObject方法,在这个中传入要拆解的对象,我们以User为例。
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
// BEGIN Android-changed: Ignore secondary exceptions during writeObject().
// writeFatalException(ex);
try {
writeFatalException(ex);
} catch (IOException ex2) {
// If writing the exception to the output stream causes another exception there
// is no need to propagate the second exception or generate a third exception,
// both of which might obscure details of the root cause.
}
// END Android-changed: Ignore secondary exceptions during writeObject().
}
throw ex;
}
}
我们看writeObject0这个方法干了什么?
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
// ......省略部分代码
// check for replacement object
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
Class repCl;
// 核心代码 1
desc = ObjectStreamClass.lookup(cl, true);
if (desc.hasWriteReplaceMethod() &&
(obj = desc.invokeWriteReplace(obj)) != null &&
(repCl = obj.getClass()) != cl)
{
cl = repCl;
desc = ObjectStreamClass.lookup(cl, true);
}
// END Android-changed: Make only one call to writeReplace.
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
// if object replaced, run through original checks a second time
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
}
}
// remaining cases
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
// 核心代码 2
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
核心代码1
在这里,我们看到拿到User类的class对象,然后创建了一个ObjectStreamClass类,我们看下这个类是干啥的。
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
// 获取类名
name = cl.getName();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
// 是否实现了serializable接口
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
Class<?> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 获取serialVersionUID
suid = getDeclaredSUID(cl);
try {
// 获取全部的字段信息
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
// 看是否有 writeObject readObject readObjectNoData方法
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
try {
fieldRefl = getReflector(fields, this);
} catch (InvalidClassException ex) {
// field mismatches impossible when matching local fields vs. self
throw new InternalError(ex);
}
if (deserializeEx == null) {
if (isEnum) {
deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {
deserializeEx = new ExceptionInfo(name, "no valid constructor");
}
}
for (int i = 0; i < fields.length; i++) {
if (fields[i].getField() == null) {
defaultSerializeEx = new ExceptionInfo(
name, "unmatched serializable field(s) declared");
}
}
initialized = true;
}
在这个类当中,我们看到其实就是拿到User类的class对象之后,获取类中的全部信息,像Class类名、是否实现了serialize接口、serialVersionUID、全部字段信息、是否存在writeObject readObject readObjectNoData方法,总之就是将类的全部信息,封装成了ObjectStreamClass类。
核心代码 2
回到前面的代码中,我们看会做一系列的判断,判断User是什么类型的对象,因为User实现了Serializable接口,所以会走到writeOrdinaryObject方法中,同时将ObjectStreamClass也传了进来。
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class "" +
obj.getClass().getName() + "", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
首先调用了writeClassDesc方法,在这个方法中,做的主要事情就是把之前获取的类信息全部全部转换为二进制数据,从writeNonProxyDesc方法中就可以看到,太深入的我就不介绍了,有精力的伙伴可以深入看一下。
private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
throws IOException
{
int handle;
if (desc == null) {
writeNull();
} else if (!unshared && (handle = handles.lookup(desc)) != -1) {
## 要如何成为Android架构师?
搭建自己的知识框架,全面提升自己的技术体系,并且往底层源码方向深入钻研。
大多数技术人喜欢用思维脑图来构建自己的知识体系,一目了然。这里给大家分享一份大厂主流的Android架构师技术体系,可以用来搭建自己的知识框架,或者查漏补缺;
![](https://img-blog.csdnimg.cn/img_convert/c59f9f753382e8a24aad38b369e70045.webp?x-oss-process=image/format,png)
> 对应这份技术大纲,我也整理了一套Android高级架构师完整系列的视频教程,主要针对3-5年Android开发经验以上,需要往高级架构师层次学习提升的同学,希望能帮你突破瓶颈,跳槽进大厂;
>
**最后我必须强调几点:**
1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。
你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
**最后我必须强调几点:**
1.搭建知识框架可不是说你整理好要学习的知识顺序,然后看一遍理解了能复制粘贴就够了,大多都是需要你自己读懂源码和原理,能自己手写出来的。
2.学习的时候你一定要多看多练几遍,把知识才吃透,还要记笔记,这些很重要! 最后你达到什么水平取决你消化了多少知识
3.最终你的知识框架应该是一个完善的,兼顾广度和深度的技术体系。然后经过多次项目实战积累经验,你才能达到高级架构师的层次。
你只需要按照在这个大的框架去填充自己,年薪40W一定不是终点,技术无止境
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**