Android中的消息处理范例与分析

Android中的消息处理实例与分析

Android中的消息处理实例与分析

摘要

本文介绍了Android中的消息处理机制,给出了Android消息处理中的几个重点类Handler、Message、MessageQueue、Looper、Runnable、Thread的详细介绍,提供了两个消息处理的实例代码,并深入分析了使用Android消息机制应该遵循的几个原则。

阅读本文的收获

在具有java基础的情况下,Android的学习比较轻松,很多人在没有深刻了解Android消息处理机制的背景下,已经能够开发出可用的app,很多人开始想学习Android消息处理机制的第一个动机是遇到了一个著名的bug“CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.”。这个bug的含义即“只有创建一个view层次的线程能够更新此view”,在更多情况下,它是想说“只有主线程能够更新UI”。
本文即是来解释如何利用Android的消息机制从主线程或者子线程中更新UI或完成其他操作。你可以学到:
1. 如何使用Android的消息实现同步、异步操作;
2. 如何在主线程和子线程发送并接收消息;
3. 消息是如何得到处理的;
4. 使用Android的消息处理机制应该遵循的几个原则;
5. 两个具体的消息处理实例源代码。

阅读本文需要的技术背景

你可能需要如下技术背景才能完全理解本文的内容,如何你没有以下背景,建议先学习相关知识:
1. Java语言基础
2. Java多线程技术
3. Android开发基本知识

第一个例子:在主线程和子线程中发送消息,在主线程中处理消息

先来看一个代码,代码地址为:
http://download.csdn.net/detail/logicteamleader/8827099
本例的作用是:创建一个Handler(处理消息的类),在界面上点击按钮就会向此Handler发送消息,Handler可以处理这些消息(后面会详细解释,消息的处理其实是多个类共同合作的结果),对UI进行修改。界面上有八个按钮,从上至下其效果分别是:
1. 使用Handler的post来传递一个Runnable的实例,该实例的run方法会在按钮点击时运行;
2. 使用Handler的postDelayed(Runnable r, long delayMillis)来传递一个Runnable的实例,该实例的run方法会在一段时间delayMillis后执行;
3. 使用Handler的sendMessage方法传递一个消息,该消息在Handler的handleMessage方法中被解析执行;
4. 使用Message的sendToTarget方法向获得该Message的Handler传递一个消息,该消息在Handler的handleMessage方法中被解析执行;
第5、6、7、8个方法与上面四个方法类似,不同的是它们都是在子线程中调用的。
源代码如下:

package com.example.wxbhandlertest;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.MessageQueue;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
/**
 * @author wxb
 * Android中的消息处理实例之一
 * * 一、在主线程内发送消息
 * 1.使用post
 * 2.使用postDelay
 * 3.使用sendMessage
 * 4.使用Message.sentToTarget
 * 二、在子线程中使用Handler
 * 1.使用post
 * 2.使用postDelay
 * 3.使用sendMessage
 * 4.使用Message.sentToTarget
 */
public class MainActivity extends Activity {
    private Runnable runnable=null;
    private Runnable runnableDelay=null;
    private Runnable runnableInThread=null;
    private Runnable runnableDelayInThread=null;
    private static TextView tv;
    private static TextView tvOnOtherThread;

    //自定义Message类型
    public final static int MESSAGE_WXB_1 = 1;
    public final static int MESSAGE_WXB_2 = 2;
    public final static int MESSAGE_WXB_3 = 3;
    public final static int MESSAGE_WXB_4 = 4;
    public final static int MESSAGE_WXB_5 = 5;

