热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

上海大厂Android面试经历:史上最通俗计算机网络分层详解,通用流行框架大全

前言:笔者出生在江西一个偏远的山村。虽然出生时已经不是那个温饱都是问题的年代,但是也谈不上有个幸福的童年。家里很穷。幼儿园并没有读,因为

前言:

笔者出生在江西一个偏远的山村。虽然出生时已经不是那个温饱都是问题的年代,但是也谈不上有个幸福的童年。家里很穷。幼儿园并没有读,因为家里觉得花那个钱没有必要,小学才开始学拼音字母。我的童年,就是和我的姐姐弟弟在山中的梯田里放牛,抓泥鳅,割鱼草。

大学上的是北大青鸟,一个成人培训机构,没有学历。在深圳打拼了6年了,一直在一些小公司打转。年初疫情突然爆发,市场经济进入寒冬,我也被公司裁员了。

这也让我意识到,本身学历就不高,如果还不发奋让自己的技术更上一个层次的话,肯定是没有未来可言的。幸而有朋友在阿里任职,给了我一个内推的机会。所以,疫情在家的这段时间,我决定逼自己一把,开始着手准备阿里的面试。终于在五一节前拿到了P5的offer。下面是我的面试经验分享。

重要概念


1、主线程(UI线程、MainThread)

当应用程序第一次启动时,会同时自动开启1条主线程,用于处理UI相关的事件(如更新、操作等)

2、子线程(工作线程)

人为手动开启的线程,执行耗时操作(如网络请求、数据加载等)

3、消息(Message)

线程间通讯的数据单元(即Handler接受 & 处理的消息对象),用于存储需要操作的通信信息

4、消息队列(Message Queue)

一种数据结构(先进先出),存储Handler发送过来的消息(Message)

5、处理者(Handler)

Handler为主线程与子线程的通信媒介,是线程消息的主要处理者。用于添加消息(Message)到消息队列(Message Queue),处理循环器(Looper)分派过来的消息(Message)

6、循环器(Looper)

消息队列(Message Queue)与处理者(Handler)的通信媒介,用于消息循环,即
(1)消息获取:循环取出消息队列(Message Queue)的消息(Message)
(2)消息分发:将取出的消息(Message)发送给对应的处理者(Handler)
每个线程只能拥有1个Looper,1个Looper可绑定多个线程的Handler,即多个线程可往1个Looper所持有的MessageQueue中发送消息,提供线程间通信的可能

(三)使用方式


3.1)Handler.sendMessage()


方式1:新建Handler子类(内部类)

// 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法class mHandler extends Handler {// 通过复写handlerMessage() 从而确定更新UI的操作@Overridepublic void handleMessage(Message msg) {...// 需执行的UI操作}}// 步骤2:在主线程中创建Handler实例private Handler mhandler = new mHandler();// 步骤3:创建所需的消息对象Message msg = Message.obtain(); // 实例化消息对象msg.what = 1; // 消息标识msg.obj = "AA"; // 消息内容存放// 步骤4:在工作线程中 通过Handler发送消息到消息队列中// 可通过sendMessage() / post()// 多线程可采用AsyncTask、继承Thread类、实现RunnablemHandler.sendMessage(msg);// 步骤5:开启工作线程(同时启动了Handler)// 多线程可采用AsyncTask、继承Thread类、实现Runnable

方式2:匿名内部类

// 步骤1:在主线程中 通过匿名内部类 创建Handler类对象private Handler mhandler = new Handler(){// 通过复写handlerMessage()从而确定更新UI的操作@Overridepublic void handleMessage(Message msg) {...// 需执行的UI操作}};// 步骤2:创建消息对象Message msg = Message.obtain(); // 实例化消息对象msg.what = 1; // 消息标识msg.obj = "AA"; // 消息内容存放// 步骤3:在工作线程中 通过Handler发送消息到消息队列中// 多线程可采用AsyncTask、继承Thread类、实现RunnablemHandler.sendMessage(msg);// 步骤4:开启工作线程(同时启动了Handler)// 多线程可采用AsyncTask、继承Thread类、实现Runnable

3.2)Handler.post()

// 步骤1:在主线程中创建Handler实例private Handler mhandler = new mHandler();// 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容// 需传入1个Runnable对象mHandler.post(new Runnable() {@Overridepublic void run() {... // 需执行的UI操作 }});// 步骤3:开启工作线程(同时启动了Handler)// 多线程可采用AsyncTask、继承Thread类、实现Runnable

(四)工作原理


4.1)工作流程解析


步骤一:异步通信准备

在主线程中创建
(1)循环器 对象(Looper)
(2)消息队列 对象(Message Queue)
(3)Handler对象
Looper、Message Queue均属于主线程,创建Message Queue后,Looper自动进入消息循环。此时,Handler自动绑定了主线程的Looper、Message Queue

步骤二:消息入队

工作线程通过Handler发送消息(Message)到消息队列(Message Queue)中,该消息内容=工作线程对UI的操作

步骤三:消息循环

