Handler的使用

文章详细分析了Android中Handler的使用常见问题,包括内存泄漏的原因和解决方法,如重复创建Handler、Handler引用外部对象导致的问题。同时,解释了Looper在消息循环中的作用,如何在子线程中正确使用Handler,以及如何避免内存泄漏,如使用静态内部类和弱引用。此外,提到了主线程默认的Looper创建时机以及如何在子线程中初始化Looper。
摘要由CSDN通过智能技术生成

引言

先来看一段代码

private Handler handler = new Handler() {
	@Override
	public void handleMessage(Message msg) {
	    super.handleMessage(msg);
	}
}

这里初始化Handler对象,存在几个问题:

  • 1、内存泄漏
  • 2、new Handler() 无参构造方法,已被废弃(我看的android-31源码)

@deprecated
Default constructor associates this handler with the {@link Looper} for the current thread. If this thread does not have a looper, this handler won’t be able to receive messages so an exception is thrown.
默认构造函数将此处理程序与当前线程的{@link Looper}相关联。如果这个线程没有looper,那么这个处理程序将无法接收消息,因此会引发异常。

  • 3、如果在子线程,需要指定Looper对象,否则会在执行handleMessage()时崩溃

内存泄漏

不能正确处理好Handler的生命周期,将会导致内存泄漏:

  • (1)handler被重复创建:如果每次使用都重新创建handler实例,但没有及时删除,会导致handler的数量不断增加,最终耗尽系统资源并导致内存泄漏。
  • (2)handler引用外部对象:
    • 如果handler在回调函数中引用了外部对象,而这些对象本身也在handler的作用域之外,则这些对象将无法被垃圾回收器回收,从而导致内存泄漏。
    • Handler是与当前线程的消息队列相关联的。如果在某些情况下,消息队列中仍有未处理的消息时,由于持有对Handler的引用,它可能会阻止Handler及其关联的对象被垃圾回收,从而导致内存泄漏。

比如, Handler 是非静态内部类的实例,它会隐性持有对所在的外部类的引用(如HandlerActivity)。那么,当 HandlerActivity关闭,而Handler中的消息还没有处理完时,handler会一直持有 HandlerActivity 实例,而无法被回收。

解决:
1、使用公共变量,避免重复创建对象;
2、初始化Handler使用静态内部类,并且持有的对象使用弱引用。

因为静态内部类不会持有对外部类的引用,且生命周期与外部类相同。当外部类实例被回收后,静态内部类实例也会被回收,这样就避免内存泄漏。

3、Handler持有的对象使用弱引用:WeakReference
4、使用结束后,移除MessageQueue中的消息:removeCallbacksAndMessages

Looper轮询器

上源码

class Looper {
    // 保存当前线程的Lopper单例对象
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 
    final MessageQueue mQueue; // 消息队列--单链表结构
    final Thread mThread; // 当前线程
    
    // 初始化Looper单例对象
    private void prepare() {
        sThreadLocal.set(new Looper(quitAllowed)); 
    } 
  
