Android 线程通信 —— Handler 使用

Android 线程通信 —— Handler 使用

Android 应用是一个多线程应用,Android 有一个主线程,在没有特定指明线程的前提下,你执行的代码都是在 Android 主线程中运行。一般来说 Android 的 View 绘制是在主线程中完成的,所以如果我们在主线程中进行耗时操作时,线程无响应就会导致 UI 绘制等其它用户所看到的东西和执行的操作卡住,给用户带来不好的体验。所以 Android 中规定了 ANR(Application Not Responding:应用无响应)。

ANR 规定当 Activity 中 5s 无响应,BroadcastReceiver 中 10s 无响应,Service 中 20s(前台)、200s(后台)无响应就会告诉用户选择继续等待或者关闭应用。所以我们要尽量避免避免 ANR。当然一款舒服的应用,就算没有规定 ANR,我们也不应该在主线程中进行耗时操作给用户带来不好的体验。

所以当我们使用多线程时,不可避免就会遇到线程通信。Android API 给我们提供了很多线程通信的方法。这里我们先来学习一下 Handler 的使用。

Handler 介绍

Handler 是 Android os 包中的一个用于进程内部、线程间通信的 API,Handler 的使用一般包含 Hander、Message、MessageQueue、Looper。

Handler 主要用于处理异步消息,使用 Hander 发出一条 Message 时,Message 会进入一个消息队列 MessageQueue 中,Looper 会循环读取 MessageQueue 中的 Message,并且将取出的 Message 交给 Handler 处理。

本文只对 Handler 的用法做一个分享,个中细节后面文章一起学习。

Handler 使用

一般我们在使用 Handler 时,只会使用到 Handler 和 Message,使用 Handler 的 sendMessage() 等方法发消息,重写 handleMessage() 去处理接收到的消息。

在看 Handler 发送和接受消息前,我们先看一下 Handler 发送的 Message 是什么。Message 是消息,也是我们 Handler 中通信的载体,Message 中有几个公有的字段,用作定义和描述任意数据对象的消息。

Message 中定义数据的包含两个 int 字段:arg1 和 arg2,还包含一个 Object 字段:obj,Message 中还有一个 int 类型的 waht 字段,作为 message code,来区分这条 message 是干什么的或者从哪来的。Messsage 的构造方法是 public 的,但是官方建议使用 Message.obtain()。

// 不建议使用
Message msg1 = new Message();
// 该方法会从全局池中返回一个 Message,避免重复创建对象
Message msg2 = Message.obtain();

Message 中的 obtain() 方法被重载了,支持传入 handler、what、arg1、arg2、obj 等入参。

简单介绍了一下 Message,我们可以正式开始看 Handler 的使用了,当然在这之前我们得先看一下如何创建一个 Handler。

Handler 有 7 个构造方法,这 7 个大家可以去看一下 Handler 类,这里就不一一贴出来了,主要说一下这 7 个构造方法干了什么。7 个构造方法主要是选择传入了 3 个参数:looper : Looper、callback : Callback、async : boolean。这三个参数主要是干什么的呢?Looper 就是上面说的用来循环读取 MessageQueue 中的 Message,CallBack 是一个接口,只有一个 handleMessage(),作用与 Handler 中 handleMessage() 方法类似,用于处理 Messsage。而且在 Handler 源码中 Handler 的 handleMessage() 和 Callback 的 handleMessage() 方法是在一起调用的。async 是干什么的呢?看名字就知道用于线程同步,如果 async 为 true,那么 Message 插入到 MessageQueue 中的过程就是异步的。但是所有带 async 的构造方法都适用了 hide 注解(hide:该 Api 不对外公开),所以我们正常来说可以使用的只有 4 个构造方法。

private Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // 处理 Message
            
            return false;
        }
    };
// 方式 1
private Handler handler = new Handler(Looper.getMainLooper(),callback);
// 方式 2
private Handler handler2 = new Handler(callback);

​ 一个 Handler 必须要持有一个 Looper,没有 Looper 的 Handler 在构造或使用时会抛出异常,这时候你们会很奇怪,那么方式 2 还有什么意义呢?其实,当调用没有 Looper 的构造方法时,Handler 会去拿当前线程的 Looper,而 Android 的主线程会自己在进程开始时创建 Looper 的,但是如果你在子线程使用方式 2 时,就会抛出异常了。所以不太建议使用方式 2。

