Android中Handler、MessageQueue、Looper三者的关系然后手写一套自己的Handler

2024-02-28 10:08

本文主要是介绍Android中Handler、MessageQueue、Looper三者的关系然后手写一套自己的Handler,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Handler、Looper、MessgaeQueue三者的分工:

  • handler 负责发送消息
  • Looper 负责轮询MessageQueue中的消息,并把消息回传给handler
  • MessageQueue 负责存储消息(先进先出)

Handler、Looper、MessgaeQueue三者的引用关系

  • Handler 中有MessageQueue对象、Looper对象
  • Looper 中有MessageQueue对象(和Handler中的是同一个对象)
  • MessageQueue 中有 Message(而Message中有Handler(target属性))

图解三者的关系:

这里写图片描述

现在我们从创建Handler到handlerMessge收到一条消息,对整个过程进行讲解:

一:创建一个Handler,做了哪些操作?Handler handler = new Handler();

public Handler(Callback callback, boolean async) {//省略若干源码...mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;
}
  • 重要的一个操作Looper.myLooper()获取当前线程绑定的Looper对象,为什么这么说呢?那就要通过Looper的源码来分析了。
  • mLooper.mQueue获取消息队列

二:Looper.myLooper(),获取当前线程绑定的Looper对象

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();public static @Nullable Looper myLooper() {return sThreadLocal.get();
}
  • sThreadLocal:内部维护着一个 ThreadLocalMap(类似HashMap)以key value形式存储
  • key:当前创建Looper对象的线程
  • value:Looper对象
  • 这里获取到Looper对象可能会是个空对象,因为我们要先调用Looper.prepare()对它进行初始化

Looper.prepare():为当前线程创建一个Looper对象

private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));
}
  • sThreadLocal.set(new Looper(quitAllowed)):这里就为我们创建了Looper对象并绑定到当前线程
  • 重点:第一次使用时 必须先调用Looper.prepare()进行初始化

MessageQueue的初始化又在哪里呢?当然是在Looper的构造函数中啦

private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}

到这里 三个对象的创建都已经完成了,上面说了使用Handler的时候必须先调用Looper.prepare()方法,但是我们平常使用的时候并没有调用它那为什么也是可以的呢?答案当然是:系统为你调用好了、那系统又在哪里调用的呢?

系统在程序启动的时候默认创建了一个线程 也就是主线程(UI线程):通过查看ActivityThread.java(这个类并不是一个线程只是一个简单的java类)源码中的main()函数,可以看到如下代码:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");SamplingProfilerIntegration.start();CloseGuard.setEnabled(false);//省略若干代码....Process.setArgV0("<pre-initialized>");//初始化我们的主线程Looper.prepareMainLooper();//省略若干代码....if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}//省略若干代码....//进行无限死循环,当然在loop内部都是通过 线程的等待唤醒机制进行死循环的(节约资源)Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}

讲到这里相信你已经理解到了这三者的相互关系,重要的操作都是在Looper中;那继续分析最后一步handler.sendMessage(msg)发送一条消息:

handler.sendMessage(msg)
//调用上面这行代码,我们继续跟踪源码,发现最终调用了如下代码
//将消息添加进MessageQueue中,并唤醒Looper.loop()中的等待 把消息取出。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}

到这里Handler的整个工作的过程就一目了然了:

handler发送一条消息——>放入MessageQueue中——>Looper.loop()死循环 取出消息 回传给handler

下面我们在自己创建一个线程中使用Handler,你就可以很清楚的理解上面的Looper

new Thread(new Runnable() {@Overridepublic void run() {Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {}};}
}).start();

这里写图片描述

可以看到:系统抛出了一个RuntimeException 提示需要调用Looper.prepare() 
那对代码改造一下:

new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {}};Looper.loop();}
}).start();

创建好了handler 最后肯定还是要调用Looper.loop()的,不然消息只是放进去了并没有取出。

