Handler原理与HandlerThread

本文主要介绍Handle的原理、如何处理Handler可能导致的内存泄漏问题,需要深入理解Handler的机制,当我们在创建Handler的时候,要明白现在用的是哪个Looper?其他线程如何使用Looper进行通信,释放Looper的区别,以及HandlerThread的原理与用法等等内容。

Handler 使用方式

假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。于是在Android中采用单线程模型处理 UI 操作,通过 Handler 切换到 UI 线程,解决子线程中无法访问 UI 的问题。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.main_tv);
        new Thread(()-> {
            textView.setText("");
        }).start();
    }
}

上面的代码会抛出CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

方式一 post(Runnable)

创建一个 Handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作。

public class MainActivity extends AppCompatActivity {

    Handler handler = new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.main_tv);
        new Thread(()-> {
            handler.post(()-> {
                textView.setText("");
            });
        }).start();
    }
}

方式二 sendMessage(Message)

public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_CONTENT_CODE = 101;
    
    Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case UPDATE_CONTENT_CODE:
                    textView.setText("");
                    break;
                default:
                    break;
            }
        }
    };
    
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.main_tv);
        new Thread(()-> {
            Message msg = Message.obtain();
            msg.what = UPDATE_CONTENT_CODE;
//            handler.sendEmptyMessage(UPDATE_CONTENT_CODE);
            handler.sendMessage(msg);
        }).start();
    }
}

Handler 存在的问题

当我们直接写出如下代码时:

Handler handler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
};

AndroidStudio会提示我们:This Handler class should be static or leaks might occur (anonymous android.os.Handler)即可能会发生内存泄漏问题!

此时就相当于这个Handler是一个内部类,非static内部类或者匿名内部类都会持有外部类的引用(textView)。如果此时Activty退出了, handler持有他的引用,则这个Activity 并不会被销毁,其实还是在内存中,所以就造成了内存泄漏 (Memory Leak) 的问题。

可以使用静态的内部类 + 虚引用可以解决这个问题:

public class MainActivity extends AppCompatActivity {
	private static final int UPDATE_CONTENT_CODE = 101;

    private TextView textView;

    private final Handler handler = new MyHandler(this);
	// 设置为静态内部类
    static class MyHandler extends Handler {
        
        private final WeakReference<MainActivity> mActivityWR;
		// 在构造方法中传入需持有的Activity实例
        public MyHandler(MainActivity activity) {
            // 使用WeakReference弱引用持有Activity实例
            mActivityWR = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            MainActivity activity = mActivityWR.get();
            if(activity != null) {
                switch (msg.what) {
                    case UPDATE_CONTENT_CODE:
                        activity.textView.setText("");
                        break;
                    default:
                        break;
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.main_tv);
        new Thread(()-> {
            Message msg = Message.obtain();
            msg.what = UPDATE_CONTENT_CODE;
            handler.sendMessage(msg);
        }).start();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 清除子线程提交的任务
        handler.removeCallbacks(postRunnable);
        // 清除对应的消息
        handler.removeMessages(UPDATE_CONTENT_CODE);
    }
}

另外还有一种解决方式:

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

这种方式会导致外部类Activity生命周期结束时消息队列中的所有消息被清空,所以更推荐使用静态内部类 + 弱引用的方式。

为什么这两种方式可以解决呢?先来看看Handler 通信机制吧:

Handler 通信机制

1、创建Handler,并采用当前线程的Looper创建消息循环系统;

2、Handler通过sendMessage(Message)或Post(Runnable)发送消息,调用enqueueMessage把消息插入到消息链表中;

3、Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage里执行。

1、Handler 发送消息

handler.sendMessage(msg);

调用链如下:sendMessage -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage -> queue.enqueueMessage。其实就是Handler的MessageQueue插入了一个新的的Message对象。

对于postRunnable而言,通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。

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

2、消息入队列

在enqueueMessage中,通过MessageQueue入队列,并为该message的target赋值为当前的handler对象,记住msg.target很重要,之后Looper取出该消息时,还需要由msg.target.dispatchMessage回调到该handler中处理消息:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

在MessageQueue中,由Message的消息链表进行入队列。

3、Looper 处理消息

再说处理消息之前,先看Looper是如何构建与获取的:

1、构造Looper时,构建消息循环队列,并获取当前线程

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

但该函数是私有的,外界不能直接构造一个Looper,而是通过Looper.prepare来构造的:

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,并把Looper对象保存在sThreadLocal中,那sThreadLocal是什么呢?

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

它是一个保存Looper的TheadLocal实例,而ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象,这样Handler就可以通过ThreadLocal来保存于获取Looper对象了。

那么Looper在loop中如何处理消息呢?

在loop中,一个循环,通过next取出MessageQueue中的消息,若取出的消息为null,则结束循环,返回。设置消息为空,可以通过MessageQueue的quit和quitSafely方法通知消息队列退出。若取出的消息不为空,则通过msg.target.dispatchMessage回调到handler中去。

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
            Long.toHexString(ident) + " to 0x"
            Long.toHexString(newIdent) + " while dispatching to "
            msg.target.getClass().getName() + " "
            msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

4、handler处理消息

Looper把消息回调到handler的dispatchMessage中进行消息处理:

若该消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递runnable时,把runnable对象赋值给了message的callback。

若handler的mCallback不为空,则交由通过callback创建handler方式去处理。

否则,由最常见创建handler对象的方式,在重写handlerMessage中处理。

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

Handler 进行子线程通信

public class OtherActivity extends AppCompatActivity {
    private static final String TAG = "OtherActivity";
    Handler handler1;
    Handler handler2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);

