前言

大家应该看过不少人分享的面试成功的面经,是不是觉得自己“说不定也可以”呢?

这里重提一个理论:幸存者偏差。当取得资讯的渠道,仅来自于幸存者时(因为死人不会说话),此资讯可能会存在与实际情况不同的偏差。

所以,今天要分享的是凉经。

重要概念

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 < msg.when) {nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 取出了消息mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);msg.markInUse();return msg;}} else {// 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1// 下次循环时,消息队列则处于等待状态nextPollTimeoutMillis = -1;}......}.....}
}// 回到分析原处/** * 分析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);

源码分析

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

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

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

源码总结

工作流程总结

方式2:使用 Handler.post()

步骤1:在主线程中创建Handler实例

具体使用

    private Handler mhandler = new  Handler();// 与方式1的使用不同:此处无复写Handler.handleMessage()

源码分析

/** * 源码分析:Handler的构造方法* 作用:*     a. 在此之前,主线程创建时隐式创建Looper对象、MessageQueue对象*     b. 初始化Handler对象、绑定线程 & 进入消息循环* 此处的源码分析类似方式1,此处不作过多描述*/
步骤2:在工作线程中 发送消息到消息队列中

具体使用

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

源码分析

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

工作流程总结

Handler.sendMessage与Handler.post比较

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

(六)内存泄露

6.1)问题描述

Handler的一般用法 = 新建Handler子类(内部类) 、匿名Handler内部类

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

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

6.2)原因讲解

1、储备知识

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

2、泄露原因描述

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

从上述示例代码可知:
上述的Handler实例的消息队列有2个分别来自线程1、2的消息(分别 为延迟1s、6s)
在Handler消息队列 还有未处理的消息 / 正在处理消息时,消息队列中的Message持有Handler实例的引用
由于Handler = 非静态内部类 / 匿名内部类(2种使用方式),故又默认持有外部类的引用(即MainActivity实例),引用关系如下图

上述的引用关系会一直保持,直到Handler消息队列中的所有消息被处理完毕
在Handler消息队列 还有未处理的消息 / 正在处理消息时,此时若需销毁外部类MainActivity,但由于上述引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。如下图:

3、总结

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

6.3)解决方案

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

解决方案1:静态内部类+弱引用(推荐:保证消息队列中所有消息都能执行)

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

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

解决方案2:当外部类结束生命周期时,清空Handler内消息队列

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

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

(七)线程安全

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

总结

首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。

另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。

之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。

下一步还是要查漏补缺,进行针对性复习。

最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!在这里也免费分享给大家,希望大家不要犯和我一样的错误呀!!!一定要看完!


获取方式:点击我的GitHub

生命周期结束时,同时清空消息队列 & 结束Handler生命周期
}