    private static Handler mHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what){
            case MESSAGE_WXB_1:
                tv.setText("invoke sendMessage in main thread");
                break;
            case MESSAGE_WXB_2:
                tv.setText("Message.sendToTarget in main thread");
                break;
            case MESSAGE_WXB_3:
                tvOnOtherThread.setText("invoke sendMessage in other thread");
                break;
            case MESSAGE_WXB_4:
                tvOnOtherThread.setText("Message.sendToTarget in other thread");
                break;  
            }
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) this.findViewById(R.id.tvOnMainThread);
        tvOnOtherThread = (TextView) this.findViewById(R.id.tvOnOtherThread);

        //方法1.post
        runnable = new Runnable(){
            public void run(){
                tv.setText(getString(R.string.postRunnable));
            }
        };
        Button handler_post = (Button) this.findViewById(R.id.btnHandlerpost);
        handler_post.setOnClickListener(new OnClickListener() {     
            @Override
            public void onClick(View v) {
                mHandler.post(runnable);
            }
        });

        //方法2:postDelay
        runnableDelay = new Runnable(){
            public void run(){
                tv.setText(getString(R.string.postRunnableDelay));
            }
        };

        Button handler_post_delay = (Button) this.findViewById(R.id.btnHandlerPostdelay);
        handler_post_delay.setOnClickListener(new OnClickListener() {       
            @Override
            public void onClick(View v) {
                mHandler.postDelayed(runnableDelay, 1000);  //1秒后执行
            }
        });

        //方法3:sendMessage
        Button btnSendMessage = (Button) this.findViewById(R.id.btnSendMessage);
        btnSendMessage.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Message msg = mHandler.obtainMessage();
                msg.what = MESSAGE_WXB_1;
                mHandler.sendMessage(msg);
            }
        });

        //方法4:Message.sendToTarget
        Button btnSendtoTarget = (Button) this.findViewById(R.id.btnSendtoTarget);
        btnSendtoTarget.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Message msg = mHandler.obtainMessage();
                msg.what = MESSAGE_WXB_2;
                msg.sendToTarget();
            }
        });

       //在其他线程中发送消息
        //1.post
        runnableInThread = new Runnable(){
            public void run(){
                tvOnOtherThread.setText(getString(R.string.postRunnableInThread));
            }
        };

        Button btnPost = (Button) this.findViewById(R.id.btnPost);
        btnPost.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        mHandler.post(runnableInThread);
                    }
                }.start();
            }
        });

        //2.postDelay
        runnableDelayInThread = new Runnable(){
            public void run(){
                tvOnOtherThread.setText(getString(R.string.postRunnableDelayInThread));
            }
        };

        Button btnPostDelay = (Button) this.findViewById(R.id.btnPostDelay);
        btnPostDelay.setOnClickListener(new OnClickListener() {             
            @Override
            public void onClick(View v) {
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        mHandler.postDelayed(runnableDelayInThread, 1000);
                    }
                }.start();
            }
        });

        //3.sendMessage
        Button btnSendMessage2 = (Button) this.findViewById(R.id.btnSendMessage2);
        btnSendMessage2.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        Message msg = mHandler.obtainMessage();
                        msg.what = MESSAGE_WXB_3;
                        mHandler.sendMessage(msg);
                    }
                }.start();
            }
        });

      //方法4:Message.sendToTarget
        Button btnSendToTarget2 = (Button) this.findViewById(R.id.btnSendToTarget2);
        btnSendToTarget2.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        mHandler.obtainMessage(MESSAGE_WXB_4).sendToTarget();
                    }
                }.start();
            }
        });
    }
}

几个规则

看完代码后,在解释具体类之前,先了解几个规则:
第一, 只有创建view的线程能够更新此view;一般来说,创建UI的是Android主线程,因此只有在主线程中才能更新UI;
第二, 处理消息的类是Handler,它依附于创建自己的线程;如果在主线程中创建Handler mHandler,则向mHandler发送的消息会在主线程中被解析;如果在子线程中创建Handler sHandler,则向sHandler发送的消息会在子线程中被解析;
第三, 发送消息有三类方法,Handler的post方法,Handler的sendMessage方法,以及Message的sentToTarget方法,它们最终其实都调用了Handler的sendMessage方法;
第四, 消息的方法可以即时也可以延时,代表就是post和postDelay,以及sendMessage和sendMessageDelayed;延时发送消息可以实现很多用户需要的界面延时效果,例如最常用的SplashWindow。
第五, post类型的方法只需要实例化一个Runnable接口,且不需要重载Handler. handleMessage方法,比较简单易用;
第六, sendMessage和sentToTarget需要重载Handler. handleMessage方法,实现对不同消息的解析。

Handler

Handler是Android消息处理中最重要的一个类,不管什么编程语言或者框架,叫Handler的类一般都很重要,也都很复杂,当年的Windows句柄类更是让人一头雾水。
幸好,Android的Handler很容易理解和使用。它就是一个消息处理的目标类,其主要作用有两个:1)它安排要执行的消息和runnable在未来某个时间点执行;2)处理来自不同线程的消息并执行操作。
使用Handler要注意以下几点:
1. Handler属于创建它的线程,并与线程的消息循环关联,同时与此线程的消息队列(MessageQueue)关联。
2. Handler发送消息的方法有两类:post类和sendMessage类,用法见例子;
3. 如果使用post发送消息,则消息中包含的Runnable会在解析消息时执行;
4. 如果使用sendMessage发送消息,则需要重载Handler. handleMessage方法,实现对不同消息的解析

Message

