Android异步处理技术

《Android 高级进阶》读书笔记
Android中,异步处理技术有很多种,常见的有Thread、AsyncTask、Handler&Looper、Executors等,在实际项目中,我们需要根据具体业务需求进行选择、一个完整的异步处理技术继承树如下:

这里写图片描述

1. Thread

线程是Java语言的一个概念,它是实际执行任务的基本单元,创建线程有两种方法。

  • 继承Thread类并重写run方法,语句如下:
public class MyThread extends Thread {
    @Override
    public void run() {
        // 具体实现逻辑
    }
}

// 需要的地方调用
MyThread myThread = new MyThread();
myThread.start(); // 使用start启动线程
  • 实现Runnable接口并实现run方法,如下:
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 实现具体逻辑
    }
}

// 需要调用的地方
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();

Android应用中各种类型的线程本质上都是基于Linux系统的pthreads,在应用层可以分为三种类型的线程。

  • 主线程:主线程也称为UI线程,随着应用启动而启动,主线程用来运行Android组件,同时刷新屏幕上的UI元素。主线程中创建的Handler会顺序执行接收到的消息,包括从其他线程中发送的消息。如果消息队列中前面的消息没有很快执行完,那么它很可能会阻塞队列中的其他消息的及时处理。
  • Binder线程:Binder线程用于不同进程之间线程的通信,每个进程都维护了一个线程池,用来处理其他进程中线程发送的消息,这些进程包括系统服务,Intents、ContentProviders和Service等。一个典型的需要使用Binder线程的场景是应用提供一个给其他进程通过AIDL接口绑定的Service。
  • 后台线程:在应用中显式创建的线程都是后台线程,也就是当刚创建出来时,这些线程的执行体是空的,需要手动添加任务。在Linux系统层面,主线程和后台线程是一样的。在Android框架中,通过WindowManager赋予了主线程只能处理UI更新以及后台线程不能直接操作UI的限制。

2. HandlerThread

HandlerThread是一个集成了Looper和MessageQueue的线程,当启动HandlerThread时,会同时生成Looper和MessageQueue,然后等待消息进行处理,它的run方法源码如下:

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

使用HandlerThread的好处是开发者不需要自己去创建和维护Looper,它的用法和普通线程一样

        HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // 处理需要处理的事务
            }
        };

HandlerThread中只有一个消息队列,队列中的消息是顺序执行的,因此是线程安全的,当然吞吐量受影响。队列中的任务可能会被前面没有执行完的任务阻塞。HandlerThread的内部机制确保了在创建Looper和发送消息之间不存在竞态条件,这个是通过将HandlerThread.getLooper()实现为一个阻塞操作实现的,只有当HandlerThread准备好接收消息之后才会返回,源码:

    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;
    }

如果具体业务要求在HandleThread开始接收消息之前要进行某些初始化操作的话,可以重写HnadlerThread的onLooperPrepared函数,例如可以在这个函数中创建与HandlerThread关联的Handler实例,这同时也可以对外隐藏我们的Handler实例

public class MyHandlerThread extends HandlerThread {
    private Handler mHandler;

    public MyHandlerThread(String name) {
        super("MyHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
    }

    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        mHandler = new Handler(getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        // Handler message
                        break;
                    case 2:
                        // Handler message
                        break;
                }
            }
        };
    }

    public void publishedMethod1() {
        mHandler.sendEmptyMessage(1);
    }

    public void publishedMethod2() {
        mHandler.sendEmptyMessage(1);
    }
}

3. AsyncQueryHandler

AsyncQueryHandler是用于在ContentProvider 上面执行异步的CRUD操作的工具类,CRUD操作会被放到一个单独的子线程中执行,当操作结束获取到结果后,将通过消息的方式传递给调用AsyncQueryHandler的线程。通常就是主线程。
AsyncQueryHandler是一个抽象类,继承自Handler,通过封装ContentResolver,HandlerThread,Handler等实现对ContentProvider的异步操作。源码:

// 查询
 public void startQuery(int token, Object cookie, Uri uri,
            String[] projection, String selection, String[] selectionArgs,
            String orderBy) {
    ...
    }   