## (七)线程安全通过创建一个Handler子类的对象,每个acvivity只需一个Handler对象。后台进程可通过两种方式Handler进行通信:message和Runnable对象,其结果实质都是将在Handler的队列中放入内容,message是放置信息,可以传递一些参数,Handler获取这些信息并将判度如何处理,而Runnable则是直接给出处理的方法。队列就是依次执行,Handler会处理完一个消息或者执行完某个处理在进行下一步,这样不会出现多个线程同时要求进行UI处理而引发的混乱现象。
这些队列中的内容(无论Message还是Runnable)可以要求马上执行,延迟一定时间执行或者指定某个时刻执行,如果将他们放置在队列头,则表示具有最高有限级别,立即执行。这些函数包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用于在队列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。## 总结首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。下一步还是要查漏补缺,进行针对性复习。**最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!在这里也免费分享给大家,希望大家不要犯和我一样的错误呀!!!一定要看完!**
[外链图片转存中...(img-54mTDKpJ-1613912569687)][外链图片转存中...(img-RROvn0ST-1613912569689)][外链图片转存中...(img-iZ2VA9SX-1613912569690)]
**获取方式:**[点击我的GitHub](https://github.com/a120464/Android-P7/blob/master/Android%E5%BC%80%E5%8F%91%E4%B8%8D%E4%BC%9A%E8%BF%99%E4%BA%9B%EF%BC%9F%E5%A6%82%E4%BD%95%E9%9D%A2%E8%AF%95%E6%8B%BF%E9%AB%98%E8%96%AA%EF%BC%81.md)

大厂架构师经验分享!怒斩获了30家互联网公司offer,使用指南相关推荐

  1. Android开发技巧!怒斩获了30家互联网公司offer,大厂直通车!

    Gradle是什么? Gradle是一种构建语言,目前是Android的默认构建工具,我们编写的编译脚本,其实就是玩Gradle的API,所以从它更底层的意义上看,是一个编程框架. 因为涉及的内容很多 ...

  2. 怒斩获了30家互联网公司offer,详细的Android学习指南

    前言 伟人曾经说过: 书是人类进步的阶梯 书中自有黄金屋,书中自有颜如玉 读书破万卷,下笔如有神 书是唯一不死的东西. 书籍是伟大的天才留给人类的遗产. 最近有很多朋友在我的公众号上提问"A ...

  3. 小白看完都会了!怒斩获了30家互联网公司offer,面试总结

    前言 移动研发火热不停,越来越多人开始学习 android 开发.但很多人感觉入门容易成长很难,对未来比较迷茫,不知道自己技能该怎么提升,到达下一阶段需要补充哪些内容.市面上也多是谈论知识图谱,缺少体 ...

  4. 怒斩获了30家互联网公司offer,赶快收藏备战金九银十!

    前言 马爸爸总结了一句话:跳槽,要么是钱不到位,要么是受了委屈. 我给自己这次的跳槽经历做了一个分析,希望能对那些想换工作的朋友有所帮助. 许多朋友想换工作,但是对"换工作"的理解 ...

  5. 最强Android教程!怒斩获了30家互联网公司offer,赶紧收藏!

    写在前面 为什么只看这一篇就够了? 现在CSDN.知乎.掘金上各路大佬层出不穷,他们身经百战.血洗杀场,总结出满满的求职干货.但同时也存在很多非良心的博主,要么活出了人类的本质,复读机一样到处转载:要 ...

  6. 怒斩获了30家互联网公司offer,赶紧收藏备战金三银四!

    前言 马爸爸总结了一句话:跳槽,要么是钱不到位,要么是受了委屈. 我给自己这次的跳槽经历做了一个分析,希望能对那些想换工作的朋友有所帮助. 许多朋友想换工作,但是对"换工作"的理解 ...

  7. 大厂架构师经验分享!成功跳槽字节跳动

    前言 毫不夸张地说,JVM是现代软件工程最成功的案例之一.因为它自带GC,又有无数可以微调的参数,且运行极其稳定可靠,所以,许多厂商的核心业务系统,才敢放心地用Java编写,运行在JVM之上. 近几年 ...

  8. Android ViewGroup介绍+实例,大厂架构师经验分享

    //每一行的高度,累加至height int lineHeight = 0; int count = getChildCount(); int left = getPaddingLeft(); int ...

  9. 大厂架构师经验分享!致Android高级工程师的一封信,架构师必备技能

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-44wTxFmF-1616839115202)(//upload-images.jianshu.io/upload_ima ...

最新文章

  1. ceph bluestore 源码分析:ceph-osd内存查看方式及控制源码分析
  2. 在Indicator中添加动态Checkbox,无需绑定数据源,支持全选 - Ehlib学习(二)
  3. Docker windows 安装MySql和Tomcat
  4. 赢得市值,失去人心,美团觉得划算吗?
  5. 二十四、数据挖掘时序模式
  6. hive 如果表不存在则创建_从零开始学习大数据系列(四十七) Hive中数据的加载与导出...
  7. nodejs 快速发布rtsp server
  8. php开发中常用函数总结,PHP开发中常用函数总结
  9. 循序渐进之Spring AOP(1) - 原理
  10. secoclient全版本下载分享
  11. python 前端素材提供
  12. deepin linux 命令行_deepin-linux常用命令大全----每天一个linux命令 - 子成君-分享出去,快乐加倍!-旧版已停更...
  13. spiders的使用
  14. html大鱼吃小鱼游戏,大鱼吃小鱼 网页游戏源码(javascript)
  15. java课程设计 考试系统,java课程设计考试系统.
  16. matlab航电系统仿真,航空航天和国防
  17. Camera sensor 基本知识
  18. 直通车开通后没有展现量,启中教育来分析
  19. 高校学籍管理系统 _数据库系统概论课程设计
  20. 基于Android的手机音乐播放器的设计

热门文章

  1. iOS 获取 当前设备 可用内存 及当前 应用 所占内存 (转)
  2. WPF引入 ttf 图标文件使用记录
  3. 戴尔外星人笔记本电脑Alienware m17 R4原装出厂windows10系统恢复原厂OEM系统
  4. hadoop虚拟机没有ens33
  5. 推荐几款前端编辑器(还在因为只知道一个编辑器被同事嘲笑吗?看完给他上一课!!!)
  6. JSON的使用与解析
  7. TD铁人PK北邮教授 何谓“狭隘的技术民族主义”
  8. Python实战小项目-飞船游戏
  9. CSS高级篇——圆角
  10. 【JavaSE-05】:常用类枚举