Handler 详解

2021-06-22/2021-06-22

参考文献:

Android多线程(Handler篇)
Android Handler消息机制原理最全解读

1. 基础使用

安卓不允许在子线程中直接更新 UI,我们需要通过 handle 来实现在子线程更新 UI

使用步骤:

  1. 创建 Handler 对象,实现 handlMessage() 方法
  2. 创建 Runnable 线程
  3. 此时产生一个 Looper,并自动创建一个消息队列 MessageQueue()
  4. Looper 轮询 MessageQueue 交给 Handler
  5. Handler 做处理

其中 1、2、5 为使用步骤,其他在后面分析源码时会讲到

 1public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 2
 3    public static final int UPDATE_TEXT = 1;
 4
 5    private TextView text;
 6
 7    private Handler handler = new Handler() {
 8        @Override
 9        public void handleMessage(Message msg) {
10            switch (msg.what) {
11                case UPDATE_TEXT:
12                    // 在这里可以进行UI操作
13                    text.setText("Nice to meet you");
14                    break;
15                default:
16                    break;
17            }
18        }
19
20    };
21
22    @Override
23    protected void onCreate(Bundle savedInstanceState) {
24        super.onCreate(savedInstanceState);
25        setContentView(R.layout.activity_main);
26        text = (TextView) findViewById(R.id.text);
27        Button changeText = (Button) findViewById(R.id.change_text);
28        changeText.setOnClickListener(this);
29    }
30
31    @Override
32    public void onClick(View v) {
33        switch (v.getId()) {
34            case R.id.change_text:
35                new Thread(new Runnable() {
36                    @Override
37                    public void run() {
38                        Message message = handler.obtainMessage();
39                        message.what = UPDATE_TEXT;
40                        handler.sendMessage(message); // 将Message对象发送出去
41                    }
42                }).start();
43                break;
44            default:
45                break;
46        }
47    }
48}

2. 异步消息处理机制

20180620112532709.png

Android 异步消息处理机制主要由五个部分组成,ThreadLocal、Message、Handle、MessageQueue 和 Looper。

2.1 ThreadLocal

他并不是解决共享对象的多线程访问问题的!!!

他并没有创建对象的拷贝或副本!!!

**目的:**他只是为了保证每个线程都拥有同一个类的不同对象
**实质:**每个线程里都 new 了同一个类的对象
**作用:**你或许觉得这样做很傻,但是如果使用全局变量就要考虑线程安全问题,而线程安全是以消耗性能为前提的,所以这种设计可以说很巧妙
**场景:**每个线程都需要相同类型的对象,又各自独立,并且与他相关的操作不少时,如,Looper,ActivityThread,AMS 等

 1private static final ThreadLocal tSession = new ThreadLocal();
 2public static Session getSession() throws Exception{
 3	Session s = tSession.get();
 4	try{
 5		if(s==null){
 6			s = getSessionFactory.openSession();
 7			tSession.set(s);
 8		}
 9	}catch(Exception e){
10
11	}
12	return s;
13}

我们看一下 ThreadLocal 的源码,主要分析 get() 和 set()

 1public T get() {
 2    Thread t = Thread.currentThread();
 3    ThreadLocalMap map = getMap(t);
 4    if (map != null) {
 5        ThreadLocalMap.Entry e = map.getEntry(this);
 6        if (e != null) {
 7            @SuppressWarnings("unchecked")
 8            T result = (T)e.value;
 9            return result;
10        }
11    }
12    return setInitialValue();
13}
14
15public void set(T value) {
16    Thread t = Thread.currentThread();
17    ThreadLocalMap map = getMap(t);
18    if (map != null)
19        map.set(this, value);
20    else
21        createMap(t, value);
22}
23
24ThreadLocalMap getMap(Thread t) {
25    return t.threadLocals;
26}

