Handler学习笔记2—相关问题

  1. 一个线程有几个Handler?多个

  1. 一个线程有几个Looper?只有一个

如何保证只有一个?

在Looper使用前会调用Looper.prepare(),该函数源码中调用sThreadLocal.set(new Looper()),sThreadLocal为ThreadLocal<Looper>实例,每个Thread中都有一个ThreadLocalMap类型的threadLocals成员变量来保存数据,通过ThreadLocal类来进行维护。set时先获取当前线程,然后获取当前线程的threadLocals,最后把ThreadLocal<Looper>作为key,Looper作为value保存起来,保证一个线程只能创建一个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));
}
//ThreadLocal
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
//获取ThreadLoaclMap
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
  1. Handler内存泄漏的原因是什么?为什么其他的内部类没有说过这个问题?

内部类持有了外部类的引用:Activity销毁时,java虚拟机认为其被Handler持有,因此不会释放。

Handler插入消息时,可插入延时消息,并且Handler对象会被Message持有,Message放入消息队列MessageQueue后,会等待到执行时间执行完毕才会销毁。

Message持有Handler,Handler持有Activity。

//Handler类
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;//持有了Handler
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

主线程中解决方法:static+弱引用,代码来源:https://blog.csdn.net/weixin_38653157/article/details/105514800

public class MainActivity extends AppCompatActivity {
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler = new MyHandler(this);
            start();
    }
    private void start() {
            Message msg = Message.obtain();
            msg.what = 1;
            mHandler.sendMessage(msg);
    }
    private static class MyHandler extends Handler {
            private WeakReference<MainActivity> activityWeakReference;
            public MyHandler(MainActivity activity) {
                activityWeakReference = new WeakReference<>(activity);
            }
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = activityWeakReference.get();
                if (activity != null) {
                    if (msg.what == 1) {
                        // 处理
                    }
                }
            }
    }
}
@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);//移除mHandler的回调和发送的消息
}

子线程中解决:当线程处理完任务后不主动调用Looper.quit()或者Looper.quitSafely()释放Looper,该线程就不会结束,会造成内存泄漏。Looper.quit()Looper.quitSafely()调用了消息队列MessageQueue.quit()函数,销毁所有队列中的Message,MessageQueue.next返回null,Looper结束,线程结束释放内存。主线程不允许调用这个两个方法,不许销毁,调了会报异常。

为什么其他的内部类没有说过这个问题:没有耗时操作,没有其余类持有内部类。

  1. 主线程为什么可以直接创建使用Handler?子线程需要做什么准备?

Handler想要运行必须有Looper在运行。在ActivityThread的main方法里面已经进行了Looper的初始化操作。所以子线程使用Handler,也需要初始并运行Looper。

public static void main(String[] args) {
    ...
    //Looper准备
    Looper.prepareMainLooper();
    ...
    //looper运行loop
    Looper.loop();
    ...
 }
  1. 多个子线程用Handler向主线程发送消息,或者同一个线程多个Handler,发送处理消息,将消息插入到同一个MessageQueue中,如何保证内部线程安全?

MessageQueue的enqueueMessage()添加消息与next()取消息通过synchronized锁保证队列不混乱,保证安全,但是会造成Handler的延迟消息时间不完全准确。

  1. 我们使用Message时如何创建它?

new Message,直接创建一个Message对象,不推荐使用,频繁创建销毁造成内存抖动。

Message.obtain 推荐使用。Message.java用单链表实现了一个size=50的栈,用作缓存消息。sPool是Message类持有的一个静态变量,该静态变量指向一个Message对象,sPool指向的对象永远是单链表的第一个元素。

public static Message obtain() {
    // 使用同步保证线程安全,因为此方法可以在任意线程调用。
    synchronized (sPoolSync) {
        if (sPool != null) {
            // 消息池头不为空,说明队列有内容,从中获取一条消息并返回。缓存的
            Message m = sPool; // 消息池的Head元素
            sPool = m.next; // 缓存池Head为下个元素(移除m)
            m.next = null; // 断开链接
            m.flags = 0; // 清除在用标记
            sPoolSize--; // 池数量减1
            return m; // 返回此消息
        }
    }
    // 消息池头为空,说明队列没有内容,直接创建一条消息并返回。
    return new Message();
}
void recycleUnchecked() {
    // 将此消息标记为在用,并判断是否添加到消息池中。
    // 清除所有属性
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    // 同步,保证线程安全。
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            // 小于最大值,则进行添加,把此Message添加到链表的头部。
            next = sPool; // 此Message的next指向链表的Head
            sPool = this; // 链表的Head为此Message 
            sPoolSize++; // 消息池数量加1
        }
    }
}
  1. 主线程中Looper死循环为什么不会导致应用卡死?

当用户启动应用时,Launcher--->application--->zygote--->虚拟器--->ActivityThread--->main函数--->初始化并运行Looper.loop,主线程相关的东西,如Activity,Service都跑在Loop中间,所有的生命周期都是以消息的方式传递存在。

loop()方法中for(;;)是一个无限循环,不停地轮询消息队列并取出消息,然后将消息分发出去。Android应用程序就是通过这个方法来达到及时响应用户操作。这个过程并不会导致ANR,ANR指应用程序在一定时间内没有得到响应或者响应时间太长。在主线程的MessageQueue没有消息时,便阻塞在MessageQueue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态。因为没有消息,即不需要响应程序,便不会出现程序无响应(ANR)现象。

总结:loop无限循环用于取出消息并将消息分发出去,没有消息时会阻塞在queue.next()里的nativePollOnce()方法里,并释放CPU资源进入休眠。Android的绝大部分操作都是通过Handler机制来完成的,如果没有消息,则不需要程序去响应,就不会有ANR。ANR一般是消息的处理过程中耗时太长导致没有及时响应用户操作。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值