Android系统源码分析:Handler源码分析及使用细节

博客主页java

1. Handler源码分析(API 29)

在分析Handler源码以前,咱们先来看下下面这条异常android

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6094)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:824)
        at android.view.View.requestLayout(View.java:16431)

做为Android开发人员,这样异常的信息应该并不陌生,产生的缘由就是在子线程操做UI控件了。那么为何在子线程操做UI控件,就会抛出异常呢?segmentfault

咱们再来看另外一条异常信息安全

java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
        at android.widget.Toast$TN.<init>(Toast.java:435)
        at android.widget.Toast.<init>(Toast.java:150)
        at android.widget.Toast.makeText(Toast.java:313)
        at android.widget.Toast.makeText(Toast.java:303)

抛出这条异常信息是由于在子线程中弹toast致使的。为何跟第一条产生的异常信息不同呢?多线程

让咱们带着疑问开始探索源码之旅吧~~~并发

一、其实第一条异常信息咱们都知道是在哪里抛出的,跟View的绘制机制有关,也就是为何不容许在子线程中操做UI控件?
这是由于Android的UI控件不是线程安全的,若是在多线程中并发访问可能会致使UI控件不可预期的状态,那么为何系统不对UI控件的访问加上锁机制呢?app

  1. 首先加上锁机制会让UI访问的逻辑变得复杂
  2. 锁机制会下降UI访问效率,由于锁机制会阻塞某些线程的执行

因此最简单且高效的方法就是采用单线程模型来处理UI操做,那么Android中子线程必定不能更新UI控件吗?async

其实Android系统在更新UI控件时,会调用ViewRootImpl类的checkThread()来检测当前线程是不是建立UI控件的线程,若是不在同一个线程就会抛出异常。ide

// ViewRootImpl.java
    public ViewRootImpl(Context context, Display display) {
       
        mThread = Thread.currentThread();
        // ... 省略无关代码
    }

    @Override
    public void requestLayout() {
        checkThread();
        // ... 
    }

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

二、只要咱们执行下面这段代码就会抛出第二条崩溃日志oop

new Thread(new Runnable() {
    @Override
    public void run() {
         Toast.makeText(MainActivity.this, "handler", Toast.LENGTH_LONG).show();
    }
}).start();
// 抛出:java.lang.RuntimeException: Can't toast on a thread that has not called

看到这里不少人感到奇怪,子线程中更新UI控件应该是第一条崩溃日志啊。

来看下Toast源码:

Toast.java

    public Toast(@NonNull Context context, @Nullable Looper looper) {
        // 建立TN
        mTN = new TN(context.getPackageName(), looper);
       // ...
    }

    private static class TN extends ITransientNotification.Stub {
      
        final Handler mHandler;

        TN(String packageName, @Nullable Looper looper) {
           // ...
            // 看到这里,我相信你们知道为何缘由了。
            if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    throw new RuntimeException(
                            "Can't toast on a thread that has not called Looper.prepare()");
                }
            }
            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                   
                }
            };
        }
    }

阅读Toast源码后发现,弹toast时须要获取当前线程的Looper,若是当前线程没有Looper,就会抛出异常。看到这里会有一个疑问,为何在main线程中弹toast不会报错呢?

咱们能够猜测,既然在main线程没有报错,那么确定main线程中已经建立过Looper对象。是谁建立的呢?

在Android系统中,App启动入口是在ActivityThread类的main方法。

ActivityThread.java

    public static void main(String[] args) {
        
        // 建立Looper对象和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        // 建立ActivityThread对象
        ActivityThread thread = new ActivityThread();
        // 建立Binder通道
        thread.attach(false, startSeq);
        
        // 主线程的Handler
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop(); // 消息循环运行

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

阅读ActivityThread源码后,咱们知道main线程的Looper在App启动时就帮咱们建立了,因此咱们能够直接在main线程中弹toast了。 Binder 线程会向 H(就是main线程的Handler) 发送消息,H 收到消息后处理Activity的生命周期,因此在主线程中能够直接更新UI控件了。

主线程的消息循环模型:
ActivityThread 经过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通讯的方式完成 ActivityThread 的请求后会回调 ApplicationThread 中的 Binder 方法,而后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中去执行。

1.1 Looper 、Thread之间的关系

先来看这段代码,在子线程中建立Handler,运行后抛出异常了

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

出现崩溃日志以下,提示说明:调用线程中没有调用过Looper.prepare(),就不能建立Handler

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)

