Handler

devtools/2024/11/13 9:02:32/
基础知识
1.简介
  • Handler机制是处理线程间通信的一种重要方式。
  • 主要作用是发送和处理Message和Runnable对象,它们可以在不同的线程之间传递信息。
  • Handler机制允许开发者在子线程中执行耗时操作,然后通过Handler将结果或事件发送回主线程,以更新UI。
2.基本原理
  • 子线程 handler 主线程构成了线程模型中的经典问题 生产者-消费者模型。

  • 生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加数据,

    消费者从存储空间中取走数据。

  • 保证数据生产消费的顺序(通过队列先进先出)。

  • 不管是生产者(子线程)还是消费者(主线程)都只依赖缓冲区(handler),生产者消费者之间不会相互持有,使他们之间没有任何耦合。

3.相关类
  • Handler:发送和接收消息 。
  • Looper:用于轮询消息队列并分发给相应的Handler进行处理,一个线程只能有一个Looper。
  • Message: 消息实体。
  • MessageQueue: 消息队列用于存储消息和管理消息。(具有优先级插入排序)
  • ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。
4. Handler工作流程

(可以形象的将Handler的工作流程看作传送带的工作流程)

  • 子线程不断添加消息到消息队列。
  • Lopper通过loop()方法开始轮巡消息队列中的消息。从消息队列中循环取出消息然后把消息交给Handler处理。
5.基本使用
  • 通过Handler.sendMessage()方法

    • sendMessage() 方法是Handler类中的一个同步方法。当调用这个方法时,消息会被加入到一个消息队列中,由主线程的MessageQueue处理。
    • sendMessage()可以传递一个Message对象,这个对象可以包含一些数据(通过obj字段)和一些执行完毕后需要执行的操作(通过callback字段,即一个Runnable对象)。
    • 更适用于那些需要排队等待执行,且需要一定时间保证能够被处理的任务。
    public class MainActivity extends AppCompatActivity {private TextView textView;private Button button;private MyHandler myHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findView();// 创建一个设定的handler(也可以用内部类的方式)myHandler = new MyHandler();//        Handler handler = new Handler(){////            @Override//            public void handleMessage(Message msg){//                // ....//            }//        };// 创建一个消息对象Message message = Message.obtain();message.what = 1; // 消息标识message.obj = "translation"; // 消息内容// 创建工作线程并开启button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Thread(new Runnable() {@Overridepublic void run() {// 发送消息到消息队列中myHandler.sendMessage(message);}}).start();}});}private void findView() {textView = findViewById(R.id.tv);button = findViewById(R.id.btn);}// 创建一个子类(继承Handler类)class MyHandler extends Handler{// 在主线程中执行的操作(更新UI等)// 通过复写handleMessage方法@Overridepublic void handleMessage(Message msg){textView.setText("你好,世界!");}}
    }
    
  • 通过Handler.post()方法

    • post方法是一个异步方法,它可以直接在当前的Handler线程(通常是主线程)中执行传递给它的Runnable对象。
    • 这个方法不需要等待消息队列的处理,不会将任务加入消息队列,它直接在调用线程中执行Runnable。
    public class MainActivity extends AppCompatActivity {private TextView textView;private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findView();Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg){super.handleMessage(msg);// 处理msg消息}};Message message = Message.obtain();/**可以直接new Message 但是有更好的方式 Message.obtain。			*因为可以检查是否有可以复用的Message,用过复用避免过多的创建、销毁Message对象达到优化内存和性能的目地*/message.what = 1;message.obj = "translation";button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {handler.post(new Runnable() {@Overridepublic void run() {textView.setText("你好,世界!");}});}});}private void findView() {textView = findViewById(R.id.tv);button = findViewById(R.id.btn);}}
    
  • 通常建议使用Handler结合Looper来处理线程间的通信,而不是直接使用Handler。

    public class MainActivity extends AppCompatActivity {private TextView textView;private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findView();Looper looper = Looper.getMainLooper();Handler handler = new Handler(looper){@Overridepublic void handleMessage(Message msg){if(msg.what == 1){textView.setText("你好,世界!");}}};Message message = Message.obtain();message.what = 1;message.obj = "translation";button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Thread(new Runnable() {@Overridepublic void run() {handler.sendMessage(message);}}).start();}});}private void findView() {textView = findViewById(R.id.tv);button = findViewById(R.id.btn);}}
    
