Handler 必知必会

概要

Handler 封装了一套消息机制,用来发送和处理消息(Message),而它常常被用来更新 UI。

创建

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback) {
        this(callback, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

    public Handler(boolean async) {
        this(null, async);
    }

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

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

Handler 构造函数最主要的目的就是获取 Looper 对象,要么在参数中提供 Looper,要么就调用 Looper.myLooper() 获取

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

Looper.myLooper() 方法其实是从 ThreadLocal 对象中获取 Looper 对象,也就是说这个 Looper 是与当前线程有关的。 而且从 public Handler(Callback callback, boolean async) {} 构造函数中抛出的异常可以看出,获取到的 Looper 对象不能为空,否则报异常。

那么通常在 Activity 中创建的 Handler 方式如下

Handler handler = new Handler();

这个 handler 获取的就是 main thread 中的 Looper 对象,然而代码并没有报错,说明确实有这个 Looper 对象存在,而这个 Looper 对象是在应用创建的时候,在 ActivityThreadmain() 方法中创建的。

    public static void main(String[] args) {

        Looper.prepareMainLooper();

        Looper.loop();

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

其中 Looper.prepareMainLooper() 就是把 Looper 对象存储到与 main thread 相关的的 Looper.mThreadLocal 对象中的。

而有时候我们会在一个工作线程中创建 Handler 对象,例如

        new Thread(){
            @Override
            public void run() {
                Handler handler = new Handler();
            }
        }.start();

然而,这段代码就会报出刚刚说到的异常,Can't create handler inside thread that has not called Looper.prepare()。 很明显是因为无法从 Looper.mThreadLocal 对象中获取与当前线程相关的 Looper 对象。而从异常信息中可以看出,需要调用 Looper.prepare() 来创建与线程相关的 Looper 对象,然而仅仅这样还不够,还需要调用 Looper.loop() 来轮询消息队列(MessageQueue)来处理消息(Message)。

那么正确的代码如下

        new Thread(){
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
        }.start();

而这段代码只是做到仅仅不报错而已,要在实际中使用还是有些小问题的,这个后面再说。

那么现在总结下,Handler 的构造函数在创建的时候会绑定一个 Looper 对象,如果没有提供 Looper 参数,就是从 Looper.mThreadLocal 中获取与当前线程相关的 Looper 对象。

发送消息

Handler 发送消息有两个方式,一种是 sendMessage() 之类的方法,一种是 post(Runnable) 之类的方法,其实这两种方法最终发送的都是 Message 对象。

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    } 

从代码中可以看到 post(Runnable) 方法最终还是创建的 Message 对象,然后把参数 r 赋值给 message.callback 变量。

Message

Message 类实现了 Parcelable 接口,也就是说可以序列化,那么自然这个类的对象可以用来保存和传输数据。

public final class Message implements Parcelable {
    public int what;

    public int arg1;
    public int arg2;
    public Object obj;

    Message next;
}

成员变量 what 用来辨认消息的类型。

成员变量 arg1, arg2, obj 是用来保存数据的,其实注意下 arg1arg2int 类型,而 objObject,这样就可以保存任意的数据。

从成员变量 Message next 可以看出 Message 可以实现一个单链表,而实际也是如此,但是这个实现是由 MessageQueue 完成的。

处理消息

我们通常用 Handler 来更新UI,发送消息有两个方法,处理消息也有两种方式,首先看 post(Runnable) 之类的方法。

public class TestActivity extends AppCompatActivity {
    private Handler mHandler = new Handler();
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        mTextView = findViewById(R.id.id_tv);
        new Thread(){
            @Override
            public void run() {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mTextView.setText("Update UI");
                    }
                });
            }
        }.start();
    }
}

代码中创建了一个线程,然后在线程中用更新 UI,使用了 Handlerpost(Runnable) 方法,在 run() 方法中更新了 UI。

我们也可以用 HandlersendMessage() 之类的方法

public class TestActivity extends AppCompatActivity {
    private Handler mHandler;
    private TextView mTextView;
    private static final int MSG_UPDATE = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        mTextView = findViewById(R.id.id_tv);
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MSG_UPDATE) {
                    mTextView.setText("Update OK");
                }
            }
        };
        new Thread() {
            @Override
            public void run() {
                mHandler.sendEmptyMessage(MSG_UPDATE);
            }
        }.start();
    }
}

mHandler.sendEmptyMessage(MSG_UPDATE) 发送了一个 Message 对象,而参数 MSG_UPDATE 就是定义 Message 对象的 what 的值。 而在 handleMessage() 方法中,我们识别了这个 what,从而更新 UI。

处理消息原理

前面说过,发送消息有两个方式,而最终都是封装在 Message 对象中。 而这个 Message 对象,会发送给 Looper 对象的 MessageQueue,而 MessageQueue 会让 Message 形成一个链表,也就是这里的队列,主要代码如下

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

        }
        return true;
    }

从实现可以看出,mMessages 是代表队列的头部,而插入的方式是从尾部插入的。

