优化 Android 线程和后台任务开发

本文讨论了Android开发中主线程的重要性及如何避免阻塞主线程,介绍了AsyncTask的使用方法及其限制,强调了合理安排任务执行以提升用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在 Android 开发中,你不应该做任何阻碍主线程的事情。但这究竟意味着什么呢?在这次海湾 Android 开发者大会讲座中,Ari Lacenski 认为对于长时间运行或潜在的复杂任务要特别小心。这一次演讲,我们将根据一个真实场景应用的需求,讨论 AsyncTask, Activity, 和 Service,逐步建立一个更易于维护的解决方案。
Android 线程 (0:46)

当我们谈论线程,我们知道一个 Android 应用程序至少有一个主线程。这个线程是随着你的Application 类的创建而同时创建的。主线程的关键职责就是绘制用户界面:它是为了处理用户交互,在屏幕上绘制像素,并进行加载 Activity。任何你添加到 Activity 里和加载资源有关的代码,都将被放置于主线程处理结束才运行,因此应用程序才能保持对于用户操作的即时响应。

你可能认为这是没有什么大不了的,例如,您可以有一个列表视图(ListView),建立一个列表,并添加一些项目。你可以将列表数据排序,然后把它传给一个列表适配器。有了资源,处理器将尝试为您绘制列表。

然而,你添加到 Activity 中的一些代码可能需要比较长时间的计算,它们不应该是在主线程中进行运行的。一个比较常见的例子是:在你的应用程序中尝试做一个网络调用。你可以试着去获得一个 URLConnection 的默认实例,并在 Activity 中运行它。这在编译的时候是不会出什么问题,但在运行时,你将会得到一个 NetworkOnMainThreadException。

让我们再回到一个不太极端的例子,回到列表视图。对于一个非静态列表,你想查询本地数据库,并将结果插入你的列表中。如果你使用 Realm,那么在 UI 线程(主线程)做这个查询是没有问题的。但是,让我们假设你使用的是 SQLite 数据库。做一个查询请求需要大量的代码,访问数据库引擎,得到的结果,对它们进行排序,并把它们交给你的 ArrayAdapter。如果所有的这一切都运行在 Activity,你的用户界面交互可能就很不顺畅了。

以前你可能会觉得,在 UI 线程读取 shared preferences,并没有什么不好的事情发生。它能有什么影响?随着你越来越多的任务,去中断或抢占 UI 线程的时间,应用程序将更多地倾向于跳过或滞后处理一些 UI 更新,经常就是导致动画将无法正确更新。用户可能会困惑这个应用是怎么了,什么可能发生最坏的情况就是,如果用户等待了一定长时间,他们会得到的系统发出的错误消息说,“应用程序没有响应。你想继续等待还是杀死这个应用?”这就相当于你走在路上,突然被贴上了一个“干掉我”的标签。就算用户留下来了他们也可能不知道当前应用程序是否是值得信赖的了。

“每个线程都会分配一个私有内存区域,该区域主要用于在执行过程中存储方法、局部变量和参数。一旦线程终止私有内存区将会被销毁。” - Anders Göransson, 《高效 Android 线程开发(Efficient Android Threading)》一书作者

我真的很喜欢这句话,它来自Efficient Android Threading 这本书,这本书讲了什么是线程和线程做什么事情。它可以帮助我了解什么是后台线程,以及它们是如何在主线程中运行的。这句话也是提醒你注意一个事实,你是你创造的线程的调度者,你应该对他们负责。Android 有很多选项或方式来协助你进行线程调度。但你应该牢记 如果你不是在主线程进行一些操作,那么你要知道你的哪些线程是目前正在运行的。

最后一点我想提出的是,对于多线程,每次执行跳跃从一个线程到另一个,会有一个短暂时间的延迟。这就是所谓的上下文切换。接下来我们将更多地讨论类似这样的链式操作,和优化一些复杂的任务系列。
AsyncTask (5:51)

现在,我想谈谈几个 Android SDK 提供的管理线程的工具。首先就是 AsyncTask,它能够使你很方便地使用它在 Activity 中执行异步任务。但这仅仅是你 能够 ,而不是你“应该”。

在 AsyncTask 中你首先会看到的两个方法之一就是 doInBackground,这是你可以在其中做耗时任务的后台线程调用的方法。第二种方法是 onPostExecute,它将运行于主线程。你可以 doInBackground 发送信息给 onPostExecute,然后继续执行后台任务。

那么 AsyncTask 的原理是什么呢?你可以 按住ctrl + 鼠标单击 这个类的名字进入到它的源代码文件。我觉得真的值得去阅读 AsyncTask 的代码,因为它会告诉你很多关于 Android 的线程模型是什么样的、它们是怎么运行的。

// AsyncTask.java
private static class InternalHandler extends Handler {
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}

这个类中最有趣的部分是有一个 handler 实例。handler 的主要工作是提供一种替代方法,以编写一系列方法,并希望它们能够互相调用,完成一系列的任务。当你向他发生事件的时候,在它的 handleMessage 中可以使用 switch 来执行不同的响应代码。

AsyncTask 通过 execute 来启动并执行一个线程。然后,你写在 doInBackground 中的代码就会自动运行。你无需关心的一点是,handler 会发送一个 MESSAGE_POST_RESULT 信号。这个信号使得 onPostExecute 被调用。这听起来很方便,但要记住在使用它的时候,不要随着时间的推移使简单的任务变得越来越复杂,那时 AsyncTask 可能就会变得很臃肿和难以控制。

让我们假设我所描述的情况实际上并不足以满足你的需要。那么可以看看接下来这个例子,你有两个任务,需要按顺序运行,这两个都是异步实现的。因为你已经使用 AsyncTask 运行一个后台线程,你可能会开始的第一个任务,然后在 doInBackground 中启动第二任务。这是 Android 线程模型不允许的事情。因此,可能的做法就是等待第一个任务完全结束再于主线程中启动第二个任务。这在 JavaScript 中也经常会类似这么回调。不过,虽然你可以这么做,但代码上看起来就经常会很糟糕:

new AsyncTask

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值