Handler 的基本使用常见问题的源码解析以及运行机制源码讲解

Posted 涂程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler 的基本使用常见问题的源码解析以及运行机制源码讲解相关的知识,希望对你有一定的参考价值。

一、定义

  • 一套android消息传递机制/异步通信机制。

二、作用

  • 多线程场景下,子线程需要将更新UI操作信息传递到主线程,实现异步消息的处理。

三、使用

使用的方式有 2 种:

  • 一种是通过 sendMessage() 的方式来实现异步通信。
  • 一种是通过 mHandler.post() 的方式来实现异步通信。

3.1 sendMessage() 方式

1. 创建 Handler对象,下面列举 3 种方式。
  • 匿名内部类
// 在主线程中通过匿名内部类创建Handler类对象。
private Handler mHandler = new Handler(){
// 通过复写handlerMessage方法更新UI
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        // ...... 执行更新UI操作
        }
};
  • 实现 Callback 接口
// 主线程中通过实现Callback 接口创建 handler 类对象。
private Handler mHandler  = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            return false;
        }
});
  • 在子线程中创建 Handler 类对象 (比较少见,基本不用)
 new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler mHandler = new Handler();
                Looper.loop();
            }
        }).start();
2. 创建 Message 对象,同时 handler 发送消息。
  1. 创建 Message 对象
// 方式一:
Message message1 = new Message();
// 方式二:
Message message2 = Message.obtain();
// 方式三:
Message message3 = mHandler.obtainMessage();
  1. Message 对象携带数据
  Message message = new Message();
  message.what = 1; // 消息的标识

  // 方式一:通过arg1,arg2 传递
  message.obj = "啦啦啦"; // object
  message.arg1 = 1; // int 类型
  message.arg2 = 2; // int 类型

  // 方式二:通过Bundle 传递
  Bundle bundle = new Bundle();
  bundle.putInt("1",1);
  message.setData(bundle);
  1. handler 发送消息
// 方式一:发送普通消息
mHandler.sendMessage(message);

// 方式二:发送空消息,传入what标识值,便于在接收消息中判断。 
mHandler.sendEmptyMessage(0);

// 方式三:延时发送消息
mHandler.sendEmptyMessageDelayed(0,1000);
3. handler 接收消息并处理。
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        // 通过发送消息时的what标识,对不同消息做判断处理
        switch (msg.what) {
            case 0:
                System.out.println("接收到的消息为空消息");
                break;
            case 1:
                System.out.println(msg.obj.toString());
                break;
            default:
                break;
            }
       }
};

3.2 mHandler.post() 方式

  • 1.创建 Handler 对象。
  • 2.在子线程通过调用实例对象的 post() 方法,传入 runnable 对象,重写 run() 方法,在重写的 run() 方法中更新 UI
private Handler mHandler = new Handler();
new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                       // 更新UI操作
                    }
                });
            }
        });

好了,关于 Handler 的基本使用就说完了,下面通过通过一个个的问题以及相应问题的源码解释来加深我们对 Handler 的理解,做到知其然知其所以然。

四、常见问题及源码解答

1. 主线程创建 Handler 与子线程创建 Handler 有什么不同 ?

主线程创建 Handler:

private Handler mHandler  = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            return false;
        }
});

子线程创建 Handler

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

通过对比 ,我们可以看到在子线程创建 Handler 多了两行代码,那么为什么在主线程创建时不需要呢?我们通过源码来解释一下:

应用在启动的时候,会调用 ActivityThread 这个类,在这个类的 main() 方法中,调用了 Looper.prepareMainLooper()

// ActivityThread 类中的 main 方法
 public static void main(String[] args) {
        // ....
        // 创建主线程的Looper
        Looper.prepareMainLooper();
        //...
        Looper.loop();
    }

Looper.prepareMainLooper();

  • 这行代码的含义就是给当前线程初始化一个 Looper,所以并不是主线程不需要调用 Looper.prepare() 方法,而是系统已经做了操作。

