Android开发者必会之 Handler

249 阅读6分钟

1.Handler的用法

让我们从一段经典而又优美的代码开始

package com.example.handler;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            startActivity(new Intent(MainActivity.this,SecondActivity.class));
            return false;
        }
    });
    private TextView mTv;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv = findViewById(R.id.tv);
        newThreadChange();
        testLeakThread();
    }
    
    private void newThreadChange(){
        new Thread(){
            @Override
            public void run() {
                mTv.setText("changed");
            }
        }.start();
    }
    
    private void testLeakThread(){
        new Thread(){
            @Override
            public void run() {
                Message message = Message.obtain();
                if (mHandler!=null){
                    mHandler.sendMessageDelayed(message,5000);
                }
            }
        }.start();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mHandler!=null){
            mHandler.removeCallbacksAndMessages(null);
            mHandler = null;
        }
    }
}



上面的代码运行并不会报错,虽然看起来肯定是错的(在子线程中操作UI),
这个里面有三个需要注意的点,可能和你写的不一样;
    1.Handler的声明方式:是通过新建了new Handler.Callback()来
    用来调用handleMessage,来处理message,并更新UI;
    2.Handler的使用和销毁message的方式:使用的时候要对Handler判空,
    在声明周期是onDestroy的时候,要把消息清空,并把Handler赋值为空,
    这么做的原因是要防止activity被销毁的时候,消息仍在处理,从而引
    起的内存泄漏;
    3.还有一个地方,你会发现在子线程操作UI也是可以运行的,而且
    正常更新成功,这是为什么呢?(下个博客会具体分析)

2.Handler源码阅读

1.sendMessageAtTime

我们都知道Handler是通过发送message来进行线程之间的通讯,所以Handler中有sendMessageAtTime(Message msg, long uptimeMillis),这个方法,而且读完代码之后你回发现所有的发送消息的方法最终都会走到这里,Handler中的post(Runnable r), 也会将Runnable封装成Message,然后再调用sendMessageAtTime,

This is BiBi, and next is the recource code.


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

//这里把Runnable封装成message,
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

//这里可以发现,最终message都是通过sendMessageAtTime,发送出去的。
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
    

2.sendMessageAtTime

我们继续看下sendMessageAtTime里面做了什么


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);
}

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

这里可以看到,MessageQueue==null 的时候,会抛出异常,我们在这里追踪一下,MessageQueue什么时候赋值的,


//这里是Handler的构造方法,在获取Looper之后,通过Looper获取的MessageQueue:mQueue
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;
}

//Looper也是在构造方法中赋值了MessageQueue:mQueue,
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

//注意:[ new Looper()这个操作是在Looper的perpare()中做的,下面会说到 ]
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
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

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

这里我们知道ThreadLocal这个数据结构是的key是线程Thread类型,value就是其泛型;
在这里value也就是Looper,map这个数据结构我们都知道,key相同的情况下,value是唯一的;
所以这里也就是一个线程中只能有一个Looper的原因;
而上面代码中我们还看到MessageQueue:mQueue是在Looper的构造器中赋值的;
所以很明显,一个线程中也只能有一个MessageQueue

3.有关Looper.prepare()

  • 这里我们已经找到MessageQueue:mQueue是什么时候赋值的了;
  • 继续提问:但是为什么MessageQueue:mQueue他会为null呢,我们这里继续分析;
  • 我们知道new Looper()之后(也就是Looper.prepare()),MessageQueue:mQueue就不为null了;
  • 反过来不初始化Looper的时候直接sendMessage,这个时候MessageQueue:mQueue就为空了,就会抛出异常了;

最终结论: 也就是说在某个线程中,如果没有对Looper进行prepare()操作,直接就sendMessage,就会抛出异常;

  • 有的同学可能提问,为什么在上面第一段代码(也就是MainActivity代码)中,没有调用Looper.prepare(),甚至连Looper的影子都没见到,也没有抛异常呢(如果你试着运行了的话),
  • 我只能说你这个问题问的好,(这是我自己对自己问的,当然问的好咯)

我们可以看Handler构造器的那段代码,也就是下面这个,

mLooper = Looper.myLooper();

然后继续点进去,

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
  • 也就是说在new Handler(...)的时候还进行着这些操作,那new Handler(...)是哪个线程里面的呢;
  • 毫无疑问,主线程;

在Looper中存在这个字段,sMainLooper

//我最喜欢的事:原封不动的copy
private static Looper sMainLooper;  // guarded by Looper.class

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

我们发现了对主线程有个特殊的处理,并发现prepareMainLooper()在很早之前(Android的Sdk里面)就已经调用了,ActivityThread.main(String[] args)

//来自ActivityThread

public static void main(String[] args) {
    ...
    ...

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
  • 写过java代码的人都知道,main函数意味着什么,
  • 也就是说主线程MainThread是系统罩着的,用不着我们管;
  • 那我们如果想在非主线程,new Handler(),然后sendMessage(),我们就要在线程最开始的地方,Looper.prepare();接着进行Looper.loop()
  • 这里还提供给您一个类,HandlerThread,供您闲来无事,观赏一下;这个里面的run()里面的步骤就是上一步的流程

4.对于Looper.loop()

这个看起来看简单,把log和注释去掉之后就是下面这几行代码


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;

    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next();
        if (msg == null) {
            return;
        }
        msg.target.dispatchMessage(msg);
   
        final long newIdent = Binder.clearCallingIdentity();
        msg.recycleUnchecked();
    }
}

  • 1.获取当前线程的Looper;
  • 2.获取Looper里面的MessageQueue;
  • 3.从队列中取Message:msg;
  • 4.并通过msg.target.dispatchMessage做相应的处理;
  • 5.直到队列中消息被取完了,循环退出,loop()结束;
前四步没有任何问题。第五步,我就有个疑问了 ; loop()真的会结束吗?

仔细思考一下,
如果loop()结束循环,也就意味着APP代码肯定会执行完,程序就直接退出了呀
                                        ---细思极恐(来自UC震惊部)

但是你的APP并不会自动退出,所以loop()肯定不会执行结束,

再次揭秘:原来秘密在queue.next();里面

据牛人解释(别人的博客里)
MessageQueue.next()的确会一直从队列中取Message;
但是当MessageQueue中的Message被取空了之后,并不会直接返回一个null;
而是会让CPU处于等待阶段,这个阶段系统不会ANR,也不执行这个线程中下面的代码;
等待阶段并不是CPU不工作了,而是CPU取处理其他事情(处理其他线程中的代码);
直到MessageQueue中又入队了新的Message,该线程处于重新运行状态;
然后CPU继续开始轮训这个线程中的代码;
也就是继续从MessageQueue中取Message,继续处理Message中的消息