Android异步消息处理机制01 —— Handler 篇

NetworkOnMainThreadException

一个简单的案例:我们想通过网络请求获取一段文字,显示在页面中

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mButton;
    private TextView mTextView;
      
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(this);
        mTextView = (TextView) findViewById(R.id.textView1);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                HttpURLConnection connection;
                BufferedReader bufferedReader;
                try {
                    URL url = new URL("https://www.baidu.com");
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    InputStream inputStream = connection.getInputStream();
                    bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                    final StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        response.append(line);
                    }

                    mTextView.setText(response.toString());

                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
        }
    }
}

思路很简单,点击事件中,通过一段普通的网络操作,向百度首页请求信息,并从返回的字节流中读取出文字,最后把结果显示在 mTextView上,但运行即可发现如下报错:

E/AndroidRuntime: FATAL EXCEPTION: main
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1145)

NetworkOnMainThreadException,“在主线程里进行网络操作异常”,可以回想起来,我们常常说诸如大型文件读写,网络操作等耗时的操作,要放在子线程里,以免阻塞主线程的进行。在 Android 应用程序里,主线程控制下的应用界面很忙,它在一直进行显示工作,由于 APP 的交互性,还必须去响应随时到来点击事件,button 键、back 键和 home 键,一旦某个按键不立即响应,就会产生卡死的效果,这是一个交互性应用要绝对避免的。

回到我们的例子,主线程中,点下按钮,开始在主线程进行访问网络操作,如果网络过程没结束,就意味着主线程内其它的操作不能进行,即使这时用户不耐烦地点下 back 键要退出,界面也不会有一点反应,这太糟糕了!

所以耗时操作放在子线程中,点击按钮,OK,让子线程去忙吧,主线程还是可以应付交互操作的。

CalledFromWrongThreadException

于是,我们新建一个子线程,将网络操作部分挪了进去,然后 start 起来

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mButton;
    private TextView mTextView;
      
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(this);
        mTextView = (TextView) findViewById(R.id.textView1);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        HttpURLConnection connection;
                        BufferedReader bufferedReader;
                        try {
                            URL url = new URL("https://www.baidu.com");
                            connection = (HttpURLConnection) url.openConnection();
                            connection.setRequestMethod("GET");
                            InputStream inputStream = connection.getInputStream();
                            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                            final StringBuilder response = new StringBuilder();
                            String line;
                            while ((line = bufferedReader.readLine())!=null){
                                response.append(line);
                            }

                            mTextView.setText(response.toString());

                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                break;
        }
    }
}

又报错了,不过这次的异常名称不太一样

E/AndroidRuntime: FATAL EXCEPTION: Thread-290
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6087)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:868)

读一下异常信息:首先这个异常是从 android.view.ViewRootImpl的 checkThread 方法抛出的,内容翻译过来是“只有创建视图层次的原始线程才能触及其视图”,翻译成人话,其实就是我们常常念叨的“只有主线程才能更改界面”。嗯,回到我们的例子,我们确实是在一个随意新建的子线程中,试图去访问并更改 mTextView 内容,这才引发的异常。

关于 ViewRootImpl 相关方法,涉及到布局绘制,AMS等纷繁的领域,笔者暂不能展开详解…如果一定要知道为什么只有主线程可以访问 UI,子线程碰都碰不得这个问题,读者可以暂且这么理解:学习并发的时候我们就知道,最大的隐患就是多线程的同步问题,如果程序里有多个子线程,在进行完数据读取后,要更改的是同一个目标 —— mTextView,就难免引发数据错乱,影响界面内容。

所以把结果都交给主线程吧,通知它一个人有条不紊地进行下去。

这样,我们的目的就很明确了,要在主线程里运行

mTextView.setText(response.toString());

但子线程的工作结果 response,要如何交给主线程?

在 Android中,这就是异步消息处理问题,Android 提供了 Handler、Message 等工具,为子线程和主线程之间的数据传递,打开了通道!

初识 Handler/Message

话不多说,看看一段正式的 Handler 代码是如何解决这个问题的:

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mButton;
    private TextView mTextView;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 200:
                    mTextView.setText((String) msg.obj);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(this);
        mTextView = (TextView) findViewById(R.id.textView1);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        HttpURLConnection connection;
                        BufferedReader bufferedReader;
                        try {
                            URL url = new URL("https://www.baidu.com");
                            connection = (HttpURLConnection) url.openConnection();
                            connection.setRequestMethod("GET");
                            InputStream inputStream = connection.getInputStream();
                            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                            final StringBuilder response = new StringBuilder();
                            String line;
                            while ((line = bufferedReader.readLine())!=null){
                                response.append(line);
                            }

                            Message message = Message.obtain();
                            message.what = 200;
                            message.obj = response.toString();
                            mHandler.sendMessage(message);

                          } catch (IOException e) {
                              e.printStackTrace();
                        }
                      }
                }).start();
                break;
        }
    }
}

