主线程中如何获取异步回调方法handleMessage()中结果数据

主线程中如何获取异步回调方法handleMessage()中结果数据,第1张

呵呵,不知道是不是我想的太简单

通信结果可以用Message去封装,msg可以包含what arg1 arg2 obj最多四种参数,obj是你需要用的,把obj附带上你的数据,貌似obj需要实现序列化接口,

然后异步线程可以调用主线程的handler发送消息msg,给handlemessage处理

如果你说的异步线程是AsynTask 那他本身就实现了上面的机制,他内部有方法,调用publishProgress(value);会把values传给onProgressUpdate(String values)处理(假设你的数据是String就写String,AsynTask<>,尖括号里面相应参数也填String)

在重写onProgressUpdate里调用service的一个方法

调不了service对象就需要在构造方法里传进来一个service对象

如果只需要一个最终结果,就在onPostExecute(Result或你喜欢的类型)方法里调用service处理这个result,,并且需要在doinBackGround你return这个结果

一、问题:在Android启动后会在新进程里创建一个主线程,也叫UI线程( 非线程安全 )这个线程主要负责监听屏幕点击事件与界面绘制。当Application需要进行耗时 *** 作如网络请求等,如直接在主线程进行容易发生ANR错误。所以会创建子线程来执行耗时任务,当子线程执行完毕需要通知UI线程并修改界面时,不可以直接在子线程修改UI,怎么办?

解决方法:Message Queue机制可以实现子线程与UI线程的通信。

该机制包括Handler、Message Queue、Looper。Handler可以把消息/ Runnable对象 发给Looper,由它把消息放入所属线程的消息队列中,然后Looper又会自动把消息队列里的消息/Runnable对象 广播 到所属线程里的Handler,由Handler处理接收到的消息或Runnable对象。

1、Handler

每次创建Handler对象时,它会自动绑定到创建它的线程上。如果是主线程则默认包含一个Message Queue,否则需要自己创建一个消息队列来存储。

Handler是多个线程通信的信使。比如在线程A中创建AHandler,给它绑定一个ALooper,同时创建属于A的消息队列AMessageQueue。然后在线程B中使用AHandler发送消息给ALooper,ALooper会把消息存入到AMessageQueue,然后再把AMessageQueue广播给A线程里的AHandler,它接收到消息会进行处理。从而实现通信。

2、Message Queue

在主线程里默认包含了一个消息队列不需要手动创建。在子线程里,使用Looperprepare()方法后,会先检查子线程是否已有一个looper对象,如果有则无法创建,因为每个线程只能拥有一个消息队列。没有的话就为子线程创建一个消息队列。

Handler类包含Looper指针和MessageQueue指针,而Looper里包含实际MessageQueue与当前线程指针。

下面分别就UI线程和worker线程讲解handler创建过程:

首先,创建handler时,会自动检查当前线程是否包含looper对象,如果包含,则将handler内的消息队列指向looper内部的消息队列,否则,抛出异常请求执行looperprepare()方法。

 - 在 UI线程 中,系统自动创建了Looper 对象,所以,直接new一个handler即可使用该机制;

- 在 worker线程 中,如果直接创建handler会抛出运行时异常-即通过查‘线程-value’映射表发现当前线程无looper对象。所以需要先调用Looperprepare()方法。在prepare方法里,利用ThreadLocal<Looper>对象为当前线程创建一个Looper(利用了一个Values类,即一个Map映射表,专为thread存储value,此处为当前thread存储一个looper对象)。然后继续创建handler, 让handler内部的消息队列指向该looper的消息队列(这个很重要,让handler指向looper里的消息队列,即二者共享同一个消息队列,然后handler向这个消息队列发送消息,looper从这个消息队列获取消息) 。然后looper循环消息队列即可。当获取到message消息,会找出message对象里的target,即原始发送handler,从而回调handler的handleMessage() 方法进行处理。

 - handler与looper共享消息队列 ,所以handler发送消息只要入列,looper直接取消息即可。

 - 线程与looper映射表 :一个线程最多可以映射一个looper对象。通过查表可知当前线程是否包含looper,如果已经包含则不再创建新looper。

5、基于这样的机制是怎样实现线程隔离的,即在线程中通信呢。 

核心在于 每一个线程拥有自己的handler、message queue、looper体系 。而 每个线程的Handler是公开 的。B线程可以调用A线程的handler发送消息到A的共享消息队列去,然后A的looper会自动从共享消息队列取出消息进行处理。反之一样。

二、上面是基于子线程中利用主线程提供的Handler发送消息出去,然后主线程的Looper从消息队列中获取并处理。那么还有另外两种情况:

1、主线程发送消息到子线程中;

采用的方法和前面类似。要在子线程中实例化AHandler并设定处理消息的方法,同时由于子线程没有消息队列和Looper的轮询,所以要加上Looperprepare(),Looperloop()分别创建消息队列和开启轮询。然后在主线程中使用该AHandler去发送消息即可。

2、子线程A与子线程B之间的通信。

