详解异步通信Handle的分析与应用

android 异步通信机制Handler的分析与运用

当我们应用程序启动时,Android系统就会创建一个主线程即UI线程,在这个UI线程中进行对UI控件的管理,如页面的刷新或者事件的响应等过程。同时Android规定在UI主线程不能进行耗时操作,否则会出现ANR现象,对此,我们一般是通过开启子线程来进行耗时操作,在子线程中通常会涉及到页面的刷新问题,这就是如何在子线程进行UI更新,对于这个问题,我们一般通过异步线程通信机制中的Handler来解决,接下来我们就来分析一下Handler机制。

常规用法

 

 public class MainActivity extends AppCompatActivity {

        @BindView(R.id.execute)

        Button execute;

        @BindView(R.id.text)

        TextView text;

//处理子线程发过来的消息

        Handler handler = new Handler() {

            @Override

            public void handleMessage(Message msg) {

                super.handleMessage(msg);

                if (msg.what == 1) {

                    text.setText("obj:" + msg.obj.toString() + "\nwhat: " + msg.what + "\narg1: " + msg.arg1);

                }

            }

        };

        @Override

        public void onCreate(Bundle savedInstanceState) {

            super.onCreate(savedInstanceState);

            setContentView(R.layout.activity_main);

            ButterKnife.bind(this);

//子线程发送消息

            new Thread(new Runnable() {

                @Override

                public void run() {

                    Message message = handler.obtainMessage();

                    message.what = 1;

                    message.obj = "子线程更新UI操作";

                    handler.sendMessage(message);

                }

            }).start();

        }

    }

以上代码就是我们一般会使用到的,子线程通过Message,给主线程发送消息进行UI操作,接下来我们就一步一步进行深究,看看android是如何实现子线程和主线程如何交互的。

首先,我们在主线程中开启一个子线程,我们用了以下方式:

    new Thread(new Runnable() {

        @Override

        public void run () {

//处理事件

        }

    }).start();

开启一个线程,通常有两种方式:

  1. 继承Thread类,覆盖run方法
  2. 实现runnable接口,实现run方法

对于第一种方法继承Thread类,覆盖run方法,我们查看源码就可以知道,最终还是实现runnable接口,所以没有多大的区别。

public class Thread implements Runnable{

       //.......

}

回归正题:

Message message = handler.obtainMessage();

message.what =1;

message.obj ="子线程更新UI操作";

handler.sendMessage(message);

我们在run方法中进行发送消息,对于第一行我们获得一个消息是通过

handler.obtainMessage();

而不是通过

Message message =new Message();

这两者有什么区别呢?还是来进入到源码中一窥究竟吧!我们首先进入Handler类中,进行查看

public final Message obtainMessage() {
    return Message.obtain(this);
}

继续进入

public static Message obtain(Handler h) {

    Message m = obtain();

    m.target = h;

    return m;

}

最终来到了Message类中

/**

 * Return a new Message instance from the global pool. Allows us to

 * avoid allocating new objects in many cases.

 */

public static Message obtain() {

    synchronized (sPoolSync) {

        if (sPool != null) {

            Message m = sPool;

            sPool = m.next;

            m.next = null;

            m.flags = 0; // clear in-use flag

            sPoolSize--;

            return m;

        }

    }

    return new Message();

}

我们仔细观察一下sPool ,这个sPool 是什么东西呢?pool是池的意思,线程有线程池,那么我们也可以认为Message也有一个对象池,我们分析一下源码可以得知:

如果Message对象池中还存在message的话,我们直接使用message,而不是创建一个新的Message

接下来就是对message进行一些常规的设置,如要传递的消息内容之类的,最后进行消息 的发送。
我们进入到消息的最后一步源码中进行查看:

handler.sendMessage(message);

会调用sendMessageDelayed方法

 

1

2

3

4

5

6

 

//Handler类

public final boolean sendMessage(Message msg)

{

return sendMessageDelayed(msg, 0);

}

 

继续深入查看

 

1

2

3

4

5

6

7

8

9

 

//Handler.java

public final boolean sendMessageDelayed(Message msg, long delayMillis)