源码分析
1.创建Looper
  • 用于轮询消息队列,一个线程只能有一个Looper。

  • 创建Looper的方法是调用Looper.prepare() 方法。

  • 主线程的Lopper在ActivityThread中的main方法中被创建出来

    //  ActivityThread.java
    public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// 省略无关代码......Looper.prepareMainLooper(); // 初始化Lopper// 省略无关代码......ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop(); // 开启轮询操作throw new RuntimeException("Main thread loop unexpectedly exited");}// Looper.prepareMainLooper();
    @Deprecated
    public static void prepareMainLooper() {prepare(false); // 消息队列不可以quit// 保证一个线程只能有一个Lopper(通过ThreadLocal)synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}
    }// Looper.Loop()
    public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");} // Looper为空时,抛出异常提醒if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}/** mInLoop是一个内部标记,用来指示Looper是否已经开始执行循环。* 如果mInLoop已经是true,说明当前线程已经处于消息循环中。* 这时候再发送新消息可能会导致队列中的消息在当前消息处理完成之前被执行。* 这可能会打乱消息处理的顺序,造成逻辑错误。*/me.mInLoop = true;// 省略代码// 轮询消息队列for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}}}
    
2.创建Handler
  • Handler中有多个构造函数选择其中一个有代表性的来查看逻辑。

    public Handler(@Nullable Callback callback, boolean async) {// 省略代码......mLooper = Looper.myLooper(); // 获取Lopper(而非创建)if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue; // 创建消息队列MessageQueuemCallback = callback;  // 初始化回调接口mAsynchronous = async;}
    
3.创建Message
  • 创建Message有多种方式如直接使用new通过构造函数来创建,通过obtain方法创建。

  • 使用obtain() 方法创建会检查是否有可以复用的Message,可用于避免过多的创建、销毁Message对象达到优化内存和性能的目地。

// Message.obtain()
public static Message obtain() {synchronized (sPoolSync) { // sPoolSync用于保证线程安全if (sPool != null) { // sPool存放的即为可以复用的MessageMessage m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}
4.Message和Handler的绑定
  • 通过调用重载的obtain方法绑定

    public static Message obtain(Handler h, Runnable callback) {Message m = obtain();m.target = h;m.callback = callback;return m;}
    
  • 通过Handler 中的enqueueMessage()绑定

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this; // 当前Handlermsg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}
    
5.消息的发送与处理
  • 消息的发送主要通过sendMessage()方法,通过一系列重将消息保存在了消息队列中,而最终由Looper取出,交给Handler的dispatchMessage进行处理。

    public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);
    }public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);
    }
    
  • 消息的处理主要通过dispatchMessage()方法

    public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
    }
    
  • 在handleMessage(Message)方法中,我们可以拿到message对象,根据不同的需求进行处理,整个Handler机制的流程就结束了。

