FrameWork中哪些模块用到了Handler机制?

在FrameWork中很多重要的模块都可以看到Handler机制的身影,通过理解这些使用场景中Handler的作用,可以让我们更加深刻的理解Handler机制。

Activity.runOnUiThread()

在Activity中,使用runOnUiThread()可以很轻松的实现到UI线程的切换,其内部实现也是Handler机制。

framework/base/core/java/android/app/Activity.java

final Handler mHandler = new Handler();

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

从代码中我们可以看到,Activity中其实是存在一个Handler成员变量的,但是由于访问权限是default,所以我们无法直接使用。而且runOnUiThread()不支持延时Runnable,所以如果想使用Handler的其他功能的话,我们还是需要自己单独定义Handler。

由于Handler的初始化发生在Activity被实例化的过程中,所以Handler绑定的线程与Activity被实例化的线程是相同的,而Activity被实例化发生在UI线程,所以我们的Runnable总是可以在UI线程执行。关于Activity被实例化的详细过程,将在下面的文章里进行介绍。

AsyncTask的异步处理

AsyncTask是Android提供给我们的一个实现异步的类,AsyncTask.doInBackground()会在线程池中运行,而AsyncTask.onPostExecute()AsyncTask.onProgressUpdate()则在UI线程中调用。在这个场景中需要进行工作线程与UI线程的切换,内部的实现机制就是Handler。

为了保证Handler在UI线程中被实例化,在ActivityThread.main()中就进行了这步操作。

由上面代码我们很容易就可以看出,应用内所有AsyncTask的UI回调都是通过同一个静态变量sHandler实现的,而sHandler在UI线程中进行的实例化,即与UI线程进行了绑定,从而保证其handleMessage()是在UI线程完成。

ActivityThread与AMS的通信

ActivityThread与AMS的通信涉及到Binder机制,当要开启一个Activity时,Instrumentation会通过ActivityManagerProxy将数据通过Binder传递给AMS,当AMS协调工作完毕后,会通过ApplicationThreadProxy通知ActivityThread开启具体的Activity,但是由于这部分通信是在Binder线程池中,因此ActivityThread需要将线程切换至UI线程,这里就用到了Handler机制。

下面,将重点介绍AMS通过ApplicationThreadProxy通知ActivityThread开启新的Activity这个过程。

因为ApplicationThreadProxy代理的是ApplicationThread,所以具体的实现是在ApplicationThread。

因为ApplicationThread是ActivityThread的内部类,所以具体的发送操作是在ActivityThread完成。

由于mH是在UI线程初始化,因此H.handleMessage()的执行线程为UI线程,从而完成了从Binder线程到UI线程的切换。

Toast的显示与隐藏

如果想在子线程中显示Toast应该怎么做呢?必须在Thread中创建消息循环,否则就会报错Can't create handler inside thread that has not called Looper.prepare()

为什么会这样呢?因为Toast的显示也需要Handler机制来完成线程的切换。

从上面的代码可以很容易看出,当Toast完成实例化的时候,就会完成TN的实例化,而在TN中的成员变量mHandler的实例化就是在Toast实例化的线程完成的,所以如果当前Toast所在线程没有绑定Looper的时候,就会抛出RuntimeException,这也是为什么在子线程中使用Toast时比如手动开启Looper循环的根本原因。

界面刷新

Android的单线程编程模式,决定了对View的界面操作都需要在UI线程执行,当我们调用View.invalidate()通知系统刷新界面时,最终会将这个请求传递到ViewRootImpl,具体流程我们会在后面的View机制中详细介绍,现在重点关注一下传递到ViewRootImpl之后的操作。

Last updated

Was this helpful?