Looper.loop();

  • 这行代码的含义就是开启主线程的轮询,这是一个死循环,会一直轮询。

读到这儿,可能有人就想问,主线程创建时,系统自动帮我们加了这两行代码,如果我们在子线程创建 Handler 时不加入这两行代码,行不行?答案是否定的,不行,会报异常:

 throw new RuntimeException("Only one Looper may be created per thread");

这个时候我们就会思考,为什么系统会抛出异常?还是需要我们从源码中找答案。这个时候我们就需要看看 new Handler()的时候,在源码中具体做了什么?

 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()");
        }
       //....
    }

从源码中可以看到会给成员变量 mLooper 赋值,一旦没有获取到值,就会抛出我们之前提到的异常,所以我们要看看 Looper.myLooper() 这个操作做了什么?

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

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

从源码看出,这个方法的返回值就是 Looper 对象,接着我们看一下 get() 方法,到底这个 Looper 对象是如何获取的?

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

这么一看,我们就知道了,获取当前线程的对象作为 key 值,然后从 ThreadLocalMap 中获取 value 值,从结果来看是没获取到,为什么呢?,我们重新回到 ActivityThread 类中 main() 方法中,点击 Looper.prepareMainLooper()prepareMainLooper() 方法:

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

再点击此方法中的 prepare() 方法:

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

下面是 sThreadLocal.set()方法:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

我们将这两个方法对着看,可以看出在应用启动时,ThreadLocalMap 里面会创建唯一一条数据,key 的值就是 主线程,value 值就是主线程的 Looper 对象,而当我们在子线程创建 Handler 的时候,是需要获取 Looper 对象的,那么此时传入的 key 的值是子线程,当然从 ThreadLocalMap 中获取不到 Looper 对象了,因为 key 的值不一样。所以取出的 Looper 对象为 null,也就抛出了上面的异常。

通过这个问题 可以做以下总结:

  • 子线程默认是没有 Looper的,如果需要使用 Handler 就必须为线程创建Looper
  • 一个线程中必须要有一个 Looper ,也就是说在一个线程中,如果想使用 Handler,必须要有一个 Looper。想要正确在子线程中创建 handler 对象,做法如上面创建 handler 对象第 3 种方法。
  • Handler创建的时候会采用当前线程的Looper来构造消息循环系统,因为应用启动时,ActivityThread类会初始化Looper,所以在主线程中默认可以使用 handler,但不能说主线程没有创建 Looper对象。

2. 更新控件内容真的只能在主线程运行 ?

首先给出结论,这句话太过绝对,不一定。不相信的童鞋可以在子线程中更新 UI,结果可能会让你惊讶,怀疑自我,哈哈,我们以 TextView.setText()为例,从源码的角度去分析一波:

一路点击 setText() 方法到底,找到最后一个 setText 方法里面的 checkForRelayout() 方法 。点击此方法,直接看到最后面的代码,可以看到不管 if 里面的代码还是 else里面的代码,最终都会走 requestLayout() (请求布局) 和 invalidate()(刷新布局)这两行代码。接着我们就看看 requestLayout() 里面做了什么事?

public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy

            //  ------ ViewRootImpl 实现了 mParent 接口 --------
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            //  ---------mParent 是一个接口---------
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

ViewRootImpl 恰恰实现了 mParent 接口,也就是会调用 ViewRootImpl 里面的 requestLayout() 方法,那么我们看一下源码:

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

里面有一个检查线程的方法 checkThread(),点击进入:

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

此段代码会检查当前线程是否是主线程,不是则抛出异常。

由此可以看出来,我们在子线程中更新 UI,如果 requestLayout() 这个检查线程的执行速度 慢于 invalidate() 绘制界面的执行速度,不会抛出异常。

