android 全局 handler,(转)Android Handler 使用详解

本文详细介绍了Android Handler的作用,为何需要Handler以解决子线程访问UI的问题,以及Handler的两种使用方式:post(Runnable)和sendMessage(Message)。还讨论了Handler存在的内存问题和异常风险,并提供了改进方案,包括使用静态内部类和弱引用避免内存泄漏,以及在onDestroy中移除消息。最后,解释了Handler、Looper、MessageQueue之间的关联和消息处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Handler:是一个消息分发对象,进行发送和处理消息,并且其 Runnable 对象与一个线程的 MessageQueue 关联。

作用:调度消息,将一个任务切换到某个指定的线程中去执行。

为什么需要 Handler?

子线程不允许访问 UI

假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。

传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。

引入 Handler

采用单线程模型处理 UI 操作,通过 Handler 切换到 UI 线程,解决子线程中无法访问 UI 的问题。

Handler 使用

方式一: post(Runnable)

创建一个工作线程,实现 Runnable 接口,实现 run 方法,处理耗时操作

创建一个 handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作。

new Thread(new Runnable() {

@Override

public void run() {

/**

耗时操作

*/

handler.post(new Runnable() {

@Override

public void run() {

/**

更新UI

*/

}

});

}

}).start();

方式二: sendMessage(Message)

创建一个工作线程,继承 Thread,重新 run 方法,处理耗时操作

创建一个 Message 对象,设置 what 标志及数据

通过 sendMessage 进行投递消息

创建一个handler,重写 handleMessage 方法,根据 msg.what 信息判断,接收对应的信息,再在这里更新 UI。

private Handler handler = new Handler(){

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

switch (msg.what) { //判断标志位

case 1:

/**

获取数据,更新UI

*/

break;

}

}

};

public class WorkThread extends Thread {

@Override

public void run() {

super.run();

/**

耗时操作

*/

//从全局池中返回一个message实例,避免多次创建message(如new Message)

Message msg =Message.obtain();

msg.obj = data;

msg.what=1; //标志消息的标志

handler.sendMessage(msg);

}

}

new WorkThread().start();

Handler 存在的问题

内存方面

Handler 被作为 Activity 引用,如果为非静态内部类,则会引用外部类对象。当 Activity finish 时,Handler可能并未执行完,从而引起 Activity 的内存泄漏。故而在所有调用 Handler 的地方,都用静态内部类。

异常方面

当 Activity finish 时,在 onDestroy 方法中释放了一些资源。此时 Handler 执行到 handlerMessage 方法,但相关资源已经被释放,从而引起空指针的异常。

避免

如果是使用 handlerMessage,则在方法中加try catch。

如果是用 post 方法,则在Runnable方法中加try catch。

Handler 的改进

内存方面:使用静态内部类创建 handler 对象,且对 Activity 持有弱引用

异常方面:不加 try catch,而是在 onDestory 中把消息队列 MessageQueue 中的消息给 remove 掉。

则使用如下方式创建 handler 对象:

/**

* 为避免handler造成的内存泄漏

* 1、使用静态的handler,对外部类不保持对象的引用

* 2、但Handler需要与Activity通信,所以需要增加一个对Activity的弱引用

*/

private static class MyHandler extends Handler {

private final WeakReference mActivityReference;

MyHandler(Activity activity) {

this.mActivityReference = new WeakReference(activity);

}

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

MainActivity activity = (MainActivity) mActivityReference.get(); //获取弱引用队列中的activity

switch (msg.what) { //获取消息,更新UI

case 1:

byte[] data = (byte[]) msg.obj;

activity.threadIv.setImageBitmap(activity.getBitmap(data));

break;

}

}

}

并在 onDesotry 中销毁:

@Override

protected void onDestroy() {

super.onDestroy();

//避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列

handler.removeCallbacks(postRunnable); //清除runnable对应的message

//handler.removeMessage(what) 清除what对应的message

}

Handler 通信机制

0a274564a4b1

handler.png

创建Handler,并采用当前线程的Looper创建消息循环系统;

Handler通过sendMessage(Message)或Post(Runnable)发送消息,调用enqueueMessage把消息插入到消息链表中;

Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage里执行。

Handler 如何关联 Looper、MessageQueue

1、Handler 发送消息

上一段很熟悉的代码:

Message msg =Message.obtain();

msg.obj = data;

msg.what=1; //标志消息的标志

handler.sendMessage(msg);

