什么是Handler?
handler 主要用于异步消息处理,当发出一个消息后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列逐一将消息去除,然后对消息进行处理.
Handler内存泄漏
代码举例说明:
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
SystemClock.sleep(3000);
message.what = 3;
handler1.sendMessage(message);
}
}).start();
private Handler handler1 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
startActivity(new Intent(TestActivity.this,ScrollingActivity.class));
return false;
}
});
从代码中可以看出,程序正在休眠3秒钟内,关闭了该页面,等到3秒结束后,程序会自动跳转到了ScrollingActivity页面,说明出现了内存泄漏。
错误示范
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("lyb======","onDestroy");
handler1.removeMessages(3);
}
大家可以想想,如果在3秒内关闭了该页面,消息还没有压入消息队列中,在onDestroy中remove的其实是null,根本就不起作用。
其实我们休眠完全不用SystemClock.sleep(3000);
换另外一个api,handler1.sendMessageDelayed(message,3000);
这样就会将消息压入队列当中了,只不过延时唤醒这样在onDestroy()
中调用handler1.removeMessages(3);
就会移除掉了,如果就是要用第一种方式,在onDestroy()
中将handler1置为空,在sendMessage的时候判断handler1是否为null就可以了
为什么不能在子线程中创建Handler
是因为应用在启动之时,会调用activityThread,而在这个类里面调用了Looper.prepareMainLooper();
里面源码显示调用了prepare(false);
这里面new了一个looper,而这个looper是主线程的looper,存在了threadLocalMap当中的,所以你在子线程中new个handler的时候,会去再threadLocalMap里面去取looper,肯定会找不到这个looper,所以会抛出了异常
TextView的setText只能再主线程里赋值吗
根据setText源码往下找,会找到checkForRelayout()
,而最终if else都会执行requestLayout();invalidate();
我们知道TextView继承View,我们来看requestLayout();
代码中有个ViewRootImpl
它是一个接口实现了ViewParent
,而这个接口里有requestLayout();
这个方法,我们来看这个方法的实现中,调用了检查线程checkThread()
,结果发现
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a
view hierarchy can touch its views.");
}
总结
如果setText的时候,执行的速度invalidate();
快于检查线程requestLayout();
不会抛出异常,反之则会
new Handler 两种写法有和区别
private Handler handler1 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
startActivity(new Intent(TestActivity.this,ScrollingActivity.class));
return false;
}
});
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
根据上面两种写法,谷歌推荐的是第一种写法
我们来看源码,handler如何取消息的。我们知道项目启动后会自动在主线程中创建一个looper,调用Looper.prepareMainLooper();
方法,根据ActivityThread源码中可以看到创建玩looper之后呢,有一个死循环,并不会抛出异常,因为有jni方法进行处理,接着我们可以看到直接在消息队列中拿到了消息messageQueue,最终调用了msg.target.dispatchMessage(msg)
方法,我们可以看到改方法中
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
根据这个代码我们可以一目了然的知道有什么区别了。
ThreadLocal用法和原理
根据new handler源码中可以看到获取looper是用 threadLocal来取的。ThreadLocal它是一个T泛型的类,说白了,它的key就是Thread而value就是T的值,在当前handler里就是looper。获取looper源码如下
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
可以看到获取了当前线程作为了ThreadLocalMap的key那么如果获取的map不为null,可以看出将map中的vlaue值取出,在该代码中value值就是looper,如果为null了,将调用setInitialValue();
方法进行赋值或者创建
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
重点
在主线程中只允许初始化一次looper,如果再new 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));
}
会直接报异常,因为sThreadLocal是全局唯一静态的
感谢
感谢大家的阅读 点个赞呗
关注我 持续更新