Android异步消息处理机制完全解析-Handler详解

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 参考资料 - 官方介绍文档 - Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系 - Android异步消息处理...

参考资料
- 官方介绍文档
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- 慕课网课程-Android面试常客Handler详解

如果在非UI线程中更新UI会出现问题吗?

实践:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.id_tv)
    TextView idTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000*5);
                    idTv.setText("Javen205测试非UI线程更新UI会出现什么异常呢?");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

我们运行项目就会以下异常

非UI线程中更新UI出现

为了更直观的看到报错原因,我们找到源码ViewRootImpl的checkThread方法,看它做了些什么。

ViewRootImpl.checkThread()
但是Android为什么要这样搞呢?

我们仔细看这句话,只有创建了View的线程才能对这个View进行操作。而我们一般View都是为了显式在UI上的。Android正是为了防止我们在非UI线程去操作这些UI上的控件,才加了限制的。因为UI体验对用户来说是最直观的,如果谁都有权限去操作一下,那UI要么很乱,要么控制很复杂。

竟然Android是不允许我们在非UI线程中去执行更新UI,那我们要怎么解决这个问题呢?那我们就要使用Android 提供的Hander机制去更新UI了

一、什么是Handler

Handler是Android提供的用来更新UI的一套机制,也是一套消息处理机制,我们可以通过它发送消息,也可以通过它处理消息。

二、为什么要使用Handler

Android在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新UI信息,就会抛出异常。

三、Handler怎么用呢?

  • 在非UI线程借助Handler.post(Runnable)更新UI

首先在Activity中实例化一个Hander

Handler handler = new Handler();

然后在子线程中调用Handler.post(Runnable)更新UI
详细的代码如下:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.id_tv)
    TextView idTv;

    Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000*5);
//                    idTv.setText("Javen205测试非UI线程更新UI会出现什么异常呢?");
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            idTv.setText("Javen205测试非UI线程更新UI会出现什么异常呢?");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}
  • 在非UI线程借助Handler.postDelayed(Runnable, DelayTime)定时执行相关动作

栗子:实现三张图片自动切换

实现三张图片自动切换

 private  int images[]= {R.drawable.image4,R.drawable.image6,R.drawable.image7};

 private int index=0;

Handler handler = new Handler();

Thread myThread = new Thread(){
        @Override
        public void run() {
            index++;
            index = index%3;
            System.out.println(index);
            idImg.setImageResource(images[index]);
            handler.postDelayed(myThread,1000);
        }
    };


handler.postDelayed(myThread,1000);

要是我们想停止图片的切换,那要如何操作呢?

  • Handler移除一个消息
handler.removeCallbacks(myThread);
  • 自定义Handler和Message

自定义Hander(customHander)

Handler customHander = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            idTv.setText("msg.arg1>"+msg.arg1+ "\nmsg.arg2>" +msg.arg2 +"\nmsg.obj>"+((Dog)msg.obj).toString());
        }
    };

实体类

package com.javen205.entity;

import java.io.Serializable;



public class Dog implements Serializable{
    private String name;
    private int  age;

