Android Handler 机制总结

写 Handler 原理的文章很多,就不重复写了,写不出啥新花样。这篇文章的主要是对 handler 原理的总结。

1、Android消息机制是什么?

Android消息机制 主要指 Handler 的运行机制以及 Handler 所附带的 MessageQueue 和 Looper 的工作流程。Handler 的主要作用是将任务切换到指定线程去执行,我们常用的就是通过 Handler 来异步更新UI(线程间的信息传递)。

2、Handler 使用方法

1、创建一个 Handler 实例用来发送接收并处理消息

        Handler myHandler = new Handler() {
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case TestHandler.GUIUPDATEIDENTIFIER:
                        myBounceView.invalidate();
                        break;
                }
                super.handleMessage(msg);
            }
        };

        myHandler.post(run);
        myHandler.sendEmptyMessage(TestHandler.GUIUPDATEIDENTIFIER);
        myHandler.sendMessage(myHandler.obtainMessage());

上面最后三行都是发送消息,你也可以在其他线程发消息,然后在主线程接收,处理 UI 逻辑;

2、如果你需要在某个线程来处理消息,那也是可以的:

   new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                mLooper = Looper.myLooper();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Log.d(TAG, "handleMessage: " + msg.what);
                    }
                };
                Looper.loop();
            }
        }).start();

这里 Looper 让该线程一直在运行,从而通过消息循环机制获取从其他线程发来的消息,传给 Handler 来处理。

如下,你在下面的线程中发送一个消息,上面的 Handler 就会收到:

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (mLooper != null) {
                    mHandler.sendEmptyMessage(123456);
                    Log.d(TAG, "sendMessage: " + 123456);
                }
            }
        }).start();

这样似乎也可以达到两个线程互相通信的效果。

3、Handler 不正确使用引发的内存泄露

 容易造成内存泄漏的一种Handler使用方法:将 Handler 声明为 Activity 的内部类。在Java语言中,非静态内部类会持有外部类的一个隐试引用,这样就可能造成外部类无法被垃圾回收。而导致内存泄漏。

那么正确的使用就是:

  • 将 Handler 声明为静态内部类。并持有外部类的若引用。
  • 在子线程中使用 Handler,这是需要我们自己创建一个 Looper 对象。

具体可参考文章:Android Handler正确使用姿势

其中该文的第二种方法贴下,我也是第一次看到:

将自定义Handler抽出去,也同样达到效果的小栗子:

首先创建一个类,通过泛型将实例传入

public class UIHandler<T> extends Handler {
    protected WeakReference<T> ref;
    public UIHandler(T cls){
        ref = new WeakReference<T>(cls);
    }
 
    public T getRef(){
        return ref != null ? ref.get() : null;
    }
}

看下 activity 中使用,直接用 myHandler 对象发送 message 即可。

private static class UIMyHandler extends UIHandler<HandlerActivity>{
 
        public UIMyHandler(HandlerActivity cls) {
            super(cls);
        }
 
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            HandlerActivity activity = ref.get();
            if (activity != null){
                if (activity.isFinishing())
                    return;
                switch (msg.what){
 
                }
            }
        }
    }
 
    private UIMyHandler myHandler = new UIMyHandler(this);

其实两种写法大同小异,只是觉得第二种方式思想更好,这种思想值得每一个人学习。

4、Handler 原理简析

它在使用的过程中主要与 Messgae、MessageQueue、和 Looper 这三个对象关联密切,Handler 机制的实现原理依赖于这三者。下面就来简单讲讲这三者和 Handler 之间的关系。

4.1 Handler :消息发送者,处理者,

