Android 异步处理技术

移动应用开发要求我们需要掌握多线程的应用,耗时的操作应该放到子线程中,避免阻塞主线程,导致 ANR。异步处理技术是为了提高应用性能,解决主线程和子线程之间通信的关键。


异步处理技术的继承树.png

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:当一个加载器被重置并且数据无效时会回调这个方法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容