    public Dog() {
    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

使用只定义Hander(customHander)发送消息

Dog dog=new Dog("萨摩耶",1);
Message message = new Message();
                message.arg1 = 1;
                message.arg2 = 2;
                message.obj = dog;
                customHander.sendMessage(message);               

复用Message

 Dog dog=new Dog("萨摩耶",1);
//                Message message = new Message();
                Message message= customHander.obtainMessage();
                message.arg1 = 1;
                message.arg2 = 2;
                message.obj = dog;
                customHander.sendMessage(message);

原理:obtainMessage()方法做了哪些操作呢?

 /**
     * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
     * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
     *  If you don't want that facility, just call Message.obtain() instead.
     */
    public final Message obtainMessage()
    {
        return Message.obtain(this);//this 就是Hander本身
    }

我们看看Message.obtain(Handler h) 又做了哪些操作呢?

 /**
     * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h; 

        return m;
    }

target就是Hander自己,指消息要发送给谁

再来看看obtain()方法

/**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

这个方法就是跟我们平常new Message 对象一样 ,只不过是在之前添加了一判断,判断系统中是否存在空的Message,如果存在就直接返回否则就创建一个Message对象。

其实发送消息也可以这样玩,不使用直接使用Hander而是使用Message的sendToTarget()方法,代码如下。

 Dog dog=new Dog("萨摩耶",1);
//                Message message = new Message();
                Message message= customHander.obtainMessage();
                message.arg1 = 1;
                message.arg2 = 2;
                message.obj = dog;
//                customHander.sendMessage(message);
                message.sendToTarget();

我们来看看Message.sendToTarget()方法源码

 /**
     * Sends this Message to the Handler specified by {@link #getTarget}.
     * Throws a null pointer exception if this field has not been set.
     */
    public void sendToTarget() {
        target.sendMessage(this);
    }

上面提到过target就是Hander自己,其实就是调用Hander自己的一个sendMessage,跟我们普通发送Message没有什么区别只是里面封装了一个target,本质还是调用了Hander.sendMessage(…)

  • Handler 消息拦截使其接收者接收不到消息

自定义Handler(interceptHander)拦截消息

Handler interceptHander = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            System.out.println("is intercept Handler>"+msg.what);
            // 设置true拦截消息
            return true;
        }
    }){
        @Override
        public void handleMessage(Message msg) {
            System.out.println("is intercept Handler");
        }
    };

interceptHander发送一个消息Message

 interceptHander.sendEmptyMessage(1);

四、Handler几种发送消息方式之间区别

可以参考:Handler发送sendMessage和postRunnable的区别

五、Handler的原理是什么?

handler原理图

  • Handler封装了消息的发送:内部会跟Looper关联

  • Looper(消息封装的载体):内部包含一个消息队列(MessageQueue),所有Handler发送的消息都会走向这个消息队列;
    Looper.Looper方法是一个死循环,不断的从MessageQueue取消息,如果有消息就处理消息,没有消息就阻塞。

  • MessageQueue(消息队列):可以添加消息,并处理消息

总结:Handler负责发送消息,Looper负责接收Handler发送的消息,并直接把消息回传给Handler自己(handleMessage),MessageQueue就是一个存储消息的容器。

六、自定义一个与线程相关的Handler

1、线程中创建一个Looper 可以使用 Looper.prepare();方法
2、实例化一个Handler
3、调用Looper.loop();方法循环处理消息

public class HandlerActivity extends AppCompatActivity {
    private MyThread myThread;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            System.out.println("UI :"+Thread.currentThread());
        }
    };

    class MyThread extends Thread{
        public Handler handler;
        @Override
        public void run() {
            Looper.prepare();

            handler= new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    System.out.println("currentThread:"+Thread.currentThread());
                }
            };

            Looper.loop();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        myThread = new MyThread();
        myThread.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread.handler.sendEmptyMessage(1);
        handler.sendEmptyMessage(1);
    }
}

输出结果:

System.out: currentThread:Thread[Thread-151,5,main]
System.out: UI :Thread[main,5,main]

七、非UI线程真的不能更新UI吗?

我们运行下面的代码测试:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        new Thread() {
            @Override
            public void run() {
                idTv.setText("Javen205测试非UI线程更新UI会出现什么异常呢?");
            }
        }.start();
    }

最开始的案例中我们去掉Thread.sleep(…)会正常运行吗?答案:可正常运行。是不是感觉有点奇怪呢?那为什么直接在Activity的onCreate中添加子线程可以直接更新UI呢?

详细解答: 为什么我们可以在非UI线程中更新UI

八、Handler异步消息处理(HandlerThread)

Android HandlerThread 完全解析

Android异步消息处理机制完全解析,带你从源码的角度彻底理解

一个简单的例子

public class HandlerThreadActivity extends AppCompatActivity {
    private Handler handler;
    private HandlerThread thread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        thread = new HandlerThread("Handler Thread");
        thread.start();
        handler = new Handler(thread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                // 处理耗时操作
               System.out.println("current thread>"+Thread.currentThread());
            }
        };
        handler.sendEmptyMessage(1);
    }
}

测试输出结果
I/System.out: current thread>Thread[Handler Thread,5,main]

九、如何在主线程给子线程发送消息