几个常见的构造方法,分别是:

  • Handler() 默认构造方法,与当前线程及其Looper实例绑定。如在主线程中执行new Handler(),那么该 handler 实例所绑定的便是 UI 线程和 UI 线程绑定的 Looper 实例。
  • Handler(Handler.Callback callback) 与当前线程及其 Looper 实例绑定,同时调用一个 callback 接口(用于实现消息处理——即在 callback 中重写 handleMessage() 方法)
  • Handler(Looper looper) 将该新建的 handler 实例与指定的 looper 对象绑定。
  • Handler(Looper looper, Handler.Callback callback) 指定该 handler 实例所绑定的 looper 实例并使用给定的回调接口进行消息处理。

4.2 Message 信息传播的载体

extends Object implements Parcelable

一个 message 对象包含一个自身的描述信息和一个可以发给 handler 的任意数据对象。这个对象包含了两个int 类型的extra 字段和一个 object 类型的 extra 字段。利用它们,在很多情况下我们都不需要自己做内存分配工作。 

虽然 Message 的构造方法是 public 的,但实例化 Message 的最好方法是调用 Message.obtain() 或 Handler.obtainMessage()(实际上最终调用的仍然是 Message.obtain() ,因为这两个方法是从一个可回收利用的 message 对象回收池中获取Message实例。该回收池用于将每次交给 handler 处理的 message 对象进行回收。 

4.3 MessageQueue 信息传递的队列

extends Object

MessageQueue 是用来存放 Message 的集合,并由 Looper 实例来分发里面的 Message 对象。同时,message 并不是直接加入到 MessageQueue 中的, 而是通过与 Looper 对象相关联的 MessageQueue.IdleHandler 对象来完成的。我们可以通过 Looper.myQueue() 方法来获得当前线程的MessageQueue。 

PS: MessageQueue 是由 Looper 来管理的,当你创建。

  public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

4.4 Looper 消息处理的动力来源

extends Object

Looper是线程用来运行消息循环(message loop)的类。默认情况下,线程并没有与之关联的Looper,可以通过在线程中调用 Looper.prepare() 方法来获取,并通过 Looper.loop() 无限循环地获取并分发 MessageQueue 中的消息,直到所有消息全部处理。

 

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

sThreadLocal 对象是一种特殊的全局性变量,它的全局性仅限于自己所在的线程,而外界所有线程一概不能访问到它,因此,每个线程的 Looper 是独立的,这也是如果我们想在一个线程中处理消息,必须先调用 Looper.prepare() ,是为了区别于线程来保存数据。

 /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

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

可以发现,当调用 Looper.prepare() 会重新创建一个新的 Looper,这也说明了确实每个线程一个 Looper。

当我们想要在其他线程传递数据到主线程时,一般会先获取主线程分 Looper,原因也是上面说的,每个每个线程一个 Looper。而 Looper 又是MessageQueue 的持有者,只有将消息放入到对应线程的 MessageQueue 中,才能在对应线程中做相应的处理。

这也是 Handler 跨线程处理消息的本质所在。

 

4.5 ThreadLocal 线程数据存储者

上源码:

public class ThreadLocal<T> {
 .....
}

这里可以看出 threadlocal 是一个范型类,这标志着 threadlocal 可以存储所有数据,作为存储数据来说,我们首先想到的是会对外提供set(), get(), remove(),等方法。 

这里可以看下 set 方法,可以发现是先获取 Thread.currentThread;数据和线程是紧密联系在一起的。

/**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据。 可以理解为 hashmap,根据key值(所处的线程)来获取values. 其中 values 里面保存着各种各样的数据。它们对ThreadLocal所做的读写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据。

到此,关于Handler 的内容基本总结完毕。

5、最后附上一张图:

概括:子线程获取主线程的 Looper,然后发送 Message。 Message 被压入主线程 Looper 中的 MessageQueue,Looper 从队列中取出一个消息,交给主线程中的 Handler 来处理。需注意,发消息和处理消息的 Handler 同属于某个 Handler 的实例。

 参考文章

1、android Handler机制之ThreadLocal详解

2、深入理解Android中的Handler机制

3、Android Handler正确使用姿势

猜你喜欢

转载自www.cnblogs.com/huansky/p/9427854.html