代码修改成这样,我们再运行,终于成功地显示了网络请求的数据。来看看现在的操作和原来有什么不同:

1.在主线程新建了一个匿名类 mHandler,并实现了它的一个方法叫 handleMessage(Message msg)
2.子线程里访问 UI 控件的那句代码被挪到了上述 handleMessage(Message msg) 方法里,空出的地方则替换成了几句 Message 相关的代码

mHandler,是主线程设置的专门负责接收子线程的消息处理类,当下子线程中 Message 相关的几句可以这么理解:

1.子线程找来一个消息盒子:Message message = Message.obtain(); // 为什么不直接 new Message(); 读者可以自己搜索一下
2.盒子上的代号设置为200:message.what = 200;
3.盒子内容是子线程的结果:message.obj = response.toString();
4.向目标 mHandler 发送:mHandler.sendMessage(message);

就这样,一个消息就由子线程,发向了身处主线程里的 mHandler,经处理后交由主线程决定呈现内容~~~

关于新建 handler 的意外

这时,子线程觉得 mHandler 这个角色很不错,决定也搞一套类似“主线程 —— Handler —— 子线程”这种模式,自己效仿主线程,也设立一个消息处理人handler(虽然好像根本没有其他线程找他通讯托办事……)但是他不管,就是要 Handler!那就来吧:

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
    ……

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
               ……
            }
        }
    };
    
    private Handler mZiHandler;

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

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                ...
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                       ……
                       mZiHandler = new Handler();
                    }
                }).start();

                break;
        }
    }
}

于是,声明了 mZiHandler 后,子线程在自己的 run() 方法里 new 了一个 Handler 做初始化,运行一下吧

RuntimeException: "Can't create handler inside thread that has not called Looper.prepare()"

报错了,可见,Handler 这个东西,不是你子线程想建就能建啊!

新角色 Looper

“不能在一个没有 prepare looper 的线程内创建 handler”,对于这个解释,子线程不是很满意,有至少两个疑问

1.Looper 是干什么的,为什么没它连 Handler 都 new 不了? 
2.主线程也没见准备Looper,为什么它就 new 了?

先不管了,让咱准备 looper 那就先 prepare 一下 looper 吧,然后再新建 handler

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
    ……

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
               ……
            }
    };
    
    private Handler mZiHandler;

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

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                       ……
                       Looper.prepare();
                       mZiHandler = new Handler();
                    }
                }).start();
                break;
        }
    }
}

果然,加上这句就正常运行了。接下来,就来回答一下子线程的两个疑问吧,先从第二个疑问开始。

主线程的 Looper 哪儿来的?

原来,应用程序启动的代码如下

public final class ActivityThread {
    public static final void main(String[] args) {
        ......
        Looper.prepareMainLooper();
        ......
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {    
            sMainThreadHandler = thread.getHandler();
        }
        ......
        AsyncTask.init();  

        if (false) {  
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));  
        }  
        Looper.loop();  
        throw new RuntimeException("Main thread loop unexpectedly exited"); 
    }
}

可以看见里面有这么一句

Looper.prepareMainLooper(); 

嗯,原来主线程在应用启动的时候就着手准备主 Looper 了,那去看看 Looper 类里这个方法具体干了什么吧。