set、get 操作的都是 ThreadLocalMap,key=当前线程,value=线程局部变量缓存值。可以看到 get() 实际上是调用 getMap(传入当前线程)来取得当前线程的 ThreadLocalMap 对象。

  • set(),实际上是调用了 ThreadLocalMap 的 set(),ThreadLocalMap 的 set() 涉及到哈希散列算法。
  • get(),从当前线程中获取 ThreadLocalMap,查询当前 ThreadLocal 变量实例对应的 Entry,如果不为 null,获取 value,如果 map 为 null,走初始化方法

由此看出不是如很多博主写的各线程用了同一个对象又相互独立那么神奇,只不过是用线程当做键在其中维护了一个私有变量而已。得到 ThreadLocalMap 后如何得到维护的变量呢,在这一句

1ThreadLocalMap.Entry e = map.getEntry(this);
2//this指代的ThreadLocal对象

以过程就很清晰了,我们来总结一下:

  • 声明一个全局公用的 ThreadLocal 实例作为 key
  • 在线程中 new 一个或取出已经存了的对象作为 value
  • 将此 key-value 放入 ThreadLocalMap 中
  • 将当前线程作为 key,ThreadLocalMap 作为值放入 ThreadLocal 中

2.2 Looper

Looper 是每个线程中 MessageQueue 的管家, 调用 loop() 方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将其取出,并传递到 handleMessage() 方法当中。每个线程中也只会有一个 Looper 对象。

作用:

  • 创建消息队列,保证当前线程只有一个 looper 和一个 MessageQueue
  • 在队列中循环取消息,从 MessageQueue 中取消息交给 target 的 dispatchMessage

方法:

  • prepare()
  • loop()

源码分析:

构造方法

1private Looper(boolean quitAllowed) {
2    mQueue = new MessageQueue(quitAllowed);
3    mThread = Thread.currentThread();
4}

很容易看出当 Looper 构造时创建了一个消息队列

prepare() 方法

1private static void prepare(boolean quitAllowed) {
2    if (sThreadLocal.get() != null) {
3        throw new RuntimeException("Only one Looper may be created per thread");
4    }
5    sThreadLocal.set(new Looper(quitAllowed));
6}

通过前面 ThreadLocal 的分析可知 sThreadLocal.get() 得到了 Looper 对象,当 Looper 存在是报错,不存在是创建一个存入 ThreadLocal 中,保证线程里有且只有一个 Looper 对象。

loop() 方法

 1/**
 2* Run the message queue in this thread. Be sure to call
 3* {@link #quit()} to end the loop.
 4*/
 5public static void loop() {
 6    final Looper me = myLooper();
 7    if (me == null) {
 8        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
 9    }
10    final MessageQueue queue = me.mQueue;
11
12    // Make sure the identity of this thread is that of the local process,
13    // and keep track of what that identity token actually is.
14    Binder.clearCallingIdentity();
15    final long ident = Binder.clearCallingIdentity();
16
17    for (;;) {
18        Message msg = queue.next(); // might block
19        if (msg == null) {
20            // No message indicates that the message queue is quitting.
21            return;
22        }
23
24        // This must be in a local variable, in case a UI event sets the logger
25        final Printer logging = me.mLogging;
26        if (logging != null) {
27            logging.println(">>>>> Dispatching to " + msg.target + " " +
28                    msg.callback + ": " + msg.what);
29        }
30
31        final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
32
33        final long traceTag = me.mTraceTag;
34        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
35            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
36        }
37        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
38        final long end;
39        try {
40            msg.target.dispatchMessage(msg);
41            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
42        } finally {
43            if (traceTag != 0) {
44                Trace.traceEnd(traceTag);
45            }
46        }
47        if (slowDispatchThresholdMs > 0) {
48            final long time = end - start;
49            if (time > slowDispatchThresholdMs) {
50                Slog.w(TAG, "Dispatch took " + time + "ms on "
51                        + Thread.currentThread().getName() + ", h=" +
52                        msg.target + " cb=" + msg.callback + " msg=" + msg.what);
53            }
54        }
55
56        if (logging != null) {
57            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
58        }
59
60        // Make sure that during the course of dispatching the
61        // identity of the thread wasn't corrupted.
62        final long newIdent = Binder.clearCallingIdentity();
63        if (ident != newIdent) {
64            Log.wtf(TAG, "Thread identity changed from 0x"
65                    + Long.toHexString(ident) + " to 0x"
66                    + Long.toHexString(newIdent) + " while dispatching to "
67                    + msg.target.getClass().getName() + " "
68                    + msg.callback + " what=" + msg.what);
69        }
70
71        msg.recycleUnchecked();
72    }
73}
  • 拿到 ThreadLocal 中的 Looper,若没有则 prepare()
  • 拿到 Looper 的 MessageQueue
  • 进入死循环,调用 msg.target.dispatchMessage(msg),发给 Handler
  • 释放资源