{

if (delayMillis < 0) {

delayMillis = 0;

}

//定时发送消息

return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}

如果我们设置了延时时间,那么会计算相应的发送时间,当前时间加上延时就是最终的消息发送时间。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

 

//Handler.java 定时发送消息

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

//消息队列

MessageQueue queue = mQueue;

if (queue == null) {

RuntimeException e = new RuntimeException(

this + " sendMessageAtTime() called with no mQueue");

Log.w("Looper", e.getMessage(), e);

return false;

}

//将消息插入队列中,等待处理

return enqueueMessage(queue, msg, uptimeMillis);

}

我们来看看最后一步,将消息插入队里中是如何实现的。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

 

//消息入队操作

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

//msg.target实际上是Handler

msg.target = this;

//异步

if (mAsynchronous) {

msg.setAsynchronous(true);

}

//消息入队

return queue.enqueueMessage(msg, uptimeMillis);

}

我们来看看大头,消息是如何入队的。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

 

//MessageQueue.java 消息入队,队列的实现其实单链表的插入和删除操作

boolean enqueueMessage(Message msg, long when) {

//指的是Handler

if (msg.target == null) {

throw new IllegalArgumentException("Message must have a target.");

}

//如果消息正在使用中

if (msg.isInUse()) {

throw new IllegalStateException(msg + " This message is already in use.");

}

synchronized (this) {

if (mQuitting) {

IllegalStateException e = new IllegalStateException(

msg.target + " sending message to a Handler on a dead thread");

Log.w(TAG, e.getMessage(), e);

msg.recycle();

return false;

}

msg.markInUse();

msg.when = when;

Message p = mMessages;

boolean needWake;

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;

for (;;) {

prev = p;

p = p.next;

if (p == null || when < p.when) {

break;

}

if (needWake && p.isAsynchronous()) {

needWake = false;

}

}

msg.next = p; // invariant: p == prev.next

prev.next = msg;

}

// We can assume mPtr != 0 because mQuitting is false.

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

当Handler将消息插入到消息队列后,那么重要的问题来了,子线程是如何和主线程通信的呢?按道理讲,既然可以将插入到队列中,那么肯定有一个东西可以从消息队列中去消息然后进行处理,对于这个东西,就是有Looper来承担了。

我们首先来看下Looper这个类:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

 

public final class Looper {

// sThreadLocal.get() will return null unless you've called prepare().

//用于存放当前线程的looper对象

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

//主线程的Looper也就是UI线程Looper

private static Looper sMainLooper; // guarded by Looper.class

//当前线程的消息队列

final MessageQueue mQueue;

//当前线程

final Thread mThread;

//...

}

我们对Looper这个类进行了简单的介绍,对于消息的获取并处理我们得进入到主线程中即ActivityThread.java类中去

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

 

public static void main(String[] args) {

//省略部分代码...

Looper.prepareMainLooper(); ------------------(1)

ActivityThread thread = new ActivityThread();

thread.attach(false);

if (sMainThreadHandler == null) {

sMainThreadHandler = thread.getHandler();

}

if (false) {

Looper.myLooper().setMessageLogging(new

LogPrinter(Log.DEBUG, "ActivityThread"));

}

//省略部分代码...

Looper.loop(); ------------------------(2)

throw new RuntimeException("Main thread loop unexpectedly exited");

}

对于Looper.prepareMainLooper()我们进行分析看看,到底是什么?

 

1

2

3

4

5

6

7

8

9

10

11

12

13

 

public static void prepareMainLooper() {

//不允许退出

prepare(false);

synchronized (Looper.class) {

//一个线程只能有一个Looper对象

if (sMainLooper != null) {

throw new IllegalStateException("The main Looper has already been prepared.");

}

//主线程的looper

sMainLooper = myLooper();

}

}

对于myLoop()是什么东东?

 

1

2

3

4

5

6

7

 

/**

* Return the Looper object associated with the current thread. Returns

* null if the calling thread is not associated with a Looper.

*/

public static @Nullable Looper myLooper() {

return sThreadLocal.get();

}

 

是我们一开始介绍的looper类中的相关变量,也就是存储Looper对象的东西,类似于一个容器。这里是取的Looper对象,那么我们在哪里进行存呢?我们进入到prepare方法中:

 

1

2

3

4

5

6

7

8

9

10

11

 

//保存当前线程的Looper对象

private static void prepare(boolean quitAllowed) {

//一个线程只允许一个Looper对象

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

}

//存入Looper对象

sThreadLocal.set(new Looper(quitAllowed));

}