1、 Handler为什么能够实现不同线程的通信?核心点在哪?

不同线程之间,每个线程拥有自己的Handler、消息队列和Looper。Handler是公共的,线程可以通过使用目标线程的Handler对象来发送消息,这个消息会自动发送到所属线程的消息队列中去,线程自带的Looper对象会不断循环从里面取出消息并把消息发送给Handler,回调自身Handler的handlerMessage方法,从而实现了消息的线程间传递。

2、 Handler的核心是一种事件激活式(类似传递一个中断)的还是主要是用于传递大量数据的?重点在Message的内容,偏向于数据传输还是事件传输。

目前的理解,它所依赖的是消息队列,发送的自然是消息,即类似事件中断。

0、 Android消息处理机制(Handler、Looper、MessageQueue与Message)

1、 Handler、Looper源码阅读

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

谢谢!

wingjay

![](>

1,一个进程至少拥有一个线程,称为主线程,如果一个线程创建了窗口,拥有GUI资源,那么也称该线程为 GUI线程 ,否则就为 工作线程 。窗口是由线程创建的,创建窗口的线程就拥有该窗口。这种线程拥有关系的概念对窗口有重要的意义:建立窗口的线程必须是为窗口处理所有消息的线程。为了使这个概念更加明确具体,可以想像一个线程建立了一个窗口,然后就结束了。

      在这种情况下,窗口不会收到一个WM_DESTROY或WM_NCDESTROY消息,因为线程已经结束,不可能被用来使窗口接收和处理这些消息。每个线程,如果它至少建立了一个窗口,都由系统对它分配一个消息队列。这个队列用于窗口消息的派送(dispatch)。为了使窗口接收这些消息,线程必须有它自己的消息循环,消息循环一般如下:

      应用程序不断的从消息队列中获取消息,然后系统通过DispatchMessage函数分派消息到相应窗口的窗口过程,使得消息得到处理。当获取到WM_QUIT消息时,GetMessage返回0,循环结束。

Handler是Android中的异步消息处理机制。当发送一个消息之后,这个消息是进入一个消息队列(MessageQueue),在消息队列中通过Looper去循环的获取队列中的消息,然后将消息分派给对应的处理者进行处理。

Message:存储需要处理 *** 作的信息

MessageQueue:先进先出,存储handler发送过来的消息

Looper:循环器,它是消息队列和handler的通信媒介,1:循环的取出消息队列中的消息;2:将取出的消息发送给对应的处理者

Handler:主线程和子线程的通信媒介,1:添加消息到消息队列; 2:处理循环器分派过来的消息

在handler机制中,Looperloop方法会不断循环获取Message, 其中的消息的获取是通过调用MessageQueue的next()方法获取的,而该方法会调用nativePollOnce()方法 ,这是一个native方法。底层的实现涉及到Linux pipe/epoll机制,nativePollOnce()被阻塞时,主线程会释放CPU资源,进入休眠状态 直到下个消息到达或者有事务发生,会通过pipe管道写端写入数据来唤醒looper工作。

Android60及以前的版本使用管道与epoll来完成Looper的休眠与唤醒的

Android60及以后的版本使用eventfd与epoll来完成Looper的休眠与唤醒的

如果不处理的话,会阻塞线程,处理方案是调用Looper的quit()(清空所有的延迟和非延迟的消息)和quitSafely()(只清空延迟消息); 这个方法会调用MessageQueue的quit()方法,清空所有的Message,并调用nativeWake()方法唤醒之前被阻塞的nativePollOnce(),使得方法next()方法中的for循环继续执行,接下来发现Message为null后就会结束循环,Looper结束。如此便可以释放内存和线程

同进程线程间内存共享,通过handler通信,消息的内容是不需要从一个线程拷贝到另一个线程,因为两个线程间可使用的内存是同一个区域。(注意:线程私有区域ThreadLocal)

管道的作用就是当一个线程准备好Message,并放入消息池,这时需要通知了一个线程B去处理这个消息。线程A向管道的写端写入数据,管道有数据便会唤醒线程B去处理消息。管道的作用是用于通知另一个线程的,这便是最核心的作用。

从内存角度,通信过程中binder涉及到一次内存拷贝,handler机制中的Message根本不需要拷贝,本身就是在同一片内存。

从CPU角度,为了Binder通信底层驱动还需要创建一个binder线程池,每次通信涉及binder线程的创建和内存的分配等比较浪费CPU资源

原因:handler发送的消息在当前handler的消息队列中,如果此时activity被finish掉了,那么消息队列的消息依旧由handler进行处理,若此时handler申明为内存类(非静态内部类),内部类持有外部类的实例引用,这样在GC垃圾回收时发现Activity还有其他引用存在,因而就不会去回首这个Activity,进而导致Activity泄漏。

方法:使用静态内部类,并且使用WeakReference包裹外部类的对象。首先静态内部类不持有外部类的引用,使用静态的handler不会导致activity的泄漏,handler定义static的同时,还要用WeakReference包裹外部类的对象。

方法有两种:

通过继承Thread类,重写Run方法来实现

通过继承接口Runnable实现多线程

主要接受子线程发送的数据, 并用此数据配合主线程更新UI

Handler的主要作用:主要用于异步消息的处理

Handler的运行过程:

当(子线程)发出一个消息之后,首先进入一个(主线程的)消息队列,发送消息的函数即刻返回,而在主线程中的Handler逐个的在消息队列中将消息取出,然后对消息进行处理。这样就实现了跨线程的UI更新(实际上还是在主线程中完成的)。

这种机制通常用来处理相对耗时比较长的 *** 作,如访问网络比较耗时的 *** 作,读取文大文件,比较耗时的 *** 作处理等。

在大白话一点的介绍它的运行过程:

启动应用时Android开启一个主线程

(也就是UI线程) , 如果此时需要一个耗时的 *** 作,例如:

联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些 *** 作放在主线程中,如果你放在主线程中的话,界面会出现假死现象(这也就是你在主线程中直接访问网络时会提示你异常的原因,如我们上篇文章所述Android主线程不能访问网络异常解决办法)。

消息在消息队列中就是按照进入队列的先后顺序排列的。

线程按照顺序从队列中取出消息并处理。

多个辅助线程,只要保证各个消息是顺序的发送给主线程,就可以保证主线程的处理顺序。

1string concatenation in loop

这个老生常谈,一个"+"号就会new 一个StringBuilder实例,但是平时code时总会发现项目里有人一个for()循环里开始用“+”去拼接字符串。。。”

用StringBuilderappend()代替;

2new Integer ()to String can be simplified to IntegertoString

stackoverflow上解释了关于两个的区别和什么时候用哪个写的很详细,高赞截图如下:

3suspicious listremove in the loop

Reports when listremove(index) is called inside the ascending counted loop This is suspicious as list becomes shorter after that and the element next to removed will not be processed Simple fix is to decrease the index variable after removal, but probably removing via iterator or using removeIf method (since Java 8) is a more robust alternative If you don't expect that remove will be called more than once in a loop, consider adding a break command after it

listremove()删除完后,内部数据会补上空缺,从而导致index下获取的值不再是原来的值

建议由

优化为如下:

然后具体解释如下:

Android 方面

当Android应用启动的时候,系统会自动创建一个供主线程使用的Looper实例。

Looper的主要工作就是一个一个处理消息队列(MessageQueue, Looper构造方法中创建)中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。

我们在主线程中实例化一个Handler时,如果没有指定其它的Looper,那么它就会自动使用主线程的Looper,如下图所示log:

所以我们发送一条消息到此Handler时,实际上消息是进入了主线程的消息处理队列,而此消息已经包含了一个Handler实例的引用:

当Looper来处理消息时,会据此引用来回调[Handler#handleMessage(Message)]:

Java中的 非静态内部类 以及 匿名内部类 会持有外部类的引用。 静态内部类 不会持有外部类的引用。

结合Android和Java方面的分析,我们应该很容易就知道了为什么会产生内存泄漏:

Handler常用于Android中线程间通信

消息处理流程有一张图可以参考下

这里有几个重要的关键点(按我自己理解翻译的一下)

1Message:消息

2MessageQueue:消息队列

3Handler:消息处理器,负责消息的收发,可以存在多个

4Looper:循环消息分发器

通信嘛,要先有传递的信息Message

MessageQueue,消息同时发又处理不完又需要保持同步,于是有了消息队列,让消息按顺序处理

Looper,消息分发器,在其loop()后会循环消息队列去取消息进行分发处理,如果没有则会阻塞

最后是Handler,负责消息的收发处理,sendMessage发送消息,重写handleMessage方法即可处理收到的消息(这里主线程直接获取了looper)如果是子线程则需要先对Looper就行初始化

介绍完功能来顺一下流程

首先主线程中ActivityThread中main方法创建了Looper

这里进入Looper

当我们需要传递消息时

通过获取对应线程Looper来初始化Handler,

初始化一个消息,Messageobtain()来获得而不是new一个Message,因为Message内部有一个消息池子,消息收到处理后,内部信息清除就可以重复使用,避免资源浪费。

然后插入到Handler所在的消息队列

然后Loop发现有新消息,派发到对应的Handler来处理消息dispatchMessage

HandlerdispatchMessage,先判断是否有callback(即Runnable),如果有走handleCallback,否则执行handleMessage

简单总计一下:

线程间通信每个线程都需要初始化Looper和消息队列,Loop不停向消息队列读取消息,通过不同线程的Handler来发送信息,如A线程有1,2两个Hander,B线程有3,4两个Hander,A线程调用3,4来发送信息,B线程即可收到 A线程发来的信息

以上就是关于主线程中如何获取异步回调方法handleMessage()中结果数据全部的内容,包括:主线程中如何获取异步回调方法handleMessage()中结果数据、[Android源码分析] - 异步通信Handler机制、windows线程与窗口之间的关系等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9645560.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-30
下一篇 2023-04-30

发表评论

登录后才能评论

评论列表(0条)

保存