2.3 Message

Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。

2.4 MessageQueue

MessageQueue 是消息队列,它主要用于存放所有由 Handler 发送过来的消息,这部分消息会一直在消息队列中,等待被处理。每个线程中只会有一个 MessageQueue 对象。

2.5 Handle

Handler 顾名思义也就是处理者的意思,它主要用于发送和处理消息。 发送消息一般使用 handler 的 sendMessage() 方法,处理消息会调用 handleMessage() 方法。

构造函数

 1public Handler(Callback callback, boolean async) {
 2    if (FIND_POTENTIAL_LEAKS) {
 3        final Class<? extends Handler> klass = getClass();
 4        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
 5                (klass.getModifiers() & Modifier.STATIC) == 0) {
 6            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
 7                klass.getCanonicalName());
 8        }
 9    }
10
11    mLooper = Looper.myLooper();
12    if (mLooper == null) {
13        throw new RuntimeException(
14            "Can't create handler inside thread that has not called Looper.prepare()");
15    }
16    mQueue = mLooper.mQueue;
17    mCallback = callback;
18    mAsynchronous = async;
19}

构造时通过 Looper.myLooper 获取当前线程保存的 Looper 实例,再获取这个 Looper 的 MessageQueue

发送消息

 1public final boolean sendMessage(Message msg)
 2{
 3    return sendMessageDelayed(msg, 0);
 4}
 5
 6/**
 7* Sends a Message containing only the what value.
 8*  
 9* @return Returns true if the message was successfully placed in to the 
10*         message queue.  Returns false on failure, usually because the
11*         looper processing the message queue is exiting.
12*/
13public final boolean sendEmptyMessage(int what)
14{
15    return sendEmptyMessageDelayed(what, 0);
16}
17
18/**
19* Sends a Message containing only the what value, to be delivered
20* after the specified amount of time elapses.
21* @see #sendMessageDelayed(android.os.Message, long) 
22* 
23* @return Returns true if the message was successfully placed in to the 
24*         message queue.  Returns false on failure, usually because the
25*         looper processing the message queue is exiting.
26*/
27public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
28    Message msg = Message.obtain();
29    msg.what = what;
30    return sendMessageDelayed(msg, delayMillis);
31}

发送消息时所有方法都实际调用了 sendMessageAtTime

 1public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
 2    MessageQueue queue = mQueue;
 3    if (queue == null) {
 4        RuntimeException e = new RuntimeException(
 5                this + " sendMessageAtTime() called with no mQueue");
 6        Log.w("Looper", e.getMessage(), e);
 7        return false;
 8    }
 9    return enqueueMessage(queue, msg, uptimeMillis);
10}

是获取 MessageQueue 调用enqueueMessage();

enqueueMessage()

1private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
2    msg.target = this;
3    if (mAsynchronous) {
4        msg.setAsynchronous(true);
5    }
6    return queue.enqueueMessage(msg, uptimeMillis);
7}