	/**
     * new Looper() 初始化消息队列和当前进程
     * /
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed); // 初始化消息队列
        mThread = Thread.currentThread(); // 获取当前线程
    }
    
	/**
     * 应用启动时,主线程入口类main()方法会调用此方法创建主线程的Looper
     * /
	public static void prepareMainLooper() {
		prepare(false);
		sMainLooper = myLooper();
	}

	/**
	 * 启动消息循环,发送消息。这个方法会一直阻塞当前线程
	 * /
	public static void loop() {
		...
		loopOnce(me, ident, thresholdOverride)
	}
	private static boolean loopOnce(final Looper me, ...) {
		Message msg = me.mQueue.next(); // 从队列获取消息
		msg.target.dispatchMessage(msg); // 分发消息
	}

	/**
	 * 停止循环,以确保线程能够正常退出
	 * 其实是调用了MessageQueue的removeAllMessagesLocked()方法
	 * 它会清空消息队列
	 * /
 	public void quit() {
        mQueue.quit(false);  // -> MessageQueue # removeAllMessagesLocked()
    }
}

MessageQueue 中两个方法的区别:

  • removeAllMessagesLocked():清空消息队列中的所有消息
  • removeCallbacksAndMessages():同时移除与指定对象相关的所有回调和消息
  • 子线程里使用Handler使用,需要指定Looper对象
    当你在子线程中创建一个Handler对象时,默认情况下它是与该子线程没有Looper相关联的。因此,调用handleMessage方法时会发生崩溃。
    因此,需要先初始化Looper对象,然后再创建Handler对象,以便让Handler能够与该Looper所在线程的消息队列进行交互。

  • 主线程里创建Handler时,可以不用显式指定Looper对象
    由于主线程已经默认存在一个Looper对象,因此我们可以直接使用Handler()构造函数来创建Handler对象,而无需指定Looper参数。

主线程什么时候默认创建了Looper对象?

  • 在应用程序启动时就被创建并关联到主线程上。这样,在主线程中执行UI操作时,就可以直接使用已经存在的Looper对象来处理消息循环。但是,如果某个子线程需要执行异步任务并且需要处理消息循环,则必须手动调用Looper.prepare()和Looper.loop()来创建Looper对象并启动消息循环。
// 主线程入口类
public final class ActivityThread {
	public static void main(String[] args) {
		// 1.新建Looper对象、MessageQueue对象
		Looper.prepareMainLooper(); // prepare() - new Looper(quitAllowed)
		// 2、轮询器,不断获取消息 Looper.loop();  
		Looper.loop();
	}			
}

写法示例1-主线程

// 初始化Handler对象
private MyHandler myHandler = new MyHandler(this);

// 静态内部类 MyHandler
private static class MyHandler extends Handler {
	private WeakReference<MainActivity> mContext; // 弱引用
	MyHandler(Context context) {
		this.mContext = new WeakReference<>(context);
	}
	@Override
	public void handleMessage(Message msg) {
		super.handleMessage(msg);
		Context context = mContext.get();
		// 消息处理
	}
}

// 发送消息
private void sendMsg() {
	Message message = Messge.obtain();
	message.what = 1;
	message.obj = "Hello, World!";
	myHandler.sendMessage(message);
	myHandler.sendMessageDelayed(message, 1000); // 延迟1秒后发送消息
	myHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            // 执行延迟执行的任务
        }
    }, 2000); // 延迟2秒后执行任务
}

@Override
protected void onDestroy() {
	super.onDestroy();
	if (myHandler != null) {
		// 发送多个延迟消息时,建议退出前移除消息,清除关联的对象
		myHandler.removeCallbacksAndMessages(null); 
	}
}

要点:静态内部类、弱引用、移除消息

写法示例2-子线程

public class MyThread extends Thread {
    private static class MyHandler extends Handler {
        public MyHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            // 处理消息
        }
    }

    @Override
    public void run() {
		// 初始化Looper
        Looper.prepare(); 
		
		// 初始化Handler对象,并为Handler显式指定Looper对象
        Handler handler = new MyHandler(Looper.myLooper()); 
		
		// 使用 handler 对象发送消息
		Message message = Message.obtain();
		message.what = 1; // 设置消息标识
		message.obj = "Hello, World!"; // 设置消息内容
		handler.sendMessage(message);

		// 启动Looper
        Looper.loop(); 
        
		// 退出Looper循环
        Looper.myLooper().quit(); 
        handler.removeCallbacksAndMessages(null); 
    }
}

要点:
1、新建Handler对象,使用静态内部类,避免内存泄漏
2、使用Handler之前,初始化Looper对象:Looper.prepare()
3、为Handler显式指定Looper对象,确保它在正确的线程中运行,并能够正确地处理消息
4、调用Looper.loop()方法启动消息循环,发送消息。这个方法会一直阻塞当前线程。
5、调用Looper.myLooper().quit()停止循环,以确保线程能够正常退出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值