为何Looper.prepare()会影响Handler的建立呢?走进Handler的源码分析:

//Handler.java

    final Looper mLooper;
    final MessageQueue mQueue;

    public Handler(@Nullable Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
       // ...
    }

---------------------------------
// Looper.java
    // 返回与调用线程相关联的Looper对象
    // 若是调用的线程没有关联Looper(也就是没调用过Looper.prepare())返回null
    public static @Nullable Looper myLooper() {
        // Looper对象怎么与调用线程关联?设计到ThreadLocal知识
        return sThreadLocal.get();
    }

从Handler构造方法中可知:在建立Handler对象时,会检查调用线程中是否有Looper关联,若是没有就抛出异常。

1.2 Handler、Looper、MessageQueue、Message之间的关系

Looper源码:

//Looper.java

   final MessageQueue mQueue;

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

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

    // 在ActivityThread中的main方法调用
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

Looper类中主要做用:

  1. 提供prepare方法,关联调用线程与Looper对象
  2. 主线程经过prepareMainLooper方法关联Looper,能够经过Looper.getMainLooper()获取主线程Looper
  3. Looper对象与MessageQueue对象创建关系,一个Looper对象对用一个MessageQueue对象

其它的线程怎么拥有本身的Looper呢?Android系统为咱们提供了一个很是方便的类:HandlerThread

// HandlerThread.java

public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        onLooperPrepared();
        Looper.loop();
    }
}

HandlerThread继承Thread类,当Thread.start()线程,调用Looper.prepare()后,就会建立一个Looper与这个Thread关联。

Handler、Looper、MessageQueue、Message之间的关系:一个Thread持有一个Looper,一个Looper持有一个MessageQueue和多个与之关联的Handler,一个Handler持有一个Looper,一个MessageQueue维护Message单项链表。

1.3 Handler工做机制

用一张图来描述Handler的工做机制

简单描述上图:使用Handler发送消息到MessageQueue,Looper不断轮询MessageQueue中消息,分发消息到Handler中处理。

Looper的loop()源码:

// Looper.java
    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;
        // ...
        for (;;) {
           // 堵塞调用,从MessageQueue中获取消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
           // ...
           // 消息的分发及处理
           msg.target.dispatchMessage(msg);
           // ...
        }
    }

Looper不断从MessageQueue中轮询到消息后,分发给Handler处理。

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

分发消息Message处理的顺序:

  1. 若是Message中设置了callback (Runnable),执行回调后直接返回
  2. 若是Handler设置了mCallback (Callback),执行回调;若是处理结果返回true,直接返回,不然执行Handler的handleMessage方法
  3. 若是Message和Handler都没有设置callback,执行Handler的handleMessage方法

总结:若是想拦截Handler中callback或者Handler中handleMessage方法,能够给Message设置callback,这样Handler中Message处理就不会被执行。

接下来分析关键的MessageQueue中的next():

Message next() {
        // ...
        int nextPollTimeoutMillis = 0;
        for (;;) {
            // 阻塞在native层
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // msg定义的when尚未到,让native继续等nextPollTimeoutMillis时长
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 有到点的msg,返回给Looper的loop处理
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        return msg;
                    }
                } else {
                    // 没有消息,在native层阻塞
                    nextPollTimeoutMillis = -1;
                }

                // 若是调用了quit(),Looper的loop()就会退出无限循环
                if (mQuitting) {
                    dispose();
                    return null;
                }
            }

            // ...
          
            // 当在处理idle handler的时,能够发送一个新的Message
            // nextPollTimeoutMillis设置为0,当即查询挂起的消息,无需等待
            nextPollTimeoutMillis = 0;
        }
    }

1.4 源码分析后的问题思考