Message是消息类,它有几个重要的属性:
public int what:消息类型
public int arg1:参数1
public int arg2:参数2
public Object obj:对象参数
以上几个参数用户可以随意定制。
值得注意的有几点:
1. Android使用消息池模式来提高消息的效率,因此一般不适用new来创建消息,而是使用obtain方法来从消息池中获取一个Message的实例;Handler类也有obtain方法;
2. Message有一个Handler作为Target,一般来说就是获取该消息的所在线程的关联Handler;因此使用sendToTarget方法发送消息时,将消息发给它的Target;

MessageQueue

MessageQueue是消息队列,一个线程最多拥有一个消息队列,这个类一般不会被程序员直接使用。它的入队方法enqueueMessage用来将一个Message加入队列,这个方法是线程安全的,因此才保证了Android的消息处理机制是线程安全的。
MessageQueue的next方法用来获取下一个Message,没有任何消息时,主线程常常在此方法处等待。

Runnable

Java的接口,它代表一个可执行的代码片段,如下所示:

public interface Runnable {

    /**
     * Starts executing the active part of the class' code. This method is
     * called when a thread is started that has been created with a class which
     * implements {@code Runnable}.
     */
    public void run();
}

Thread

Java的线程类,大家都应该很熟悉了。

第二个例子:在子线程中创建消息处理循环

第一个例子描述了如何在多个线程中发送消息,并在主线程中统一接收和处理这些消息;第二个例子则描述如何在子线程中建立一个消息循环,并从主线程发送消息给子线程,让子线程处理这些消息。
第二个例子中有两个消息循环,两个Handler,主线程首先向子线程发送一个消息,子线程的收到消息后再向主线程发送一个消息,主线程收到消息后更新UI。
例子代码如下:
http://download.csdn.net/detail/logicteamleader/8827401
源代码如下:

package com.example.wxbloopinthread;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    private static TextView tv = null;

    //自定义Message类型
    public final static int MESSAGE_WXB_1 = 1;

    //主线程中创建Handler
    private static Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what){
            case MESSAGE_WXB_1:
                tv.setText("主线程发送,子线程接收消息后回发,主线程修改UI");
                break;
            }
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);     
        setContentView(R.layout.activity_main);
        tv = (TextView) this.findViewById(R.id.textView1);

        //创建子线程
        new LooperThread().start();

        //点击按钮向子线程发送消息
        Button btn = (Button) this.findViewById(R.id.btnSendMessage);
        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                LooperThread.sHandler.sendEmptyMessage(MESSAGE_WXB_1);
            }
        });
    }

    //定义子线程
    static class LooperThread extends Thread {
        public static Handler sHandler = null;

        public void run() {
            //创建消息循环
            Looper.prepare();

            sHandler = new Handler() {
                public void handleMessage(Message msg) {
                    switch(msg.what){
                    case MESSAGE_WXB_1:
                        mHandler.sendEmptyMessage(MESSAGE_WXB_1);
                        break;
                    }
                }
            };
            //开启消息循环
            Looper.loop();
        }
    }
}

第二个例子使用了另一个重要的类Looper,它是代表Android中的消息循环处理类。

Looper

Looper被用来创建一个线程的消息循环。线程默认情况下是没有消息循环的,要创建消息循环,必须先调用Looper.prepare,然后在合适的地方调用Looper.loop,这个loop方法就开始循环处理本线程接收到的消息,直到loop循环被停止。
在大部分情况下,Looper都是和Handler一起使用,它通过Handler接收和处理消息。
使用Looper要注意以下几点:
1. Android的主线程是默认已经创建了Looper对象的,因此不能在主线程中调用Looper.prepare;
2. Looper.prepare和Looper.loop都是静态方法,调用时要注意,不要使用new来创建一个Looper;

总结

使用Android消息的方法有以下几种:
1. 使用Handler和Runnable,即时或延时发送一个消息;
2. 使用Handler和Message,即时或延时发送一个消息,需重载Handler. handleMessage方法;
需要注意的规则有以下几条:
1. 只有创建view的线程能够更新此view;一般来说,创建UI的是Android主线程,因此只有在主线程中才能更新UI;
2. 处理消息的类是Handler,它依附于创建自己的线程;如果在主线程中创建Handler mHandler,则向mHandler发送的消息会在主线程中被解析;如果在子线程中创建Handler sHandler,则向sHandler发送的消息会在子线程中被解析;
3. Looper被用来创建一个线程的消息循环,要创建消息循环,必须先调用Looper.prepare,然后在合适的地方调用Looper.loop。
具体的体会,还是要多看代码才行。