那么我们如何生成 Looper 呢?

    // 获取主线程 Looper
    private Looper mainLooper = Looper.getMainLooper();

    // 子线程中只需要调用 
    Looper.prepare();

Handler 的发送 Message 有很多种方法,这里我们先看一下最简单的使用:

    private void sendMessage(int arg1, int arg2, Object obj){
        
        Message message = Message.obtain();
        message.arg1 = arg1;
        message.arg2 = arg2;
        message.obj = obj;
        // Handler 将 message 发送出去
        handler1.sendMessage(message);

    }

如果我们使用 Handler 是为了线程通信,那么 sendMessage() 和 handleMessage() 分别在不同的线程处理就可以。那怎么处理 Message 呢?我们可以使用 CallBack 的 handleMessage() 或者 Handler 的 handleMessage():

    private Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // 处理 Message

            return false;
        }
    };

    private Handler handler1 = new Handler(Looper.getMainLooper(),callback){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 处理 msg
        }
    };

我们可以看到,Callback 的 handleMessage() 需要返回一个 boolean 值,返回 true 就会拦截事件,CallBack 处理完成后不交给 Handler 处理了,如果返回 false 则 Callback 处理完成后还会交给 Handler 处理。

我们来看一个具体示例:

Case:网络下载一张图片,成功展示图片,失败展示吐司消息:


    private Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // 处理 Message

            return false;
        }
    };

    private Handler handler1 = new Handler(Looper.getMainLooper(),callback){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 处理 msg
            
            if (msg != null && msg.obj instanceof Bitmap){
                Bitmap bitmap = (Bitmap) msg.obj;
                imageView.setImageBitmap(bitmap);
            }else {
                Toast.makeText(context,"Fail",Toast.LENGTH_SHORT).show();
            }
        }
    };

    private void getImage(String url){

        Thread thread = new Thread(){
            @Override
            public void run() {
                super.run();
                // 下载图片,这里为伪代码
                Bitmap bitmap = DownloadUtil.download(url);
                Message msg = Message.obtain();
                
                if (bitmap != null){
                    msg.obj = bitmap;
                }else {
                    msg.obj = null;
                }
                handler1.sendMessage(msg);
            }
        };
        
        thread.start();

    }

上面是一个子线程与主线程的简单通信,Handler 发送消息的方法还有很多,我们接着来看。

// sendMessage 使用的是 sendMessageDelayed()
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

// sendMessageDelayed() 使用的是 sendMessageAtTime()
// 该方法指一定在 delayMillis 后才可能处理该 Message
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    // SystemClock.uptimeMillis() 方法是取不包含深度休眠(锁屏等)在内的开机到现在的时间
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// sendMessageAtTime() 使用的是 enqueueMessage()
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);
}

Handler 还有 post() 方法,其实 post 方法也是调用的 send() 方法,传进来的 Runnable 对象赋值给了 Message 的 Callback:

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
// getPostMessage() 是 Message.obtain() 了一个 Message,并且将 Runnable 作为了 Message 的 Callback
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

那么 Message 的 Callback 是干什么的呢?上面说到,我们使用 sendMessage() 时,需要去 Handler.Callback 或者是 handleMessage() 去处理消息,而使用 post() 时,我们就可以不用在上述方法中去处理消息,当 Looper 循环从 MessageQueue 中取出这个消息时,就会运行 callback 的 run 方法。相当于代替了上面的 handleMessage 或者 Handler.Callback 的代码:

private Handler handler = new Handler(Looper.getMainLooper());
public void dealData(String msg){
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 子线程中去使用 Handler post 一个消息,没有 start,所以并不是开启了一个新线程
            handler.post(new Runnable() {
                @Override
                public void run() {
                    textView.setText(msg);
                }
            });
        }
    }).start();
}

