任何可能需要较长时间的操作都不应该放在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