将 msg 的 target 属性赋值为 Handler 自己,实现了 Message 与 Handler 的绑定,并调用了 MessageQueue 中的 enqueueMessage() 方法

 1boolean enqueueMessage(Message msg, long when) {
 2    if (msg.target == null) {
 3        throw new IllegalArgumentException("Message must have a target.");
 4    }
 5    if (msg.isInUse()) {
 6        throw new IllegalStateException(msg + " This message is already in use.");
 7    }
 8
 9    synchronized (this) {
10        if (mQuitting) {
11            IllegalStateException e = new IllegalStateException(
12                    msg.target + " sending message to a Handler on a dead thread");
13            Log.w(TAG, e.getMessage(), e);
14            msg.recycle();
15            return false;
16        }
17
18        msg.markInUse();
19        msg.when = when;
20        Message p = mMessages;
21        boolean needWake;
22        if (p == null || when == 0 || when < p.when) {
23            // New head, wake up the event queue if blocked.
24            msg.next = p;
25            mMessages = msg;
26            needWake = mBlocked;
27        } else {
28            // Inserted within the middle of the queue.  Usually we don't have to wake
29            // up the event queue unless there is a barrier at the head of the queue
30            // and the message is the earliest asynchronous message in the queue.
31            needWake = mBlocked && p.target == null && msg.isAsynchronous();
32            Message prev;
33            for (;;) {
34                prev = p;
35                p = p.next;
36                if (p == null || when < p.when) {
37                    break;
38                }
39                if (needWake && p.isAsynchronous()) {
40                    needWake = false;
41                }
42            }
43            msg.next = p; // invariant: p == prev.next
44            prev.next = msg;
45        }
46
47        // We can assume mPtr != 0 because mQuitting is false.
48        if (needWake) {
49            nativeWake(mPtr);
50        }
51    }
52    return true;
53}

对前一个方法进行补充,把 msg 放入 MessageQueue 中,这时候轮询取消息(在前面 Looper 已经分析),调用 dispatchMessage()

 1public void dispatchMessage(Message msg) {
 2    if (msg.callback != null) {
 3        handleCallback(msg);
 4    } else {
 5        if (mCallback != null) {
 6            if (mCallback.handleMessage(msg)) {
 7                return;
 8            }
 9        }
10        handleMessage(msg);
11    }
12}

最终调用 handlerMessage() 方法

1/**
2* Subclasses must implement this to receive messages.
3*/
4public void handleMessage(Message msg) {
5}

一个空方法,在这里处理操作,end

2.6 总结

  • Looper.prepare() 在本线程中存入 ThreadLocalMap 中一个 Looper 并创建一个 MessageQueue 对象
  • Looper.loop() 循环取 Message 中的消息,回调 msg.target.dispatchMessage(msg)
  • Handler 构造得到 Looper 并与 MessageQueue 关联
  • Handler.sendMessage 会给 msg.target 赋值为自己并加入 MessageQueue 中
  • 重写 handlMessage() 处理

了解了 Message、Handle、MessageQueue 以及 Looper 的基本概念之后,我们再来对异步消息处理的整个过程梳理一遍。首先需要在主线程当中创建一个 Handle 对象,并重写 handleMessage()方法。然后当子线程中需要进行 UI 操作时,就创建一个 Message 对象,并通过 Handle 将这条消息发送出去。之后这条信息会被添加到 MessageQueue 的队列中等待被处理,而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息,最后分发回 Handle 的 handleMessage() 方法中。由于 Handle 是在主线程中创建的,所以此时handleMessage() 方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行 UI 操作了。

2012773427f1704bddebca6.webp

一条 Message 经过这样一个流程的辗转调用后,也就是从子线程进入到主线程,从不能更新 UI 变成了可以更新 UI,整个异步消息处理机制的核心思想也就是如此了。

3. 补充

3.1 Handler 用于两个子线程通信

