Android 多线程使用总结

目录

  1. AsyncTask类
  2. Loaders框架
  3. HandlerThread类
  4. IntentService类
  5. 线程池ThreadPoolExecutor
  6. 参考

APK启动时会执行ActivityThread中main()方法,其中创建ActivityThread的线程作为主线程,由于该线程主要与UI交互相关,所以称为UI线程;UI线程中不能执行耗时操作,否则容易引起ANR,所以Google建议开启worker thread执行耗时操作,然后通过Handler机制通知主线程刷新UI。系统提供三种方法:

  1. Activity.runOnUiThread(Runnable)
  2. View.post(Runnable)
  3. View.postDelayed(Runnable,long)

使得worker thread中方便的刷新View,但上述方法不便于管理。针对这种场景,系统对Handler机制进行一定轻量级封装,方便worker thread通知主线程,我们主要分析以下几个类:

AsyncTask类

AsyncTask类对Thread和Handler进行封装,适用于后台执行短时间任务并通知UI的场景,demo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}

protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}

protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}

new DownloadFilesTask().execute(url1, url2, url3);

Tips:
1. AsyncTask三个泛型参数Params,Progress和Result(如果不需要则传Void),其意义如下:

参数 意义
Params 启动AsyncTask时传入的参数,传入doInBackground()
Progress 标识后台任务进度,通常在doInBackground()中通过调用publishProgress()方法传递给onProgressUpdate()
Result doInBackground()返回的结果,传给onPostExecute()

2. AsyncTask执行流程示意图:

3. 建议看源码理解其实现原理,使用AsyncTask需要遵循下列规则:
a. AsyncTask类必须在UI线程load,Android4.3后系统自动提供保证
b. AsyncTask实例必须在UI线程创建
c. execute(Params…)必须在UI线程调用
d. 不要手动调用onPreExecute(),onPostExecute(Result),doInBackground(Params…)和onProgressUpdate(Progress…)
e. AsyncTask实例只能执行一次

4. 从Andorid 3.0起,AsyncTask实例是顺序执行的;如果希望并发执行,可以调用函数executeOnExecutor(java.util.concurrent.Executor, Object[])
并传入参数AsyncTask#THREAD_POOL_EXECUTOR,THREAD_POOL_EXECUTOR定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);

public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};

private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);

/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

5. AsyncTask本身设计存在一些问题,比如说:生命周期的控制,Configuration变化容易引起内存泄露等,参考blog([1])关于AsyncTask和Loaders的对比,建议Activity/Fragment中使用Loaders框架代替AsyncTask。

Loaders框架

专门在Activity和Fragment中实现异步数据加载功能,内部对AsyncTask进行封装,有以下特征:

  1. Activity/Fragment天然支持;
  2. 提供异步加载数据,并且数据源变化时进行通知;
  3. Activity Configuration变化时不需要re-query。

Loaders框架主要涉及LoaderManager,LoaderManager.LoaderCallbacks,Loader,AsyncTaskLoader和CursorLoader几个类,官方只提供对数据源是Cursor的封装,可以自定义Loader对任意数据源进行封装。具体用法参照[2],这里我们关注主要执行流程以及自定义Loader时需要Override的函数,并给出自定义Loader模板;建议参照源码理解。

Loaders框架各个类的关系以及流程图如下:

通常自定义Loader时需要Override上述黄色的方法,参考[3]给出详细的解释,并总结自定义Loader的模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
public class SampleLoader extends AsyncTaskLoader<List<SampleItem>> {

// We hold a reference to the Loader’s data here.
private List<SampleItem> mData;

public SampleLoader(Context ctx) {
// Loaders may be used across multiple Activitys (assuming they aren't
// bound to the LoaderManager), so NEVER hold a reference to the context
// directly. Doing so will cause you to leak an entire Activity's context.
// The superclass constructor will store a reference to the Application
// Context instead, and can be retrieved with a call to getContext().
super(ctx);
}

/****************************************************/
/** (1) A task that performs the asynchronous load **/
/****************************************************/

@Override
public List<SampleItem> loadInBackground() {
// This method is called on a background thread and should generate a
// new set of data to be delivered back to the client.
List<SampleItem> data = new ArrayList<SampleItem>();

// TODO: Perform the query here and add the results to 'data'.

return data;
}

/********************************************************/
/** (2) Deliver the results to the registered listener **/
/********************************************************/

@Override
public void deliverResult(List<SampleItem> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
releaseResources(data);
return;
}

// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<SampleItem> oldData = mData;
mData = data;

if (isStarted()) {
// If the Loader is in a started state, deliver the results to the
// client. The superclass method does this for us.
super.deliverResult(data);
}

// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}

/*********************************************************/
/** (3) Implement the Loader’s state-dependent behavior **/
/*********************************************************/

@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}

