★27.多线程与异步加载

AsyncTask

  • 使用AsyncTask在后台进行任务,比如网络请求。

创建

  • T1doInBackground()传入的参数类型。
  • T2:用于发布进度。
  • T3doInBackground()的返回类型,同时也是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):粗暴清理,通常不用这个。
  • 在创建的FragmentActivityonStop()onDestroy()方法里清理。
  • 注意处理在FragmentActivity中发生设备旋转时重新创建AsyncTask的问题。

注意事项

  • AsyncTask不适合大量使用的场景,从 Android3.2 起,多个AsyncTask只会在同一个 后台线程 中运行。

Loader——官方教程

基础部分

启动Loader

简单示例

// 参数一:用于标识Loader的唯一ID。
// 参数二:在构建时提供给Loader的可选参数。
// 参数三:LoaderManager.LoaderCallbacks接口实现,LoaderManager将调用此实现来报告Loader事件。
getLoaderManager().initLoader(0, null, this);

示例解说

  • 通常会在ActivityonCreate()FragmentonActivityCreated()内初始化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的实现,大致实现思路是:
    1. 重写run(),在里面执行:
      1. 调用Looper.prepared()
      2. 加锁获取此线程的Looper
      3. 创建Handler实例。
      4. 调用Looper.loop()
    2. 提供quit(),在里面调用Looper.quit()

示例

  • 警告:此示例不完善,还要调用Looper.quit(),否则Thread会长时间不退出,占用性能,具体参考 AndroidHandlerThread的实现。
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实例。

简单示例

FragmentActivity
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的生命周期与其所在 线程 一样长,所以不能保留生命周期比 线程 短的对象,如ActivityFragment等,以防发生 内存泄漏 ,通常使用 弱引用 来保留这些对象来解决这个问题。
  • 一个Looper可以有多个Handler,但是一个Handler只能对应一个Looper
  • 一个 线程 可以通过传递它的Handler给另一个 线程 ,另一个 线程 用这个Handler post()一个Runnable对象来通信。
  • Handler不再有用时或 弱引用 指向的ActivityFragment已经被销毁时,最好在对应的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由以下三个元素组成:
    • Whatint类型的 消息ID
    • obj:随Message发送的用户指定数据。
    • target:处理MessageHandler

注意事项

  • 多线程 使用容器时,使用线程安全的容器,字符串用StringBuffer

你可能感兴趣的:(★27.多线程与异步加载)