写在前面
CSDN话题挑战赛第1期
活动详情地址:https://marketing.csdn.net/p/bb5081d88a77db8d6ef45bb7b6ef3d7f
参赛话题:前端面试宝典
话题描述:欢迎各位加入话题创作得小伙伴,如果我没有猜错得话,我觉得你是应该同我一样是一位前端人。如今前端在IT事业中的占比越来越重,已经成为不可缺少的部分,前端技术也是层出不穷,各种技术类、技术框架也蜂拥而出,前端面试的难度也随之增加,如果我们拥有一套前端面试宝典。如果你是应聘者:你就可以从容的solo面试官,如果你是面试官:你就可以将应聘者拷问到骨子里!
总之我们大家一起将自己的面试经验以及学习到的知识点汇聚于此,形成一套体系的前端面试宝典。让读者无论是面试还是学习都能够有非常大的收获。就让我们携手共筑前端面试宝典吧!!!
创作模板:
在Android 的中,高级面试中,我们经常会被问到Handler 相关的知识点,而且占重比例还比较大,这是什么呢?下面一起来看一张图:
由上图我们可以看出,整个APP 的启动流程: Launcher(APP): zygote -> jvm -> ActivityThread.main()
ActivityThread.main() 就是我们APP 独有的main 启动方法,如图所示,绿色的部分,就是Handler 为我们开辟的独有空间,启动主线程独有的Looper
,将当前App 独立出来。
由此我们可以得出一个结论:Handler 并不是只属于进程通讯,进程通讯只是Handler 的附属功能,而Handler 的真正功能是 所有的代码,都是在Handler 中运行的
这里面试官其实想了解的是,你对Handler的认知,如果这个问题打不出来,那么面试官也不会再去问了。
在说答案之前,先看一直 Handler 的执行流程图:
由上图,我们可以看出:
这里面试官其实想了解的是,你对Handler 流程及源码的理解。
handler -> sendMessage -> messageQueue.enqueueMessage -> looper.loop() -> messasgeQueue.next() -> handler.dispatchMessage() -> handler.handerMessage()
,handler 发送message,进入messageQueue.enqueueMessage 队列,进入looper中经过loop 死循环的不断遍历,驱动队列一直前进,经过handler.dispatchMessage() 分发给handler.handerMessage,这样我们就走完了整个的Handler 流程,也可以直接看错,一个线程中,只有一个Looper。
,this 就是上下文,唯一的ThreadLocal key,key 唯一了,那么value 也就是唯一的,ThreadLocal在创建的时候,会有一个判断,如果已经创建了,会报异常,所以一个线程有唯一的ThreadLocal 就有唯一的looper。如下图:ThreadLocal 线程隔离工具类
ThreadLocal 创建源码
应该是考官想知道,你对于GC回收 JVM 相关的东西吧。
在这里,我我们先看一段代码:
Handler handler = new Handler(){
@SuppressLint("HandlerLeak")
@Override
public void handleMessage(@NonNull Message msg) {
Log.d("tiger", "handleMessage: ");
View view = null;
click(view);
MainActivity2.this.click(view);
}
};
public void click(View view) {
}
sendMessage -> sendMessageAtTime -> enqueueMessage
在 enqueueMessage 中,有段代码 msg.target = this;
,意思就是Message 会持有当前的handler,handler 已经成为了massage的一部分,假如我设置一个消息需要等待20分钟后执行,那么就意味,我的message会一直等待20分钟之后才会执行,message 持有 handler,handler 持有 (this)activity,这样就导致GC无法回收,JVM 通过可达性算法,告诉我们,没法到达,就无法回收。内部类的生命周期,一旦在外部类生命周期中被别的生命周期持有了,那么外部类也不能被释放。好烦啊,我也不知道啊,为什么还要要求这个?
ActivityThread.main()
方法中,就创建了looper.loop()
,源码如下:Looper.prepare();
和 Looper.loop();
就可以了。请看下面代码 public void click (View view){
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// do something()
}
};
Looper.loop();
}
}).start();
}
对于源码的掌握程度
loop()
是一个死循环,想要退出,必须msg == null
。请看下面源码:public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.recycleUnchecked();
}
}
Message next() {
for (;;) {
synchronized (this) {
if (mQuitting) {
dispose();
return null;
}
}
}
}
void quit(boolean safe) {
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
}
}
Looper.loop()
进行循环, 对queue 进行轮询操作,当消息达到执行时间就取出来,当MessageQueue 为空的时候,队列阻塞,等消息调用queue massage 的时候,通知队列,可以取出消息,停止阻塞。nativePollOnce(long ptr, int timeoutMillis)
执行阻塞操作,timeoutMillis 为 -1 表示无限等待,直到事件发生为止,如果为0,无需等待,立即执行。请看下面源码: Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);// 循环进入阻塞状态,等待执行时间到达后唤醒
synchronized (this) {
if (msg != null) {
if (now < msg.when) {
// 消息不为空,并且没有到执行时间,nextPollTimeoutMillis 不为-1
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
}
}
}
Message next() {
if (ptr == 0) { // mPtr==0,表示中断循环,
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
if (msg != null) {
} else {
// 无消息,timeoutMillis为-1表示无限等待,直到有事件发生为止
nextPollTimeoutMillis = -1;
}
}
}
}
// mPtr==0
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
// 唤醒
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
boolean needWake;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
}
// mPtr != 0 循环没有中断,进行唤醒操作.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
线程锁,后续会更多 synchronized 相关的东西
难道是想知道有没有使用过?
Message message = new Message();
Message message1 = Message.obtain();
查看源码的时候,发现内部调用的也是obtain() 方法。Message message2 = handler.obtainMessage();
难道是 ANR 的机制?
什么是ANR?
原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下
点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!
收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!
✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!
写在最后
CSDN话题挑战赛第1期