AsyncTask
- 使用
AsyncTask
在后台进行任务,比如网络请求。
创建
-
T1
:doInBackground()
传入的参数类型。
-
T2
:用于发布进度。
-
T3
:doInBackground()
的返回类型,同时也是onPostExecute()
的参数类型。
private class FetchItemsTask extends AsyncTask {
@Override
protected T3 doInBackground(T1... params) {
// 不能在此处更新主线程UI。在此处进程后台任务,比如网络请求。
publishProgress(/* T2... values */); // 发布进度。
return /* 返回T3类型数据。 */;
}
@Override
protected void onPostExecute(T3 items) {
/* doInBackground调用完毕后会调用此函数,可以在此方法中更新主线程UI。 */
}
@Override
protected void onProgressUpdate(T2... values) {
/* 发布进度,可在此处更新主线程UI。 */
}
}
执行
new FetchItemsTask().execute(/* T1类型 params... */);
清理
-
AsyncTask.cancel(false)
:温和清理,仅通知,由AsyncTask
检查isCancelled()
状态决定停止。
-
AsyncTask.cancel(true)
:粗暴清理,通常不用这个。
- 在创建的
Fragment
或Activity
的onStop()
或onDestroy()
方法里清理。
- 注意处理在
Fragment
或Activity
中发生设备旋转时重新创建AsyncTask
的问题。
注意事项
-
AsyncTask
不适合大量使用的场景,从 Android3.2 起,多个AsyncTask
只会在同一个 后台线程 中运行。
Loader
——官方教程
基础部分
启动Loader
简单示例
// 参数一:用于标识Loader的唯一ID。
// 参数二:在构建时提供给Loader的可选参数。
// 参数三:LoaderManager.LoaderCallbacks接口实现,LoaderManager将调用此实现来报告Loader事件。
getLoaderManager().initLoader(0, null, this);
示例解说
- 通常会在
Activity
的onCreate()
或Fragment
的onActivityCreated()
内初始化Loader
。
- 调用
initLoader()
会确保Loader
已初始化且处于活动状态:
- 如果 ID 指定的
Loader
已存在,则将重复使用上次创建的Loader
。
- 如果 ID 指定的
Loader
不存在,则initLoader()
将触发LoaderManager.LoaderCallbacks.onCreateLoader()
。
重启Loader
// 参数一:用于标识Loader的唯一ID。
// 参数二:在构建时提供给Loader的可选参数。
// 参数三:LoaderManager.LoaderCallbacks接口实现,LoaderManager将调用此实现来报告Loader事件。
getLoaderManager().restartLoader(0, null, this);
LoaderManager.LoaderCallbacks
-
LoaderManager.LoaderCallbacks
包括以下方法:
-
onCreateLoader()
:针对指定的 ID 进行实例化并返回新的Loader
。
-
onLoadFinished()
:将在Loader
完成加载时调用,此时应该移除旧数据的使用。
-
onLoaderReset()
:将在Loader
重置且其数据因此不可用时调用,此时应该移除新旧数据的使用。
AsyncTaskLoader
简介
- 继承于
Loader
,相对于AsyncTask
,有很多优点。
简单示例
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 开始ID为1的Loader(没有就创建并运行),不需要捕获加载器引用,因为通常不需要直接与加载器交互。
getSupportLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks() {
@Override
public Loader onCreateLoader(final int id, final Bundle args) {
return new ImageLoadingTask(MainActivity.this);
}
@Override
public void onLoadFinished(final Loader loader, final Bitmap result) {
// TODO 使用新数据,停止使用旧数据
}
@Override
public void onLoaderReset(final Loader loader) {
// TODO 清除新旧数据的使用
}
});
}
private static class ImageLoadingTask extends AsyncTaskLoaderEx {
public ImageLoadingTask(Context context) {
super(context);
}
@Override
public Bitmap loadInBackground() {
// TODO 加载并返回数据
}
}
}
AsyncTaskLoaderEx.java
// 继承AsyncTaskLoader并重写一些方法以便于监视数据变化
public abstract class AsyncTaskLoaderEx extends AsyncTaskLoader {
private T mData;
public AsyncTaskLoaderEx(final Context context) {
super(context);
}
// 当得到结果时调用
@Override
public void deliverResult(final T data) {
// 如果处于Reset状态,那么我们不需要这个数据
if (isReset()) {
if (data != null) {
releaseResources(data);
}
}
// 更新数据
T oldData = mData;
mData = data;
// 如果处于Started状态,马上分发结果
if (isStarted()) {
super.deliverResult(data);
}
// 不需要旧数据
if (oldData != null) {
releaseResources(oldData);
}
}
// 加载器开始时调用
@Override
protected void onStartLoading() {
// 如果有现成结果,分发结果
if (mData != null) {
deliverResult(mData);
}
// TODO 可在此处开始监视内容变化,并在内容发生改变时调用Loader#onContentChanged()来通知发生改变,
// TODO 之后会导致forceLoad()被调用。可以注册Receiver来监视。
// 内容发生改变,返回True且清除Flag
if (takeContentChanged() || mData == null) {
// 强制重新加载,将导致调用onForceLoad()
forceLoad();
}
}
// 加载器停止时调用
@Override
protected void onStopLoading() {
cancelLoad();
}
// 加载器完成前被取消时调用
@Override
public void onCanceled(T data) {
super.onCanceled(data);
releaseResources(data);
}
// 加载器重置时调用
@Override
protected void onReset() {
super.onReset();
// 确保加载器已停止
onStopLoading();
// 释放数据
if (mData != null) {
releaseResources(mData);
mData = null;
}
// TODO 停止监视内容变化
}
private void releaseResources(T data) {
if (data == null)
return;
else {
// TODO 释放资源
}
}
}
CursorLoader
- 继承于
AsyncTaskLoader
,通常用于从ContentProvider中加载数据。
多线程通信
1. 线程
简介
- UI主线程 在创建的时候就已经拥有一个
Looper
对象。
- 不能
start()
一个 线程 两次,即便这个 线程 已经执行完毕,也不能重启。
Thread
简介
- Android 中的
Thread
就是 Java 中的Thread
,没有进行封装,不建议直接使用Thread
来做 后台线程 。
- 若想使用
Thread
来做 后台线程 ,需要做一些准备工作,比如创建一个Looper
对象等,相比之下,HandlerThread
则是封装好的。
- 具体如何使用
Thread
的来做 后台线程 ,可以参考HandlerThread
的实现,大致实现思路是:
- 重写
run()
,在里面执行:
- 调用
Looper.prepared()
。
- 加锁获取此线程的
Looper
。
- 创建
Handler
实例。
- 调用
Looper.loop()
。
- 提供
quit()
,在里面调用Looper.quit()
。
示例
- 警告:此示例不完善,还要调用
Looper.quit()
,否则Thread
会长时间不退出,占用性能,具体参考 Android 的HandlerThread
的实现。
class LooperThread extends Thread {
public Handler mHandler;
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
HandlerThread
(推荐)
简介
-
HandlerThread
是在Thread
的基础上封装的,使得Looper
已经被创建,不需要自己手动创建。
- 关于
HandlerThread.onLooperPrepared()
,有两个要点:
-
HandlerThread.onLooperPrepared()
在Looper
首次检查Message
队列之前调用。
- 通常在
HandlerThread.onLooperPrepared()
里面创建Handler
实例。
简单示例
在Fragment
或Activity
中
onCreate()
// 创建UI线程的Handler。
Handler uiThreadHandler = new Handler();
// 创建后台线程,并把UI线程的Handler传递过去,使得后台线程可以利用此Handler来更新UI。
mBackgroundThread = new BackgroundThread<>(uiThreadHandler);
// 通过在UI线程中实现后台线程的一个Listener,来使得后台线程知道如何更新UI。
mBackgroundThread.setListener(/* 重写Listener回调 */);
// start后台线程,注意,不能start一个线程两次,即便这个线程已经执行完毕,也不能重启。
mBackgroundThread.start();
// 通过调用Handler#getLooper(),使线程创建Looper,并调用onLooperPrepared。
mBackgroundThread.getLooper();
销毁
// 清空消息队列
@Override
public void onDestroyView() {
super.onDestroyView();
mBackgroundThread.clearQueue();
}
// 终止后台线程
@Override
public void onDestroy() {
super.onDestroy();
mBackgroundThread.quit();
}
使用
// 传递必要的参数给后台线程,后台线程利用这些参数创建消息并发送给Handler。
mBackgroundThread.queueMessage(photoHolder, galleryItem.getUrl());
定义HandlerThread
HandlerThread
public class BackgroundThread extends HandlerThread {
private static final String TAG = "BackgroundThread";
private static final int MESSAGE_DOWNLOAD = 0;
// 用于把Message.obj和url做成索引,这样只要知道obj就能获取url了。注意,使用完成后要remove以避免内存泄漏。
private ConcurrentMap mRequestMap = new ConcurrentHashMap<>();
private Listener mListener;
private Handler mHandler;
private Handler mUiThreadHandler;
// 【UI线程】
// 保存来自【UI线程】的Handler,用于更新【UI线程】的UI。
public BackgroundThread(Handler uiThreadHandler) {
super(TAG);
mUiThreadHandler = uiThreadHandler;
}
// 【当前线程】
// 唯一需要重写的方法,在onLooperPrepared()中创建当前线程Handler实例。
@Override
protected void onLooperPrepared() {
mHandler = new RequestHandler<>(this);
}
// 【UI线程】
// 封装消息,发送给当前线程的Handler
public void queueMessage(T obj, String url) {
if (url != null) {
mRequestMap.put(obj, url);
mHandler.obtainMessage(MESSAGE_DOWNLOAD, obj).sendToTarget();
}
}
// 【UI线程】
// 清空消息队列
public void clearQueue() {
mHandler.removeMessages(MESSAGE_DOWNLOAD);
}
}
Handler
部分(在HandlerThread
内部定义)
// 【当前线程】
// 将Handler定义为嵌套类(static内部类)以避免内存泄漏。
private static class RequestHandler extends Handler {
private WeakReference> mBackgroundThreadWeakRef;
// 可以在构造函数中传入外部类对象,用WeakReference保留此对象的引用,这样可以避免内存泄漏。
public RequestHandler(BackgroundThread backgroundThread) {
mBackgroundThreadWeakRef = new WeakReference<>(backgroundThread);
}
@Override
public void handleMessage(Message msg) {
// Todo: 此处执行后台线程需要做的工作,执行完毕后使用【UI线程】的Handler来更新UI。
if (msg.what == MESSAGE_DOWNLOAD) {
// 使用WeakReference.get()来获取对象。
BackgroundThread backgroundThread = mBackgroundThreadWeakRef.get();
// 在使用对象前要判断是否为null来确保对象还存在。
if (backgroundThread == null) return;
// Todo: 后台线程的工作。
// @SuppressWarnings("unchecked") T target = (T) msg.obj; 转换obj,再通过obj从map中取出必要的数据。
// 通过【UI线程】的Handler,post一个Runnable对象,在此Runnable对象中更新UI。
backgroundThread.mUiThreadHandler.post(/* Runnable对象 */);
}
}
}
Listener
部分(在HandlerThread
内部定义)
// 通过设置Listener来让主线程告知工作线程如何更新UI,接着使用此Listener来更新UI。
public interface Listener {
void onDownloadCompleted(T target, Bitmap bitmap);
}
public void setListener(Listener listener) {
mListener = listener;
}
2. Looper
- Android 中,每个 线程 (包含 UI主线程 )都分别只拥有一个
Looper
。
- 在
Looper.prepared()
之前无法创建Handler
。
- 在调用
Looper.quit()
以前,Looper
所在的 线程 会一直存在,所以若 后台线程 任务已经完成,务必直接或间接调用Looper.quit()
,否则 后台线程 会一直占用资源不释放。
3. Handler
简介
-
Handler
的作用是处理Message
。
-
Handler
总是保留着 当前线程 的Looper
的引用。
-
Handler
的生命周期与其所在 线程 一样长,所以不能保留生命周期比 线程 短的对象,如Activity
、Fragment
等,以防发生 内存泄漏 ,通常使用 弱引用 来保留这些对象来解决这个问题。
- 一个
Looper
可以有多个Handler
,但是一个Handler
只能对应一个Looper
。
- 一个 线程 可以通过传递它的
Handler
给另一个 线程 ,另一个 线程 用这个Handler
post()
一个Runnable
对象来通信。
- 在
Handler
不再有用时或 弱引用 指向的Activity
或Fragment
已经被销毁时,最好在对应的onDestroy()
中调用Handler.removeCallbacksAndMessages()
来清空 消息队列 。另外,调用Looper.quit()
的时候,也会清空 消息队列 。
使用方式
// 将Handler定义为嵌套类(static内部类)以避免内存泄漏。(此处外部类为Activity)
private static class RequestHandler extends Handler {
private WeakReference mWeakRef;
// 可以在构造函数中传入外部类对象,用WeakReference保留此对象的引用,这样可以避免内存泄漏。
public RequestHandler(Activity activity) {
mWeakRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
if (msg.what == /* 消息 */) {
// 先判断WeakReference指向的对象还存在。
if (mWeakRef.get() == null) {
return;
}
// 使用WeakReference.get()来获取对象,并使用。
Activity activity = mWeakRef.get();
}
}
}
4. MessageQueue
- 多个 线程 共享同一个
MessageQueue
,多个 线程 通过MessageQueue
来通信。
5. Message
-
Message
的目标是Handler
。
- 多个
Message
可以引用同一目标Handler
。
- 使用
Handler.obtainMessage()
创建Message
的两个好处:
- 会自动设置
Message
的目标为Handler
。
- 会从公共循环池里获取
Message
,能复用废弃的Message
,相比创建新实例,性能更高。
- 通过
Message.sendToTarget()
来发送给它的Handler
。
-
Message
由以下三个元素组成:
-
What
:int
类型的 消息ID 。
-
obj
:随Message
发送的用户指定数据。
-
target
:处理Message
的Handler
。
注意事项
- 多线程 使用容器时,使用线程安全的容器,字符串用
StringBuffer
。