您的位置:首页 > 移动开发 > Android开发

让Android应用保持灵敏响应:AsyncTask

2015-11-25 01:30 363 查看
任何可能需要较长时间的操作都不应该放在UI主线程里,而应该创建一个工作线程去处理。当前台Activity未在5秒内对用户点击事件或者BroadcastReceiver接收到广播后未在10秒内返回,Android就会抛出一个应用无响应(ANR)的对话框,以供用户选择是否关闭该应用。

如何避免ANR?

创建一个处理长时间操作的工作线程的最高效方式是继承AsyncTask类:

// 注意:AsyncTask适用于短时间(最多几秒)的操作。
// 如果任务耗时更长,推荐使用java.util.concurrent包中的
// Executor、ThreadPoolExecutor和FutureTask。

// AsyncTask<Params, Progress, Result>的三个类型
// 分别对应参数、进度、结果的数据类型。如若用不着,可用Void代替。
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
// AsyncTask执行流程按调用顺序可分为四步,
// 第二步和第四步最常用,用不着的方法可以不Override。

// 第一步:在UI主线程中运行。
@Override
protected void onPreExecute() {
// 一般用来设置任务相关的UI,比如进度条
}

// 第二步:在第一步完成后立马由工作线程运行。
// 参数由UI主线程调用的execute(Params...)方法传进来(见下文),
// 返回的参数传递给第四步。
// 可调用publishProgress(Progress...)来把工作进度告知UI主线程,
// 后者在第三步中进行响应。
@Override
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
// 告知UI主线程当前的工作进度
publishProgress((int) ((i / (float) count) * 100));
// 当UI主线程调用cancel(false)来取消任务时,尽早退出
if (isCancelled()) break;
}
return totalSize;
}

// 第三步:在publishProgress(Progress...)调用后由UI主线程运行。
@Override
protected void onProgressUpdate(Integer... progress) {
// 显示任务进度
}

// 第四步:在第二步(任务)完成后由UI主线程执行。
// 注意:若任务被取消,则不会调用,
// 而是会调用后面的onCancelled(Result)方法。
@Override
protected void onPostExecute(Long result) {
// 当任务完成时的处理代码
showDialog("Downloaded " + result + " bytes");
}

// 当任务被取消,并且从第二步返回时,由UI主线程执行。
@Override
protected void onCancelled(Long result) {
// 可以不实现此方法
}
}


而UI主线程只需要简单地创建一个异步工作线程实例并调用其execute()方法即可:

// 创建一个异步工作线程实例。
// 注意:AsyncTask的实例只能由UI主线程创建。
DownloadFilesTask downloadFilesTask = new DownloadFilesTask();

// 注意:此方法只能由UI主线程调用,
// 并且只能调用一次,后续调用会抛出异常。
// 若想多次调用,可以另外创建AsyncTask实例再调用。

// 另外,从Android 1.6(API 4)开始,execute方法支持多任务并发。
// 但从Android 3.0(API 11)起,execute方法改为单线程运行,
// 新任务加入等待队列,以此避免修改共享资源时冲突
//(AsyncTask并不会也不可能知道怎么帮任务加锁解锁)等问题。

// 如若多任务能避免冲突(在doInBackground方法中处理好锁的问题),
// 或者多任务并不会冲突,可调用:
// downloadFilesTask.executeOnExecutor(AsyncTask.
//     THREAD_POOL_EXECUTOR, url1, url2, url3);
downloadFilesTask.execute(url1, url2, url3);

// 当任务需要取消时调用。
// 参数为true时强制终止工作线程,为false时允许运行中的任务继续运行
//(false时工作线程可以通过调用isCancelled()来得知任务被取消)。
downloadFilesTask.cancel(false);


如果需要更复杂的交互,可以继承Thread或者HandlerThread类。这时,应在自定义的工作线程中调用:

android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);


来降低工作线程的优先级。由于新线程的默认优先级与UI主线程一样,因此若不这样设置仍会拖慢应用的响应速度。此外,UI主线程还应提供一个Handler给工作线程回复任务完成情况。

对于在BroadcastReceiver中需要处理长时间操作的情况,可以使用IntentService类。

另外,可以用StrictMode类来监测UI主线程或者其他组件中的长时间操作:

public void onCreate() {
// 注意仅在开发者版本才做这些检测,发布版本就不必了
if (DEVELOPER_MODE) {
// 设置线程检测政策
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
// All包括CustomSlowCalls、DiskReads、DiskWrites、
// Network、ResourceMismatches
.detectAll()
// 把违规信息显示在系统log中
.penaltyLog()
// 创建ThreadPolicy实例
.build());
// 设置虚拟机检测政策
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
// All包括ActivityLeaks、CleartextNetwork、
// FileUriExposure、LeakedClosableObjects、
// LeakedRegistrationObjects、LeakedSqlLiteObjects
.detectAll()
// 把违规信息显示在系统log中
.penaltyLog()
// 创建VmPolicy实例
.build());
}
super.onCreate();
}


需要注意的是,StrictMode并不保证能发现所有的问题,比如在JNI中的硬盘、网络访问就不一定会触发它。

参考资料:

Android > Develop > Training > Keeping Your App Responsive

https://developer.android.com/intl/zh-cn/training/articles/perf-anr.html

Android > Develop > Reference > AsyncTask

https://developer.android.com/reference/android/os/AsyncTask.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  AsyncTask