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。

具体的体会,还是要多看代码才行。

时间: 2024-10-07 05:28:31

Android中的消息处理实例与分析的相关文章

Android中的软件安全和逆向分析[二]—apk反破解技术与安全保护机制

在Android应用开发中,当我们开发完软件之后,我们不希望别人能够反编译破解我们的应用程序,不能修改我们的代码逻辑.实际上,在应用程序的安全机制考虑中,我们希望自己的应用程序安全性高,通过各种加密操作等来增大竞争对手的反编译破解成本.设想,竞争对手开发一个同样的应用程序需要10天,而破解我们的软件程序需要100天,那么势必会打消黑客程序员破解我们应用程序的念头.如何增加对手的破解成本,就需要考验我们应用程序的安全性有多高,加密技术有多强.一个优秀的应用程序,不仅能为用户带来利益,同时也能保护自

Android中相机和相册使用分析

Android中相机和相册使用分析 欢迎转载,但请尊重原创(文章来自不易,转载请标明转载出处,谢谢) 在手机应用程序中,使用自带的相机拍照以及相册选择喜欢的图片是最常见不过的用户需求,那么怎么合理使用相机和相册来选择照片是重要的,下面就以项目中实际需求为例进行说明,这里实现的功能如下: 1 使用相机和相册选择图片,并裁剪较小图片(常用于剪裁小图) 2 使用相机和相册选择图片,并裁剪较大图片(常用于裁剪大图) 具体的实现功能清楚了,那么就一一进行说明,具体如下(这里不会罗列怎么上传图片到服务端,只

Android中app卡顿原因分析示例

在知乎回答了一个“为什么微博的app在iPhone比Android上流畅”的问题.后面部分是一个典型的动画卡顿的性能分析过程,因此帖在这里.有编程问题可以在这里交流.知乎链接. ========================================================= 我来说下我所知道的事情.我不知道iOS为什么流畅,但我知道一些Android为什么不流畅的原因. 首先,就题主所说的问题,我用iPad和小米Pad对比了一下微博滑动滚屏这件事情(2014年8月10日目前微博

android中倒计时控件CountDownTimer分析

android中倒计时控件CountDownTimer分析 1 示例代码 new CountDownTimer(10000, 1000) { public void onTick(long millisUntilFinished) { LogUtil.i(TAG, "seconds remaining: " + millisUntilFinished / 1000); } public void onFinish() { LogUtil.i(TAG, "done!"

Android中线程间通信原理分析:Looper,MessageQueue,Handler

自问自答的两个问题 在我们去讨论Handler,Looper,MessageQueue的关系之前,我们需要先问两个问题: 1.这一套东西搞出来是为了解决什么问题呢? 2.如果让我们来解决这个问题该怎么做? 以上者两个问题,是我最近总结出来的,在我们学习了解一个新的技术之前,最好是先能回答这两个问题,这样你才能对你正在学习的东西有更深刻的认识. 第一个问题:google的程序员们搞出这一套东西是为了解决什么问题的?这个问题很显而易见,为了解决线程间通信的问题.我们都知道,Android的UI/Vi

Android中RelativeLayout和LinearLayout性能分析

先看一些现象吧:用eclipse或者Android studio,新建一个Activity自动生成的布局文件都是RelativeLayout,或许你会认为这是IDE的默认设置问题,其实不然,这是由 android-sdk\tools\templates\activities\BlankActivity\root\res\layout\activity_simple.xml.ftl 这个文件事先就定好了的,也就是说这是Google的选择,而非IDE的选择.那SDK为什么会默认给开发者新建一个默认的

Android中异步消息处理机制

1. Thread Local Storage (线程局部存储)      我们通过位于android.os包下的Looper.class源码可以看到成员变量区有一个线程局部变量sThreadLocal,该类的作用是线程局部存储?那么是线程局部存储TLS?这个问题可以从变量作用域的角度来理解. 变量的常见作用域一般包括以下几种. 函数内部变量.其作用区域是该函数,即每次调用该函数,该变量都会重新回到初始值. 类内部的变量.其作用就是该类所产生的对象,即只要该对象没有被销毁,则对象内部的变量则一直

Android中的NestedScrollingParent和NestedScrollingChild分析

在分析SwipeRefreshLayout源码的时候发现该类实现了NestedScrollingParent和NestedScrollingChild两个接口,甚是好奇,于是结合了网上的资料,然后根据我个人的理解写下本章. 这个两个接口是为了更好解决事件冲突的. 在这里 nested scrolling 就翻译为嵌套滚动吧. 但是这和以前用过的dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent和requestDisallowIntercep

Coredump介绍及如何在Android中开启和使用来分析Crash等问题

文章目录: Coredump简介及使用... 1 目录... 2 一.什么是Coredump. 3 二.Coredump产生的原因... 3 三.如何控制产生Coredump. 4 四.使用Coredump的准备... 4 五.开始使用Coredump. 5 一.什么是Coredump 有些C/C++程序或者通过JNI调用了C/C++的APK程序可以通过编译, 但在运行时会出现错误,比如常见的signal 11 (SIGSEGV),这样的程序都是可以通过编译的,而且这样的错误一般情况下不会像编译