一、 Looper.loop() 死循环为何不会致使应用卡死?
这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,经过往 pipe 管道写端写入数据来唤醒主线程工做。这里采用的 epoll 机制,是一种IO多路复用机制,能够同时监控多个描述符,当某个描述符就绪(读或写就绪),则马上通知相应程序进行读或写操做,本质同步I/O,即读写是阻塞的。 因此说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

2.Handler的使用细节

2.1 子线程更新UI的方法

一、直接在main线程建立Handler,那么Looper就会与main线程绑定,子线程就能够经过该Handler更新UI

// 使用方式
    private static Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

二、用Activity的runOnUiThread方法,判断调用线程是不是main线程,若是不是,使用Handler发送到main线程。原理也是使用Handler机制

// Activity.java源码

    final Handler mHandler = new Handler();

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

三、建立Handler时,在Handler构造方法中传入main Looper。能够在子线程建立该Handler,而后更新UI。

// 使用方式
  new Thread(new Runnable() {
       @Override
       public void run() {
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    //  TODO 更新UI
                    Log.d(TAG, "run: "+Thread.currentThread().getName());
                    // run: main
                }
           });
       }
  }).start();

四、View.post(Runnable action),先看下源码API 29:

// View.java源码

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

   private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

在来看下HandlerActionQueue的源码:

// HandlerActionQueue.java源码

   private HandlerAction[] mActions;
   private int mCount;
   public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

   // 关键:executeActions调用时机不一样,可能致使不会被执行
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

这种方式有不靠谱的地方,区别在于API 24(Android7.0)以上,可能post()的Runnable,永远不能运行。

根本缘由在于,API 24(Android7.0)以上 executeActions() 方法的调用时机不一样,致使 View 在没有 mAttachInfo 对象的时候,表现不同了。而executeActions()执行只会在View.dispatchAttachedToWindow()方法中调用

若是你只是经过 new 或者使用 LayoutInflater 建立了一个 View ,而没有将它经过 addView() 加入到 布局视图中去,你经过这个 View.post() 出去的 Runnable ,将永远不会被执行到。

举一个例子说明问题:

private ViewGroup mRootLayout;

    private Handler handler = new Handler(Looper.getMainLooper());

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

        mRootLayout = findViewById(R.id.rootLayout);

        final View view = new View(this);

        Log.e("View.post()>>>>", "当前设备的SDK的版本:" + Build.VERSION.SDK_INT);
        view.post(new Runnable() {
            @Override
            public void run() {
                Log.e("View.post()>>>>", "直接new一个View,而后post的Runnable被执行了");
            }
        });

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.e("View.post()>>>>", "delay addView被执行了");
                mRootLayout.addView(view);
            }
        }, 1000);
    }
}

在API 24(Android7.0)如下运行结果。从执行结果中的时间能够看出,new出来的View,post的Runnable当即被执行了。

11-04 14:40:20.614 View.post()>>>>: 当前设备的SDK的版本:19
11-04 14:40:20.664 View.post()>>>>: 直接new一个View,而后post的Runnable被执行了
11-04 14:40:22.614 View.post()>>>>: delay addView被执行了

而在API 24(Android7.0)以上(包括7.0)运行的结果。从执行时间上能够看出,post的Runnable没有当即被执行,而是addView后才被执行。

2019-11-04 14:44:36.240 View.post()>>>>: 当前设备的SDK的版本:28
2019-11-04 14:44:38.243 View.post()>>>>: delay addView被执行了
2019-11-04 14:44:38.262 View.post()>>>>: 直接new一个View,而后post的Runnable被执行了

2.2 sendMessage发送消息

经过Handler能够发送的Message的方法以下:

public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)

public final boolean sendMessage(@NonNull Message msg)
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis)
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis)
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg)

public final boolean post(@NonNull Runnable r)
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis)
public final boolean postDelayed(@NonNull Runnable r, long delayMillis)
public final boolean postAtFrontOfQueue(@NonNull Runnable r)

这些方法最终调用的是enqueueMessage方法

// Handler.java源码

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Handler源码分析到这了,可是它的精髓远远不止这些,还有不少鲜为人知的秘密,须要咱们去探索。
下一篇:Handler扩展知识探索~~~

若是个人文章对您有帮助,不妨点个赞鼓励一下(^_^)

相关文章
相关标签/搜索