由Looper引发的crash及Looper源代码笔记

Crash

项目线上有一个crash,Exception与相同。但出问题的原因经过分析,并不是多线程操作Adapter导致的,而是androidannotation的@UIThread标签导致的。
@UIThread实际上是给MainLooper post了一个Runnable,如果有两个都是@UIThread的函数嵌套调用,如:

@UIThread
void notifyChange(){
}
@UIThread
void bar(){
    change();
    notifyChange();
    foo();
}

看起来bar()的执行顺序是:change()-notifyChange()-foo(),实际上执行顺序是:change()-MainLooper.post(notifyChange())-foo()-something-notifyChange()。bar与notifyChange直接可能有其他操作插入
UI线程之所以存在,就是为了所见顺序即运行顺序,避免多线程执行被打断的情况(即上述something处)。然而乱用Looper使得打断成为了可能。

一个不错的系统讲解

Handler

  • 所有时间基础都是SystemClock.uptimeMillis()原因
  • 可以使用runWithScissors运行同步程序块,未列入api
  • 非静态内部的Handler子类可能造成内存泄露(Lint扫描)。因为Looper会持有Message,Message是一个Active Command,持有Handler以自执行。如果Message一直不被释放,则Handler所在类的引用也一直不会被GC

Looper

  • 用ThreadLocal保证当前Looper只有一个线程,包括后绑定都不可以
  • MainLooper仅仅是系统在App启动时在UI线程prepare的一个普通Looper,静态成员变量
  • 循环用的是for(;;),bytecode跟while(true)一样…
  • Looper持有线程的强引用,可能导致线程不被释放
  • 有一个神奇的阻塞looper的函数:postSyncBarrier。其后postSyncBarrier的Looper都会被阻塞,直到removeSyncBarrier被调用
  • 时间使用的是SystemClock.uptimeMillis()

MessageQueue

  • 有一个IdleHandler接口
  • 在finalize里释放native资源
  • 使用的是一个单向链表,按时间排序存放Message
  • Message使用了对象池的方法,减少重复new对象。Message的生命周期是可控的,使用非常频繁,所以非常适合对象池
  • IdleHandler实际是在搜索后发现没有需要执行的Message时执行的,如果有Message,则会return退出函数,不执行到IdleHandler处

ActivityThread

Looper与UI事件,Crash中something的由来。from《深入理解Android内核设计思想》

  • 这里prepare了MainLooper
  • 所有UI事件都被加到了MainLooper里面,可能是postInvalidate这种send一个Message到MainLooper,更多的是借由调用执行的代码

自己写任务队列还是用Handler

对于重复进行同类型工作的WorkerThread,Handler是做了一个非常通用的封装的,免去了任务队列维护,同时,用的技术也比较底层,看起来会比BlockQueue要快一些。能想到的问题有:

  • 不能遍历任务队列
  • 线程生命周期和任务队列周期是割裂的
  • Message本身比较容易内存泄露

如果没有这些特殊要求,用Handler的效果会好很多,方便稳定。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值