疑问一:为什么在主线程中系统调用了Looper.loop()对主线程进行死循环,那Activity的生命周期或者其他的UI更新操作是怎么进行的呢?这里还是要继续查看ActivityThread.java这个类的main()函数源码进行解释:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");SamplingProfilerIntegration.start();CloseGuard.setEnabled(false);//省略若干代码....Process.setArgV0("<pre-initialized>");//初始化我们的主线程Looper.prepareMainLooper();//省略若干代码....//获取更新ui操作或者其他操作的handlerif (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}//省略若干代码....//进行无限死循环,当然在loop内部都是通过 线程的等待唤醒机制进行死循环的(节约资源)Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}
  1. 从上面源码中可以看到 首先初始化了一个sMainThreadHandlerHandler对象,那这个对象有什么用呢,下面在告诉你。
  2. 初始化完sMainThreadHandler后执行了Looper.loop();对主线程进行死循环
  3. 最后一行代码就比较骚了我的理解大概就是:如果Looper.loop() 退出了死循环,那么主线程就直接奔溃了、那么你的程序也就退出了。所以主线程是从应用启动一直运行到你程序退出的一个线程。
  4. 我们尝试调用如下代码:
 //将主线程退出Looper.getMainLooper().quit();

与我们预想的一模一样 
这里写图片描述

  1. 我们都知道手动创建一个线程,当run函数中的代码执行完毕那么线程就会自动退出的,那我们的主线程是肯定是不可以退出的(上面已经演示过了),所以主线程最后进行了Looper.loop();让线程进入一个死循环这样线程就一直在运行了。

那么问题来了:Activity中的生命周期函数是怎么被调用的呢?

  1. 当然是使用在死循环之前创建的sMainThreadHandler对象了,它是ActivityThread中的一个内部类class H extends Handler{},Activity的一些生命周期回调函数都是通过这个来进行回调的。我们来查看一下这个H类里的handlerMessage(Message msg)函数
public void handleMessage(Message msg) {switch (msg.what) {case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");final ActivityClientRecord r = (ActivityClientRecord) msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;case RELAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");ActivityClientRecord r = (ActivityClientRecord)msg.obj;handleRelaunchActivity(r);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;case PAUSE_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");SomeArgs args = (SomeArgs) msg.obj;handlePauseActivity((IBinder) args.arg1, false,(args.argi1 & USER_LEAVING) != 0, args.argi2,(args.argi1 & DONT_REPORT) != 0, args.argi3);maybeSnapshot();Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;case PAUSE_ACTIVITY_FINISHING: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");SomeArgs args = (SomeArgs) msg.obj;handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;// 截取了部分代码,感兴趣的可以查看下源码}

总结:主线程进行UI操作都是通过这个sMainThreadHandler 发送消息 通知界面进行相应的操作

疑问二:为什么在子线程发送一条消息,消息就在主线程中了呢?

原因:创建一个Handler的时候 同时也创建好了Looper 创建Looper的时候又创建好了MeessageQueue 这些都是在主线程中创建的(Handler一般都在主线程进行初始化),这样当你在其他任何线程中发送一条消息的时候,消息都是在MeessageQueue中,Looper循环从MeessageQueue中取出消息在调用msg.target.dispatchMessage(msg);这样消息就回到了创建Handler的地方 也就是主线程中。(如有错误还望指出)

接下来就根据我们上面分析的内容来自己实现一套Handler:

需要创建的主要类:

  1. MyHandler
  2. MyMessage
  3. MyLooper
  4. MyMessageQueue

MyHandler 这里只给出了主要的实现代码,源码中其他的一些代码都已省略

