线程的基本使用:
目前大多数安卓应用是基于Java开发的,所以其线程的实现方式跟java一样,一般有以下三种:
1>新建一个继承自Thread的类,重写run()方法,在run()里面处理耗时操作,然后通过 new MyThread().start() 开启线程;
class MyThread extends Thread {
@Override
public void run() {
//处理耗时操作,比如网络请求
}
}
2> 使用继承的耦合度有点高,更多的时候我们会选择创建一个类,使其实现Runnable接口,通过new Thread(MyThread).start()开启线程;
class MyThread implements Runnable{
@Override
public void run() {
}
}
3>更长见的方式是使用匿名内部类:
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
更新UI
我们一般认为,Android的主线程是线程不安全的,不能在子线程中更新UI,我们现在看个案例,当我点击文字的时候,在子线程中改变它的内容:
会报如下异常:
其实稍加琢磨,我们就会明白安卓这种设计方案的缘由,如果任何一个线程都可以对UI进行刷新,那么就会有这样的情况,A线程把当前界面的文字更新成“我是A线程”,B线程又把文字内容更新成“我是B线程”,这不乱套了吗,用户体验非常不好!即便这样,我们很多时候需要在子线程中获取网络数据、遍历用户目录等耗时操作执行完成之后,根据执行结果更新UI,那怎么玩?其实Android为此提供了一套异步消息处理机制,老规矩,先看我修改后的代码:
1>创建Handler实例,并重写handlerMessage方法,在此方法中刷新UI:
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TETX:
mTvWelcome.setText(getString(R.string.string_welcome));
break;
default:
break;
}
}
};
2>在子线程中通过Handler实例发送一条消息:
case R.id.tv_welcome://在子线程中改变文字,会报android.view.ViewRootImpl$CalledFromWrongThreadException这个异常
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作执行完后,发送消息给handler处理
Message message = Message.obtain();
message.what = UPDATE_TETX;
handler.sendMessage(message);
}
}).start();
break;
异步消息处理机制
Android中异步消息处理包含四个成员:Message、Handler、MessageQueue和Looper。
1>Message是线程之间传递的消息,其what、arg1、arg2字段可以携带整型数据,obj字段可以携带一个Object对象;
2>Handler是消息处理者,主要职责是发送和处理消息;
3>MessageQueue顾名思义也就是消息队列的意思,主要用于存放Handler发送的消息,这些消息会一直存放其中,等待被处理。每个线程中只有一个MessageQueue对象;
4>Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环中,当检测到MessageQueue中存在消息时,就会将它们一一取出,传递到Handler的handlerMessage()中处理,每个线程也只有一个Looper对象。
至此我们已经了解异步消息处理的四个成员,再总结一下整个使用流程:
1>首先,需要在主线程中创建一个Handler对象,并重写handleMessage()方法;
2>当子线程处理完耗时操作后,需要将处理结果反馈到UI中时,此时需要创建一个Message对象,并让其what字段携带一个int值,然后通过Handler对象发送出去;
3>之后该消息会被添加到MessageQueue中等待被处理,而Looper会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler对象中的handleMessage()方法中。
ps:由于Handler对象是在主线程中创建的,所以可以在handleMessage()方法中安心地进行UI操作。
异步消息处理机制流程图:
彩蛋
我之前修改的代码有如下提示:
大概意思是说这么写会导致内存泄漏,怎么就泄露了呢?其实提示内容中也提到了,“由于此Handler被声明为内部类,因此可能会阻止外部类被垃圾回收。如果Handler使用Looper或MessageQueue作为主线程以外的线程,则没有问题。如果Handler正在使用主线程的Looper或MessageQueue,则需要修复Handler声明”。可能大家还是不太懂,我的理解是这样的:当我们通过Handler对象的sendMessage()方法发送一个Message对象时,该Message对象持有该Handler对象的引用(正是依靠这个引用,Looper在消息队列中取出该Message对象后,才能准确地将该Message对象分派回该Handler对象)。而我们在主线程中创建Handler对象时,为了重写其handleMessage()方法,使用了匿名内部类的方式来创建该Handler对象。而匿名内部类和非静态内部类都是隐性地持有一个对外部类的引用。所以,该Handler对象持有外部类MyServiceActivity的引用。
那么问题来了:Message对象持有Handler对象引用,Handler对象持有该Activity的引用,如果Message对象在子线程中被发送至消息队列,然后一直没有被处理,该Activity所在的主线程也会一直挂着,而不会被内存回收。所以,会导致内存泄露。
既然知道了缘由,怎么解决呢?
其实提示信息已经给出了解决方案,那就是通过静态内部类的方式创建Handler,因为静态内部类不会持有外部类对象的引用。但是这样还不够,因为我要在handlerMessage()中刷新UI,没有外部类引用怎么访问外部类的成员?终极方案:对于这种使用了静态内部类来避免内存泄露,同时又需要调用外部类成员的情况:可以使用弱引用,也就是说我们在该内部类中声明一个对外部类对象的弱引用。这样即可以调用外部类的成员,又不会导致内存泄露。
这下大家应该清楚了,且看我修改后的代码:
//创建Handler对象
private MyHandler myHandler = new MyHandler(this);
private static class MyHandler extends Handler {
//使该内部类持有外部类MyServiceActivity的弱引用
private WeakReference<MyServiceActivity> weakReference;
public MyHandler(MyServiceActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TETX:
MyServiceActivity myServiceActivity = weakReference.get();
myServiceActivity.mTvWelcome.setText(myServiceActivity.getString(R.string.string_welcome));
break;
default:
break;
}
}
}
此篇博文至此告一段落,如果各位同学有任何疑问 ,请私信或者评论区留言与我交流~