我们可以做一个操作来验证一下,在子线程中延迟一秒,更新 UI,结果报错信息如下:

 Process: com.example.handlerdemo, PID: 10569
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:360)
        at android.view.View.requestLayout(View.java:23093)
        at android.widget.TextView.checkForRelayout(TextView.java:8908)
        at android.widget.TextView.setText(TextView.java:5730)
        at android.widget.TextView.setText(TextView.java:5571)
        at android.widget.TextView.setText(TextView.java:5528)
        at com.example.handlerdemo.MainActivity$3.run(MainActivity.java:75)
        at java.lang.Thread.run(Thread.java:764)

可以看出来指向的报错地方就是我们阅读源码看到的 ViewRootImpl.requestLayout 这个检查线程的地方。

所以才说更新 UI的操作只能在主线程,这句话太过绝对了,不一定就是的,要看情况分析。

3. 主线程创建 Handler 两种写法有何区别?

方式一:

 private Handler mHandler1 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            return false;
        }
    });

方式二:

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

            mTextView.setText(msg.obj.toString());
        }
    };

用过的人都知道 方式二 有黄色警告,这是谷歌备胎的 api,不推荐使用。

有了前面的分析,我们都知道在 AcitivityThread 类的 main() 方法中会除了会创建 Looper 对象, 还会调用 Looper.loop() 方法不断的循环消息队列,点击此方法:

// Looper 类

 for (;;) {
 //....
 try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } 
 }
 //...

在这个循环中,会调用 msg.targetdispatchMessage() 方法去分发消息,那么这个 msg.target 是什么呢?msgMessage 对象,我们进入 Message 类的源码,看到以下代码:

// Message 类

@UnsupportedAppUsage
/*package*/ Handler target;

由此看出 msg.target 就是 Handler 对象,所以其实调用的是 HandlerdispatchMessage() 方法。那么我们就看看 Handler 类中的 dispatchMessage() 方法:

// Handler 类

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

if 里面的判断是给 mHandler.post() 方法准备的,我们先不看,直接看到 else 里面的方法, 首先判断 mCallback 是否为空,mCallbackHandler 类中的一个成员变量,它是一个接口:

// Handler 类

final Callback mCallback;
public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }

我们在方式一中的写法就是传入了一个新建的Handler的callback 对象,所以判断不为空,就会走 mCallback.handleMessage(); 方法,也就是我们实现 callBack 接口中的 handleMessage() 方法,在我们重写的方法中,return true 或者 return false 其实并没有区别,为什么这么说 ?

如果我们返回 true,那么源码中直接 return, 如果我们返回false ,在源码中就会走下面的 handleMessage(), 这个是 Handler 类中暴露出来的可以重写的公开方法,但是它的方法名和接口中的方法名一样,我们重写不了,在方式一的 Handler 类中是无法重写 handleMessage() 方法,所以才说写 return true 或者 return false 并没有区别。

如果我们用的是方式二的写法,那么就会走下面的 handleMessage() 方法。也就是走我们在方式二中重写的 Handler 类中暴露出来的可以重写的公开方法 handleMessage()

好了,这两种方式的区别就说完了,现在来说一下之前要忽略的 dispatchMessage() 方法里面的 if 里面的代码 为什么说是给 mHandler.post()准备的呢?

我们看一下自己写的 mHandler.post():

new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler1.post(new Runnable() {
                    @Override
                    public void run() {
                          //.....
                    }
                });
            }
        });

我们再看一下 Handler 类中的 post() 源码:

// Handler 类

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

可以看到我们传入的 Runnable 的对象, 会调用 getPostMessage(r) 方法将传入的Runnable 的对象进行一个封装。那么这个方法中做了什么事情呢?

// Handler 类

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

可以看到源码中将传入的 Runnable 对象赋值给了 Message.callback, 而这个 Message.callbackMessage 类中的 callback 属性,它就是是一个 Runnable 对象:

 // Message 类中的

 Android培训HandlerThread的使用及源码解析

Android笔记之从源码解析Handler中ThreadLocal的作用以及IntentService解析

Android笔记之从源码解析Handler中ThreadLocal的作用以及IntentService解析

Android Handler消息机制源码解析

android源码解析之-->异步任务AsyncTask

Jetty的工作原理解析以及与Tomcat的比较