接下来我们看一下Looper.loop()的方法:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

 

/**

* Run the message queue in this thread. Be sure to call

* {@link #quit()} to end the loop.

*/

public static void loop() {

//获取当前线程的Looper对象

final Looper me = myLooper();

//主线程中不需要手动调用Looper.prepare()方法,

//当我们使用子线程时需要手动调用Looper.prepare()方法,否则会报异常。

if (me == null) {

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

}

//当前线程中的消息队列

final MessageQueue queue = me.mQueue;

//省略部分代码...

//死循环,不断处理消息

for (;;) {

Message msg = queue.next(); // might block

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

//省略部分代码...

try {

//msg.target就是Handler,Handler处理消息

msg.target.dispatchMessage(msg);

} finally {

if (traceTag != 0) {

Trace.traceEnd(traceTag);

}

}

//省略部分代码...

//消息回收

msg.recycleUnchecked();

}

}

Looper.loop()其实就是不断的从队列中获取消息,然后进行处理。

在上面方法中,有一行代码:msg.target.dispatchMessage(msg);我们进入内部去详细查看一下实现:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

 

//Handler.java 分发消息

/**

* Handle system messages here.

*/

public void dispatchMessage(Message msg) {

// msg.callback== Runnable callback;

if (msg.callback != null) {

//如果有runnable,那么则实现它的run方法里面的内容

handleCallback(msg);

} else {

//否则处理handleMessage方法

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

handleCallback(msg);方法实现

 

1

2

3

4

 

private static void handleCallback(Message message) {

//msg.callback== Runnable callback;

message.callback.run();

}

handleMessage方法实现

 

1

2

3

 

public interface Callback {

public boolean handleMessage(Message msg);

}

对于上面那个方法,其实就是我们在主线程中实现的方法:

 

1

2

3

4

5

6

7

8

9

10

 

//消息处理

Handler handler = new Handler() {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

if (msg.what == 1) {

text.setText("obj:" + msg.obj.toString() + "\nwhat: " + msg.what + "\narg1: " + msg.arg1);

}

}

};


到此,基本上就已经分析了大概,不过,我们在实际的开发过程中有时候会碰到这个问题:

 

1

 

Can't create handler inside thread that has not called Looper.prepare()

而我们的代码是如何写的呢?

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

 

//自定义一个Thread继承自Thread

private class MyThread extends Thread {

private Handler myThreadHandler;

@Override

public void run() {

myThreadHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

if (msg.what == 1) {

//todo...

}

}

};

Message message = new Message();

message.what = 1;

message.obj = "MyThread Message Handler";

myThreadHandler.sendMessage(message);

}

}

上述代码很简单,就是自定义一个Thread,然后申明一个Handler来使用,然后通过下面代码进行线程通信:

 

1

2

3

4

5

6

 

myThreadBtn.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

new MyThread().start();

}

});

为什么上面简单的代码会出现这个问题呢?而我们在Activity中申明的Handler就可以直接用,而不会出现上述的error?其实,我们在UI主线程中使用Handler时已经调用过了Looper.prepare()和Looper.loop(),我们返回到上面的ActivityThread.java类中的main方法处,我们可以发现,其实UI主线程已经调用过了,

 

1

2

3

 

Looper.prepareMainLooper();

//...

Looper.loop();

而在我们子线程却需要我们自己手动调用一下,知道了原因所在,我们来修改一下,再次运行,即可得出正确的答案。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

 

private class MyThread extends Thread {

private Handler myThreadHandler;

@Override

public void run() {

//注意此处的prepare方法

Looper.prepare();

myThreadHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

if (msg.what == 1) {

//todo...

}

}

};

Message message = new Message();

message.what = 1;

message.obj = "MyThread Message Handler";

myThreadHandler.sendMessage(message);

//注意此处的loop方法