而还有更多的 post 方法,实现逻辑是一样的,只不过是调用了不同的 send() 方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是Android开发与应用,原书配套的课件,作者张荣,目录如下。 第1章 Android简介 1.1 手机操作系统 1.2 Android起源 1.3 Android特征 1.4 Android体系结构 1.4.1 应用层 1.4.2 应用框架层 1.4.3 系统库层 1.4.4 内核层 1.5 小结 练习 第2章 Android开发环境 2.1 Java开发环境安装 2.1.1 安装JDK 2.1.2 安装Eclipse 2.1.3 Eclipse中文包的安装 2.2 Android SDK 2.2.1 安装ADT 2.2.2 安装Android SDK 2.3 Android模拟器 2.3.1 创建AVD 2.3.2 开发环境测试 2.3.3 模拟器的使用 2.4 SDK中的常用命令 2.4.1 adb命令 2.4.2 Android命令 2.5 小结 练习 第3章 Android应用程序框架 3.1 第一个Android应用程序 3.2 Android项目结构 3.3 APK文件结构 3.4 Android应用程序权限 3.5 Activity及其生命周期 3.5.1 什么是Activity 3.5.2 Activity生命周期 3.6 Intent简介 3.6.1 Intent属性与过滤器 3.6.2 Intent启动系统Activity 3.7 小结 练习 第4章 视图组件 4.1 视图组件的使用模式 4.1.1 视图组件的定义 4.1.2 资源的访问 4.1.3 生成视图组件资源标识 4.1.4 视图组件的引用 4.1.5 视图组件的事件响应 4.1.6 组件的常用属性 4.2 常用组件 4.2.1 文本框 4.2.2 编辑框 4.2.3 图片按钮 4.2.4 图片视图 4.2.5 单选按钮 4.2.6 复选按钮 4.2.7 下拉列表 4.2.8 自动完成文本框 4.2.9 日期、时间选择器 4.3 高级组件 4.3.1 进度条 4.3.2 拖动条 4.3.3 评分条 4.3.4 选项卡 4.4 提示框与警告对话框 4.4.1 消息提示框 4.4.2 警告对话框 4.5 小结 练习 第5章 视图界面布局 5.1 界面布局设计 5.1.1 线性布局 5.1.2 表格布局 5.1.3 帧布局 5.1.4 相对布局 5.1.5 绝对布局 5.1.6 复用XML布局文件 5.2 控制视图界面的其他方法 5.2.1 代码控制视图界面 5.2.2 代码和XML联合控制视图界面 5.3 多界面的使用 5.3.1 使用Intent封装数据 5.3.2 使用Bundle封装数据 5.3.3 获取另一个界面返回结果 5.4 小结 练习 第6章 Android数据存储与共享 6.1 数据存储与共享方式概述 6.2 首选项信息 6.2.1 私有数据存储 6.2.2 公有数据存储与共享 6.3 数据文件 6.3.1 内存数据文件 6.3.2 SD卡数据文件 6.4 SQLite数据库 6.4.1 SQLite基本操作 6.4.2 SQLiteOpenHelper 6.5 Content Provider 6.5.1 使用Content Provider发布数据 6.5.2 使用Content Resolver获取数据 6.6 小结 练习 第7章 多线程及消息处理 7.1 Android线程概述 7.1.1 创建线程 7.1.2 操作线程 7.2 UI线程与非UI线程 7.3 多线程中的常用类 7.3.1 Handler类 7.3.2 AsyncTask类 7.3.3 Timer定时器 7.4 Android线程通信机制 7.5 小结 练习 第8章 网络通信 8.1 通过HTTP访问网络 8.1.1 测试用Web服务器 8.1.2 WebView组件 8.1.3 HttpURLConnection 8.2 Socket编程 8.3 数据的解析 8.3.1 JSON数据解析 8.3.2 XML数据解析 8.4 Web Service访问 8.5 小结 练习 第9章 多媒体应用 9.1 音频与视频的播放 9.1.1 MediaPlayer 9.1.2 SoundPool 9.1.3 VideoView 9.1.4 SurfaceView 9.2 摄像头的使用 9.2.1 摄像头意图Intent 9.2.2 Camera类 9.3 小结 练习 实验一 Android开发环境搭建 实验二 界面设计:基本组件 实验三 界面设计:布局管理器 实验四 多线程应用 实验五 基于文件的日程安排 实验六 基于SQLite的通信录 实验七 天气预报应用 实验八 音乐播放器及相机拍摄

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值