安卓Handle的深入剖析和使用

2024-09-03 02:32

本文主要是介绍安卓Handle的深入剖析和使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在公司开发项目你不能说handle用的不多,反正这种更新主线程的机制是必须要懂的。面试的时候也总会叫你回答handle、looper、MessageQueen和Message的区别,所以你不仅仅只是会用而且必须要知道handle的运行机制。本文参考了很多的博主的文章见解,包括里面的原理和图解(http://blog.csdn.net/itachi85/article/details/8035333)。

handle:是安卓程序的一套更新ui的机制,它也是一套消息的处理机制,所以我们既可以用它来创建、发送消息,也能用它来处理消息,如果我们不遵循这些机制的话,程序就会crash。

那么ui线程是不能直接更新吗?

一般情况是不能的,但是还是有特殊情况:比如直接在子线程里更新textView的值,不会报错但是如果加了Threed.sleep()就会报错,因为textview的源码里面会有Inviliad()一直追溯父类会发现里面有个viewRoot的checkThread()方法,而activity的生成准确的说是OnResume()然后里面会创建rootImp,所以判断一下就会报错。所以还是推荐用handle机制来进行ui刷新。

一、handle是处理Ui线程的消息机制

直接在UI线程中开启子线程来更新TextView显示的内容,运行程序我们会发现,如下错 误:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.翻译过来就是:只有创建这个控件的线程才能去更新该控件的内容。

安卓为什么只能通过Handle机制更新ui线程呢?

最根本的原因就是处理多线程的并发问题。假如在一个activity里面有多个线程去更新ui的操作并且都没有进行加锁的机制,那么可能出现界面错乱和线程堵塞。而handle机制给我们一套解决并发问题的机制,提供了Handler 和 Looper 来满足线程间的通信。Handler先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(MessageExchange)。

线程可以分为:ui线程(ActivityThread)和渲染线程(RenderThread):<surfaceView(就包含了这种机制,所以只需拿到getHandle()就能够进行ui的刷新),然后我们用的AsyncTask(AsyncTask的本质是一个线程池,所有提交的异步任务都会在这个线程池中的工作线程内执行,当工作线程需要跟UI线程交互时,工作线程会通过向在UI线程创建的Handler)>.

主线程一般不做耗时操作(如果UI线程花太多时间处理后台的工作,当UI事件发生时,让用户等待时间超过5秒而未处理,Android系统就会给用户显示ANR提示信息,广播10秒,服务20秒)。

那什么是ui线程(主线程)呢?

有时候面试的时候也会问我们安卓的程序入口,通常我们也会说application,但是你一定要知道我们的主线程就是ActivityThread,所以我们就有必要去了解安卓的程序的消息机制了。它管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),并根据AMS的要求(通过IApplicationThread接口,AMS为Client、ActivityThread.ApplicationThread为Server)负责调度和执行activities、broadcasts和其它操作。

那么我们来看下IApplicationThread接口

                                                                       

由上图我们可以看到ActivityThread 有几个比较重要的成员变量,会在创建ActivityThread对象时初始化。

(1)final ApplicationThread mAppThread = new ApplicationThread();

ApplicationThread继承自ApplicationThreadNative, 而ApplicationThreadNative又继承自Binder并实现了IApplicationThread接口。IApplicationThread继承自IInterface。这是一个很明显的binder结构,用于于Ams通信。IApplicationThread接口定义了对一个程序(linux的进程)操作的接口。ApplicationThread通过binder与Ams通信,并将Ams的调用,通过下面的H类(也就是Handler)将消息发送到消息队列,然后进行相应的操作,入activity的start, stop。

(2)final H mH = new H();

          private final class H extends Handler

mH负责处理ApplicationThread发送到消息队列的消息,所以handle是贯穿整个安卓程序的消息机制的传递。

(3). handleLaunchActivity(r, null);

从中就可以看出,这里就将进行启动activity的工作了。

方法中主要调用了这一句:

Activity a = performLaunchActivity(r, customIntent);

(4)performLaunchActivity()

进行了一些初始化和赋值操作后,创建activity。

activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);

然后调用:

mInstrumentation.callActivityOnCreate(activity, r.state);

这一句就会调用到acitivity的onCreate方法了,就进入了大多数应用开发的入口了,所以handle的重要性不言而喻了吧。

二、Handler的重点剖析

先看handle、looper、MessageQueen和Message的关系。

1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。 
2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。
3) Message Queue(消息队列):用来存放线程放入的消息。 

4)线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。 