消息出队:Looper循环取出消息队列(Message Queue)中的消息(Message)
消息分发:Looper将去除的消息(Message)发送给创建该消息的处理者(Handler)
在消息循环过程中,若消息队列为空,则线程阻塞。

步骤四:消息处理

处理者Handler接受循环器Looper发送过来的消息(Message)
处理者Handler根据消息(Message)进行UI操作

4.2)工作流程图

在这里插入图片描述

4.3)示意图

在这里插入图片描述

4.4)线程Thread、循环器Looper、处理者Handler对应关系

(1)1个线程(Thread)只能绑定1个循环器(Looper),但可以有多个处理者
(2)1个循环器(Looper)可绑定多个处理者(Handler)
(3)1个处理者(Handler)只能绑定1个循环器(Looper)
在这里插入图片描述

(五)源码分析


5.1)核心类

Handler机制包括3个重要类:1、处理者 Handler2、循环器 Looper3、消息队列 MessageQueue

1、类图

在这里插入图片描述

2、核心方法

在这里插入图片描述

5.2)源码分析

记录一次Handler使用步骤

方式1:使用Handler.sendMessage()


准备步骤1:创建循环器对象Looper&消息队列对象MessageQueue

Looper.prepareMainLooper()
为主线程(UI线程)创建1个循环器对象(Looper),同时也会自动创建1个对应的消息队列对象(MessageQueue)
该方法在主线程(UI线程)创建时自动调用,不需手动生成。在Android应用进程启动时,会默认创建1个主线程(ActiviyThread,也叫UI线程),创建时,会自动调用ActivityThread的1个静态main方法=应用程序的入口main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象