        new ThreadA().start();
        new ThreadB().start();
    }

    class ThreadA extends Thread {
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            handler1 = new Handler(Looper.myLooper()){
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
                            + " msg.what = " + msg.what);
                }
            };
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(handler2 != null) handler2.sendEmptyMessage(2) ;
            Looper.loop();
        }
    }

    class ThreadB extends Thread {
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            handler2 = new Handler(Looper.myLooper()){
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
                            + " msg.what = " + msg.what);
                }
            };
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(handler1 != null) handler1.sendEmptyMessage(5) ;
            Looper.loop();
        }
    }
}

1、调用Looper类的 prepare() 方法可以为当前线程创建一个消息循环,调用loop() 方法使之处理信息,直到循环结束。

2、Handler有几个构造重载,如果构造时不提供Looper类对象参数,默认会获取当前线程的Looper对象,即将当前线程的消息循环作为Handler关联的消息循环。

3、消息处理机制中,消息存放在一个消息队列中,而线程围绕这个队列进入一个无限循环,直到程序退出。

如果队列中有消息,线程就会把消息取出来,并分发给相应的Handler进行处理;

如果队列中没有消息,线程就会进入空闲等待状态,等待下一个消息的到来;

HandlerThread 本质

在上面利用Handler完成两个线程通信的代码中,需要调用Looper.prepare() 为一个线程开启一个消息循环,默认情况下Android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。) Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。 然后通过Looper.loop() 让Looper开始工作,从消息队列里取消息,处理消息。

所以要使用Handler完成线程之间的通信,首先需要调用Looper.prepare() 为该线程开启消息循环,然后创建Handle,然后调用 Looper.loop() 开始工作。这都是很常规的流程,所以:

HandlerThread 已经帮我们做好了这些事情,HandlerThread 本质上就是一个普通Thread,只不过内部建立了Looper。

HandlerThread 用法

public class OtherActivity extends AppCompatActivity {
    private static final String TAG = "OtherActivity";
    private Handler handler1;
    private Handler handler2;
    private HandlerThread handlerThread1;
    private HandlerThread handlerThread2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);

        // 创建HandlerThread
        handlerThread1 = new HandlerThread("handle-thread-1");
        handlerThread2 = new HandlerThread("handle-thread-2");
        // 开启HandleThread
        handlerThread1.start();
        handlerThread2.start();

        handler1 = new Handler(handlerThread1.getLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
                            + " msg.what = " + msg.what);
            }
        };
        
        handler2 = new Handler(handlerThread2.getLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
                        + " msg.what = " + msg.what);
            }
        };

        handler2.sendEmptyMessage(2);
        handler1.sendEmptyMessage(5);
    }
    
    // 释放资源
    @Override
    protected void onDestroy() {
        super.onDestroy();
        handlerThread1.quit();
        handlerThread2.quitSafely();
    }
}

创建 Looper 并执行 loop() 的线程在任务结束的时候,需要手动调用 quit。反之,线程将由于 loop() 的轮询一直处于可运行状态,CPU 资源无法释放。更有可能因为 Thread 作为 GC Root 持有超出生命周期的实例引发内存泄漏。

SDK 推荐使用 quitSafely() 去终止 Looper,原因是其只会剔除执行时刻晚于当前调用时刻的 Message。这样可以保证 quitSafely 调用的那刻,满足执行时间条件的 Message 继续保留在队列中,在都执行完毕才退出轮询。

那么主线程需要 quit 吗?其实不需要,在内存不足的时候 App 由 AMS 直接回收进程。因为主线程极为重要,承载着 ContentProvider、Activity、Service 等组件生命周期的管理,即便某个组件结束了,它仍有继续存在去调度其他组件的必要! 换言之,ActivityThread 的作用域超过了这些组件,不该由这些组件去处理它的结束。比如,Activity destroy 了,ActivityThread 仍然要处理其他 Activity 或 Service 等组件的事务,不能结束。

Handler 在系统中的身影

其实Android本身就是一个巨大的消息处理机,ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。APP进程中UI事件的执行代码段都是由ActivityThread提供的。也就是说,主线程实例是存在的,只是创建它的代码我们不可见。ActivityThread的main函数就是在这个主线程里被执行的。

public final class ActivityThread {

    //...
    private static ActivityThread sCurrentActivityThread;
    public static ActivityThread currentActivityThread() {
        return sCurrentActivityThread;
    }
    private void attach(boolean system) {
         sCurrentActivityThread = this;
         //...
    }
   public static void main(String[] args) {
        //....

        // 创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        // 创建ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        // 建立Binder通道 (创建新线程)
        thread.attach(false);

        Looper.loop(); //消息循环运行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施即可。