// 插入
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) {
    ...
    }

AsyncQueryHandler的子类实现可以根据实际需求实现回调函数

public class MyAsyncQueryHandler extends AsyncQueryHandler {
    public MyAsyncQueryHandler(ContentResolver cr) {
        super(cr);
    }

    @Override
    protected void onDeleteComplete(int token, Object cookie, int result) {
        super.onDeleteComplete(token, cookie, result);
    }

    @Override
    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
        super.onQueryComplete(token, cookie, cursor);
    }

    @Override
    protected void onInsertComplete(int token, Object cookie, Uri uri) {
        super.onInsertComplete(token, cookie, uri);
    }

    @Override
    protected void onUpdateComplete(int token, Object cookie, int result) {
        super.onUpdateComplete(token, cookie, result);
    }
}

4. IntentService

IntentService和Service一样的生命周期,同时也提供了在后台线程中处理异步任务的机制。与HandlerThread类似,IntentService也是在一个后台线程中顺序执行所有的任务。通过Context.startService传递一个Intent类型的参数可以启动IntentService的异步执行。当后台线程队列中所有任务处理完成之后,IntentService将会结束它的生命周期,因此IntentService不需要开发者手动结束。
IntentService本身是一个抽象类,因此,使用前需要集成它并实现onHandleIntent方法,在这个方法中实现具体的后台处理业务逻辑,同时在子类的构造方法中需要调用super(String name)传入子类的名称,语句如下:

public class SimpleIntentService extends IntentService {
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public SimpleIntentService(String name) {
        super(SimpleIntentService.class.getName());
        setIntentRedelivery(true);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 这个方法是在后台线程中调用的
    }
}

setIntentRedelivery()方法设置为true,那么IntentService的onStartCommand方法将会返回START_REDELIVER_INTENT。这时,如果onHandleIntent方法返回之前进程死掉了,那么进程将会重新启动,intent将会重新投递。
不要忘记在AndroidManifest.xml文件中注册SimpleIntentService.

<service android:name=".SimpleIntentService"/>

再来看下IntentService的源码,事实上IntentService是通过HandlerThread来实现后台任务处理的,代码逻辑很简单,如下:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    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);
        }
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     * with your preferred semantics.
     *
     * <p>If enabled is true,
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
     * {@link #onHandleIntent(Intent)} returns, the process will be restarted
     * and the intent redelivered.  If multiple Intents have been sent, only
     * the most recent one is guaranteed to be redelivered.
     *
     * <p>If enabled is false (the default),
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
     * dies along with it.
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

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

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

    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     * @see android.app.Service#onStartCommand
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    ...
}

5. Executor Framework

为了解决频繁出现线程的创建和销毁影响应用的性能,使用Java Executor框架可以通过线程池等机制解决这个问题,改善应用体验。Executor框架为开发者提供如下功能:

  • 创建工作线程池,同时通过队列来控制能够在这些线程执行的任务的个数
  • 检测导致线程意外终止的错误
  • 等待线程执行完成并获取执行结果
  • 批量执行线程,并通过固定的顺序获取执行结果
  • 在合适的时机启动后台线程,从而保证线程执行结果可以很快反馈给用户

Executor 框架的基础是一个名为Executor 的接口定义,Executor 的主要目的是分离任务的创建和它的执行,最终实现上述所说的功能点。

public interface Executor{
    void execute(Runnable command)
}

开发者可以通过实现Executor 接口并重写execute 方法从而实现自己的Executor 类,最简单的是直接在这个方法中创建一个线程来执行Runnable。

public class SimpleExecutor implements Executor {
    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }
}

当然实际应用中很少是这么简单,通常需要增加类似队列,任务优先级等参数,最终实现一个线程池。线程池是任务队列和工作线程的集合,这两者组合起来实现生产消费者模式。Executor框架为开发者提供了预定义的线程池实现

  • 固定大小的线程池:通过Executors.newFixedThreadPool(n)创建,其中n表示线程池中线程的个数。
  • 可变大小的线程池:通过Executors.newCachedThreadPool()创建,当有新的任务需要执行时,线程池会增加新的线程来处理它,空闲的线程会等待60秒来执行新任务,当没有任务时自动销毁,因此可变大小线程池也会根据任务队列的大小而变化。
  • 单个线程的线程池:通过Executors.newSingleThreadExecutor()创建,这个线程池中永远只有一个线程来执行串行执行任务列表中的任务。