public static void main(String[] args) {... // 仅贴出关键代码Looper.prepareMainLooper(); // 1\. 为主线程创建1个Looper对象,同时生成1个消息队列对象(MessageQueue)// 方法逻辑类似Looper.prepare()// 注:prepare():为子线程中创建1个Looper对象ActivityThread thread = new ActivityThread(); // 2\. 创建主线程Looper.loop(); // 3\. 自动开启 消息循环 ->>下面将详细分析}

Looper.prepare()
为当前线程(子线程)创建1个循环器对象(Looper),同时也会自动创建1个对应的消息队列对象(MessageQueue)
需要在子线程中手动调用改方法

public static final void prepare() {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}// 1\. 判断sThreadLocal是否为null,否则抛出异常//即 Looper.prepare()方法不能被调用两次 = 1个线程中只能对应1个Looper实例// 注:sThreadLocal = 1个ThreadLocal对象,用于存储线程的变量sThreadLocal.set(new Looper(true));// 2\. 若为初次Looper.prepare(),则创建Looper对象 & 存放在ThreadLocal变量中// 注:Looper对象是存放在Thread线程里的// 源码分析Looper的构造方法->>分析a}/** * 分析a:Looper的构造方法**/private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);// 1\. 创建1个消息队列对象(MessageQueue)// 即 当创建1个Looper实例时,会自动创建一个与之配对的消息队列对象(MessageQueue)mRun = true;mThread = Thread.currentThread();}

创建主线程时,会自动调用ActivityThread的1个静态的main();而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象,即 主线程的Looper对象自动生成,不需手动生成;
而子线程的Looper对象则需手动通过Looper.prepare()创建,在子线程若不手动创建Looper对象 则无法生成Handler对象;
根据Handler的作用(在主线程更新UI),故Handler实例的创建场景 主要在主线程
生成Looper & MessageQueue对象后,则会自动进入消息循环:Looper.loop()

准备步骤2:消息循环

/** * 源码分析: Looper.loop()* 作用:消息循环,即从消息队列中获取消息、分发消息到Handler* 特别注意:* a. 主线程的消息循环不允许退出,即无限循环* b. 子线程的消息循环允许退出:调用消息队列MessageQueue的quit()*/public static void loop() {...// 仅贴出关键代码// 1\. 获取当前Looper的消息队列final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}// myLooper()作用:返回sThreadLocal存储的Looper实例;若me为null 则抛出异常// 即loop()执行前必须执行prepare(),从而创建1个Looper实例final MessageQueue queue = me.mQueue;// 获取Looper实例中的消息队列对象(MessageQueue)// 2\. 消息循环(通过for循环)for (;;) {// 2.1 从消息队列中取出消息Message msg = queue.next(); if (msg == null) {return;}// next():取出消息队列里的消息// 若取出的消息为空,则线程阻塞// ->> 分析1 // 2.2 派发消息到对应的Handlermsg.target.dispatchMessage(msg);// 把消息Message派发给消息对象msg的target属性// target属性实际是1个handler对象// ->>分析2// 3\. 释放消息占据的资源msg.recycle();}
}/** * 分析1:queue.next()* 定义:属于消息队列类(MessageQueue)中的方法* 作用:出队消息,即从 消息队列中 移出该消息*/Message next() {...// 仅贴出关键代码// 该参数用于确定消息队列中是否还有消息// 从而决定消息队列应处于出队消息状态 or 等待状态int nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}// nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态 nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;// 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序if (msg != null) {if (now }// 回到分析原处/** * 分析2:dispatchMessage(msg)* 定义:属于处理者类(Handler)中的方法* 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作*/public void dispatchMessage(Message msg) {// 1\. 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息// 则执行handleCallback(msg),即回调Runnable对象里复写的run()// 上述结论会在讲解使用“post(Runnable r)”方式时讲解if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}// 2\. 若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息(即此处需讨论的)// 则执行handleMessage(msg),即回调复写的handleMessage(msg) ->> 分析3handleMessage(msg);}}/** * 分析3:handleMessage(msg)* 注:该方法 = 空方法,在创建Handler实例时复写 = 自定义消息处理方式**/public void handleMessage(Message msg) { ... // 创建Handler实例时复写}

总结:
(1)消息循环的操作 = 消息出队 + 分发给对应的Handler实例
(2)分发给对应的Handler的过程:根据出队消息的归属者通过dispatchMessage(msg)进行分发,最终回调复写的handleMessage(Message msg),从而实现 消息处理 的操作
(3)特别注意:在进行消息分发时(dispatchMessage(msg)),会进行1次发送方式的判断:
若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息,则直接回调Runnable对象里复写的run()若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息,则回调复写的handleMessage(msg)

步骤1:在主线程中 通过匿名内部类 创建Handler类对象

/** * 具体使用*/private Handler mhandler = new Handler(){// 通过复写handlerMessage()从而确定更新UI的操作@Overridepublic void handleMessage(Message msg) {...// 需执行的UI操作}};/** * 源码分析:Handler的构造方法* 作用:初始化Handler对象 & 绑定线程* 注:* a. Handler需绑定 线程才能使用;绑定后,Handler的消息处理会在绑定的线程中执行* b. 绑定方式 = 先指定Looper对象,从而绑定了 Looper对象所绑定的线程(因为Looper对象本已绑定了对应线程)* c. 即:指定了Handler对象的 Looper对象 = 绑定到了Looper对象所在的线程*/public Handler() {this(null, false);// ->>分析1}
/** * 分析1:this(null, false) = Handler(null,false)*/public Handler(Callback callback, boolean async) {...// 仅贴出关键代码// 1\. 指定Looper对象mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}// Looper.myLooper()作用:获取当前线程的Looper对象;若线程无Looper对象则抛出异常// 即 :若线程中无创建Looper对象,则也无法创建Handler对象// 故 若需在子线程中创建Handler对象,则需先创建Looper对象// 注:可通过Loop.getMainLooper()可以获得当前进程的主线程的Looper对象// 2\. 绑定消息队列对象(MessageQueue)mQueue = mLooper.mQueue;// 获取该Looper对象中保存的消息队列对象(MessageQueue)// 至此,保证了handler对象 关联上 Looper对象中MessageQueue}

当创建Handler对象时,则通过 构造方法 自动关联当前线程的Looper对象 & 对应的消息队列对象(MessageQueue),从而 自动绑定了 实现创建Handler对象操作的线程
总结:
在这里插入图片描述

步骤2:创建消息对象

具体使用

Message msg = Message.obtain(); // 实例化消息对象msg.what = 1; // 消息标识msg.obj = "AA"; // 消息内容存放

源码分析

/** * 源码分析:Message.obtain()* 作用:创建消息对象* 注:创建Message对象可用关键字new 或 Message.obtain(),建议使用obtain()创建消息对象,避免每次都使用new重新分配内存。(当池内无消息对象可复用,则用关键词new创建)*/public static Message obtain() {// Message内部维护了1个Message池,用于Message消息对象的复用// 使用obtain()则是直接从池内获取synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}// 建议:使用obtain()”创建“消息对象,避免每次都使用new重新分配内存}// 若池内无消息对象可复用,则还是用关键字new创建return new Message();}

步骤3:在工作线程中发送消息到消息队列

具体使用

mHandler.sendMessage(msg);

源码分析

/** * 源码分析&#xff1a;mHandler.sendMessage(msg)* 定义&#xff1a;属于处理器类&#xff08;Handler&#xff09;的方法* 作用&#xff1a;将消息 发送 到消息队列中&#xff08;Message ->> MessageQueue&#xff09;*/public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);// ->>分析1}/** * 分析1&#xff1a;sendMessageDelayed(msg, 0)**/public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis <0) {delayMillis &#61; 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() &#43; delayMillis);// ->> 分析2}/** * 分析2&#xff1a;sendMessageAtTime(msg, SystemClock.uptimeMillis() &#43; delayMillis)**/public boolean sendMessageAtTime(Message msg, long uptimeMillis) {// 1\. 获取对应的消息队列对象&#xff08;MessageQueue&#xff09;MessageQueue queue &#61; mQueue;// 2\. 调用了enqueueMessage方法 ->>分析3return enqueueMessage(queue, msg, uptimeMillis);}/** * 分析3&#xff1a;enqueueMessage(queue, msg, uptimeMillis)**/private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {// 1\. 将msg.target赋值为this// 即 &#xff1a;把 当前的Handler实例对象作为msg的target属性msg.target &#61; this;// 请回忆起上面说的Looper的loop()中消息循环时&#xff0c;会从消息队列中取出每个消息msg&#xff0c;然后执行msg.target.dispatchMessage(msg)去处理消息// 实际上则是将该消息派发给对应的Handler实例 // 2\. 调用消息队列的enqueueMessage&#xff08;&#xff09;// 即&#xff1a;Handler发送的消息&#xff0c;最终是保存到消息队列->>分析4return queue.enqueueMessage(msg, uptimeMillis&#xff09;;}/** * 分析4&#xff1a;queue.enqueueMessage(msg, uptimeMillis&#xff09;* 定义&#xff1a;属于消息队列类&#xff08;MessageQueue&#xff09;的方法* 作用&#xff1a;入队&#xff0c;即 将消息 根据时间 放入到消息队列中&#xff08;Message ->> MessageQueue&#xff09;* 采用单链表实现&#xff1a;提高插入消息、删除消息的效率*/boolean enqueueMessage(Message msg, long when) {...// 仅贴出关键代码synchronized (this) {msg.markInUse();msg.when &#61; when;Message p &#61; mMessages;boolean needWake;// 判断消息队列里有无消息// a. 若无&#xff0c;则将当前插入的消息 作为队头 & 若此时消息队列处于等待状态&#xff0c;则唤醒if (p &#61;&#61; null || when &#61;&#61; 0 || when // 不断从消息队列中取出Handler发送的消息 & 分发到对应Handler
// 最终回调Handler.handleMessage()处理消息

总结
Handler发送消息的本质 &#61;
将消息对象的target属性设置为当前Handler实例&#xff08;将Message绑定到Handler&#xff0c;使执行消息循环时将消息派发给对应的Handler实例&#xff09;
获取对应的消息队列对象MessageQueue&#xff0c;调用MessageQueue.enqueueMessage()&#xff0c;将Handler需发送消息入队到绑定线程的消息队列中。

之后&#xff0c;随着Looper对象的无限消息循环&#xff0c;不断从消息队列中取出Handler发送的消息&根据target分发到对应Handler&#xff0c;最终回调Handler.handleMessage()处理消息

源码总结

在这里插入图片描述

在这里插入图片描述

工作流程总结

在这里插入图片描述

方式2&#xff1a;使用 Handler.post()


步骤1&#xff1a;在主线程中创建Handler实例

具体使用

private Handler mhandler &#61; new Handler()&#xff1b;// 与方式1的使用不同&#xff1a;此处无复写Handler.handleMessage()

源码分析

/** * 源码分析&#xff1a;Handler的构造方法* 作用&#xff1a;* a. 在此之前&#xff0c;主线程创建时隐式创建Looper对象、MessageQueue对象* b. 初始化Handler对象、绑定线程 & 进入消息循环* 此处的源码分析类似方式1&#xff0c;此处不作过多描述*/

步骤2&#xff1a;在工作线程中 发送消息到消息队列中

具体使用

mHandler.post(new Runnable() {&#64;Overridepublic void run() {//传入1个Ruunable对象&#xff0c;复写run()从而指定UI操作... // 需执行的UI操作 }});

源码分析

/** * 源码分析&#xff1a;Handler.post&#xff08;Runnable r&#xff09;* 定义&#xff1a;属于处理者类&#xff08;Handler&#xff09;中的方法* 作用&#xff1a;定义UI操作、将Runnable对象封装成消息对象 & 发送 到消息队列中&#xff08;Message ->> MessageQueue&#xff09;* 注&#xff1a;* a. 相比sendMessage()&#xff0c;post&#xff08;&#xff09;最大的不同在于&#xff0c;更新的UI操作可直接在重写的run&#xff08;&#xff09;中定义* b. 实际上&#xff0c;Runnable并无创建新线程&#xff0c;而是发送 消息 到消息队列中*/public final boolean post(Runnable r){return sendMessageDelayed(getPostMessage(r), 0);// getPostMessage(r) 的源码分析->>分析1// sendMessageDelayed&#xff08;&#xff09;的源码分析 ->>分析2}/** * 分析1&#xff1a;getPostMessage(r)* 作用&#xff1a;将传入的Runable对象封装成1个消息对象**/private static Message getPostMessage(Runnable r) {// 1\. 创建1个消息对象&#xff08;Message&#xff09;Message m &#61; Message.obtain();// 注&#xff1a;创建Message对象可用关键字new 或 Message.obtain()// 建议&#xff1a;使用Message.obtain()创建&#xff0c;// 原因&#xff1a;因为Message内部维护了1个Message池&#xff0c;用于Message的复用&#xff0c;使用obtain&#xff08;&#xff09;直接从池内获取&#xff0c;从而避免使用new重新分配内存// 2\. 将 Runable对象 赋值给消息对象&#xff08;message&#xff09;的callback属性m.callback &#61; r;// 3\. 返回该消息对象return m;} // 回到调用原处/** * 分析2&#xff1a;sendMessageDelayed(msg, 0)* 作用&#xff1a;实际上&#xff0c;从此处开始&#xff0c;则类似方式1 &#61; 将消息入队到消息队列&#xff0c;* 即 最终是调用MessageQueue.enqueueMessage&#xff08;&#xff09;**/public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis <0) {delayMillis &#61; 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() &#43; delayMillis);// 请看分析3}/** * 分析3&#xff1a;sendMessageAtTime(msg, SystemClock.uptimeMillis() &#43; delayMillis)**/public boolean sendMessageAtTime(Message msg, long uptimeMillis) {// 1\. 获取对应的消息队列对象&#xff08;MessageQueue&#xff09;MessageQueue queue &#61; mQueue;// 2\. 调用了enqueueMessage方法 ->>分析3return enqueueMessage(queue, msg, uptimeMillis);}/** * 分析4&#xff1a;enqueueMessage(queue, msg, uptimeMillis)**/private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {// 1\. 将msg.target赋值为this// 即 &#xff1a;把 当前的Handler实例对象作为msg的target属性msg.target &#61; this;// 请回忆起上面说的Looper的loop()中消息循环时&#xff0c;会从消息队列中取出每个消息msg&#xff0c;然后执行msg.target.dispatchMessage(msg)去处理消息// 实际上则是将该消息派发给对应的Handler实例 // 2\. 调用消息队列的enqueueMessage&#xff08;&#xff09;// 即&#xff1a;Handler发送的消息&#xff0c;最终是保存到消息队列return queue.enqueueMessage(msg, uptimeMillis&#xff09;;}// 注&#xff1a;实际上从分析2开始&#xff0c;源码 与 sendMessage&#xff08;Message msg&#xff09;发送方式相同
消息对象的创建 &#61; 内部 根据Runnable对象而封装
发送到消息队列的逻辑 &#61; 方式1中sendMessage&#xff08;Message msg&#xff09;

源码总结

在这里插入图片描述

在这里插入图片描述

工作流程总结

在这里插入图片描述

Handler.sendMessage与Handler.post比较

工作流程类似&#xff0c;区别在于
1、Handler.post不需外部创建消息对象&#xff0c;而是内部根据传入的Runnable对象封装消息对象
2、回调的消息处理方法是&#xff1a;复写Runnable对象的run()

&#xff08;六&#xff09;内存泄露


6.1&#xff09;问题描述

Handler的一般用法 &#61; 新建Handler子类&#xff08;内部类&#xff09; 、匿名Handler内部类

/** * 方式1&#xff1a;新建Handler子类&#xff08;内部类&#xff09;*/ public class MainActivity extends AppCompatActivity {public static final String TAG &#61; "carson&#xff1a;";private Handler showhandler;// 主线程创建时便自动创建Looper & 对应的MessageQueue// 之后执行Loop()进入消息循环&#64;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1\. 实例化自定义的Handler类对象->>分析1//注&#xff1a;此处并无指定Looper&#xff0c;故自动绑定当前线程(主线程)的Looper、MessageQueueshowhandler &#61; new FHandler();// 2\. 启动子线程1new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 1;// 消息标识msg.obj &#61; "AA";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();// 3\. 启动子线程2new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 2;// 消息标识msg.obj &#61; "BB";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();}// 分析1&#xff1a;自定义Handler子类class FHandler extends Handler {// 通过复写handlerMessage() 从而确定更新UI的操作&#64;Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:Log.d(TAG, "收到线程1的消息");break;case 2:Log.d(TAG, " 收到线程2的消息");break;}}}}/** * 方式2&#xff1a;匿名Handler内部类*/ public class MainActivity extends AppCompatActivity {public static final String TAG &#61; "carson&#xff1a;";private Handler showhandler;// 主线程创建时便自动创建Looper & 对应的MessageQueue// 之后执行Loop()进入消息循环&#64;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1\. 通过匿名内部类实例化的Handler类对象//注&#xff1a;此处并无指定Looper&#xff0c;故自动绑定当前线程(主线程)的Looper、MessageQueueshowhandler &#61; new Handler(){// 通过复写handlerMessage()从而确定更新UI的操作&#64;Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:Log.d(TAG, "收到线程1的消息");break;case 2:Log.d(TAG, " 收到线程2的消息");break;}}};// 2\. 启动子线程1new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 1;// 消息标识msg.obj &#61; "AA";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();// 3\. 启动子线程2new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 2;// 消息标识msg.obj &#61; "BB";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();}

严重警告:This Handler class should be static or leaks might occur(null)
警告原因&#61;该Handler类由于没有设置为静态类&#xff0c;可能会导致内存泄露。

6.2&#xff09;原因讲解


1、储备知识

主线程的Looper对象的生命周期 &#61; 该应用程序的生命周期
在Java中&#xff0c;非静态内部类 & 匿名内部类都默认持有 外部类的引用

2、泄露原因描述

/** * 方式1&#xff1a;新建Handler子类&#xff08;内部类&#xff09;*/ public class MainActivity extends AppCompatActivity {public static final String TAG &#61; "carson&#xff1a;";private Handler showhandler;// 主线程创建时便自动创建Looper & 对应的MessageQueue// 之后执行Loop()进入消息循环&#64;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1\. 实例化自定义的Handler类对象->>分析1//注&#xff1a;此处并无指定Looper&#xff0c;故自动绑定当前线程(主线程)的Looper、MessageQueueshowhandler &#61; new FHandler();// 2\. 启动子线程1new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 1;// 消息标识msg.obj &#61; "AA";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();// 3\. 启动子线程2new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 2;// 消息标识msg.obj &#61; "BB";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();}// 分析1&#xff1a;自定义Handler子类class FHandler extends Handler {// 通过复写handlerMessage() 从而确定更新UI的操作&#64;Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:Log.d(TAG, "收到线程1的消息");break;case 2:Log.d(TAG, " 收到线程2的消息");break;}}}}/** * 方式2&#xff1a;匿名Handler内部类*/ public class MainActivity extends AppCompatActivity {public static final String TAG &#61; "carson&#xff1a;";private Handler showhandler;// 主线程创建时便自动创建Looper & 对应的MessageQueue// 之后执行Loop()进入消息循环&#64;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1\. 通过匿名内部类实例化的Handler类对象//注&#xff1a;此处并无指定Looper&#xff0c;故自动绑定当前线程(主线程)的Looper、MessageQueueshowhandler &#61; new Handler(){// 通过复写handlerMessage()从而确定更新UI的操作&#64;Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:Log.d(TAG, "收到线程1的消息");break;case 2:Log.d(TAG, " 收到线程2的消息");break;}}};// 2\. 启动子线程1new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 1;// 消息标识msg.obj &#61; "AA";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();// 3\. 启动子线程2new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 2;// 消息标识msg.obj &#61; "BB";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();}
}

从上述示例代码可知&#xff1a;
上述的Handler实例的消息队列有2个分别来自线程1、2的消息&#xff08;分别 为延迟1s、6s&#xff09;
在Handler消息队列 还有未处理的消息 / 正在处理消息时&#xff0c;消息队列中的Message持有Handler实例的引用
由于Handler &#61; 非静态内部类 / 匿名内部类&#xff08;2种使用方式&#xff09;&#xff0c;故又默认持有外部类的引用&#xff08;即MainActivity实例&#xff09;&#xff0c;引用关系如下图
在这里插入图片描述

上述的引用关系会一直保持&#xff0c;直到Handler消息队列中的所有消息被处理完毕
在Handler消息队列 还有未处理的消息 / 正在处理消息时&#xff0c;此时若需销毁外部类MainActivity&#xff0c;但由于上述引用关系&#xff0c;垃圾回收器&#xff08;GC&#xff09;无法回收MainActivity&#xff0c;从而造成内存泄漏。如下图&#xff1a;
在这里插入图片描述

3、总结

&#xff08;1&#xff09;当Handler消息队列 还有未处理的消息 / 正在处理消息时&#xff0c;存在引用关系&#xff1a; “未被处理 / 正处理的消息 -> Handler实例 -> 外部类”
&#xff08;2&#xff09;若出现 Handler的生命周期 > 外部类的生命周期 时&#xff08;即 Handler消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时&#xff09;&#xff0c;将使得外部类无法被垃圾回收器&#xff08;GC&#xff09;回收&#xff0c;从而造成 内存泄露

6.3&#xff09;解决方案

从上面可看出&#xff0c;造成内存泄露的原因有2个关键条件&#xff1a;
1、存在“未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系
2、Handler的生命周期 > 外部类的生命周期

解决方案1&#xff1a;静态内部类&#43;弱引用&#xff08;推荐&#xff1a;保证消息队列中所有消息都能执行&#xff09;

&#xff08;1&#xff09;原理
1、将Handler的子类设置成 静态内部类&#xff1a;默认不持有外部类的引用&#xff0c;从而使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 的引用关系 不复存在。
2、使用WeakReference弱引用持有Activity实例&#xff1a;弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描时&#xff0c;一旦发现了只具有弱引用的对象&#xff0c;不管当前内存空间足够与否&#xff0c;都会回收它的内存
&#xff08;2&#xff09;解决代码

public class MainActivity extends AppCompatActivity {public static final String TAG &#61; "carson&#xff1a;";private Handler showhandler;// 主线程创建时便自动创建Looper & 对应的MessageQueue// 之后执行Loop()进入消息循环&#64;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1\. 实例化自定义的Handler类对象->>分析1//注&#xff1a;// a. 此处并无指定Looper&#xff0c;故自动绑定当前线程(主线程)的Looper、MessageQueue&#xff1b;// b. 定义时需传入持有的Activity实例&#xff08;弱引用&#xff09;showhandler &#61; new FHandler(this);// 2\. 启动子线程1new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 1;// 消息标识msg.obj &#61; "AA";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();// 3\. 启动子线程2new Thread() {&#64;Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg &#61; Message.obtain();msg.what &#61; 2;// 消息标识msg.obj &#61; "BB";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();}// 分析1&#xff1a;自定义Handler子类// 设置为&#xff1a;静态内部类private static class FHandler extends Handler{// 定义 弱引用实例private WeakReference reference;// 在构造方法中传入需持有的Activity实例public FHandler(Activity activity) {// 使用WeakReference弱引用持有Activity实例reference &#61; new WeakReference(activity); }// 通过复写handlerMessage() 从而确定更新UI的操作&#64;Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:Log.d(TAG, "收到线程1的消息");break;case 2:Log.d(TAG, " 收到线程2的消息");break;}}}
}

解决方案2&#xff1a;当外部类结束生命周期时&#xff0c;清空Handler内消息队列

&#xff08;1&#xff09;原理
当 外部类&#xff08;此处以Activity为例&#xff09; 结束生命周期时&#xff08;此时系统会调用onDestroy&#xff08;&#xff09;&#xff09;&#xff0c;清除 Handler消息队列里的所有消息&#xff08;调用removeCallbacksAndMessages(null)&#xff09;
不仅使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 不复存在&#xff0c;同时 使得 Handler的生命周期&#xff08;即 消息存在的时期&#xff09; 与 外部类的生命周期 同步
&#xff08;2&#xff09;解决代码

&#64;Overrideprotected void onDestroy() {super.onDestroy();mHandler.removeCallbacksAndMessages(null);// 外部类Activity生命周期结束时&#xff0c;同时清空消息队列 & 结束Handler生命周期}

&#xff08;七&#xff09;线程安全

通过创建一个Handler子类的对象&#xff0c;每个acvivity只需一个Handler对象。后台进程可通过两种方式Handler进行通信&#xff1a;message和Runnable对象&#xff0c;其结果实质都是将在Handler的队列中放入内容&#xff0c;message是放置信息&#xff0c;可以传递一些参数&#xff0c;Handler获取这些信息并将判度如何处理&#xff0c;而Runnable则是直接给出处理的方法。队列就是依次执行&#xff0c;Handler会处理完一个消息或者执行完某个处理在进行下一步&#xff0c;这样不会出现多个线程同时要求进行UI处理而引发的混乱现象。
这些队列中的内容&#xff08;无论Message还是Runnable&#xff09;可以要求马上执行&#xff0c;延迟一定时间执行或者指定某个时刻执行&#xff0c;如果将他们放置在队列头&#xff0c;则表示具有最高有限级别&#xff0c;立即执行。这些函数包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用于在队列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。

最后

简历首选内推方式&#xff0c;速度快&#xff0c;效率高啊&#xff01;然后可以在拉钩&#xff0c;boss&#xff0c;脉脉&#xff0c;大街上看看。简历上写道熟悉什么技术就一定要去熟悉它&#xff0c;不然被问到不会很尴尬&#xff01;做过什么项目&#xff0c;即使项目体量不大&#xff0c;但也一定要熟悉实现原理&#xff01;不是你负责的部分&#xff0c;也可以看看同事是怎么实现的&#xff0c;换你来做你会怎么做&#xff1f;做过什么&#xff0c;会什么是广度问题&#xff0c;取决于项目内容。但做过什么&#xff0c;达到怎样一个境界&#xff0c;这是深度问题&#xff0c;和个人学习能力和解决问题的态度有关了。大公司看深度&#xff0c;小公司看广度。大公司面试你会的&#xff0c;小公司面试他们用到的你会不会&#xff0c;也就是岗位匹配度。

面试过程一定要有礼貌&#xff01;即使你觉得面试官不尊重你&#xff0c;经常打断你的讲解&#xff0c;或者你觉得他不如你&#xff0c;问的问题缺乏专业水平&#xff0c;你也一定要尊重他&#xff0c;谁叫现在是他选择你&#xff0c;等你拿到offer后就是你选择他了。

另外&#xff0c;描述问题一定要慢&#xff01;不要一下子讲一大堆&#xff0c;慢显得你沉稳、自信&#xff0c;而且你还有时间反应思路接下来怎么讲更好。现在开发过多依赖ide&#xff0c;所以会有个弊端&#xff0c;当我们在面试讲解很容易不知道某个方法怎么读&#xff0c;这是一个硬伤…所以一定要对常见的关键性的类名、方法名、关键字读准&#xff0c;有些面试官不耐烦会说“你到底说的是哪个&#xff1f;”这时我们会容易乱了阵脚。正确的发音&#43;沉稳的描述&#43;好听的嗓音决对是一个加分项&#xff01;

最重要的是心态&#xff01;心态&#xff01;心态&#xff01;重要事情说三遍&#xff01;面试时间很短&#xff0c;在短时间内对方要摸清你的底子还是比较不现实的&#xff0c;所以&#xff0c;有时也是看眼缘&#xff0c;这还是个看脸的时代。

希望大家都能找到合适自己满意的工作&#xff01;
如果需要PDF版本可以在GitHub中自行领取&#xff01;

  • 或者点击这里自行下载&#xff0c;直达领取链接

进阶学习视频

附上&#xff1a;我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 &#xff08;含BAT、小米、华为、美团、滴滴&#xff09;和我自己整理Android复习笔记&#xff08;包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。&#xff09;

sageDelayed()以及用于在队列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。

最后

简历首选内推方式&#xff0c;速度快&#xff0c;效率高啊&#xff01;然后可以在拉钩&#xff0c;boss&#xff0c;脉脉&#xff0c;大街上看看。简历上写道熟悉什么技术就一定要去熟悉它&#xff0c;不然被问到不会很尴尬&#xff01;做过什么项目&#xff0c;即使项目体量不大&#xff0c;但也一定要熟悉实现原理&#xff01;不是你负责的部分&#xff0c;也可以看看同事是怎么实现的&#xff0c;换你来做你会怎么做&#xff1f;做过什么&#xff0c;会什么是广度问题&#xff0c;取决于项目内容。但做过什么&#xff0c;达到怎样一个境界&#xff0c;这是深度问题&#xff0c;和个人学习能力和解决问题的态度有关了。大公司看深度&#xff0c;小公司看广度。大公司面试你会的&#xff0c;小公司面试他们用到的你会不会&#xff0c;也就是岗位匹配度。

面试过程一定要有礼貌&#xff01;即使你觉得面试官不尊重你&#xff0c;经常打断你的讲解&#xff0c;或者你觉得他不如你&#xff0c;问的问题缺乏专业水平&#xff0c;你也一定要尊重他&#xff0c;谁叫现在是他选择你&#xff0c;等你拿到offer后就是你选择他了。

另外&#xff0c;描述问题一定要慢&#xff01;不要一下子讲一大堆&#xff0c;慢显得你沉稳、自信&#xff0c;而且你还有时间反应思路接下来怎么讲更好。现在开发过多依赖ide&#xff0c;所以会有个弊端&#xff0c;当我们在面试讲解很容易不知道某个方法怎么读&#xff0c;这是一个硬伤…所以一定要对常见的关键性的类名、方法名、关键字读准&#xff0c;有些面试官不耐烦会说“你到底说的是哪个&#xff1f;”这时我们会容易乱了阵脚。正确的发音&#43;沉稳的描述&#43;好听的嗓音决对是一个加分项&#xff01;

最重要的是心态&#xff01;心态&#xff01;心态&#xff01;重要事情说三遍&#xff01;面试时间很短&#xff0c;在短时间内对方要摸清你的底子还是比较不现实的&#xff0c;所以&#xff0c;有时也是看眼缘&#xff0c;这还是个看脸的时代。

希望大家都能找到合适自己满意的工作&#xff01;
如果需要PDF版本可以在GitHub中自行领取&#xff01;

进阶学习视频

[外链图片转存中…(img-Qn7UrxWC-1612426315641)]

附上&#xff1a;我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 &#xff08;含BAT、小米、华为、美团、滴滴&#xff09;和我自己整理Android复习笔记&#xff08;包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。&#xff09;

[外链图片转存中…(img-QwqD3Rcz-1612426315642)]


推荐阅读
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • JVM钩子函数的应用场景详解
    本文详细介绍了JVM钩子函数的多种应用场景,包括正常关闭、异常关闭和强制关闭。通过具体示例和代码演示,帮助读者更好地理解和应用这一机制。适合对Java编程和JVM有一定基础的开发者阅读。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 在 Java 中,`join()` 方法用于使当前线程暂停,直到指定的线程执行完毕后再继续执行。此外,`join(long millis)` 方法允许当前线程在指定的毫秒数后继续执行。 ... [详细]
  • 【线段树】  本质是二叉树,每个节点表示一个区间[L,R],设m(R-L+1)2(该处结果向下取整)左孩子区间为[L,m],右孩子区间为[m ... [详细]
  • 本文介绍了 Go 语言中的高性能、可扩展、轻量级 Web 框架 Echo。Echo 框架简单易用,仅需几行代码即可启动一个高性能 HTTP 服务。 ... [详细]
  • C#实现文件的压缩与解压
    2019独角兽企业重金招聘Python工程师标准一、准备工作1、下载ICSharpCode.SharpZipLib.dll文件2、项目中引用这个dll二、文件压缩与解压共用类 ... [详细]
  • 本文介绍了 Java 中 io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evfiltSock() 方法的使用及其代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 本文总结了Java初学者需要掌握的六大核心知识点,帮助你更好地理解和应用Java编程。无论你是刚刚入门还是希望巩固基础,这些知识点都是必不可少的。 ... [详细]
  • Hadoop的文件操作位于包org.apache.hadoop.fs里面,能够进行新建、删除、修改等操作。比较重要的几个类:(1)Configurati ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 实验九:使用SharedPreferences存储简单数据
    本实验旨在帮助学生理解和掌握使用SharedPreferences存储和读取简单数据的方法,包括程序参数和用户选项。 ... [详细]
  • poj 3352 Road Construction ... [详细]
author-avatar
zhangmeicheng18
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有