线程的应用场景
Android是单线程模型,Activity、Service、Broadcast等组件的创建,都是在主线程完成的,即UI线程。但如果需要执行一些耗时的操作时,比如:I/O的读写、大文件的读写、数据库操作以及网络上传和下载等操作都需要很长的时间,如果主线程中有较多的耗时任务,就会降低界面的响应速度,甚至失去响应,如果失去响应超过 5秒,系统就会提示强行关闭程序。解决办法就是,使用子线程Thread。
线程与进程的区别
线程是进程内的一个执行单元,也是进程内的可调度实体。
两者的区别:
1.地址空间:每个进程都至少存在一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间。
2.线程是CPU处理器调度的基本单位,但进程不是。
3.进程是资源分配,并且拥有单位的,同一个进程内的线程可以共享进程里的资源。
线程间通信
Handler机制,是Runnable 和 Activity交互的桥梁。在run中发送Message,在Handler里通过不同的Message,执行不同的任务。
涉及的类:
Handler:负责消息的发送和处理,通过它可以实现子线程 和 主线程之间的通信。
Looper: 负责管理已接收的线程消息和消息队列。
Message: 消息载体。
MessageQueue:消息队列,遵循先进先出的原则,它用来保存待处理的线程消息。
线程安全问题
当使用多个子线程来同时访问一个数据时,会出现线程安全的问题,比如:多个线程同时操作同一个数据,会导致数据不一致的问题。所以我们一般通过同步机制来解决这样的问题:synchronized(){ }同步代码块 和synchronized 同步方法。
子线程中更新UI的几种方式
handle.handleMessage(Message msg);
handle.post(Runnable r);
runOnUiThread(Runnable r);
View.post(Runnable r);
子线程更新UI的详细解释看这里: 面试官:子线程 真的不能更新UI ?
Handler机制
1. Handler定义
Handler是一套Android消息传递机制。
2. Handler的作用
在多线程应用场景中,将子线程中需要更新UI的操作消息,传递到UI主线程,从而实现子线程通知UI更新最终实现异步消息处理。
3 .相关概念
Message: 消息载体,里面存储这线程消息。
Handler. : 负责消息的发送和处理,子线程中使用sendMessage() 发送消息;在handleMessage()中处理。
MessageQueue:消息队列,遵循先进先出的原则,存储着sendMessage()发送来的子线程消息。
Looper: 消息循环器,负责从MessageQueue中循环取消息,再将取出的消息分发给 handleMessage(),来处理消息。每个线程只能有一个Looper (它是通过ThreadLocal实现的唯一性) ,即多个子线程可以借助同一个Handler进行通信。
4. Handler工作机制
1. Handler机制的工作流程:
- Handler初始化: 在主线程中创建Looper、MessageQueue、Handler,创建完成后,Looper则自动进入消息循环状态 。同时,Handler自动绑定Looper和MessageQueue。如果消息队列为空,则线程阻塞。主线程中由于在ActivityThread的main()方法中已经初始化过了,所以就不需要在初始化Looper和MessageQueue了。
- 子线程发送消息:子线程中通过Handler向消息队列MessageQueue中发送消息。
- 消息循环: Looper 循环取出MessageQueue中的Message消息。Looper 将循环取出的消息分发给Handler中的handleMessage()方法。
- 接收处理消息: 在handleMessage(Message msg)方法中处理消息。
2. Looper的作用
- 创建Looper对象
- 创建MessageQueue对象
- 让Looper对象持有当前线程
Looper相关方法:
- Looper.prepare()———为当前线程创建一个Looper;
- Looper.loop() ——— 开启消息循环;
- Looper.prepareMainLooper() ——— 为主线程创建Looper时使用,在ActivityThread有用到。
- Looper.getMainLooper() ——— 通过该方法可以获取主线程的Looper。
- Looper.quit() ——— 退出Looper循环。
- Looper.quitSafely() ——— 自己创建的Looper,在不使用的时候,需要退出。
3. Thread、Handler、Looper的对应关系
- 一个Thread线程只能绑定到一个Looper循环器上,但可以有多个Handler实例处理者。
- 一个Looper循环器可以绑定多个Handler实例。比如主线程的main()方法中创建了Looper和Handler对象,但是我们开发过程中仍然可以创建其他Handler对象。
- 一个Handler对象处理者,只能绑定到一个Looper循环器中。
5. Handler的使用
Handler的使用有两种方式:handler.sendMessage (Message msg)、handler.post (Runnable r)。
5.1 子线程向主线程发送消息
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private Handler mHandler;
private Button btnSendeToMainThread;
private static final int MSG_SUB_TO_MAIN= 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnSendeToMainThread = (Button) findViewById(R.id.btn_sendto_mainthread);
// 1.创建Handler,并重写handleMessage方法
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息
switch (msg.what) {
case MSG_SUB_TO_MAIN:
// 打印出处理消息的线程名和Message.obj
Log.e(TAG, "接收到消息: " + Thread.currentThread().getName() + ","+ msg.obj);
break;
default:
break;
}
}
};
btnSendeToMainThread.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建一个子线程,在子线程中发送消息
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = MSG_SUB_TO_MAIN;
msg.obj = "这是一个来自子线程的消息";
// 2.发送消息
mHandler.sendMessage(msg);
}
}).start();
}
});
}
}
5.2 主线程向子线程发送消息
// 创建一个子线程,并在子线程中创建一个Handler,且重写handleMessage
new Thread(new Runnable() {
@Override
public void run() {
//子线程中创建Handler接收器,就必须创建Looper。
Looper.prepare();
subHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息
switch (msg.what) {
case MSG_MAIN_TO_SUB:
Log.e(TAG, "接收到消息:" + Thread.currentThread().getName() + ","+ msg.obj);
break;
default:
break;
}
}
};
Looper.loop();
}
}).start();
btnSendToSubThread = (Button) findViewById(R.id.btn_sendto_subthread);
btnSendToSubThread.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message msg = Message.obtain();
msg.what = MSG_MAIN_TO_SUB;
msg.obj = "这是一个来自主线程的消息";
// 主线程中发送消息
subHandler.sendMessage(msg);
}
});
6. Handler导致内存泄漏分析
下面的两种写法会导致内存泄漏。
/**
* 方式1:新建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类对象->>分析1
//注:此处并无指定Looper,故自动绑定当前线程(主线程)的 Looper、MessageQueue
showhandler = new FHandler();
// 2. 启动子线程1
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";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
// 3. 启动子线程2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2;// 消息标识
msg.obj = "BB";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
}
// 分析1:自定义Handler子类
class FHandler extends 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;
}
}
}
}
/**
* 方式2:匿名Handler内部类
*/
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);
//1. 通过匿名内部类实例化的Handler类对象
showhandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;
}
}
};
// 2. 启动子线程1
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";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
// 3. 启动子线程2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2;// 消息标识
msg.obj = "BB";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
}
}
1. 储备知识
在Java中,非静态内部类 和 匿名内部类 都是会默认持有外部类的引用的。
主线程中的Looper对象的生命周期 = 该应用程序的生命周期。
2. 内存泄漏原因描述
当Handler消息队列MessageQueue中,还有未处理的消息或者正在处理的消息时,消息队列中的Message会持有Handler实例的引用,而Handler实例又持有着外部类MainActivity的引用。此时如果外部类MainActivity被销毁,但由于上述的引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。
最终原因,是由于Looper的生命周期 > MainActivity的生命周期。
3. 内存泄漏的解决方案
解决方案一:
静态内部类 + 弱引用
原理:静态内部类,默认不会持有外部类的引用。从而使得逐层的引用关系不复存在;
弱引用对象拥有的生命周期很短暂。垃圾回收器执行扫描时,一旦发现了具有弱引用的对象,
不管内存空间是否充足,都会回收这个弱引用对象的内存。
解决方案二:
外部类被销毁时,清空MessageQueue消息队列。
这样不仅使得逐层的引用关系不复存在,同时使得Looper的生命周期与外部类实现了同步。
清除消息队列的方法是:removeCallbackAndMessages(null);
7. Handler中的Looper如何停止
可以通过调用 handler.getLooper().quit(),执行完退出之后,Looper.loop() 后面的代码才会执行。否则后面的代码是永远都不会执行的。
public class LooperActivity extends AppCompatActivity {
private static final String TAG = "LooperActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_looper);
btn = (Button) findViewById(R.id.btn);
// 开启一个子线程,去执行异步任务
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Log.e(TAG, "Looper.loop之前" );
// Looper.loop方法是一个死循环
Looper.loop();
// 得不到执行
Log.e(TAG, "Looper.loop之后" );
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 调用Looper的quit方法,停止Looper
mHandler.getLooper().quit();
}
});
}
}
8. Looper 死循环为什么不会导致应用卡死?
Launch桌面的图标第一次启动 Activity 时,会最终走到 ActivityThread 的 main 方法,在 main 方法里面创建了 Looper 和 MessageQueue 来处理主线程的消息,然后 Looper.loop 方法就进入死循环。我们的 Activity 的生命周期都是通过 Handler 机制来处理的。在看看看 loop 方法循环:
主线程主要依靠的就是消息循环,一旦退出消息循环,那么应用也就退出了。 Looper.loop() 方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理消息事件,就不会产生 ANR 异常。
造成 ANR 异常的不是主线程阻塞,而是主线程中 Looper 的消息处理过程发生了任务阻塞从而无法响应手势操作,不能及时刷新UI。
阻塞与ANR无必然关系,虽然主线程在没有消息可处理的时候是阻塞状态的,但是只要保证有消息的时候能够得到立即处理,程序就不会发生 ANR。
总结:应用卡死跟这个Looper 没有关系,应用在没有消息需要处理的时候,Looper 它是在睡眠,释放线程;卡死是 ANR,而 Looper 是睡眠。
9. MessageQueue源码详解
1. 源码-----插入消息
boolean enqueueMessage(Message msg, long when) {
//即不允许添加同步消息屏障,实现异步消息了。
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
//. . . . . .
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//当队列为空,第一次发来的 message,或者新发来的 message 的时间更小的情况,就走if块,添加到链表头部。
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
//如果此时Looper处于阻塞状态,则唤醒。并循环执行 message 的读取。
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
// 将发来的 message 插入到 queue 队列的中间或者尾部。
// 这种情况下,我们不需要唤醒事件队列。除非有一个消息屏障在队列的头部,并且它是队列中最早的异步消息。
// 判断当前发送来的 message 是否是一个异步消息,如果是异步消息,并且队列 queue头部含有 消息屏障
// p.target = null, 同时当前 queue是处于阻塞状态。则设为 “需要唤醒队列”。
//这里的 p 消息,也有可能是 postAsyncBarrier()中传到 消息队列里的,因此此处需要处理异步消息的情况
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 执行一个 for循环,并通过时间的对比,将新发来的消息,插入到队列中适当的位置。
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//如果队列头部有了 target=null 的一个消息屏障,并且当前发送过来的 message 是一个异步消息。
// 如果添加 message 之前,队列中还有其他异步消息等待处理,则就不唤醒 queue。否则就唤醒。
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
2. 源码——Looper
public static void loop() {
boolean slowDeliveryDetected = false;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
// 将事件交由 Handler处理
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
// 回收msg,以便 Message.obtain() 实现复用。
msg.recycleUnchecked();
}
}
3. 源码——MessageQueue
Message next() {
// mPtr,Native层的 MessageQueue。
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int nextPollTimeoutMillis = 0;
for (;;) {
// Native层设置阻塞时间—延迟消息的时间
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; // 获取链表头部信息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// 循环遍历,查找第一个 异步消息。会直接忽略 queue 中的其他消息。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
// 根据延迟消息,设置阻塞时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 返回一个即时消息,或者返回一个已到时间的 延迟消息。
mBlocked = false; // 设置为 queue未阻塞状态
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// queue 队列中没有消息时,会返回 -1,表示 nativePollOnce() 会一直阻塞
nextPollTimeoutMillis = -1;
}
}
}
}
4. 延迟消息Handler.postDelay(new Runnable(), 2000)
//使用方式
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
//延迟一秒后,执行这里面的内容
}
},1000);
handler.removeCallback();
传入的Runnable,最终存储在了mesage.callback 中。也是一种延迟消息,当到时间时,looper循环取出后,会执行Handler中的dispatchMessage()方法。判断如果 callback 不为空,就执行 callback.run()。callback就是一个Rnnable对象。
Hanlder同步消息屏障
原理:Android 源码分析 - Handler的同步屏障机制
实例:Android中异步消息和同步屏障
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println("打印: " + msg.what);
switch (msg.what){
case 999:
//第四步 异步消息执行完之后必需,移除消息屏障,否则会一直阻塞 queue。
removeSyncBarrier();
break;
}
}
};
new Thread(new Runnable() {
int max = 0;
@Override
public void run() {
while (max != 10) {
handler.sendEmptyMessageDelayed(max, 5000 + 500 * max);
max++;
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
message.what = 999;
// 第一步 设置 message为 异步消息
message.setAsynchronous(true);
// 第二步 设置消息屏障
postSyncBarrier();
// 第三步 发送消息
handler.sendMessageDelayed(message, 0);
}
}).start();
}
private int token = 0;//设置消息屏障时返回的token,用于删除消息屏障时使用。
// 反射执行投递同步屏障
public void postSyncBarrier() {
Method method = null;
try {
method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
token = (int) method.invoke(Looper.getMainLooper().getQueue());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
// 反射执行移除同步屏障
public void removeSyncBarrier() {
Method method = null;
try {
method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier", int.class);
method.invoke(Looper.getMainLooper().getQueue(), token);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
源码分析 --- 消息屏障
步骤一、Message设置为异步消息
步骤二 、在 queue中设置消息屏障
// 8.0之后该方法改为 @hide隐藏了,因此需要通过反射获取。
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
//注意⚠️ 这里添加message的时候,target没有设置,默认为null — — 即消息屏障
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
// 从链表的头部开始,找出队列中最后一个message,或者找出queue 中间的延迟时间晚于当前message延迟时间的
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // 如果queue队列前面有其他message,则将当前 message插入到这两个 message中间
msg.next = p;
prev.next = msg;
} else { // 如果前面没有 message,则将当前的 message设置为最头部的 message。
msg.next = p;
mMessages = msg;
}
// 返回一个 token,用于接下来的 移除工作。
return token;
}
}
步骤三、发送消息
步骤四、移除消息屏障
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}