预定义的线程池都是基于ThreadPoolExecutor类之上构建的,而通过ThreadPoolExecutor开发者可以自定义线程池的一些行为,我们主要来看看这个类的构造函数定义。

    // 需要如下几个参数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
    }
  • corePoolSize:核心线程数,核心线程会一直存在于线程池中,即使当前没有任务需要处理;当线程数小于核心线程数时,即使当前有空闲的线程,线程池也会优先创建新的线程来处理任务。
  • maximumPoolSize:最大线程数,当线程数大于核心线程数,且任务队列已经满了,这时线程池就会创建新的线程,直到线程数量达到最大线程数为止。
  • keepAliveTime:线程的空闲存活时间,当线程的空闲时间超过这个时,线程会被销毁,直到线程数等于核心线程数。
  • unit:keepAliveTime的单位,可选的有TimeUnit类中的NANOSECONDS、MICROSECONDS、MILLISECONDS和SECONDS。
  • workQueue:线程池所使用的任务缓存队列。

6. AsyncTask

AsyncTask是在Executor框架基础上进行的封装,它实现将耗时任务移动到工作线程中执行,同时提供方便的接口实现工作线程和主线程的通信,使用AsyncTask一般会用到如下方法。

public class FullTask extends AsyncTask{

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Object doInBackground(Object[] params) {
        return null;
    }

    @Override
    protected void onProgressUpdate(Object[] values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onPostExecute(Object o) {
        super.onPostExecute(o);
    }

    @Override
    protected void onCancelled(Object o) {
        super.onCancelled(o);
    }
}

除了doInBackground方法是在工作线程中执行,其他的都是在主线程中执行。如果使用AsyncTask执行的任务需要并行执行的话,那么在API Level大于13的版本上建议使用executeOnExecutor代替execute。

7. Loader

Loader是Android3.0开始引入的一个异步数据加载框架,它使得在Activity或者Fragment中异步加载数据变得很简单,同时它在数据发生变化时,能够及时发出消息通知。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():当一个加载器被重置并且数据无效时会回调这个方法

一个例子看懂所有:

public class ContactActivity extends ListActivity implements LoaderManager.LoaderCallbacks {
    private static final int CONTACT_NAME_LOADER_ID = 0;

    // 这里的PROJECTION 只获取ID和用户名两个字段
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME
    };

    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        initAdapter();

        // 通过LoaderManager 初始化Loader,这会回调到onCreateLoader
        getLoaderManager().initLoader(CONTACT_NAME_LOADER_ID, null, this);
    }

    SimpleCursorAdapter mAdapter;

    private void initAdapter() {
        new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, null,
                new String[]{ContactsContract.Contacts.DISPLAY_NAME},
                new int[]{android.R.id.text1}, 0);
        setListAdapter(mAdapter);
    }

    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        // 实际创建Loader的地方,此处使用CursorLoader
        return new CursorLoader(this, ContactsContract.Contacts.CONTENT_URI,
                CONTACTS_SUMMARY_PROJECTION, null, null,
                ContactsContract.Contacts.DISPLAY_NAME + " ASC");
    }

    @Override
    public void onLoadFinished(Loader loader, Object data) {
        // 后台线程中加载完数据后,回调这个方法将数据传递给主线程
        mAdapter.swapCursor((Cursor) data);
    }

    @Override
    public void onLoaderReset(Loader loader) {
        // Loader 被重置后的回调,在这里可以重新刷新页面数据
        mAdapter.swapCursor(null);
    }

}

8. 总结

Android 平台提供了如此多的异步处理技术,我们在进行选择的时候需要根据具体的业务需求而定,综合考虑一下几个因素:

  • 尽量使用更少的系统资源,例如CPU和内存等
  • 为应用提供更好的性能和响应度
  • 实现和使用起来不复杂
  • 写出来的代码是否符合好的设计,是否易于理解和维护
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值