[译] 使用AsyncTask的光明和黑暗面之三

后台任务执行

因为AsyncTask是异步执行其任务的,所以可以顺序或并发地执行多个任务。执行环境可以在应用程序中显式定义;否则,它是由平台隐式设置的。启动执行的方法决定如何执行任务。如下表所示:

[译] 使用AsyncTask的光明和黑暗面之三_第1张图片

  • execute(Params…)

这个函数让任务以单线程队列方式或线程池队列方式运行,依赖于平台版本而有所不同。

  • execute(Runnable)

在API级别11中添加,用于执行可运行任务,而不是重写doInBackground。 Runnable在AsyncTask内部执行环境中处理,但不使用消息传递在线程之间进行通信。 不调用onPreExecute,onPostExecute和onCancelled,并且无法发布进度。 该用例可能应替换为其他解决方案

  • executeOnExecutor(Excutor,Params..)

在API级别11中添加,用于配置处理任务的实际执行环境。它可以利用内部执行环境或使用自定义Executor。

Excutor可以是以下列出的其中之一:

(1) AsyncTask.THREAD_POOL_EXECUTOR

     任务在线程池中并发处理。在KitKat中,线程池大小是基于可用的CPU内核数量:N+1个核心线程和最多2*N+1个线程,工作队列可以容纳128个任务。因此,一个拥有四个可用内核的设备最多可以容纳137个任务。

(2) AsyncTask.SERIAL_EXECUTOR

     确保线程安全执行任务的顺序任务调度程序。它不包含自己的线程,而是依赖THREAD_POOL_EXECUTOR执行。它将任务存储在一个无限队列中,并将每个任务依次传递给THREAD_POOL_EXECUTOR来执行。这些任务可以在线程池的不同线程中执行,但是SERIAL_EXECUTOR保证在前一个任务完成之前不会将连续的任务添加到线程池中,因此线程安全性得以保留。

下图总结了这两种Executor如何操作和使用线程池

[译] 使用AsyncTask的光明和黑暗面之三_第2张图片

两种Executor都将AsyncTask工作线程用于doInBackground回调。 这些线程没有附加Looper,因此AsyncTask无法接收来自其他线程的消息。 此外,线程的优先级降低为Process.THREAD_PRIORITY_BACKGROUND,以使其对UI线程的干扰减少。

应用程序全局执行

可以从应用程序中的任何组件定义和执行AsyncTask的实现,并且处于RUNNING状态中的多个实例可以共存。但是,所有AsyncTask实例共享一个应用程序范围的全局执行属性。

这意味着即使两个不同的线程同时启动两个不同的任务(如下面的示例所示),它们也将按顺序执行。无论哪个先被运行时环境执行,都会阻止另一个执行,直到它终止:

new FirstLongTask().execute();
...
new SecondLongTask().execute();

 

[译] 使用AsyncTask的光明和黑暗面之三_第3张图片

不管AsyncTask实现是从活动、服务还是应用程序的任何其他部分执行的,它们仍然使用相同的应用程序全局执行环境并按顺序运行。

注意:AsyncTask实例的应用程序全局执行带来执行环境饱和,后台任务延迟甚至根本不执行的风险。

因为所有的AsyncTask实例共享这个全局execution,它们都可以相互影响,这取决于执行环境:

  • Sequential execution (SERIAL_EXECUTOR)

顺序执行的任务将不会在工作线程上进行处理,直到处理完应用程序中的所有先前任务为止。这适用于通过executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)或通过API级别13或更高版本的execute()启动的任何任务。

  • Concurrent execution (THREAD_POOL_EXECUTOR)
     

一个拥有所有可用内核的四核设备可以同时处理5个异步任务。

当第6个任务启动时,它将被放置在等待队列中,直到前5个任务之一完成并使工作线程空闲。这可能看起来很奇怪,因为我前面说过THREAD_POOL_EXECUTOR可以容纳137个任务,但原因在于ThreadPoolExecutor的实现中有5个核心池线程。当核心池线程都被占用时,实现将选择排队而不是创建新线程。

跨平台版本执行

重要的是要知道任务是顺序执行还是并发执行。通过execute启动的任务按顺序运行,而executeOnExecutor可以与并发Executor一起运行。但是,executeOnExecutor是在API级别11(Honeycomb)中首先添加的。本节说明在跨平台版本处理AsyncTask时需要了解的内容。下表总结了执行方面的差异。

