Android 消息传递 - Handler

Android 为了线程安全,不允许我们在 UI 线程外操作 UI

但是,经常,我们需要 UI 线程之外刷新界面,比如异步下载完成要更新 UI,比如数据下载成功后要通知 UI 数据已经刷新,需要重绘

这样的情况下,要怎么做呢?

不用着急

我们有很多方式可以达到异步更新 UI 的目的,比如 runOnUiThread() ,或者更高级的 事务总线

当然了,如标题所言,我们这章节只讲 Android 提供了 Handler

Handler

Android 提供了 android.os.Handler 类用于通知 UI 更新,当然了,它能做的事不止这些

我们先来看看 Handler 的执行流程

名词 说明
UI 线程 就是主线程,系统在创建 UI 线程的时候会初始化一个 Looper 对象,同时也会创建一个与其关联的 MessageQueue;
Handler 作用就是发送与处理信息,如果希望 Handler 正常工作,在当前线程中要有一个 Looper 对象
Message Handler接收与处理的消息对象
MessageQueue 消息队列,先进先出管理 Message,在初始化 Looper 对象时会创建一个与之关联的 MessageQueue
Looper 每个线程只能够有一个 Looper,管理 MessageQueue,不断地从中取出 Message 分发给对应的 Handler 处理

因此,如果子线程想修改 Activity 中的 UI 组件,就需要新建一个 Handler 对象

然后通过这个对象向主线程发送信息,而发送的信息会先到主线程的 MessageQueue 进行等待,由 Looper 按先入先出顺序取出,再根据 message 对象的 what 属性分发给对应的 Handler 进行处理

Handler 方法

方法 说明
void handleMessage(Message msg) 处理消息的方法,一般都会被重写
sendEmptyMessage(int what) 发送空消息
sendEmptyMessageDelayed(int what,long delayMillis) 指定延时多少毫秒后发送空信息
sendMessage(Message msg) 立即发送信息
sendMessageDelayed(Message msg) 指定延时多少毫秒后发送信息
boolean hasMessage (int what) 检查消息队列中是否包含 what 属性为指定值的消息
如果是参数为 (int what,Object object),除了判断 what 属性,还需要判断 Object 属性是否为指定对象的消息

Handler 的使用

Handler 的应用场景有两种

  1. Handler 写在主线程中
  2. Handler 写在子线程中

下面我们就分别对不同的场景写一个范例来演示一下如何使用

Handler 写在主线程中

主线程中,系统已经初始化了一个 Looper 对象

我们可以直接创建 Handler 对象,然后就可以进行信息的发送与处理

范例

我们通过 Timer定时器,定时修改 ImageView 显示的内容,从而形成帧动画

  1. 创建一个 空的 Android 项目 cn.twle.android.HandlerMessage

  2. 下载 /static/i/android/tuzi.zip 并把所有的图片拖到 res/drawable 目录下

  3. 修改 activity_main.xml 添加一个 ImageView

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:gravity="center" >
    
        <ImageView  
            android:id="@+id/tuzi"  
            android:layout_width="48dp"  
            android:layout_height="48dp"
            android:src="@drawable/tuzi_1"
            android:layout_alignParentLeft="true"  
            android:layout_alignParentTop="true" />
    
    </RelativeLayout>
    
  4. 修改 MainActivity.java

    package cn.twle.android.handlermessage;
    
    import android.support.v7.app.AppCompatActivity;
    import android.widget.ImageView;
    import android.os.Handler;
    import android.os.Message;
    import android.os.Bundle;
    
    import java.util.TimerTask;
    import java.util.Timer;
    
    public class MainActivity extends AppCompatActivity {
    
        private ImageView tuzi;
    
        //定义切换的图片的数组id  
        int imgids[] = new int[]{  
            R.drawable.tuzi_1,
            R.drawable.tuzi_2,
            R.drawable.tuzi_3,  
            R.drawable.tuzi_4,
            R.drawable.tuzi_5,
            R.drawable.tuzi_6,  
            R.drawable.tuzi_7,
            R.drawable.tuzi_8,
            R.drawable.tuzi_9,
            R.drawable.tuzi_10,
            R.drawable.tuzi_11,
            R.drawable.tuzi_12
        };  
        int imgstart = 0;
    
        final Handler myHandler = new Handler()  
        {  
            @Override  
            //重写 handleMessage 方法,根据 msg  what的 值判断是否执行后续操作  
            public void handleMessage(Message msg) {  
                if(msg.what == 0x123) {
    
                    tuzi.setImageResource(imgids[imgstart++ % 12]);  
                }  
            }  
        };
    
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            tuzi = (ImageView) findViewById(R.id.tuzi);
    
            //使用定时器,每隔 100 毫秒让 handler 发送一个空信息  
            new Timer().schedule(new TimerTask() {            
                @Override  
                public void run() {  
                    myHandler.sendEmptyMessage(0x123);
    
                }  
            }, 0,200);  
        }
    
    }
    