很简单啊,在一个线程创建 Handler,另外一个线程通过持有该 Handler 的引用调用 sendMessage 发送消息啊!

 1private Handler handler;
 2private void handlerDemoByTwoWorkThread() {
 3    Thread hanMeiMeiThread = new Thread() {
 4        @Override
 5        public void run() {
 6            Looper.prepare();
 7            handler = new Handler() {
 8                @Override
 9                public void handleMessage(Message msg) {
10                    Log.d(TAG, "hanMeiMei receiver message: " + ((String) msg.obj));
11                    Toast.makeText(MainActivity.this, ((String) msg.obj), Toast.LENGTH_SHORT).show();
12                }
13            };
14            Looper.loop();
15        }
16    };
17    Thread liLeiThread = new Thread() {
18        @Override
19        public void run() {
20            try {
21                Thread.sleep(2000);
22            } catch (InterruptedException e) {
23                e.printStackTrace();
24            }
25            Message message = handler.obtainMessage();
26            message.obj = "Hi MeiMei";
27            handler.sendMessage(message);
28        }
29    };
30    hanMeiMeiThread.setName("韩梅梅 Thread");
31    hanMeiMeiThread.start();
32    liLeiThread.setName("李雷 Thread");
33    liLeiThread.start();
34
35    /*
36    * 搞定,我们创建了两个Thread,liLeiThread和hanMeiMeiThread两个线程,很熟悉的名字啊!
37    * 跟之前的代码没太大区别hanMeiMeiThread创建了Handler,liLeiThread通过Handler发送了消息。
38    * 只不过此处我们只发送一个消息,所以没有使用what来进行标记
39    */
40}
  • 在非主线程使用 handle 要在创建前调用 Looper.prepare(),创建后调用 Looper.loop()
  • 主线程已经帮我们创建好了 Looper,所以无需手动创建
  • 在使用 handler 的时候,在 handler 所创建的线程需要维护一个唯一的 Looper 对象, 每个线程对应一个 Looper,每个线程的 Looper 通过 ThreadLocal 来保证
  • Looper 对象的内部又维护有唯一的一个 MessageQueue,所以一个线程可以有多个 handler,
    但是只能有一个 Looper 和一个 MessageQueue。
  • Message 在 MessageQueue 不是通过一个列表来存储的,而是将传入的 Message 存入到了上一个
    Message 的 next 中,在取出的时候通过顶部的 Message 就能按放入的顺序依次取出 Message。
  • Looper 对象通过 loop() 方法开启了一个死循环,不断地从 looper 内的 MessageQueue 中取出 Message,
    然后通过 handler 将消息分发传回 handler 所在的线程。

3.2 Handler 内存泄露问题

为什么会出现内存泄漏问题呢?
首先 Handler 使用是用来进行线程间通信的,所以新开启的线程是会持有 Handler 引用的,
如果在 Activity 等中创建 Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。

首先,非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该 Handler,线程没有被销毁,则意味着 Activity 会一直被 Handler 持有引用而无法导致回收。
同时,MessageQueue 中如果存在未处理完的 Message,Message 的 target 也是对 Activity 等的持有引用,也会造成内存泄漏。

解决办法

(1)使用静态内部类+弱引用的方式:

静态内部类不会持有外部类的的引用,当需要引用外部类相关操作时,可以通过弱引用还获取到外部类相关操作,弱引用是不会造成对象该回收回收不掉的问题,不清楚的可以查阅 JAVA 的几种引用方式的详细说明。

 1private Handler sHandler = new TestHandler(this);
 2
 3static class TestHandler extends Handler {
 4    private WeakReference<Activity> mActivity;
 5    TestHandler(Activity activity) {
 6        mActivity = new WeakReference<>(activity);
 7    }
 8
 9    @Override
10    public void handleMessage(Message msg) {
11        super.handleMessage(msg);
12        Activity activity = mActivity.get();
13        if (activity != null) {
14            //TODO:
15        }
16    }
17}

(2) 在外部类对象被销毁时,将 MessageQueue 中的消息清空。例如,在 Activity 的 onDestroy 时将消息清空。

1@Override
2protected void onDestroy() {
3    handler.removeCallbacksAndMessages(null);
4    super.onDestroy();
5}

3.3 Message 的创建方式

创建 Message 对象的时候,有三种方式,分别为:

  1. Message msg = new Message();
  2. Message msg1 = Message.obtain();
  3. Message msg2 = handler.obtainMessage();

我们一般使用第三种方式,第三种实质上调用的是第二种,相对于第一种方式来说,可以避免重复创建 Message 对象,节约内存。

评论
发表评论
       
       
取消