什么是UI线程?
应用的主UI线程的概念及其重要性是每个Android开发者都应理解。当一个应用启动,系统会为应用创建一个名为“main”的主线程。这个主线程(也就是UI主线程)主要负责把事件分发给合适的view或者widget, 因此它非常重要。它也是你的应用和应用的UI交互的线程。例如,如果你点击了屏幕上的一个按钮,UI线程会把点击时间交给view处理,view接到事件后会设置它的pressed状态,然后向事件队列中发送一个invalidate请求。 UI线程会依次读取队列并且告诉view去重绘自己。
为什么Android的UI操作是线程不安全的
经常听到一句话,因为Android的UI操作不是线程安全的。所以就出现了Handler。
理解这句话首先要知道什么是线程安全?什么是线程不安全?
线程安全:
就是多个线程访问时,采用加锁机制,当一个线程访问该类的某个数据时,进行保护,其它线程不能进行访问直到线程读取完,其它线程才可以使用。不会出现数据不一致或者数据污染。
线程不安全:
就是不采用加锁机制,也就是不提供数据访问保护,可以多个线程同时进行访问。这样就有可能产生多个线程先后更改数据造成所得到的数据是脏数据。
所以在Android中,如果有多个线程都在同时更改UI,就会造成界面混乱不堪。如果采用加锁机制会降低运行效率。所以Android选择了线程不安全但是性能好这个方式。也就是我们常听到的一句话,Android出于对性能的考虑所以UI线程是不安全的。所以Android设计主线程MainThread为单线程模型,规定只能在主线程中更改UI元素。
由于计主线程MainThread为单线程模型所以不能再主线程进行耗时操作,这样会阻塞ui线程,造成页面卡顿。,如果UI线程被阻塞5秒以上,用户会收到“程序未响应”(ANR)的提示对话框,并且会强制退出。
耗时造作如网络请求,图像处理,数据库操作等必须,如网络请求,图像处理,数据库操作等必须放在子线程处理。而我们在这些操作之后需要把结果反馈给用户,进行ui操作。
子线程想要更改UI必须通知主线程。这就涉及到了线程间通信,也就是出现了Handler消息机制来实现线程间的通信。
了解几个名词
- UI线程:就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue
- Handler:作用就是发送与处理信息,如果希望Handler正常工作,在当前线程中必须要有一个Looper对象
- Message:Handler接收与处理的对象。Handler也能接收与处理Runnable对象
- MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue
- Looper:每个线程只能够有一个Looper,Looper负责创建并管理当前线程中的MessageQueue,调用loop方法后就会在一个无限循环体中不断地从MessageQueue中取出Message并分发给对应的Handler,最后回调handleMessage()方法处理此消息。Looper才是整个机制的核心!
Message对象的内部实现是链表,最大长度是50,用于缓存消息对象,达到重复利用消息对象的目的,以减少消息对象的创建,所以通常我们要使用obtainMessage方法来获取消息对象
Handler 实现步骤
1.在主线程通过 Handler handle=new Handler()创建一个Handler对象,并称重写handlenessage方法
创建Hanlde 时 ,执行了这两个 方法 ,拿到主线程的Looper对象和 messageQueue对象。
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;1.在主线程通过 Handler handle=new Handler()创建一个Handler对象
private final Handler mMessageHandler = new Handler(){
@Override
public void handleMessage(Message msg){
....;
}
}
通过 识别 msg.what 来进行相应的操作
2.创建一个线程进行耗时操作,在执行结束后通过 sendMessage
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
Message msg=new Message();
handle.sendMessage(msg);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
handler.post方法 直接发送一段代码。
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
handler.post(new Runnable() {
@Override
public void run() {
mTest.setText("post");//更新UI
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
内部也是调用
sendMessageDelayed 方法,他是把 Runnable 封装成一个Message对象,通过 m.callback = r,把runnable 设置为回调。
Looper 开启循环不断的从messageQueue中 获取消息,然后通过 handle的handlemessage 方法进行处理。
处理完成后返回到Lopper中不断地获取
handler内存泄漏及解决办法
handle不是静态内部类,所以 handle会隐匿的持有activity的引用。当activity要被回收是,因为handler在做耗时操作没有被释放,handler持有的activity的引用不能被释放导致activity没有被回收停留在内存中造成内存泄露
1.handler设为静态内部类
2.handler持有activity的弱引用
3.在Activity生命周期onDestroy 中调用 handler.removeCallback 方法