安卓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

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]