public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class
    final MessageQueue mQueue;
    ...
    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 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));
    }
    ...
    /**
     * Return the Looper object associated with the current thread.  Returns null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
}

一行一行看过程,走到 prepare(false),先检查 sThreadLocal.get(),目前的情况是程序刚启动,如果这刚开始还没设置呢,就 get 到了一个不为空的 looper,显然是不合理的。那就报错吧,“一条线程只能有一个 looper”。如果顺顺利利地 get 到了 null,才现场 new 一个不可取消的 looper,并为当前Looper类的静态成员 sThreadLocal set 进去这个 looper,这个 Looper,就是主线程独有的 MainLooper。至此,我们应用程序的主线程里就始终存在着这个主 Looper对象了。

(读者到这里可能有疑问,如果子线程都准备了自己的 looper,想获得这个 looper 时,应该走的是上面代码中 myLooper() 方法,然后走 sThreadLocal.get(),但是!这个 sThreadLocal 是静态类,也就是所有 looper 对象共享的啊,get 的时候岂不所有线程得到的都是同一份 looper ??? 其实这就是 ThreadLocal 的妙用了,先回答你,在不同线程里 sThreadLocal.get() 的并不是同一个结果,而是线程各自的 looper 哦!之后会新开文章详细介绍的。)

Looper 在创建 handler 时的作用

主线程的 looper 哪儿来的了知道了,那么回到第一个疑问:为什么没有 looper,handler 就建立不了呢?

我们看看 Handler 的源码吧,其构造方法有重载,不过最终都辗转到其中一个

public class Handler {
        
    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;
    ...
    public Handler(Callback callback, boolean async) {
        ...
        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;
    }
}

真相大白了!Handler 的成员里就有 mLooper!而且在 Handler 的构造方法里,其对 mLooper 进行了初始化和检查。

初始化时,拿的是当前所在线程的 looper:Looper.myLooper()。检查时,如果发现当前线程 Looper 为空,就会报出我们之前见的"Can't create handler inside thread that has not called Looper.prepare()"异常!

同时我们看到了 Handler 的成员里还有个 Callback 成员,还有个 MessageQueue 成员,前者先留个伏笔,后者是 MessageQueue,熟悉吗?翻看上面的 Looper 源码,MessageQueue 本身是 Looper 的成员,这里的 handler 在构造方法中,经由 mLooper,也得到了 mQueue 的引用。

事实上,mLooper 作为 Handler 的必需成员,其自身成员 mQueue 也担负着重要功能!我们马上讲到。

目前,为什么没 Looper 就不能创建 Handler 子线程也算是明白了。

mQueue 在 Handler 里的作用

整理一下思路,还记得子线程发送 message 给 mHandler 的操作吗?当时包装好消息盒子,最后一步是干什么来着?

mHandler.sendMessage(message);

没错,就这么一句,子线程自己好像没干什么,包装好的消息盒子就发到 mHandler 那里了,然后又到 mHandler 的 handleMessage() 方法里了。这么说,是 mHandler 找各子线程一个个搜集消息盒子然后直接处理?子线程不确定,它想搞清楚自己的消息盒子是怎么流转的,于是决定去 Handler 的 sendMessage(message) 源码里看个究竟。

mHandler.sendMessage(message) 方法几经辗转,最后到了 Handler 的这个方法里

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

可以看到,子线程的消息,连同发送时间,都被记录走了,等等,我们看到了什么?mQueue!接着看 enqueueMessage(queue, msg, uptimeMillis),看 mQueue 在这里干什么

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

代码最后,程序由 Handler 的方法,走进了 MessageQueue 的 enqueueMessage(msg, uptimeMillis) 方法,看来消息盒子都进 mQueue 里去了!

boolean enqueueMessage(Message msg, long when) {
    ...
    synchronized (this) {
        ...
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

可以看出,在 mQueue 内部,Message 作为 Node 以链表的形式,按时间顺序一个一个地链接了起来,同时可以看见其内部操作都有同步锁加持,保证了多线程下传递消息的安全。

子线程明白了,消息盒子从自己内部发给 mHandler 后,并不是直接就被处理了,而是悄悄排在了它内部的 mQueue 里,mHandler 应该就是以这种方式才有条不紊地将各个消息处理的吧。

嗯,至此,子线程明白了自己的消息盒子去了哪里,又以怎样的形式排列着,全靠的是 mHandler 的 mQueue!

至于 mHandler 内部的处理方式,子线程不在意,毕竟消息的处理自己可说不上话。

Looper 在 Handler 处理 Message 时的作用

再总结一下吧,一条条子线程,有消息就包一个盒子,发给主线程的 handler,在 handler 内部,盒子按时间顺序链接在 mQueue 里,现在的疑问是,这些盒子是如何一个个送到我们重写的 handleMessage() 方法里的呢。

按道理,盒子要被一个个取出,我们就得看见 mQueue 链表遍历的操作,mQueue 是 Handler 成员,看看 Handler 源码有没有遍历操作看了一遍,似乎没有,别忘了,mQueue 也是 Looper 的成员,再去 Looper 源码看看,这时,一个叫 loop() 的方法引起了我们的注意。

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 (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();
    }
}

看见没!mQueue 就是在这里被遍历,将 Message 各个取出的!很好,取出后走到

msg.target.dispatchMessage(msg);

嗯,这应该就是 Message 的处理过程了。

至此,我们知道,mQueue 中排列的消息,是通过 Looper 的 loop() 方法遍历取出并交由 mHandler 处理的。

再回看 ActivityThread 的 main() 方法,果然当时其创建完主线程的 MainLooper,紧接着就 loop 了起来,动作真快啊!回想起某个傻傻的子线程,上来就设立 handler 失败不说,等准备了 looper 再设立 handler,最后还忘了 loop(),就算有线程给你发消息你也分发处理不起来呀,图样啊~不过,这个子线程愿意学习先进技术,并有获取其他线程消息,自己处理的想法,敢想敢干,以后的文章肯定可以见到他发光发热的一天!

等等,先别回忆了,这里又有疑问啦,两个:

1.处理 Message 的确实应该是 mHandler 我们知道,但你这里的处理者是 msg.target 啊,这俩是一个东西吗?
2.处理方法名字对不上啊,你这里是 dispatchMessage(msg) ,我们新建的 mHandler 重写的方法叫 handleMessage(Message msg) 啊

先看第一个问题:

通过查看 Message 的源码,我们了解到其含有一个 Handler 类型的成员,不出意外,这个 msg.target 应该就是我们的 mHandler 了。那什么时候设置的这个 target 的呢?往回看 Handler 的 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 方法,第一句 :

msg.target = this; 

this 是什么,当然就是我们的 mHandler 啦,所以没毛病,msg.target 就是我们的 mHandler!

再看第二个问题:

这里消息处理的方法叫 dispatchMessage(msg) ,而我们新建的 mHandler 重写的方法叫 handleMessage(Message msg) ,不多说,看看前者的源码:

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

可以看出,Message 的处理方法有三个走向

走向一:

被处理的 Message 如果自带 callback 的话,就走 handleCallback(Message message),嚯,消息盒子还能带着回调去让 Handle 处理呐。再看看其内部实现:

private static void handleCallback(Message message) {
    message.callback.run();
}

直接运行所携带的 callback 的 run() 方法了,等于说,子线程带着自己定义的处理方式,直接找 Handler 让它照着办,脸可真够大的……

并且我们应该可以得知,Message 自带的 callback 是 Runnable 类型的。

至于当时发送 Message 时怎么加上个 Runnable 的,读者请先翻翻源码自己看一下,文章最后会讲,到时再来验证。

走向一说完,感觉这个走向应该不是我们重写所实现的……

走向二:

如果 mCallback 不为空,就走 mCallback.handleMessage(msg)

怎么又是一个 callback!先别急,此 mCallback 非彼 callback,之前的 callback 属于 Message的成员,还是 Runnable类型。这里的 mCallback呢,我们回看一下上面的 Handler 源码处,还记得我们当时留的伏笔吗

public interface Callback {
    public boolean handleMessage(Message msg);
}

原来,这个 mCallback 是 Handler 的成员,而且类型也不是 Runnable,而是一个要求实现 handleMessage(Message msg) 方法的接口,该方法也正是该走向的真正处理方式。等于说,我们新建 mHandler 的时候,不是以匿名类的方式重写 handleMessage(Message msg),而是给一个普通 Handler 对象设置一个 Callback 成员,这个成员是个接口,自己要 handleMessage(Message msg)方法。

和走向一一样,怎么给一个普通的 mHandler 设置 Callback,也翻一翻 Handler 源码的构造函数去试试吧。

不过,这里虽然名字也是 handleMessage(Message msg),但也不是我们重写所产生的效果……

走向三:

如果 message 既没有自带 callback,mHandler 也没有设置 Callback 成员,或者有 Callback 成员但处理结果返回 false,那就走向了最终的 handleMessage(Message msg) 方法。

这个也叫 handleMessage(Message msg)!!!找源码看一下

 /**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}

子类要接收 Message 必须实现的方法!没错了,这就是我们原程序里的重写方式,我们当时写的,就是走向三。

最后,我们将走向一和走向二的写法也展示一下吧。

走向一:你可能没有找到给 message 设置 Runnable 类型的 callback 的操作,不过看下面的程序

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
    ...
    private Handler mHandler = new Handler();
    ...
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        ...
                        try {
                            ...
                            //方式一
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    mTextView.setText(response.toString());
                                }
                            });

                            //方式二
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    mTextView.setText(response.toString());
                                }
                            });

                            //方式三,利用方式二
                            mTextView.post(new Runnable() {
                                @Override
                                public void run() {
                                    mTextView.setText(response.toString());
                                }
                            });

                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
        }
    }
}

以上三种常见方式,翻阅源码,可以知道其内部都是通过将 Runnable 实现类包装给一个新建的 message 作 callback,然后发送给主线程的 handler,也就是走向一的方式(读者可以亲手去跟进证实一下)。

注意,此时的 handler 只需简单的一个声明创建,毕竟处理方式被受理的 message 指定了要走自带的 callback 的 run() 方法,那自己执行就是,不需要再重写或添加什么处理方式。

走向二:你应该写得出来吧

public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {

    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case 200:
                    mTextView.setText((String) msg.obj);
                    break;
            }
            return true; 
        }
    });

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

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        ...
                        try {
                            ...
                            Message message = Message.obtain();
                            message.what = 200;
                            message.obj = response.toString();
                            mHandler.sendMessage(message);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                break;
        }
    }
}

和原程序走向三的写法相比,只有新建 mHandler 一处不同,就是并非实现 handleMessage(Message msg),而是将一个实现了 handleMessage(Message msg) 方法的 Callback 实现类作为参数传入到 Handler 的带参构造方法里。

到此,我们的异步消息处理 Handler 篇算是讲完咯~~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容