文章目录
- 2. 子线程中更新UI
- 2.1 异步消息处理机制
- 2.1.1 经典问题1:ActivityThread的main方法
- 2.1.2 经典问题2:Looper.loop();方法为什么不会导致循环卡死?
- 2.2 AsyncTask
- 2.3 Activity类中提供了runOnUiThread(Runnable action)方法
- 2.4 Handler方式
- 2.5 View的post方法
2. 子线程中更新UI
使用子线程之后,随之而来的是对UI
更新的问题。在Android
是不允许在子线程中进行UI
更新的。但是有些时候,我们必须在子线程执行一些耗时任务,然后根据任务的执行结果来更新相应的UI
控件。对于这种情况,Android
提供了一套异步消息处理机制。所以我们必须了解如何进行异步消息通信。
2.1 异步消息处理机制
异步消息处理主要由四个部分组成:Message
、Hanlder
、MessageQueue
和Looper
。
所涉及到的全部元素:Thread
、Handler
、Looper
、MessageQueue
、ThreadLocal
、ThreadLocalMap
。
-
Thread
:消息机制的作用域; -
Handler
:负责开启和结束消息机制,主要用于发送和处理消息; -
Looper
:负责管理消息队列,接受Handler
传送过来的消息并将队列中的消息传递给Handler
; -
Message
:为线程之间传递的消息。 -
MessageQueue
:消息队列,相当于消息管道,内部使用一个Message
链表实现消息的存和取。 -
ThreadLocal
:为线程提供数据存储功能,所存储的数据只属于该线程; -
ThreadLocalMap
:为线程提供数据存储功能的具体集合;
在Handler源码阅读笔记(一)和Handler.sendMessage()与 Handler.post() & 子线程更新UI中已经粗浅的介绍了这四个部分。这里就不再继续复述。
工作过程如下:
-
Handle
通过sendMessage()
等方法发送一个消息,调用MessageQueue
的enqueueMessage
方法 将消息添加到消息队列中; -
Looper
的loop
方法发现新消息后,从队列中取出消息,最后将其转发到Handle
中,最终在handleMessage
进行处理。 -
Looper
是运行在创建handler
的线程中,这样将Handler
中的业务逻辑切换到 穿件Handler
的线程中去了。
2.1.1 经典问题1:ActivityThread的main方法
我们知道,app
运行时通过Zygote fork
来创建一个进程,用于承载App
上运行的各种组件。大多数情况一个App
就运行在一个进程中,除非在AndroidManifest.xml
中配置Android:process
属性来指定Activity
等的运行进程。
我们知道Android
程序的入口在ActivityThread
类中的main
方法中,那么不妨粗略的看下这个方法:
public static void main(String[] args) {
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
对于prepareMainLooper
方法:
// Looper.java
public static void prepareMainLooper() {
prepare(false); // quitAllowed = false
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
// 构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在ActivityThread
类中创建Looper
对象的时候,创建的是一个不允许退出的Looper
对象。默认的Looper.prepare()
方法默认是允许退出的。
在Looper.prepare()
中会调用Looper
的构造方法来创建Looper
对象示例,在其构造方法中,我们可以看见这里初始化了一个消息队列MessageQueue
。
在Looper
底层使用ThreadLocal<Looper>
来进行存储。不妨看下这个ThreadLocal
类的一些方法:
// ThreadLocal.java
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap
是ThreadLocal
的一个内部类,类似于HashMap
。且继承了WeakReference
弱引用。从t.threadLocals
可以知道,每个线程类内部都关联了一个ThreadLocalMap
对象,在这个Map
对象中我们可以通过ThreadLocal
对象得到存储的目标对象,也即是Looper
对象。
同时对于main
方法中的sMainThreadHandler = thread.getHandler();
方法,这里的Hanlder
是在定义的时候直接初始化的:
// ActivityThread.java
final H mH = new H();
class H extends Handler
final Handler getHandler() {
return mH;
}
故而也就是在程序载入的时候,就直接初始化了Hanlder
示例。也就是说到目前为止,在主线程中就已经穿个件好了Looper
实例,而Looper
在初始化的时候会初始化对应的MessageQueue
、ThreadLocal
、ThreadLocalMap
对象,并且每个线程存在ThreadLocalMap
对象,故而直接通过当前线程对象,得到ThreadLocalMap
对象,然后从该对象中获取到初始化设置的Looper
对象。
也就是在main
方法中,就初始化了消息机制中的各个对象。
并且在最后使用Looper.loop();
方法来开启了一个死循环,以保证app
不会退出。
2.1.2 经典问题2:Looper.loop();方法为什么不会导致循环卡死?
在该方法中,我们可以看到:
public static void loop() {
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
...
}
}
在这个死循环中使用了MessageQueue
的next()
方法,并且当消息队列中没有消息的时候,该方法可能会阻塞。而next()
方法的实现如下:
Message next() {
...
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
...
}
}
保证其不会卡死的关键就在于nativePollOnce
方法,这个方法在Java
中是一个native
方法:
private native void nativePollOnce(long ptr, int timeoutMillis);
因为使用了Linux
下的pipe
和epoll
机制。nativePollOnce()
方法,这便是一个native
方法,再通过JNI
调用进入Native
层,在Native
层的代码中便采用了管道机制。管道的一端的读,另一端写,标准的生产者消费者模式。
Handler
机制中管道作用就是当一个线程A
准备好Message
,并放入消息池,这时需要通知另一个线程B
去处理这个消息。线程A
向管道的写端写入数据,管道有数据便会唤醒线程B
去处理消息。管道主要工作是用于通知另一个线程的,这便是最核心的作用。
简单说就是在主线程的MessageQueue
没有消息时,便阻塞在loop
的queue.next()
中的nativePollOnce()
方法里,此时主线程会释放CPU
资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe
管道写端写入数据来唤醒主线程工作。这里采用的epoll
机制,是一种IO
多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O
,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU
资源。
上段归纳内容来源:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
2.2 AsyncTask
通过上面的部分,我们知道了异步消息处理机制的一些常识。在Android
中还提供了一种非常简单的方式来从子线程切换到主线程。用来作为替代Thread + Handler
的辅助类。AsyncTask
可以很轻松地执行异步任务并更新ui
,但由于context
泄露,回调遗漏,configuration
变化导致崩溃等原因,在api 30(Android 11)
中AsyncTask
被正式废弃。而建议使用:
-
java.util.concurrent
包下的相关类; - 协程;
当然,AsyncTask
背后的实现原理也是基于异步消息机制,且底层使用线程池来实现线程的复用,帮我们做了一些封装。AsyncTask
类是一个抽象类,我们如果需要使用就需要实现其子类。
在继承这个类的时候,需要指定三个泛型参数,比如:
class MyAsyncTask extends AsyncTask<Params, Progress, Result>
- 泛型
Params
表示执行时候需要传入的参数,比如Http
请求的Uri
。如果不需要可指定为Void
。 - 泛型
Progress
表示后台执行的百分比; - 泛型
Result
表示后台执行任务最终返回的结果,比如String
等。
比如:
class MyAsyncTask extends AsyncTask<Void, Integer, String>
一共提供了下面几个方法:
因为context
泄露、配置发生改变的崩溃等原因,注意到这里已经不推荐使用了。
public class MyAsyncTask extends AsyncTask<Void, Integer, String> {
@Override
protected void onPreExecute() {
// 任务执行前,在UI线程中执行
super.onPreExecute();
}
@Override
protected void onPostExecute(String s) {
// 任务执行完毕,在UI线程中执行
super.onPostExecute(s);
}
@Override
protected void onProgressUpdate(Integer... values) {
// 该方法中可以对UI进行更新,在UI线程中执行
super.onProgressUpdate(values);
}
@Override
protected String doInBackground(Void... voids) {
// 在子线程中运行,可以做耗时操作。不可进行UI操作,
// 如果需要更新UI操作,可以调用 publishProgress(); 方法
// 会去调用onProgressUpdate方法。
return null;
}
}
需要注意到一点,那么就是只有doInBackground
方法才是在子线程中调用,可以在这个方法中做耗时操作,其他方法不可,且不要在doInBackground
方法进行UI更新。
比如:
public class MyAsyncTask extends AsyncTask<Void, Integer, String> {
private ProgressBar mBar;
public MyAsyncTask(ProgressBar bar){
this.mBar = bar;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 该方法中可以对UI进行更新
mBar.setProgress(values[0]);
}
@Override
protected String doInBackground(Void... voids) {
// 所有代码在子线程中运行,不可进行UI操作;
// 如果需要更新UI操作,可以调用 publishProgress(); 方法。它会去调用onProgressUpdate方法。
int count = 0;
while(count < 100){
count+=5;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
publishProgress(count);
}
return null;
}
}
Activity
:
public class MainActivity extends AppCompatActivity {
private ProgressBar bar;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bar = findViewById(R.id.id_bar);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyAsyncTask task = new MyAsyncTask(bar);
task.execute();
}
});
}
}
2.3 Activity类中提供了runOnUiThread(Runnable action)方法
比如上面的案例可以改写为:
public class MainActivity extends AppCompatActivity {
private ProgressBar bar;
private Button button;
private int count = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bar = findViewById(R.id.id_bar);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
while(count < 100){
count+=5;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(()->{bar.setProgress(count);});
}
}
}).start();
}
});
}
}
我们追踪下这个方法:
// Activity.java
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
底部调用的handler
的post
方法,也就是说我们可以使用handler
的post
方法来进行完成任务。
2.4 Handler方式
① sendMessage
public class MainActivity extends AppCompatActivity {
private ProgressBar bar;
private Button button;
private int count = 0;
private Handler mhandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bar = findViewById(R.id.id_bar);
button = findViewById(R.id.button);
mhandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
bar.setProgress(msg.arg1);
}
};
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
while(count < 100){
count+=5;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.arg1 = count;
mhandler.sendMessage(msg);
}
}
}).start();
}
});
}
}
② handler.post
public class MainActivity extends AppCompatActivity {
private ProgressBar bar;
private Button button;
private int count = 0;
private Handler mhandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bar = findViewById(R.id.id_bar);
button = findViewById(R.id.button);
mhandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
bar.setProgress(msg.arg1);
}
};
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
while(count < 100){
count+=5;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
mhandler.post(()->{bar.setProgress(count);});
}
Looper.loop();
}
}).start();
}
});
}
}
2.5 View的post方法
直接替换mhandler.post(()->{bar.setProgress(count);});
代码为:
bar.post(()->{bar.setProgress(count);});
即可达到一样的效果。
解读:
// Activity.java
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
// View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
// Handler.java
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0); // enqueueMessage(queue, msg, uptimeMillis);
}
不难发现这几个方法的底层最后都是使用到了Handler
的消息机制。