文章目录
- 多线程
- Handler
- 相关概念
- UI线程/主线程
- Message
- Message Queue
- Looper
- Handler
- 使用步骤
- Handler.sendMessage()
- Handler.post()
- Handler 机制工作原理
- Handler内存泄露
- 前置知识
- 案例分析
- 解决方案一:静态内部类+弱引用
- 解决方案一:onDestroy()时清空Handler内的消息队列。
- AsynTask
- 线程间通信
多线程
多线程的应用在Android开发中是非常常见的,常用方法主要有:
Handler
Handler是Android开发中的一种线程间消息传递机制,使用Handler可以在多个线程并发更新UI的同时,保证线程安全。
相关概念
UI线程/主线程
在Android开发中,为了UI操作是安全的,规定只允许UI线程更新Activity里的UI组件,UI线程也是主线程!
UI线程在程序第1次启动时自动开启,处理与UI相关的事件。
Message
Message是线程间通信的数据单元,存储需要操作的通信信息。
Message Queue
一种先进先出,用于存储Handler发送过来的Message的数据结构。
Looper
Looper直译为循环器,它是Message Queue与Handler之间的通信媒介,Looper是持有MessageQueue
的,它的作用包含:
- 消息获取:循环取出Message Queue中的消息。
- 消息分发:将取出的Message发送给对应的Handler。
每个线程中只能拥有一个Looper,一个Looper可绑定多个线程的Handler,也就是说,多个线程的Handler可以向一个线程的Looper所持有的MessageQueue中发送Message。
Handler
Handler直译为处理者,它是子线程与主线程间的通信媒介,线程消息的主要处理者。Handler持有 Looper 的实例,直接持有looper的消息队列。
- Handle可以添加Message到Message Queue。
- 可以处理Looper分发来的消息。
使用步骤
Handler.sendMessage()
Handler.sendMessage()的入参是Message对象,可以在工作线程中使用Handler.sendMessage(),发送Message,然后在主线程中接收Message,执行更新UI的操作。
可以通过新建Handler的子类或者构建匿名内部类的形式实现。
/**
* 方式1:新建Handler子类(内部类)
*/
// 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法
class mHandler extends Handler {
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需执行的UI操作
}
}
// 步骤2:在主线程中创建Handler实例
private Handler mhandler = new mHandler();
// 步骤3:创建所需的消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
// 步骤4:在工作线程中 通过Handler发送消息到消息队列中
// 可通过sendMessage() / post()
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
mHandler.sendMessage(msg);
// 步骤5:开启工作线程(同时启动了Handler)
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
/**
* 方式2:匿名内部类
*/
// 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
private Handler mhandler = new Handler(){
// 通过复写handlerMessage()从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需执行的UI操作
}
};
// 步骤2:创建消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
// 步骤3:在工作线程中 通过Handler发送消息到消息队列中
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
mHandler.sendMessage(msg);
// 步骤4:开启工作线程(同时启动了Handler)
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
Handler.post()
Handler.post()的入参是一个Runnable对象,重写Runnable对象的run()方法,进行UI的更新。
下边是一个利用handle的postDelayed实现的验证码倒计时。
Handler countHandler = new Handler(); //验证码发送等待倒计时
// 发送验证码倒计时
private final Runnable myRunnable = new Runnable() {
@Override
public void run() {
if(wait >=0) {
sendCodeButton.setText(String.valueOf(wait));
countHandler.postDelayed(this, 1000);
//从当前时间开始延迟delayMillis时间后执行Runnable
wait--;
}
else{
wait = 60;
sendCodeButton.setText("重新发送");
sendCodeButton.setEnabled(true);
countHandler.removeCallbacks(this);
}
}
};
countHandler.postDelayed(myRunnable,1000);
Handler 机制工作原理
Handler机制的工作流程主要包括4个步骤:
- 异步通信准备
- 消息入队
- 消息循环
- 消息处理
一个线程只能有一个Looper,但可以绑定到多个Handler。在工作进程通过Handler发送Message到消息队列中,Looper将消息再传递给创建该消息的Handler,Handler在主线程中接收到消息,并执行UI操作。
Handler内存泄露
前置知识
- 内存泄露出现的原因:
当一个对象已经不再使用时,本身被回收但却因为有另外一个正在使用的对象持有它的引用,从而导致它不能被回收,这就导致了内存泄露。 - 主线程的Looper对象的生命周期 = 该应用程序的生命周期
在java中,非静态内部类
和匿名内部类
都默认持有外部类的引用(可以调用this)
案例分析
在java中如果按照下边的方式使用Handler会警告出现内存泄露。
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主线程创建时便自动创建Looper & 对应的MessageQueue
// 之后执行Loop()进入消息循环
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. 通过匿名内部类实例化的Handler类对象
//注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
showhandler = new Handler(){
// 通过复写handlerMessage()从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;
}
}
};
出现内存泄露的原因:
- 在Handler消息队列还有未处理的消息/正在处理的消息时,消息队列中的Message持有Handler实例的引用(Message是Handler的内部类)。
- 由于Handle 是非静态内部类,那么它又持有外部MainActivity类的引用。
- 上述引用关系会一直保存,直到Handler消息队列中的消息被处理完毕。
- 如果在Handler消息队列还有未处理的消息,用户关闭了APP,此时需要销毁MainActivity,但是由于上述引用关系,导致垃圾回收器无法回收MainActivity,从而导致内存泄露。
解决方案一:静态内部类+弱引用
- 使用静态内部类构建Handler实例,并使用WeakReference弱引用持有外部类,保证外部类能被回收。因为:弱引用的对象拥有短暂的生命周期,在垃圾回收器线程扫描时,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 实例化自定义的Handler类对象->>分析1
// 注:
// a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
// b. 定义时需传入持有的Activity实例(弱引用)
showhandler = new FHandler(this);
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息标识
msg.obj = "AA";// 消息存放
showhandler.sendMessage(msg);
}
}.start();
}
// 设置为:静态内部类
private static class FHandler extends Handler{
// 定义 弱引用实例
private WeakReference<Activity> reference;
// 在构造方法中传入需持有的Activity实例
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity实例
reference = new WeakReference<Activity>(activity); }
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;
}
}
}
}
解决方案一:onDestroy()时清空Handler内的消息队列。
- 当外部类结束生命周期,清空Handler内的消息队列。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
}
activity被销毁前一定会执行onDestroy方法,在此处清空Handler内的消息队列,就可以保证Handler被正常销毁。
为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即 静态内部类 + 弱引用的方式
AsynTask
AsyncTask是一个在不需要开发者直接操作多线程和Handler的情况下的帮助类,适用于短时间的操作(最多几秒)。