一、多线程
1. 主线程(UI线程)和子线程(非UI线程)
我们在启用程序的时候,一般会创建一个主线程(也称UI线程),对UI更新只能在主线程中进行,比如你无法新建一个子线程(也称非UI线程)来实现对TextView的setText函数的调用,你只能在主线程中更新UI。
为什么不能在子线程中更新UI呢,因为UI控件不是线程安全的,多线程并发可能带来不安全问题,比如在主线程和子线程同时修改TextView的内容,那么会出现更新不同步的问题,所以我们只能在主线程更新UI。
而为了提供良好的用户体验,我们必须保证程序有高响应性,所以不能在UI线程中进行耗时的计算或I/O操作,比如你想读取(或下载)很多张图片来设置UI界面的imageview,因为会耗时一定时间,你不能在主线程上执行这个操作,原因有:一是当读取图片时,其他操作都阻塞在这里,如果你在读取图片的同时,触发某按钮的点击事件,那事件会在读取图片之后才发生;二是如果主线程在5秒内没有响应,Android操作系统会关闭程序,如果读取图片耗时过长,那程序就会被动关闭。
所以我们会选择重新创建一个子线程来进行这些耗时的操作,但是前面也讲过,读取完图片之后是无法直接在子线程中进行imageview的操作的,我们需要回到主线程进行UI更新。
解决以上问题主要有以下三个方法,本文主要介绍第一种
- Handler
- AsyncTask
- RxJava
二、handle使用
1. handle机制
-
Looper通过一个死循环,当有消息Message加入队列时, 通过 FIFO的顺序处理消息。
-
一个Message中包括了处理Message的Handler对象还有消息内容。
-
Handler与主线程是同一个线程,所以我们在子线程中完成计算之后,可以通过向消息队列(MessageQueue)加入一个消息,通知特定的 Handler去更改UI。
-
使用handle有两种方式,一种是Post,一种是sendMessage。
-
机制如下
2. Post(Runnable)方法
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) { //点击事件
new Thread(new Runnable() { //创建一个子线程
@Override
public void run() { //子线程的操作
SystemClock.sleep(3000); //延迟3秒,当作是耗时
final String text = "change";
new Handler().post(new Runnable() { //post,回到主线程
@Override
public void run() { //回到主线程的操作,更新UI
tv1.setText(text);
}
});
}
}).start;//Thread.start()
}
});
如上所示,这个点击事件如果不使用子线程和handle,将会执行3s时间,而其他控件此时无法响应事件。具体解释已在注释中说清。
3. sendMessage(Message)方法
继承Handler类,重写handleMessage函数
public class Myhandle extends Handler{
@Override
public void handleMessage(Message msg) { //重写,在里面实现回到主线程的操作
super.handleMessage(msg);
if(msg.what == 1){ //这个什么意思后面讲
tv1.setText(msg.obj.toString()); //更新UI操作
}
}
}
//实例化一个
myhandle = new Myhandle();
以上也可以这样写,两者是一样的
private Handler Myhandle = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 1){ //这个什么意思后面讲
tv1.setText(msg.obj.toString());
}
}
};
在点击事件里面创建一个子线程,重写run函数,注意观察与post的区别
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) { //点击事件
new Thread(new Runnable() { //子线程
@Override
public void run() { //重写run函数,子线程的操作
SystemClock.sleep(3000); //延迟3秒,当作是耗时
//返回一个message实例,最好不要重新new,避免多次创建message
//Message msg = myhandle.obtainMessage();和下面是一样的。
Message msg = Message.obtain();
msg.obj = "change";
msg.what = 1;
//将msg发送出去,这时会回到主线程,就是myhandle类的handleMessage去进行操作
myhandle.sendMessage(msg);
}
}).start();
}
});
Message解释
message是Handler接收与处理的消息对象,message可以设置what,obj,arg1以及arg2,what是标志位,用于区分不同子线程,进而在主线程进行不同的操作,obj是任一对象,用来传递数据,arg是int类型。
三、handle导致的内存泄露
1. 原因
当我们的handle是非静态内部类时,如果我们子线程还未完成工作时,我们就将Activity强行关闭,因为在Java中,非静态内部类和匿名类会引用了它们的外部类。此时handle会隐含的持有外部类的引用,即Activity,所以Activity就一直无法被回收,相当于它虽然被关闭了,但还被handle占用着,而handle被子线程占用着(因为子线程尚未执行完,而该线程持有Handler的引用),所以就会导致内存泄露。
2. 解决方法
- 将handle声明为static,这样handle就不会持有外部类的引用(或变为弱引用)
- 在关闭Activity前将所有子线程移除,这样就不会占用handle了
- handler被delay的Message引用了,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除。