public class MyHandler {/*** 消息拦截*/private MyCallback mCallback;/*** 轮询器*/private MyLooper looper;/*** 消息队列*/private MyMessageQueue queue;public MyHandler() {this(null);}/*** handler 进行初始化* 1.获取当前线程所绑定的Looper对象*/public MyHandler(MyCallback mCallback) {looper = MyLooper.myLooper();if (looper == null) {throw new RuntimeException("如果当前线程未初始化Looper对象,则需要调用Looper.prepare();");}this.mCallback = mCallback;queue = looper.queue;}/*** 处理消息*/public void handleMessage(MyMessage msg) {}/*** 对消息进行分发*/public void dispatchMessage(MyMessage msg) {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}/*** 发送一条消息*/public final boolean sendMessage(MyMessage msg) {//...中间一系列函数调用,最终执行如下重点代码msg.target = this;return queue.enqueueMessage(msg);}/*** 用来拦截handler发送的消息*/public interface MyCallback {boolean handleMessage(MyMessage msg);}
}

MyMessage 消息实体类

public class MyMessage {public int what;public int arg1;public int arg2;public Object obj;public MyHandler target;public MyHandler getTarget() {return target;}public void setTarget(MyHandler target) {this.target = target;}public void sendToTarget() {target.sendMessage(this);}
}

MyLooper 循环取消息

public class MyLooper {/*** ThreadLocal 内部维护着一个 ThreadLocalMap(类似HashMap)以key value形式存储* key:当前创建Looper对象的线程* value: Looper对象*/static final ThreadLocal<MyLooper> sThreadLocal = new ThreadLocal<>();/*** 当前所在线程*/private final Thread mThread;/*** 消息队列*/public final MyMessageQueue queue;/** 初始化MyLooper对象并绑定他所在的线程* 一个线程内部维护着同一个(Handler Looper MessageQueue)*/public static void prepare() {sThreadLocal.set(new MyLooper());}/*** 初始化消息队列*/private MyLooper() {queue = new MyMessageQueue();mThread = Thread.currentThread();}/*** 返回与当前线程关联的Looper对象* 返回null 则线程中没有初始化一个Looper对象*/public static MyLooper myLooper() {return sThreadLocal.get();}public static void loop() {/*** 在Looper 源码 loop 函数中* <p>使用将消息发送回handler* <p>msg.target.dispatchMessage(msg);*/MyLooper looper = myLooper();MyMessageQueue queue = looper.queue;for (; ; ) {MyMessage msg = queue.next();if (msg != null) {msg.target.dispatchMessage(msg);}}}
}

MyMessageQueu 存储消息

public class MyMessageQueue {private int position = 0;/*** 存储消息*/private List<MyMessage> list = new ArrayList<>();/*** 添加一条消息** @return 是否添加成功*/public boolean enqueueMessage(MyMessage msg) {synchronized (this) {//省略落干源码list.add(msg);}return true;}/*** 获取最新的条消息*/public MyMessage next() {synchronized (this) {if (list.size() > 0) {if (position <= list.size() - 1) {MyMessage message = list.get(position);position++;return message;} else {position = 0;list.clear();}}return null;}}
}

这篇关于Android中Handler、MessageQueue、Looper三者的关系然后手写一套自己的Handler的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/755175

相关文章

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

POJ1269 判断2条直线的位置关系

题目大意:给两个点能够确定一条直线,题目给出两条直线(由4个点确定),要求判断出这两条直线的关系:平行,同线,相交。如果相交还要求出交点坐标。 解题思路: 先判断两条直线p1p2, q1q2是否共线, 如果不是,再判断 直线 是否平行, 如果还不是, 则两直线相交。  判断共线:  p1p2q1 共线 且 p1p2q2 共线 ,共线用叉乘为 0  来判断,  判断 平行:  p1p

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

android应用中res目录说明

Android应用的res目录是一个特殊的项目,该项目里存放了Android应用所用的全部资源,包括图片、字符串、颜色、尺寸、样式等,类似于web开发中的public目录,js、css、image、style。。。。 Android按照约定,将不同的资源放在不同的文件夹中,这样可以方便的让AAPT(即Android Asset Packaging Tool , 在SDK的build-tools目

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使

Android Environment 获取的路径问题

1. 以获取 /System 路径为例 /*** Return root of the "system" partition holding the core Android OS.* Always present and mounted read-only.*/public static @NonNull File getRootDirectory() {return DIR_ANDR