运行效果如下


Handler 写在子线程中

如果将 Handler 写在子线程中的,那就要我们自己创建一个 Looper 对象

  1. 调用 Looper.prepare() 方法为当前线程创建 Looper对象,而它的构造器会创建配套的 MessageQueue

  2. 创建 Handler 对象,重写 handleMessage() 方法处理来自于其它线程的信息

  3. 调用 Looper.loop() 方法启动 Looper

范例

输入一个数,计算后通过 Toast 输出在这个范围内的所有质数

  1. 创建一个 空的 Android 项目 cn.twle.android.HandlerMessage

  2. 修改 activity_main.xml 添加一个输入框和一个按钮

    <LinearLayout  
        xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:orientation="vertical">  
        <EditText  
            android:id="@+id/etNum"  
            android:inputType="number"  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            android:hint="请输入上限"/>  
        <Button  
            android:layout_width="match_parent"  
            android:layout_height="wrap_content"  
            android:onClick="cal"  
            android:text="计算"/>    
    </LinearLayout>
    
  3. 修改 MainActivity.java

    package cn.twle.android.handlermessage;
    
    import android.os.Looper;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Handler;
    import android.os.Message;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.Toast;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MainActivity extends AppCompatActivity {
    
        static final String UPPER_NUM = "upper";  
        EditText etNum;  
        CalThread calThread;  
        // 定义一个线程类  
        class CalThread extends Thread  
        {  
            public Handler mHandler;
    
            public void run()  
            {  
                Looper.prepare();  
                mHandler = new Handler()  
                {  
                    // 定义处理消息的方法  
                    @Override  
                    public void handleMessage(Message msg)  
                    {  
                        if(msg.what == 0x123)  
                        {  
                            int upper = msg.getData().getInt(UPPER_NUM);  
                            List<Integer> nums = new ArrayList<Integer>();  
                            // 计算从2开始、到upper的所有质数  
                            outer:  
                            for (int i = 2 ; i <= upper ; i++)  
                            {  
                                // i处于从2开始、到i的平方根的所有数  
                                for (int j = 2 ; j <= Math.sqrt(i) ; j++)  
                                {  
                                    // 如果可以整除,表明这个数不是质数  
                                    if(i != 2 && i % j == 0)  
                                    {  
                                        continue outer;  
                                    }  
                                }  
                                nums.add(i);  
                            }  
                            // 使用Toast显示统计出来的所有质数  
                            Toast.makeText(MainActivity.this , nums.toString()  
                                , Toast.LENGTH_LONG).show();  
                        }  
                    }  
                };  
                Looper.loop();  
            }  
        }  
        @Override  
        public void onCreate(Bundle savedInstanceState)  
        {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            etNum = (EditText)findViewById(R.id.etNum);  
            calThread = new CalThread();  
            // 启动新线程  
            calThread.start();  
        }  
        // 为按钮的点击事件提供事件处理函数  
        public void cal(View source)  
        {  
            // 创建消息  
            Message msg = new Message();  
            msg.what = 0x123;  
            Bundle bundle = new Bundle();  
            bundle.putInt(UPPER_NUM ,  
                Integer.parseInt(etNum.getText().toString()));  
            msg.setData(bundle);  
            // 向新线程中的Handler发送消息  
            calThread.mHandler.sendMessage(msg);  
        }  
    }
    

运行效果如下


参考文档

  1. Android Handler

Android 基础教程

关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.