1.Handler创建消息
       Handler通过创建消息来完成接收和处理UI的更新。Android消息机制中引入了消息池。Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。消息池提高了消息对象的复用,减少系统垃圾回收的次数。消息的创建流程如图所示。

2.Handler发送消息

UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。

Handler、Looper、MessageQueue的初始化流程如图所示:


Hander持有对UI主线程消息队列MessageQueue和消息循环Looper的引用,子线程可以通过Handler将消息发送到UI线程的消息队列MessageQueue中。

3.Handler处理消息

UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。

子线程通过Handler、Looper与UI主线程通信的流程如图所示。


每一个线程里可含有一个Looper 对象以及一个MessageQueue 数据结构。在你的应用程序里,可以定义Handler 的子类别来接收Looper 所送出的消息。

那么什么情况下面我们的子线程才能看做是一个有Looper的线程呢?我们如何得到它Looper的句柄呢?
Looper.myLooper();            //获得当前的Looper
Looper.getMainLooper () ;  //获得UI线程的Lopper
我们看看Handle的初始化函数,如果没有参数,那么他就默认使用的是当前的Looper,如果有Looper参数,就是用对应的线程的Looper。
如 果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用 Looper.loop();之后就进入了消息循环,这个之后就可以发消息、取消息、和处理消息。这个如何发送消息和如何处理消息可以再其他的线程中通过 Handle来做,但前提是我们的Hanle知道这个子线程的Looper,但是你如果不是在子线程运行 Looper.myLooper(),一般是得不到子线程的looper的。

三、handel的使用

我们先来看handle的构造:

**
        * Use the provided {@link Looper} instead of the default one and take a callback* interface in which to handle messages.  Also set whether the handler* should be asynchronous.
        *
        * Handlers are synchronous by default unless this constructor is used to make* one that is strictly asynchronous.
        *
        * Asynchronous messages represent interrupts or events that do not require global ordering* with represent to synchronous messages.  Asynchronous messages are not subject to* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
        *
        * @param looper The looper, must not be null.
        * @param callback The callback interface in which to handle messages, or null.
        * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
        * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
        *
        * @hide
*/
public Handler(Looper looper, Callback callback, boolean async) {mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
        }

handle的looper是一定不为空的默认会执行prepareMainLooper()方法,然后放入ThreadLocal池中用来处理多线程,而callBack{}是一个接口的回调,当我们使用的时候调用里面的handleMessage(msg)时是一个带返回值的,我们可以通过对这个返回值值的控制来打断handle的消息传递机制,如果我们返回的是false的话那么就不会打断handle对消息的处理,如果返回值设置为true那么将不会执行handle的消息处理只会执行callback{}里面的handleMessage()方法,不过我们是一般是不会这么去做的。

常见的使用刷新ui的方法:

A:常见方法:(1)runOnUiThread(runnale)                       (2)view.post() 

上述2方法看源码就知道里面也是调用了handle的方法,或者本来就是ui线程里面操作

B、handle刷新ui的方法包括:(1)handle post           (2)handle sendMessage,

1 handle post

handle不和Message配套使用的时候,直接使用handle.post(runable)其实你没有看到Message对象作为参数的传入,但是里面已经是通过looper.loop()已经push了一个message对象到消息队列里用来进行ui刷新。你传入一个runnable对象那么Handle就会调用

private static Message getPostMessage(Runnable r) {Message m = Message.obtain();
    m.callback = r;
    return m;
}

其实这里面同时调用了sendMessageDelayed(getPostMessage(r), 0)的方法,而后面那个参数赋值为0代表的是delayMillis(延迟毫秒数),可见postDelayed(r,delayMillis)和

handle.post(runable)是同一个方法的不同表现形式。

2  handle sendMessage

使用handle必然是离不开Message

Message对象参数我们必须熟悉里面包括:what,obj,data,target,arg;其中我们通过what来区分不同的消息处理,obj是一个object对象是便于我们传递任一的对象,data是一个bundle对象,target是一个handle对象,arg用于传递一个整数值,所以我们使用时可以根据自己的需要来设置。

Message对象的生成:(1)handle.obtanMessage()            (2)new meassage()

这两种方法推荐使用前一个,第一种是一个Message的池来管理

public static Message obtain(Handler h) {Message m = obtain();
    m.target = h;
    return m;
}

其中这target就相当于this,然后当池中的Message对象为空的时候就相当于第二种方法

public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;
            sPool = m.next;
            m.next = null;
            sPoolSize--;
            return m;
        }}return new Message();
}