从sendMessageQueue开始追踪,函数调用关系:sendMessage -> sendMessageDelayed ->sendMessageAtTime,在sendMessageAtTime中,携带者传来的message与Handler的mQueue一起通过enqueueMessage进入队列了。

对于postRunnable而言,通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。

2、消息入队列

在enqueueMessage中,通过MessageQueue入队列,并为该message的target赋值为当前的handler对象,记住msg.target很重要,之后Looper取出该消息时,还需要由msg.target.dispatchMessage回调到该handler中处理消息。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

msg.target = this;

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg, uptimeMillis);

}

在MessageQueue中,由Message的消息链表进行入队列

boolean enqueueMessage(Message msg, long when) {

if (msg.target == null) {

throw new IllegalArgumentException("Message must have a target.");

}

if (msg.isInUse()) {

throw new IllegalStateException(msg + " This message is already in use.");

}

synchronized (this) {

if (mQuitting) {

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) {

// New head, wake up the event queue if blocked.

msg.next = p;

mMessages = msg;

needWake = mBlocked;

} else {

// Inserted within the middle of the queue. Usually we don't have to wake

// up the event queue unless there is a barrier at the head of the queue

// and the message is the earliest asynchronous message in the queue.

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.next

prev.next = msg;

}

// We can assume mPtr != 0 because mQuitting is false.

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

3、Looper 处理消息

再说处理消息之前,先看Looper是如何构建与获取的:

构造Looper时,构建消息循环队列,并获取当前线程

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

但该函数是私有的,外界不能直接构造一个Looper,而是通过Looper.prepare来构造的:

public static void prepare() {

prepare(true);

}

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));

}

这里创建Looper,并把Looper对象保存在sThreadLocal中,那sThreadLocal是什么呢?

static final ThreadLocal sThreadLocal = new ThreadLocal();

它是一个保存Looper的TheadLocal实例,而ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象,这样Handler就可以通过ThreadLocal来保存于获取Looper对象了。

TheadLocal 如何保存与获取Looper?

public void set(T value) {

Thread currentThread = Thread.currentThread();

Values values = values(currentThread);

if (values == null) {

values = initializeValues(currentThread);

}

values.put(this, value);

}

public T get() {

Thread currentThread = Thread.currentThread();

Values values = values(currentThread);

if (values != null) {

Object[] table = values.table;

int index = hash & values.mask;

if (this.reference == table[index]) {

return (T) table[index + 1];

}

} else {

values = initializeValues(currentThread);

}

return (T) values.getAfterMiss(this);

}

在 set 中都是通过 values.put 保存当前线程的 Looper 实例,通过 values.getAfterMiss(this)获取,其中put和getAfterMiss都有key和value,都是由Value对象的table数组保存的,那么在table数组里怎么存的呢?

table[index] = key.reference;

table[index + 1] = value;

很显然在数组中,前一个保存着ThreadLocal对象引用的索引,后一个存储传入的Looper实例。

接下来看Looper在loop中如何处理消息

在loop中,一个循环,通过next取出MessageQueue中的消息

若取出的消息为null,则结束循环,返回。

设置消息为空,可以通过MessageQueue的quit和quitSafely方法通知消息队列退出。

若取出的消息不为空,则通过msg.target.dispatchMessage回调到handler中去。

public static void loop() {

final Looper me = myLooper();

if (me == null) {

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

}

final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,

// and keep track of what that identity token actually is.

Binder.clearCallingIdentity();

final long ident = Binder.clearCallingIdentity();

for (;;) {

Message msg = queue.next(); // might block

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

// This must be in a local variable, in case a UI event sets the logger

Printer logging = me.mLogging;

if (logging != null) {

logging.println(">>>>> Dispatching to " + msg.target + " " +

msg.callback + ": " + msg.what);

}

msg.target.dispatchMessage(msg);

if (logging != null) {

logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);

}

// Make sure that during the course of dispatching the

// identity of the thread wasn't corrupted.

final long newIdent = Binder.clearCallingIdentity();

if (ident != newIdent) {

Log.wtf(TAG, "Thread identity changed from 0x"

Long.toHexString(ident) + " to 0x"

Long.toHexString(newIdent) + " while dispatching to "

msg.target.getClass().getName() + " "

msg.callback + " what=" + msg.what);

}

msg.recycleUnchecked();

}

}

4、handler处理消息

Looper把消息回调到handler的dispatchMessage中进行消息处理:

若该消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递runnable时,把runnable对象赋值给了message的callback。

若handler的mCallback不为空,则交由通过callback创建handler方式去处理。

否则,由最常见创建handler对象的方式,在重写handlerMessage中处理。

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值