6.MessageQueue

  • MessageQueue.enqueueMessage()向消息队列添加消息。

    • 消息具有执行时间,消息队列中的消息遵循按执行时间从小到大排列。
    • 插入消息时遵循插入排序。
    boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");} // 消息无目标,则抛出异常synchronized (this) { // 同步块,保证线程安全if (msg.isInUse()) { // 消息是否在使用中throw new IllegalStateException(msg + " This message is already in use.");}if (mQuitting) { // Handler是否正在退出状态IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse(); // 消息标记为使用中msg.when = when; // 设置消息时间Message p = mMessages;boolean needWake;// 判断执行时间是否早于队头,或者队列为空if (p == null || when == 0 || when < p.when) {msg.next = p;mMessages = msg;needWake = mBlocked;} else {needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;// 找到合适的位置插入消息for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// 唤醒if (needWake) {nativeWake(mPtr);}}return true;
    }
    
  • MessageQueue.next()从消息队列中获取消息使用for循环。

    Message next() {final long ptr = mPtr;if (ptr == 0) { // 检查消息队列是否已经停止return null;}int pendingIdleHandlerCount = -1; // 记录待处理的空闲处理器的数量。int nextPollTimeoutMillis = 0;// 进入一个无限循环,直到找到消息或者处理完所有空闲处理器。for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}nativePollOnce(ptr, nextPollTimeoutMillis);// 进入同步块,尝试获取下一个消息。synchronized (this) {// 省略代码......}// 省略代码......nextPollTimeoutMillis = 0;}
    }
    
常见问题
1.一个线程有几个 Handler?一个线程有几个 Looper?如何保证?

一个线程只有一个handler机制,但是可以有无数个handler,需要的情况下可以new任意多个。

一个线程有一个Lopper。通过ThreadLocal保证,其中的prepare方法在创建Loopper之前会先get一下,如果已存在则创建失败(见上述源码有具体涉及)。

2.Handler内存泄漏原因? 为什么其他的内部类没有说过有这个问题?

因为Handler是一个匿名内部类,持有了外部类对象。不同Massage的执行时长,持有不同的Handler,GC分析可达不能被回收。

3.为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?

主线程要创建Handler,所以要去new。而且主线程默认提供了一个Looper。

在子线程中要进行一个Looper.preare()和Lopper.loop(),即创建出一个轮询机制,否则的话创建Handler无用。

4.什么是Handler,它在Android中的作用是什么?

Handler是Android中用于线程间通信的一个组件。

在Android中,UI操作需要在主线程(UI Thread)上执行,而耗时操作则通常在其他线程(如子线程)上执行。

Handler的主要作用就是确保这些耗时操作的结果最终能在主线程上更新UI。

5.Handler是如何工作的?

创建Handler实例,通常与一个Looper关联,Looper负责处理消息队列中的消息。

通过post()方法将Runnable对象或者sendMessage()方法发送消息到消息队列。

Looper不断地从消息队列中取出消息,并将其分发给对应的Handler。

Handler处理消息,如果消息是Runnable对象,则调用run()方法;如果消息是Message对象,则调用handleMessage()方法。

6.如何在Handler中处理消息?

在Handler中处理消息主要是通过重写handleMessage()方法来实现的。这个方法接收一个Message对象作为参数,在这个方法中可以进行UI的更新或者其他操作。

7.如何确保Handler在正确的线程上工作?

Handler默认与创建它的线程的Looper关联。如果你需要确保Handler在一个特定的线程上工作,你可以在创建Handler时指定一个特定的Looper。

例如,如果你想要确保Handler在工作线程上工作,你可以创建一个工作线程并为其创建一个Looper,然后在这个Looper上创建Handler。

8.如何使用Handler来更新UI?

在子线程中完成耗时操作。

使用Handler的post()方法或者sendMessage()方法将一个Runnable对象或者Message对象发送到消息队列。

Looper从消息队列中取出消息,并将其分发给对应的Handler。

Handler调用handleMessage()方法处理消息,如果在post()方法中使用了Runnable,则直接调用run()方法;如果使用了sendMessage()方法,则处理Message对象。

在handleMessage()方法中进行UI更新。


http://www.ppmy.cn/devtools/18470.html

相关文章

Linux的用户及管理

目录 root(超级管理员) su切换用户&#xff1a; sudo临时root: 用户和用户组 创建用户组 删除用户组 创建用户 删除用户 查看用户所在的组 修改用户所在的组 getent passwd 权限认识&#xff1a; 修改权限控制-chmod chmod: 例&#xff1a; 用数字表示权限&…

鸿蒙OpenHarmony【轻量系统 编译】 (基于Hi3861开发板)

编译 OpenHarmony支持hb和build.sh两种编译方式。此处介绍hb方式&#xff0c;build.sh脚本编译方式请参考[使用build.sh脚本编译源码]。 使用build.sh脚本编译源码 进入源码根目录&#xff0c;执行如下命令进行版本编译。 ./build.sh --product-name name --ccache 说明&…

llama3本地部署

目录 II.下载 II.验证ollama安装 II.安装llama3 和启动 II.命令行调用 II.api调用 II.参考文献 II.下载 https://ollama.com/download/windows OllamaSetup.exe https://github.com/meta-llama/llama3 II.验证ollama安装 cmd ollama II.安装llama3 和启动 ollama run …

算法 || 二分查找

目录 二分查找 在排序数组中查找元素的第一个和最后一个位置 搜索插入位置 一个数组经过划分后具有二段性的都可以用二分查找 二分查找 704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; ​ 暴力解法&#xff1a;直接遍历数组&#xff0c;找到 target 便返回下标&am…

CB2-2CARD之Debian(Bookworm)安装Gnome看CCTV

CB2-2CARD之Debian&#xff08;Bookworm&#xff09;安装Gnome看CCTV 1. 源由2. 需求3. Debian系统桌面3.1 系统安装3.2 磁盘扩容3.3 系统更新3.4 Gnome安装 4. 测试4.1 CCTV网页测试4.2 系统空闲测试4.3 Firefox CPU占用率测试 5. 总结 1. 源由 近些年来&#xff0c;随着国内…

基于JavaWEB的学生考勤管理系统(含论文)

本系统是用Java语言写的&#xff0c;基于JavaWEB的学生考勤管理系统 主要有三大模块&#xff0c;学生&#xff0c;教师和管理员模块&#xff0c;功能如下&#xff1a; 学生模块 教师模块&#xff1a; 管理员模块

计算机网络【第一章】

目录 1.1、什么是因特网 ①描述因特网的具体构成 ②根据为分布式应用提供的联网基础设施来描述 网络协议 1.2、 网络边缘 接入网 1、家庭接入&#xff1a;DSL、电缆、FTTH、拨号和卫星 2、企业接入&#xff08;和家庭接入&#xff09;&#xff1a;以太网和WiFi 3、广域…

python--使用pika库操作rabbitmq实现需求

Author: wencoo Blog&#xff1a;https://wencoo.blog.csdn.net/ Date: 22/04/2024 Email: jianwen056aliyun.com Wechat&#xff1a;wencoo824 QQ&#xff1a;1419440391 Details:文章目录 目录正文 或 背景pika链接mqpika指定消费数量pika自动消费实现pika获取队列任务数量pi…