那么现在已经有了消息队列,如何取出消息呢? 这就是 Looper.loop() 方法做的事情,它会无限地循环从 MessageQueue 中取出消息,然后让 Handler 来处理

    public static void loop() {
        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;
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;            
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            msg.recycleUnchecked();
        }
    }

LooperLoop() 方法开启了一个无限循环,从 MessageQueue 中取出 Message 对象,然后交给了 HandlerdispatchMessage() 方法。

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

内存泄露

Handler 相关有一个很著名的问题就是内存泄露问题,通常在 Android Studio 中会提示,其实在构造函数中也有踪迹可寻。

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
    }

从这段代码可以看出,如果 Handler 对象的类不是静态,并且是匿名内部类或内部类或局部类的时候,就会发出警告可以造成内存泄露。

如下代码,Android Studio 就会提示这个警告信息

public class TestActivity extends AppCompatActivity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
    }
}

这个警告信息只是告诉你可能会发生内存泄露,但是并没有说一定会发生。那么什么时候会发生呢?

public class TestActivity extends AppCompatActivity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {

        }
    };
    private static final int MSG_UPDATE = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        mHandler.sendEmptyMessageDelayed(MSG_UPDATE, 1000 * 60);
        finish();
    }
}

Activity 被销毁的时候,例如屏幕旋转,这段代码就会造成内存泄露。原因如下

  1. mHandler 是匿名内部类对象,而匿名内部类默认是会持有外部类的引用,也就是 Activity
  2. Messagetarget 变量指的就是与之相关的 Handler 对象。
  3. finish() 被调用的时候,Activity 销毁,而 Message 还没有被处理,message.target 持有了 mHandler 对象,同时 mHandler 持有了外部 Activity 的引用,这样就导致了 Activity 无法被回收,从而内存泄露。

通过分析原因,我们发现关键点在于,Handler 对象是持有了外部 Activity 引用造成的。那么内部类同样也会持有外部类 Activity 的引用,也可能会造成内存泄露,例如:

public class TestActivity extends AppCompatActivity {
    private Handler mHandler = new LeakHandler();
    private static final int MSG_UPDATE = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        mHandler.sendEmptyMessageDelayed(MSG_UPDATE, 1000 * 60);
        finish();
    }

    private class LeakHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {

        }
    }

}

解决内存泄露问题

既然知道了根本原因是因为 Handler 对象持有了外部类 Activity 的引用,那么就要断绝这层关系,有两个方式

  1. 使用静态的内部类继承 Handler
  2. 单独写一个类

以最常用的静态内部类举例

public class TestActivity extends AppCompatActivity {
    private Handler mHandler = new LeakHandler();
    private static final int MSG_UPDATE = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        mHandler.sendEmptyMessageDelayed(MSG_UPDATE, 1000 * 60);
        finish();
    }

    private static class LeakHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {

        }
    }
}

然而静态内部类并不能调用外部类的非静态方法,而如果我们想调用 Activity 的非静态方法咋办呢? 我们可以创建外部类 Activity 的弱引用

public class TestActivity extends AppCompatActivity {
    private Handler mHandler ;
    private static final int MSG_UPDATE = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        mHandler = new LeakHandler(this);
        mHandler.sendEmptyMessageDelayed(MSG_UPDATE, 1000 * 60);
        finish();
    }

    private void doSth() {

    }

    private static class LeakHandler extends Handler {
        private WeakReference<AppCompatActivity> mActivity;

        public LeakHandler(AppCompatActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            if (mActivity.get() != null) {
                TestActivity activity = (TestActivity) mActivity.get();
                activity.doSth();
            }
        }
    }
}

LeakHandler 通过构造函数通过创建外部类 ActivityWeakReference 对象,从而在 Activity 销毁的时候,可以让 Activity 被回收,这样也就避免了内存泄露问题。

然而,这种方式看起来总感觉有那么点不顺畅,其实解决这个问题还可以从另外一个角度思考,既然是因为消息没有处理完导致的问题,那么我们可以在 Activity 销毁的时候移除这些消息即可。

public class TestActivity extends AppCompatActivity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {

        }
    };
    private static final int MSG_UPDATE = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        mHandler.sendEmptyMessageDelayed(MSG_UPDATE, 1000 * 60);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

然而然而,这该死的 IDE 还是会发出警告,因为它并不知道我们正确的处理了呀,怎么办呢? 我们可以手动传入一个 Looper 对象。

    private Handler mHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {

        }
    };

终于 IDE 没有提示这该死的警告了,但是这并不代表解决了内存泄露问题,还是需要调用 mHandler.removeCallbacksAndMessages(null);

优化

通常我们会发送一个消息,就会先创建一个消息,例如

        Message msg = new Message();
        msg.what = MSG_UPDATE;
        mHandler.sendMessage(msg);

然而,这可能有点浪费资源,我们知道 MessageQueue 是维护了一个消息队列,那么复用 Message 对象就变得尤为重要,而 Message 类提供了一个方法可以用来获取复用的 Message 对象,这个方法是 obtain(), 那么代码可以这样写

        Message msg = Message.obtain();
        msg.what = MSG_UPDATE;
        mHandler.sendMessage(msg);

