当我们启动一个App的时候,Android系统会启动一个Linux进程,该进程包含一个线程,称为UI线程或主线程。通常情况下,一个应用的所有组件都运行在这个进程中。当然,你也可以通过修改四大组件(activity、service、provider、receiver)在Manifest.xml中的代码块(android:process属性)来指定它们运行在不同的进程中。当一个组件启动时,如果该进程已经存在了,那么该组件就会直接通过这个进程被启动,并运行在进程的UI线程中。
UI线程中运行着许多重要的逻辑,如系统事件处理、用户输入事件处理、UI绘制、Service、Alarm等。如下图所示:
UI线程包含的逻辑
而我们编写的代码则是穿插在这些逻辑中间,例如对用户触摸事件的检测和响应、对用户输入的处理、自定义View的绘制等。如果我们插入的代码比较耗时,如网络请求或数据库读取,就会阻塞UI线程其他逻辑的执行,从而导致界面卡顿。如果卡顿时间超过5秒,系统就会报ANR错误。因此,如果要执行耗时的操作,我们需要另起线程执行。
在新线程执行完耗时的逻辑后,往往需要将结果反馈给界面,进行UI更新。然而,Android的UI toolkit不是线程安全的,不能在非UI线程进行UI的更新。所有对界面的更新必须在UI线程进行。
为了实现多线程操作,Android提供了四种常用的方式:
1. Handler+Thread
2. AsyncTask
3. ThreadPoolExecutor
4. IntentService
下面分别介绍这四种方式:
Android主线程包含一个消息队列(MessageQueue),该消息队列里面可以存入一系列的Message或Runnable对象。通过一个Handler你可以往这个消息队列发送Message或者Runnable对象,并且处理这些对象。每次你新创建一个Handle对象,它会绑定于创建它的线程(也就是UI线程)以及该线程的消息队列,从这时起,这个handler就会开始把Message或Runnable对象传递到消息队列中,并在它们出队列的时候执行它们。
以下是Handler Thread原理图:
```
+---------------------+
| UI线程 |
+---------------------+
|
v
+---------------------+
| 工作线程 |
+---------------------+
```
Handler可以把一个Message对象或者Runnable对象压入到消息队列中,进而在UI线程中获取Message或者执行Runnable对象,Handler把压入消息队列有两类方式,Post和sendMessage:
1. Post方式:
Post允许把一个Runnable对象入队到消息队列中。它的方法有:
- post(Runnable)/postAtTime(Runnable,long)/postDelayed(Runnable,long)
对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。
2. sendMessage:
sendMessage允许把一个包含消息数据的Message对象压入到消息队列中。它的方法有:sendEmptyMessage(int)/sendMessage(Message)/sendMessageAtTime(Message,long)/sendMessageDelayed(Message,long)
Handler如果使用sendMessage的方式把消息入队到消息队列中,需要传递一个Message对象,而在Handler中,需要重写handleMessage()方法,用于获取工作线程传递过来的消息,此方法运行在UI线程上。Message是一个final类,所以不可被继承。
以下是handler定义和handler sendMessage用法的示例代码:
```java
// 定义Handler
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息的逻辑
}
};
// 使用Handler发送Post请求
handler.post(new Runnable() {
@Override
public void run() {
// 在UI线程上执行的代码
}
});
// 使用Handler发送Message请求
Message message = Message.obtain();
message.what = 1; // 设置消息类型
handler.sendMessage(message);
```
AsyncTask是Android提供的一种轻量级的异步类,可以直接继承AsyncTask并在类中实现异步操作。它提供了接口来反馈当前异步执行的程度,并最终将执行结果反馈给UI主线程。
要使用AsyncTask,首先需要创建一个继承自AsyncTask的子类,并重写onPreExecute()、doInBackground()和onPostExecute()这三个方法。其中,onPreExecute()方法用于在后台任务开始执行前进行一些准备工作,doInBackground()方法用于执行实际的异步操作,而onPostExecute()方法则用于在后台任务完成后更新UI。
以下是一个简单的示例代码:
```java
public class MyAsyncTask extends AsyncTask
@Override
protected void onPreExecute() {
// 在后台任务开始执行前进行一些准备工作
// 可以在这里更新UI进度等信息
}
@Override
protected String doInBackground(Void... params) {
// 执行实际的异步操作
// 这里可以进行耗时的操作,如网络请求、文件读写等
return "异步任务的结果";
}
@Override
protected void onPostExecute(String result) {
// 在后台任务完成后更新UI
// 这里可以将结果显示在UI上,如更新TextView的内容等
}
}
```
在上述代码中,MyAsyncTask继承自AsyncTask,并指定了三个泛型参数:Void表示没有返回值,Integer表示后台任务的进度参数类型为整数,String表示后台任务的返回结果类型为字符串。在onPreExecute()方法中可以进行一些准备工作,比如更新UI进度;在doInBackground()方法中执行实际的异步操作;在onPostExecute()方法中根据后台任务的结果更新UI。
需要注意的是,由于AsyncTask是基于线程池的异步执行方式,因此在使用时要注意避免过多的任务同时执行导致内存溢出或性能问题。另外,从Android 3.0开始,默认采用了串行任务执行器,因此如果需要并行执行多个异步任务,可以考虑使用其他方式来实现。
以下是重构后的内容:
```java
class DownloadTask extends AsyncTask
// AsyncTask
@Override
protected void onPreExecute() {
// 在第一个执行方法中调用 super.onPreExecute(),即执行父类的 onPreExecute() 方法
super.onPreExecute();
}
@Override
protected String doInBackground(Integer... params) {
// 在第二个执行方法中执行,onPreExecute()方法完成后会调用此方法
for (int i = 0; i <= 100; i++) {
publishProgress(i);
try {
Thread.sleep(params[0]);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "执行完毕";
}
@Override
protected void onProgressUpdate(Integer... progress) {
// 当 doInBackground() 调用 publishProgress() 时触发此函数。虽然调用时只有一个参数,但这里取到的是一个数组,因此需要使用 progresss[0]来获取第n个参数的值
// 这里使用 progress[0] 作为进度的表示方式,并通过 super.onProgressUpdate(progress) 把进度更新给上层监听器
tv.setText(progress[0] + "%");
super.onProgressUpdate(progress);
}
@Override
protected void onPostExecute(String result) {
// 在 doInBackground() 返回后触发此函数。换句话说,就是 doInBackground() 执行完后触发。这里的 result 就是上面 doInBackground() 的返回值,因此这里是 "执行完毕"
setTitle(result);
super.onPostExecute(result);
}
}
```
AsyncTask和ThreadPoolExecutor都是用于处理异步任务的类,但是它们有一些区别。
AsyncTask是Google封装的一个异步处理类,通过实现内部的doInBackground方法就能实现子线程进行耗时操作。它使用的核心机制是线程池机制,最多同时运行5个核心线程,剩下的排队。如果同时有超过5个耗时操作使用了AsyncTak就会导致其余的任务会有线程阻塞的风险。
ThreadPoolExecutor提供了一组线程池,可以管理多个线程并行执行。这样一方面减少了每个并行任务独自建立线程的开销,另一方面可以管理多个并发线程的公共资源,从而提高了多线程的效率。所以ThreadPoolExecutor比较适合一组任务的执行。
适用范围:
1. 单个异步任务的处理:可以使用AsyncTask。
2. 处理多个异步任务代码显得较多:可以使用ThreadPoolExecutor。