// Begin monitoring the underlying data source.
if (mObserver == null) {
mObserver = new SampleObserver();
// TODO: register the observer
}

if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}

@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();

// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}

@Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();

// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}

// The Loader is being reset, so we should stop monitoring for changes.
if (mObserver != null) {
// TODO: unregister the observer
mObserver = null;
}
}

@Override
public void onCanceled(List<SampleItem> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);

// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}

private void releaseResources(List<SampleItem> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}

/*********************************************************************/
/** (4) Observer which receives notifications when the data changes **/
/*********************************************************************/

// NOTE: Implementing an observer is outside the scope of this post (this example
// uses a made-up "SampleObserver" to illustrate when/where the observer should
// be initialized).

// The observer could be anything so long as it is able to detect content changes
// and report them to the loader with a call to onContentChanged(). For example,
// if you were writing a Loader which loads a list of all installed applications
// on the device, the observer could be a BroadcastReceiver that listens for the
// ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular
// Loader whenever the receiver detects that a new application has been installed.
// Please don’t hesitate to leave a comment if you still find this confusing! :)
private SampleObserver mObserver;
}

HandlerThread类

该Thread内自动创建Handler,由于其run()是一个无限循环方法,所以需要手动使用quit()或quitSafely()方法终止线程执行,其源码HandlerThread#run方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

IntentService类

IntentService内部使用HandlerThread实现了长耗时任务在后台线程运行的场景,使用时只需Override其

intent)```即可,使用时注意以下几点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

1. 不能直接与UI交互,通常可以通过broadcast实现
2. 任务是顺序执行的,所有任务执行完毕后自动关闭service
3. 任务无法中断

其关键源码如下:

``` java
public abstract class IntentService extends Service {
...
@Override
public void onCreate() {
...
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();

mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}

@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}

private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
}

@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}

@Override
public void onDestroy() {
mServiceLooper.quit();
}

/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
* When all requests have been handled, the IntentService stops itself,
* so you should not call {@link #stopSelf}.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
*/
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
...
}

线程池ThreadPoolExecutor

对Java提供的线程池总结,具体使用方法参见Oracle文档(参考[4])。

其构造方法:

1
2
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

其中,workQueue有三中策略:

strategy example
Direct handoffs SynchronousQueue
Unbounded queues LinkedBlockingQueue
Bounded queues ArrayBlockingQueue

Rejected tasks strategy:

strategy explanation
ThreadPoolExecutor.AbortPolicy default,throw RejectedExecutionException upon
ThreadPoolExecutor.CallerRunsPolicy the thread that invokes execute itself runs the task
ThreadPoolExecutor.DiscardPolicy a task that cannot be executed is simply dropped
ThreadPoolExecutor.DiscardOldestPolicy discards the oldest unhandled request and then retries execute

执行过程:

  1. 线程池中线程数小于corePoolSize时,直接启动核心线程执行任务
  2. 线程池中线程数小于等于corePoolSize时,任务插入任务队列
  3. 如果任务队列已满,线程池中线程数小于maximumPoolSize时,启动非核心线程执行任务
  4. 如果任务队列已满,线程池中线程数等于maximumPoolSize时,调用RejectedExecutionHandler进行通知

四种常用线程池的构造参数:

Type corePoolSize maximumPoolSize keepAliveTime unit workQueue
newFixedThreadPool nThreads nThreads 0L TimeUnit.MILLISECONDS new LinkedBlockingQueue()
newSingleThreadExecutor 1 1 0L TimeUnit.MILLISECONDS new LinkedBlockingQueue()
newCachedThreadPool 0 Integer.MAX_VALUE 60L TimeUnit.SECONDS new SynchronousQueue()
newScheduledThreadPool corePoolSize Integer.MAX_VALUE 10L MILLISECONDS new DelayedWorkQueue()

参考

[1] http://saurabhsharma123k.blogspot.com/2013/11/android-loader-versus-asynctask.html
[2] https://developer.android.com/guide/components/loaders.html
[3] http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
[4] http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
[5] https://developer.android.com/guide/components/processes-and-threads.html
[6] https://developer.android.com/training/run-background-service/index.html