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的效果会好很多,方便稳定。