然后handle可以使用handler.dispatchMessage(msg);handel.sendMessage(),handle.sendEmptyMessage()等就不说了,自己去看代码。

使用注意:

1、使用handle的时候如果在子线程去创建,会报handle没有准备的错误

  java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
     at android.os.Handler.<init>(Handler.java:121)
     at com.cao.android.demos.handles.HandleTestActivity$MyThread$1.<init>(HandleTestActivity.java:86)
     at com.cao.android.demos.handles.HandleTestActivity$MyThread.run(HandleTestActivity.java:86)

所以我们就要指定looper对象,使用new handle(Looper.getMainLooper())来处理。

2、.Handler对Activity finish影响。

在开发的过程中碰到一个棘手的问题,调用Activity.finish函数Acitivity没有执行生命周期的ondestory函数,后面查找半天是因为有一个handler成员,因为它有一个delay消息没有处理,调用Activity.finish,Activity不会马上destory,所以记得在Ativity finish前清理一下handle中的未处理的消息,这样Activity才会顺利的destory



这篇关于安卓Handle的深入剖析和使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1131772

相关文章

MySQL的ALTER TABLE命令的使用解读

《MySQL的ALTERTABLE命令的使用解读》:本文主要介绍MySQL的ALTERTABLE命令的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、查看所建表的编China编程码格式2、修改表的编码格式3、修改列队数据类型4、添加列5、修改列的位置5.1、把列

Python使用FFmpeg实现高效音频格式转换工具

《Python使用FFmpeg实现高效音频格式转换工具》在数字音频处理领域,音频格式转换是一项基础但至关重要的功能,本文主要为大家介绍了Python如何使用FFmpeg实现强大功能的图形化音频转换工具... 目录概述功能详解软件效果展示主界面布局转换过程截图完成提示开发步骤详解1. 环境准备2. 项目功能结

SpringBoot使用ffmpeg实现视频压缩

《SpringBoot使用ffmpeg实现视频压缩》FFmpeg是一个开源的跨平台多媒体处理工具集,用于录制,转换,编辑和流式传输音频和视频,本文将使用ffmpeg实现视频压缩功能,有需要的可以参考... 目录核心功能1.格式转换2.编解码3.音视频处理4.流媒体支持5.滤镜(Filter)安装配置linu

Redis中的Lettuce使用详解

《Redis中的Lettuce使用详解》Lettuce是一个高级的、线程安全的Redis客户端,用于与Redis数据库交互,Lettuce是一个功能强大、使用方便的Redis客户端,适用于各种规模的J... 目录简介特点连接池连接池特点连接池管理连接池优势连接池配置参数监控常用监控工具通过JMX监控通过Pr

apache的commons-pool2原理与使用实践记录

《apache的commons-pool2原理与使用实践记录》ApacheCommonsPool2是一个高效的对象池化框架,通过复用昂贵资源(如数据库连接、线程、网络连接)优化系统性能,这篇文章主... 目录一、核心原理与组件二、使用步骤详解(以数据库连接池为例)三、高级配置与优化四、典型应用场景五、注意事

使用Python实现Windows系统垃圾清理

《使用Python实现Windows系统垃圾清理》Windows自带的磁盘清理工具功能有限,无法深度清理各类垃圾文件,所以本文为大家介绍了如何使用Python+PyQt5开发一个Windows系统垃圾... 目录一、开发背景与工具概述1.1 为什么需要专业清理工具1.2 工具设计理念二、工具核心功能解析2.

Linux系统之stress-ng测压工具的使用

《Linux系统之stress-ng测压工具的使用》:本文主要介绍Linux系统之stress-ng测压工具的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、理论1.stress工具简介与安装2.语法及参数3.具体安装二、实验1.运行8 cpu, 4 fo

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

使用C#删除Excel表格中的重复行数据的代码详解

《使用C#删除Excel表格中的重复行数据的代码详解》重复行是指在Excel表格中完全相同的多行数据,删除这些重复行至关重要,因为它们不仅会干扰数据分析,还可能导致错误的决策和结论,所以本文给大家介绍... 目录简介使用工具C# 删除Excel工作表中的重复行语法工作原理实现代码C# 删除指定Excel单元

MySQL 事务的概念及ACID属性和使用详解

《MySQL事务的概念及ACID属性和使用详解》MySQL通过多线程实现存储工作,因此在并发访问场景中,事务确保了数据操作的一致性和可靠性,下面通过本文给大家介绍MySQL事务的概念及ACID属性和... 目录一、什么是事务二、事务的属性及使用2.1 事务的 ACID 属性2.2 为什么存在事务2.3 事务