[译] 使用AsyncTask的光明和黑暗面之三_第4张图片

起初,执行始终是顺序执行的,但是为了获得性能,API级别4中更改了execute方法以同时处理任务。 不幸的是,依赖于有序或线程安全执行的任务在暴露于无序和非线程安全的环境时可能会失败。 因此,在API级别11中,使用executeOnExecutor方法扩展了该API。 在API级别13中,execute方法已还原为顺序执行以恢复以前的安全行为。 executeOnExecutor方法支持自定义的Executor实现,可用于并发执行。
同样在API级别13中,该平台向应用程序的AndroidManifest.xml文件中的targetSdkVersion添加了检查,以避免现有应用程序出现意外行为:

注意:execute的执行行为取决于平台的API级别和应用程序清单中的targetSdkVersion。

在所有平台版本上需要一致执行行为的应用程序必须通过设置targetSdkVersion来自行处理。
在API级别13之前,不可能使用AsyncTask在所有平台版本之间实现顺序执行。通过设置targetSdkVersion <13+或更改实际的执行程序(取决于平台版本),可以实现跨平台版本的并发执行:

  • Consistent sequential execution(一致的顺序执行)
     

无法保证在API级别4-10上顺序执行AsyncTask实例,因为execute是并发的,并且直到API级别11才可以使用executeOnExecutor。相反,要求一致顺序执行的后台任务应使用Executors.newSingleThreadExecutor或HandlerThread。

  • Consistent concurrent execution(一致的并发执行)

targetSdkVersion设置确定如何跨平台版本一致地实现并发执行。 对于低于13的API级别,execute方法就足够了,但是对于更高的API级别,应用程序必须根据构建来更改执行调用:

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR1) {
    new MyAsyncTask().execute();
} else {
    new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

 

如果每次都要执行API检查,那将非常繁琐,因此您可以定义一个包装器类,该包装器类可用于处理平台检查:

public class ConcurrentAsyncTask {
    public static void execute(AsyncTask as) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR2) {
            as.execute(...);
        } else {
            as.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ...);
        }
    }
}


调用者将要执行的AsyncTask传递给该包装类:

ConcurrentAsyncTask.execute(new MyAsyncTask());

自定义Excution

AsyncTask中预定义的executors (SERIAL_EXECUTOR和THREAD_POOL_EXECUTOR)是应用程序全局的,这可能会在应用程序执行大量任务时造成性能损失。为了规避全局执行,任务应该在自定义执行器中处理:

new AsyncTask().executeOnExecutor(Params, MyCustomExecutor);

自定义执行程序替换了AsyncTask中的执行环境,但保留了线程之间的通信以进行进度更新:覆盖方法的调用方式与预定义执行程序的调用方式相同。 通过将定制的非全局执行程序与AsyncTask结合使用,可以更好地处理顺序执行;

Example: Nonglobal sequential execution(非全局的顺序执行)

如果一个组件中的任务必须等待另一组件中的任务完成之后才能执行,则在应用程序中全局共享的顺序执行可能会导致意外的执行延迟。 因此,为了利用顺序执行(但避免应用程序全局行为),应在任务之间共享自定义执行程序。
下面是一个非常简单的示例,显示了在Activity和Service中都使用顺序执行程序Executors.newSingleThreadExecutor。 因为任务是在不同的组件中执行的,但是仍然需要相同的执行程序,所以执行程序实例保存在Application实例中:

public class EatApplication extends Application {
    private Executor customSequentialExecutor;
    public Executor getCustomSequentialExecutor() {
        if (customSequentialExecutor == null) {
        customSequentialExecutor = Executors.newSingleThreadExecutor();
    }
        return customSequentialExecutor;
    }
}

 

public class MyActivity extends Activity {
    private void executeTaskSequentially() {
        new MyActivityAsyncTask().executeOnExecutor(
            ((EatApplication)getApplication).getCustomSequentialExecutor()
        );
    }
}
    
public class MyService extends Service {
     private void executeTaskSequentially() {
        new MyServiceAsyncTask().executeOnExecutor(
            ((EatApplication)getApplication).getCustomSequentialExecutor()
        );
    }
}

 

你可能感兴趣的:([译] 使用AsyncTask的光明和黑暗面之三)