移动应用开发要求我们需要掌握多线程的应用,耗时的操作应该放到子线程中,避免阻塞主线程,导致 ANR。异步处理技术是为了提高应用性能,解决主线程和子线程之间通信的关键。
Thread
线程是 CPU 调度的最小单位,是实际执行任务的基本单元。Thread 是 Android 中异步处理技术的基础。
创建线程有两种方法。
- 继承 Thread 类并重写 run 方法。
public class MyThread extends Thread {
@Override
public void run() {
super.run();
}
}
MyThread myThread = new MyThread();
myThread.start();
- 实现 Runnable 接口并实现 run 方法。
class MyRunnable implements Runnable {
@Override
public void run() {
}
}
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
Android 应用中各种类型的线程本质上都基于 Linux 系统的 pthreads,在应用层可以分为三种类型的线程。
- 主线程:主线程也称为 UI 线程,随着应用启动和启动,用来运行 Android 组件,同时刷新屏幕上的 UI 元素。只有主线程才能操作 UI,是因为 Android 的 UI 工具包不是线程安全的,如果非主线程更新 UI 组件,会抛出异常。
- Binder 线程:Binder 线程用于不同进程之间的通信,每个进程都维护了一个线程池,用来处理其他进程中线程发送的消息,这些进程包括系统服务、Intents、ContentProviders 和 Service 等。
- 后台进程:在应用中显示创建的线程都是后台线程,刚创建的时候,这些线程的执行体是空的,需要手动添加任务。后台进程不能直接操作 UI 。
HandlerThread
HandlerThread 是一个集成了 Looper 和 MessageQueue 的线程,当启动 HandlerThread 时,会同时生成 Looper 和 MessageQueue,然后等待消息进行处理。HandlerThread 源码如下。
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
....
}
public Handler getThreadHandler() {
....
}
public boolean quit() {
....
}
public boolean quitSafely() {
....
}
public int getThreadId() {
return mTid;
}
}
使用 HandlerThread 的好处是不需要自己去维护和创建 Looper。
HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i("ThreadActivity", msg.what + "");
}
};
handler.sendEmptyMessage(0);
HandlerThread 只有一个消息队列,队列中的消息是顺序执行的,因此是线程安全的,吞吐量会因此受到一定影响,队列中的任务可能会被前面没有执行完的任务阻塞。HandlerThread 的内部机制确保了在创建 Looper 和发送消息之间不存在竞态条件,这个是通过 HandlerThread 中的 getLooper 方法实现为一个阻塞操作实现的,只有当 HandlerThread 准备好接受消息之后才会返回。
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
可以重写 HandlerThread 的 onLooper 方法,在开始接收消息之前进行某些初始化操作。
public class MyHandlerThread extends HandlerThread {
public MyHandlerThread(String name) {
super(name);
}
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
}
}
AsyncQueryHandler
AsyncQueryHandler 是用于 ContentProvider 上执行异步的 CRUD(Create、Read、Update、Delete)操作的工具类,CRUD 操作会被放到一个单独的子线程中执行,当操作结束获取到结果后,将通过消息的方式传递给调用 AsyncQueryHandler 的线程,通常就是主线程。
AsyncQueryHandler 封装了四个方法来操作 ContentProvider。
public final void startInsert(int token, Object cookie, Uri uri,
ContentValues initialValues)
public final void startUpdate(int token, Object cookie, Uri uri,
ContentValues values, String selection, String[] selectionArgs)
public final void startDelete(int token, Object cookie, Uri uri,
String selection, String[] selectionArgs)
public void startQuery(int token, Object cookie, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)g selection, String[] selectionArgs)
根据需要实现下面的回调函数,从而得到上面的 CRUD 操作的返回结果。
@Override
public void startQuery(int token, Object cookie, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy) {
super.startQuery(token, cookie, uri, projection, selection, selectionArgs, orderBy);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
}
@Override
protected void onUpdateComplete(int token, Object cookie, int result) {
super.onUpdateComplete(token, cookie, result);
}
@Override
protected void onInsertComplete(int token, Object cookie, Uri uri) {
super.onInsertComplete(token, cookie, uri);
}
@Override
protected void onDeleteComplete(int token, Object cookie, int result) {
super.onDeleteComplete(token, cookie, result);
}
IntentService
Service 各个生命周期运行在主线程,所以为了 Service 能够在子线程处理耗时任务,Android 引入了一个 Service 的子类:IntentService。当后台线程队列中所有任务都处理完毕之后,IntentService 将结束他的生命周期。
public class SimpleIntentService extends IntentService {
public SimpleIntentService(String name) {
super(name);
setIntentRedelivery(true); // onStartCommand 方法会返回 START_REDELIVER_INTENT,如果 onHandleIntent 方法返回之前进程死掉,那么进程会重新启动,intent 会重新传递
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 后台线程调用
}
}
onStartCommand 方法会返回 START_REDELIVER_INTENT,如果 onHandleIntent 方法返回之前进程死掉,那么进程会重写启动,intent 会重新传递。
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
IntentService 的内部是通过 HandlerThread 来实现后台任务的处理。
Executor Framework
线程的创建和销毁存在开销,如果应用中频繁的创建线程销毁线程,会影响到应用的性能。使用 Java Executor 框架可以通过线程池等机制解决这个问题,改善应用体验。Executor 框架提供了如下功能。
- 创建工作线程池,同时通过队列来控制能够在这些线程执行的任务的个数
- 检测导致线程意外终止的错误
- 等待线程执行完成并获取执行结果
- 批量执行线程,并通过固定的顺序获取执行结果
- 在合适的时机启动后台线程,从而保证线程执行结果可以很快反馈给用户
Executor 框架的基础是 Executor 的接口定义,Executor 的主要目的是分离任务的创建和执行,最终实现上述的功能。
public interface Executor {
void execute(Runnable var1);
}
实现 Executor 并重写 execute 方法。最简单的是直接在这个方法中创建一个线程来执行 Runnable。
public class SimpleExecutor implements Executor {
@Override
public void execute(@NonNull Runnable runnable) {
new Thread(runnable).start();
}
}
使用 execute 通常需要增加类似队列,任务优先级等功能,最终实现一个线程池。线程池是任务队列和工作线程的集合,这两者组合起来实现生产者消费者模式。Executor 框架提供了预定义的线程池实现。
- 固定大小的线程池:通过 Executors.newFixedThreadPool(n),n 表示线程池中线程的个数。
- 可变大小的线程池:通过 Executors.newCachedThreadPool() 创建,当有新任务需要执行时,线程池会创建新的线程来处理它,空闲的线程会等待 60 s 来执行新任务,当没有任务可执行时就自动销毁,因此可变大小线程池会根据任务队列大小而变化。
- 单个线程的线程池:通过 Executors.newSingleThreadExecutor() 创建,这个线程池永远只有一个线程来串行执行任务队列中的任务。
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
)
- corePoolSize:核心线程数,核心线程会一直存在于线程池中,即使当前没有任务需要处理,当线程数小于核心线程数时,即使当前有空闲线程,线程池也会优先创建新的线程来处理任务。
- maximumPoolSize“最大线程数,当线程数大于核心线程数,且任务队列已经满了,这时线程池就会创建新的线程,直到线程数量达到最大线程数为止。
- keepAliveTime:线程的空闲存活事件,当线程的空闲事件超过这个时间,就会被销毁,直到线程数等于核心线程数。
- unit:keepAliveTime 的单位,可选的的有 TimeUnit 类中的 MICROSECONDS, MILLISECONDS,NANOSECNDS,SECONDS。
- workQuene:线程池所使用的任务缓冲队列。
AsyncTask 是在 Executor框架基础上进行的封装,实现将耗时任务移动到工作线程中执行,同事提供方便实现工作线程和主线程的通信。
public class SimpleTask extends AsyncTask {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Object doInBackground(Object[] objects) {
return null;
}
@Override
protected void onProgressUpdate(Object[] values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
doInBackground 方法在工作线程中执行,其他的都是在主线程中执行。
API Level | execute | executeOnExecutor |
---|---|---|
1-3 | 串行执行 | 没有这个方法 |
4 -10 | 并行执行 | 没有这个方法 |
11 - 12 | 并行执行 | 串行或者并行 |
13+ | 串行执行 | 串行或者并行 |
一个应用中使用的所有 AsyncTask 实力会共享全局的属性,如果 AsyncTask 中的任务是串行执行,那么应用中所有的 AsyncTask 都会进行排队,只有等前面的任务执行完成之后,才会接着执行下一个 AsyncTask 中的任务;如果 AsyncTask 是异步执行,那么在四核 CPU 系统上,最多只有五个任务可以同时进行,其他任务需要在队列中排队,等待空闲的线程。
public abstract class AsyncTask<Params, Progress, Result> {
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
}
Loader
Loader 是 安卓 3.0 引入的一个异步数据加载框架,在数据源发生变化时,能够及时发出消息通知。Loader 设计的 API。
- Loader:加载器框架的基类,封装了实现异步数据加载的接口,当一个加载器被激活后,它就会开始监视数据源并在数据发生改变时发送新的效果。
- AsyncTaskLoader:Loader 的子类,是基于 AsyncTask 实现的异步数据加载,是一个抽象类,子类必须实现 loadInBackground 方法,在其中进行具体的数据加载操作。
- CursorLoader:AsyncTaskLoader 的子类,封装了对 ContentResolver 的 query 操作,实现从 ContentProvider 中查询数据的功能。
- LoaderManager:抽象类,Activity 和 Fragment 默认都会关联一个 LoaderManager 对象,只需要通过 getLoaderManager 即可获取。LoaderManager 用来管理一个或者多个加载器对象。
- LoaderManager.LoaderCallbacks:LoaderManager 的回调接口,主要有三个方法。
- onCreateLoader:初始化并返回一个新的 Loader 实例。
- onLoadFinished:当一个加载器完成加载过程之后会回调这个方法。
- onLoaderReset:当一个加载器被重置并且数据无效时会回调这个方法。