背景
Handler相关知识
Handler与内存泄露
Handler本身不存在内存泄露,内存泄露是循环引用造成的,只是Handler的特性决定了他常常持有外部Context,也就成了一个常见的循环引用场景. 解决这个问题的常用方式是使用弱引用
static class MyHandler : Handler {
var context: WeakReference<Context>
constructor(context: Context) {
this.context = WeakReference(context)
}
override fun handleMessage(msg: Message) {
when (msg.what) {
SKIP_MAIN -> {
context.get()?.apply {
startActivity(Intent(this, MainActivity::class.java))
}
}
else -> throw IllegalArgumentException()
}
super.handleMessage(msg)
}
}
复制代码
当然也可以主动移出消息,在外部对象对应生命周期,把Handler赋空来解决
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mHandler = MyHandler(this);
}
override fun onDestroy() {
super.onDestroy()
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
复制代码
子线程可以更新UI吗?
可以利用 Looper
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
复制代码
Looper中有死循环为什么没有卡死
- 进程与线程
一般情况下App运行在一个进程,这个进程中有多个线程. 假如其中一个线程满负荷了,会导致整个应用卡死吗?
当然不会,CPU会保证每个task尽可能公平的享有时间片,也就是说一个线程跑满了并不会影响到其他线程的任务得不到执行吗,从而不会让整个应用卡死. 除非这个线程是主线程,因为他接管了很多重要的工作包括UI.
- 卡顿的本质
从UI绘制的角度来说, 16ms内完成绘制我们不会感受到掉帧,即UI上的卡顿. 虽然执行了一段死循环代码,但是只要保证在16ms周期内让该响应的事件响应了,该绘制的UI绘制了,我们同样不会感受到卡顿.
- pipe/epoll机制
虽然不会造成卡顿,主线程死循环这样做是不是很消耗性能?
在Loop的queue.next()方法里面使用了Linux的pipe/epoll机制, 这种管道机制和我们常用的监听回调的方式很像. 也就是说有事件才唤醒,没事件就阻塞.
所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
- 为什么要设计为死循环
之所以这里要设计为死循环只是为了保证任务不退出,
例如系统组件Activity的生命周期方法,全都依赖Handler的消息机制,如果Loop退出了,系统消息都没办法响应了.
所以如果你尝试退出主线程Loop会发生异常
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
at android.os.MessageQueue.quit(MessageQueue.java:415)
at android.os.Looper.quit(Looper.java:240)
复制代码
LocalThread是什么
看一个case,如果我想数据是以线程为作用域并且不同线程具有不同的数据副本.
int num = 1;
Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
new Thread("thread1") {
@Override
public void run() {
num = 2;
}
}.start();
new Thread("thread2") {
@Override
public void run() {
num = 3;
}
}.start();
logd("现在num是:"+num);
复制代码
也就是上面栗子,我是期望
在主线程 num = 1
在thread1 num = 2
在thread2 num = 3
一般来说可以用一个Map,用线程的name作为key,把数据存储进去. 而LocalThread正是这种方式的封装
// 申明
private ThreadLocal<Integer> num = new ThreadLocal<>();
// 使用
num.set(1);
Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
new Thread("thread1") {
@Override
public void run() {
num.set(2);
}
}.start();
new Thread("thread2") {
@Override
public void run() {
num.set(3)
}
}.start();
logd("现在num是:"+num);
复制代码