public class HandlerThreadActivity extends AppCompatActivity {
    @BindView(R.id.id_btn1)
    Button idBtn1;
    @BindView(R.id.id_btn2)
    Button idBtn2;
    private Handler threadhandler;
    private HandlerThread thread;

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            System.out.println("UI thread>" + Thread.currentThread());
            // 给主线程发送消息
            Message message = new Message();
            message.what =1;
            threadhandler.sendMessageDelayed(message, 1000);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread);
        ButterKnife.bind(this);
        thread = new HandlerThread("Handler Thread");
        thread.start();
        threadhandler = new Handler(thread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // 处理耗时操作
                System.out.println("current thread>" + Thread.currentThread());
                // 给主线程发送消息
                Message message = new Message();
                message.what =1;
                handler.sendMessageDelayed(message, 1000);
            }
        };
//        threadhandler.sendEmptyMessage(1);
    }

    @OnClick({R.id.id_btn1, R.id.id_btn2})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.id_btn1:
                handler.sendEmptyMessage(1);
                break;
            case R.id.id_btn2:
                handler.removeMessages(1);
                threadhandler.removeMessages(1);
                break;
        }
    }
}

十、Android中更新UI的几种方式

  • handler sendMessage
  • runOnUIThread
  • handler post
  • view post

项目源码地址:https://github.com/Javen205/Hander

推荐阅读
AndroidStudio多渠道打包
Android依赖管理与私服搭建
Android Studio 上传aar(Library)到JCenter
Android版-支付宝APP支付
Android版-微信APP支付
支付宝Wap支付你了解多少?
一张二维码集成微信、支付宝支付

安利时间:
JPay是对微信App支付、支付宝App支付的二次封装,对外提供一个相对简单的接口以及支付结果的回调

极速开发微信公众号是对微信公众平台接口的二次封装。包括开发者模式、事件回调监听、微信模板消息、微信客服消息、自定义菜单、微信支付、素材管理等

如遇到问题欢迎留言交流

目录
相关文章
|
1月前
|
Java 开发工具 Android开发
Android与iOS开发环境搭建全解析####
本文深入探讨了Android与iOS两大移动操作系统的开发环境搭建流程,旨在为初学者及有一定基础的开发者提供详尽指南。我们将从开发工具的选择、环境配置到第一个简单应用的创建,一步步引导读者步入移动应用开发的殿堂。无论你是Android Studio的新手还是Xcode的探索者,本文都将为你扫清开发道路上的障碍,助你快速上手并享受跨平台移动开发的乐趣。 ####
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
25天前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
23天前
|
PHP 开发者 UED
PHP中的异常处理机制解析####
本文深入探讨了PHP中的异常处理机制,通过实例解析try-catch语句的用法,并对比传统错误处理方式,揭示其在提升代码健壮性与可维护性方面的优势。文章还简要介绍了自定义异常类的创建及其应用场景,为开发者提供实用的技术参考。 ####
|
27天前
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
27天前
|
缓存 NoSQL Java
千万级电商线上无阻塞双buffer缓冲优化ID生成机制深度解析
【11月更文挑战第30天】在千万级电商系统中,ID生成机制是核心基础设施之一。一个高效、可靠的ID生成系统对于保障系统的稳定性和性能至关重要。本文将深入探讨一种在千万级电商线上广泛应用的ID生成机制——无阻塞双buffer缓冲优化方案。本文从概述、功能点、背景、业务点、底层原理等多个维度进行解析,并通过Java语言实现多个示例,指出各自实践的优缺点。希望给需要的同学提供一些参考。
46 7
|
26天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
1月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
64 8
|
1月前
|
Java 测试技术 API
Java 反射机制:深入解析与应用实践
《Java反射机制:深入解析与应用实践》全面解析Java反射API,探讨其内部运作原理、应用场景及最佳实践,帮助开发者掌握利用反射增强程序灵活性与可扩展性的技巧。
105 4
|
1月前
|
存储 消息中间件 算法
深入探索操作系统的心脏——内核机制解析
本文旨在揭示操作系统核心——内核的工作原理,通过剖析其关键组件与机制,为读者提供一个清晰的内核结构图景。不同于常规摘要的概述性内容,本文摘要将直接聚焦于内核的核心概念、主要功能以及其在系统管理中扮演的角色,旨在激发读者对操作系统深层次运作原理的兴趣与理解。

热门文章

最新文章

推荐镜像

更多