Handler 也提供了一种方法,来获取可复用的 Message 对象

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

Message 中的实现如下

    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;
        return m;
    }

有点要注意的就是,它把从 Message.obtain() 中获取的 Message 对象的 target 指定了为此 Handler 对象。

那么我们代码也可以这样写

        Message msg = mHandler.obtainMessage();
        msg.what = MSG_UPDATE;
        msg.sendToTarget();

由于 obtainMessage() 指定了 targetmHandler,因此就可以调用 msg.sendToTarget() 方法来发送消息,其实也就是调用了 HandlersendMessage() 方法。

HandlerThread

前面所说的都是在 main thread 中创建 Handler ,它会绑定 main thread 中的 Looper 对象,然后 Handler 会把消息发送给 Looper.messageQueue,然后通过 Looper.loop() 来循环取出消息并处理。

有时候,我们想把消息在工作线程中处理,例如耗时的任务。

public class MyHandlerThread extends Thread {
    private Handler mHandler;

    @Override
    public void run() {
        Looper.prepare();
        mHandler = new Handler(Looper.myLooper()) {
            @Override
            public void handleMessage(Message msg) {

            }
        };
        Looper.loop();
    }

    public Handler getHandler() {
        return mHandler;
    }
}

在线程运行的时候,创建了 Looper 对象,并开启了轮循消息,并且创建了相应的 Handler 对象。 这样就可以通过 getHandler() 方法获取与线程相关的 Handler,并发送消息,例如

public class TestActivity extends AppCompatActivity {

    public static final int MSG_WORK = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        MyHandlerThread thread = new MyHandlerThread();
        thread.start();
        thread.getHandler().sendEmptyMessage(MSG_WORK);
    }

}

然而运行的时候,十有八九会报空指针异常,why? 这是因为多线程并发问题,在 main thread 中要使用另外一个线程的东西,而这个东西此时还没有创建出来。

那么我们可以通过加锁解决这个问题,然而又有一个问题出现在面前,当创建这个线程的 Activity 销毁的时候,我们也需要手动停止这个线程,我们需要让 Looper 停止循环。然而这一切的一切,HandlerThread 类帮我们都解决了。

public class TestActivity extends AppCompatActivity {

    public static final int MSG_WORK = 0x110;

    public Handler mWorkerHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        // 创建线程
        HandlerThread handlerThread = new HandlerThread("worker");
        // 启动线程
        handlerThread.start();
        // 创建与线程 Looper 相关的 Handler
        mWorkerHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MSG_WORK) {
                    Log.d("david", "worker thread: " + Thread.currentThread());
                }
            }
        };
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("david", "main thread: " + Thread.currentThread());
        mWorkerHandler.sendEmptyMessage(MSG_WORK);
    }
}

运行这段代码就可知道,任务是在工作线程中处理的,而非主线程。

利用 HandlerThread 实现线程交互

那么我们在工作线程中处理完了任务,想更新 UI 怎么办呢? 最简单的方式是直接调用 ActivityrunOnUiThread(Runnable) 方法。

        mWorkerHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MSG_WORK) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Log.d("david", "update UI in: " + Thread.currentThread());
                        }
                    });
                }
            }
        };

runOnUiThread() 也是利用的 Handler 原理。

其实我们还可以创建与主线程相关的 Handler 来更新 UI,例如

public class TestActivity extends AppCompatActivity {

    public static final int MSG_WORK = 0x110;
    private static final int MSG_UPDATE = 0x111;
    private Handler mWorkerHandler;
    private Handler mUIHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        // 创建与主线程相关的 Handler
        mUIHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                Log.d("david", "update UI in: " + Thread.currentThread());
            }
        };

        // 创建工作线程
        HandlerThread handlerThread = new HandlerThread("worker");
        // 启动工作线程
        handlerThread.start();
        // 创建与工作线程相关的
        mWorkerHandler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MSG_WORK) {
                    Log.d("david", "do work in: " + Thread.currentThread());
                    // 向主线程相关的 Handler 发送消息更新 UI
                    mUIHandler.sendEmptyMessage(MSG_UPDATE);
                }
            }
        };
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 向工作线程发送消息来处理耗时任务
        mWorkerHandler.sendEmptyMessage(MSG_WORK);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 防止可能发生的内存泄露问题
        mWorkerHandler.removeCallbacksAndMessages(null);
        mUIHandler.removeCallbacksAndMessages(null);
    }    
}

思考

Google 为何要强制用 Handler 机制来更新 UI ? 如果没有 Handler 机制,多线程更新 UI 会导致并发问题,如果采用加锁,又会消耗性能。 Hanlder 机制能够对消息进行调度,在恰当的时候更新 UI,而又不会损耗性能,实则一举两得。

总结

通过学习本文,可以知道的事情如下:

  1. 粗略了解 Hanlder 的原理
  2. 如何使用 Handler
  3. 如何有效复用 Message 对象
  4. 如何实现线程交互。
  5. 如何解决 Handler内存泄露问题
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值