Looper.loop();

}

}

以上修改内容即可得出正确的答案,接下来我们来总结一下:

为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。

除了通过Handler的sendMessage方法来进行子线程和主线程进行通信外,我们还可以通过以下的方法来达到相同的效果。

1、handler.post
我们来仔细分析一下源代码进行说明。

 

1

2

3

4

 

public final boolean post(Runnable r)

{

return sendMessageDelayed(getPostMessage(r), 0);

}

调用handler.post方法,将runnable参数转化为一条message进行发送的。接着我们进入getPostMessage(r)中进行分析看看。

 

1

2

3

4

5

 

private static Message getPostMessage(Runnable r) {

Message m = Message.obtain();

m.callback = r;

return m;

}

将传递进来的runnable参数赋值给Message的callback变量,赋值给它有什么用呢?我们还记不记得在Handler的dispatchMessage时会做一个判断???

 

1

2

3

4

5

6

7

8

9

10

11

12

13

 

public void dispatchMessage(Message msg) {

//判断Message的callback是否为null,这里的callback就是runnable

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

上面的callback是否为null的判断决定着整个流程,如果callback不等于null的话我们进入handleCallback(msg)方法中一窥究竟。

 

1

2

3

 

private static void handleCallback(Message message) {

message.callback.run();

}

看到没?直接调用了run方法,其中的message.callback就是Runnable,所以它会直接执行run方法。所以对于在子线程中更新UI时使用handler.post 方法时,直接在run方法中进行UI更新如:

 

1

2

3

4

5

6

 

mHandler.post(new Runnable() {

@Override

public void run() {

handlerText.setText("result: this is post method");

}

});

2、view.post
同理,我们也是进入到源码中进行分析一下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

 

/**

* <p>Causes the Runnable to be added to the message queue.

* The runnable will be run on the user interface thread.</p>

*

* @param action The Runnable that will be executed.

*

* @return Returns true if the Runnable was successfully placed in to the

* message queue. Returns false on failure, usually because the

* looper processing the message queue is exiting.

*

* @see #postDelayed

* @see #removeCallbacks

*/

public boolean post(Runnable action) {

final AttachInfo attachInfo = mAttachInfo;

if (attachInfo != null) {

return attachInfo.mHandler.post(action);

}

// Postpone the runnable until we know on which thread it needs to run.

// Assume that the runnable will be successfully placed after attach.

getRunQueue().post(action);

return true;

}

通过该方法的注释我们就知道,首先会将runnable放进到Message队列中去,然后在UI主线程中运行,调用handler的post方法,本质的原理都是一样的。

3、runOnUiThread
对于runOnUiThread方法,我们从字面上也可以了解到是在主线程中运行的,我们详细分析一下:

 

1

2

3

4

5

6

7

 

public final void runOnUiThread(Runnable action) {

if (Thread.currentThread() != mUiThread) {

mHandler.post(action);

} else {

action.run();

}

}

代码很简单,就是先判断一下当前线程是否是UI主线程,是的话,直接运行run方法,不是的话通过Handler来实现。

通过以上四种在子线程中更新UI的方法,其内在的本质都是一样的,都是借助于Handler来实现的,本篇分析了异步通信机制中的Handler,通过本篇的学习与了解,对于实际项目中如果遇到相似的问题的话,我想应该可以迎刃而解了。知其然而知所以然!

最后我们来总结一下本篇文章中涉及到的各个对象的意思:

1、MessageQueue

消息队列,它的内部存储了一组数据,以队列的形式向外提供了插入和删除的工作。但是它的内部实现并不是队列,而是单链表

2、Looper

会不停检查是否有新的消息,如果有就调用最终消息中的Runnable或者Handler的handleMessage方法。对应提取并处理消息。

3、Handler

Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过post的一系列方法以及send的一系列方法来实现,不过最后都是通过send的一系列方法实现的。对应添加消息和处理线程。

4、Message

封装了需要传递的消息,并且本身可以作为链表的一个节点,方便MessageQueue的存储。

5、ThreadLocal

一个线程内部的数据存储类,通过它可以在指定的线程中储存数据,而其它线程无法获取到。在Looper、AMS中都有使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值