九、安卓系统中的并发
在本章中,我们将解释 Android 中的并发性。我们将给出例子和建议,并将并发应用到我们的 Journaler 应用中。通过演示AsyncTask
类的使用,我们已经触及了一些基础知识,但现在我们将深入挖掘。
在本章中,我们将涵盖以下主题:
- 处理程序和线程
AsyncTask
- 安卓 Looper
- 延迟执行
安卓并发介绍
我们应用的默认执行是在主应用线程上执行的。这种执行必须是有效的!如果某个东西运行太久,我们就会得到 ANR——一个安卓应用没有响应的消息。为了避免 ANRs,我们在后台运行代码。安卓提供了一些机制,因此我们可以高效地做到这一点。异步运行操作不仅带来了良好的性能,还带来了出色的用户体验。
主流中泓线
所有用户界面更新都是从一个线程执行的。这是主线。所有事件都收集在一个队列中,并由Looper
类实例处理。
下图解释了相关类之间的关系:
需要注意的是,主线程更新都是你看到的 UI。但是,也可以从其他线程中完成。直接从其他线程执行此操作会导致异常,应用可能会崩溃。为了避免这种情况,通过从当前活动上下文中调用runOnUiThread()
方法,在主线程上执行所有与线程相关的代码。
处理程序和线程
在安卓系统中,线程可以通过使用线程以标准方式执行。不建议在没有任何控制的情况下直接激发裸线程。所以,为了这个目的,你可以使用ThreadPools
和Executor
类。
为了演示这一点,我们将更新我们的应用。用名为TaskExecutor
的类创建一个名为execution
的新包。确保它看起来像这样:
package com.journaler.execution
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
class TaskExecutor private constructor(
corePoolSize: Int,
maximumPoolSize: Int,
workQueue: BlockingQueue<Runnable>?
) : ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
0L,
TimeUnit.MILLISECONDS,
workQueue
) {
companion object {
fun getInstance(capacity: Int): TaskExecutor {
return TaskExecutor(
capacity,
capacity * 2,
LinkedBlockingQueue<Runnable>()
)
}
} }
我们用成员方法扩展了ThreadPoolExecutor
类和companion
对象,用于执行器实例化。让我们把它应用到我们现有的代码中。我们将从以前的AsyncTask
班转到TaskExecutor
班。打开NoteActivity
类,更新如下:
class NoteActivity : ItemActivity() {
...
private val executor = TaskExecutor.getInstance(1)
...
private val locationListener = object : LocationListener {
override fun onLocationChanged(p0: Location?) {
p0?.let {
LocationProvider.unsubscribe(this)
location = p0
val title = getNoteTitle()
val content = getNoteContent()
note = Note(title, content, p0)
executor.execute {
val param = note
var result = false
param?.let {
result = Db.insert(param)
}
if (result) {
Log.i(tag, "Note inserted.")
} else {
Log.e(tag, "Note not inserted.")
}
}
}
}
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
{}
override fun onProviderEnabled(p0: String?) {}
override fun onProviderDisabled(p0: String?) {}
}
...
private fun updateNote() {
if (note == null) {
if (!TextUtils.isEmpty(getNoteTitle()) &&
!TextUtils.isEmpty(getNoteContent())) {
LocationProvider.subscribe(locationListener)
}
} else {
note?.title = getNoteTitle()
note?.message = getNoteContent()
executor.execute {
val param = note
var result = false
param?.let {
result = Db.update(param)
}
if (result) {
Log.i(tag, "Note updated.")
} else {
Log.e(tag, "Note not updated.")
}
}
}
}
... }
如你所见,我们用执行者代替了AsyncTask
。我们的执行器一次只能处理一个线程。
除了标准的线程方法之外,安卓还提供了 Handlers 作为开发人员的选项之一。处理程序不是线程的替代品,而是一种补充!处理程序实例向其父线程注册自己。它代表了一种向特定线程发送数据的机制。我们可以发送Message
或Runnable
类的实例。让我们用一个例子来说明它的用法。如果一切正常,我们将使用绿色指示器更新“备注”屏幕。如果数据库持久化失败,它将是红色的。它的默认颜色是灰色。打开activity_note.xml
文件,用指示器将其展开。指示器将是平面视图,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black_transparent_40"
android:orientation="vertical">
...
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/indicator"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_margin="10dp"
android:background="@android:color/darker_gray" />
<EditText
android:id="@+id/note_title"
style="@style/edit_text_transparent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title"
android:padding="@dimen/form_padding" />
</RelativeLayout>
...
</LinearLayout>
</ScrollView>
现在,当我们添加指示器时,它将根据数据库插入结果改变颜色。像这样更新你的NoteActivity
类源代码:
class NoteActivity : ItemActivity() {
...
private var handler: Handler? = null
....
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handler = Handler(Looper.getMainLooper())
...
}
...
private val locationListener = object : LocationListener {
override fun onLocationChanged(p0: Location?) {
p0?.let {
...
executor.execute {
...
handler?.post {
var color = R.color.vermilion
if (result) {
color = R.color.green
}
indicator.setBackgroundColor(
ContextCompat.getColor(
this@NoteActivity,
color
)
)
}
}
}
}
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
{}
override fun onProviderEnabled(p0: String?) {}
override fun onProviderDisabled(p0: String?) {}
}
...
private fun updateNote() {
if (note == null) {
...
} else {
...
executor.execute {
...
handler?.post {
var color = R.color.vermilion
if (result) {
color = R.color.green
}
indicator.setBackgroundColor
(ContextCompat.getColor(
this@NoteActivity,
color
))
}
}
}
} }
构建并运行您的应用。创建新便笺。输入标题和消息内容后,您会注意到指示器的颜色变为绿色。
我们将做一些更多的更改,并对Message
类实例做同样的事情。根据以下示例更新您的代码:
class NoteActivity : ItemActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message?) {
msg?.let {
var color = R.color.vermilion
if (msg.arg1 > 0) {
color = R.color.green
}
indicator.setBackgroundColor
(ContextCompat.getColor(
this@NoteActivity,
color
))
}
super.handleMessage(msg)
}
}
...
}
...
private val locationListener = object : LocationListener {
override fun onLocationChanged(p0: Location?) {
p0?.let {
...
executor.execute {
...
sendMessage(result)
}
}
}
override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
{}
override fun onProviderEnabled(p0: String?) {}
override fun onProviderDisabled(p0: String?) {}
}
...
private fun updateNote() {
if (note == null) {
...
} else {
...
executor.execute {
...
sendMessage(result)
}
}
}
...
private fun sendMessage(result: Boolean) {
val msg = handler?.obtainMessage()
if (result) {
msg?.arg1 = 1
} else {
msg?.arg1 = 0
}
handler?.sendMessage(msg)
}
...
}
注意处理程序实例化和sendMessage()
方法。我们从Handler
类中使用obtainMessage()
方法获得了Message
实例。作为消息参数,我们传递了一个整数数据类型。根据其值,我们将更新指示器颜色。
阿明克塔斯克
您可能已经注意到,我们已经在应用中使用了AsyncTask
类。现在,我们将向前推进一步——我们将在执行者身上运行它。我们为什么要这么做?
首先,默认情况下所有AsyncTasks
都是由安卓按顺序执行的。为了并行执行它,我们需要在执行器上执行它。
等等!还有更多。现在,当我们并行执行任务时,假设您执行了其中的几个。假设我们从两个开始。没关系。他们将执行他们的操作,并在完成后向我们报告。然后,想象我们运行其中的四个。在大多数情况下,如果它们执行的操作不是太重,它们也会工作。然而,在某个时刻,我们并行运行五十个AsyncTasks
。
然后,你的应用变慢了!一切都会变慢,因为无法控制任务的执行。我们必须管理任务,以便保持性能。所以,我们开始吧!我们将继续到目前为止更新的同一堂课。如下更改您的NoteActivity
:
class NoteActivity : ItemActivity() {
...
private val threadPoolExecutor = ThreadPoolExecutor(
3, 3, 1, TimeUnit.SECONDS, LinkedBlockingQueue<Runnable>()
)
private class TryAsync(val identifier: String) : AsyncTask<Unit,
Int, Unit>() {
private val tag = "TryAsync"
override fun onPreExecute() {
Log.i(tag, "onPreExecute [ $identifier ]")
super.onPreExecute()
}
override fun doInBackground(vararg p0: Unit?): Unit {
Log.i(tag, "doInBackground [ $identifier ][ START ]")
Thread.sleep(5000)
Log.i(tag, "doInBackground [ $identifier ][ END ]")
return Unit
}
override fun onCancelled(result: Unit?) {
Log.i(tag, "onCancelled [ $identifier ][ END ]")
super.onCancelled(result)
}
override fun onProgressUpdate(vararg values: Int?) {
val progress = values.first()
progress?.let {
Log.i(tag, "onProgressUpdate [ $identifier ][ $progress ]")
}
super.onProgressUpdate(*values)
}
override fun onPostExecute(result: Unit?) {
Log.i(tag, "onPostExecute [ $identifier ]")
super.onPostExecute(result)
}
}
...
private val textWatcher = object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
...
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2:
Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int,
p3: Int) {
p0?.let {
tryAsync(p0.toString())
}
}
}
...
private fun tryAsync(identifier: String) {
val tryAsync = TryAsync(identifier)
tryAsync.executeOnExecutor(threadPoolExecutor)
}
}
因为这实际上不是我们将保存在 Journaler 应用中的东西,所以不要提交这些代码。如果您愿意,可以将其创建为单独的分支。我们创建了ThreadPoolExecutor
的新实例。构造函数接受几个参数,如下所示:
corePoolSize
:这表示池中保留的线程数量最少。maximumPoolSize
:这表示池中允许的最大线程数。keepAliveTime
:如果线程数大于核心,非核心线程将等待一个新的任务,如果在此参数定义的时间内没有得到一个,它们将终止。Unit
:表示keepAliveTime
的时间单位。-
WorkQueue
:这表示将用于保存任务的队列实例。 -
我们将在这个执行者身上运行我们的任务。
AsyncTask
具体化将记录其生命周期内的所有事件。在main
方法中,我们将等待 5 秒。运行应用,尝试添加一个以Android
为标题的新注释。观察您的 Logcat 输出:
08-04 14:56:59.283 21953-21953 ... I/TryAsync: onPreExecute [ A ]
08-04 14:56:59.284 21953-23233 ... I/TryAsync: doInBackground [ A ][ START ]
08-04 14:57:00.202 21953-21953 ... I/TryAsync: onPreExecute [ An ]
08-04 14:57:00.204 21953-23250 ... I/TryAsync: doInBackground [ An ][ START ]
08-04 14:57:00.783 21953-21953 ... I/TryAsync: onPreExecute [ And ]
08-04 14:57:00.784 21953-23281 ... I/TryAsync: doInBackground [ And ][ START ]
08-04 14:57:01.001 21953-21953 ... I/TryAsync: onPreExecute [ Andr ]
08-04 14:57:01.669 21953-21953 ... I/TryAsync: onPreExecute [ Andro ]
08-04 14:57:01.934 21953-21953 ... I/TryAsync: onPreExecute [ Androi ]
08-04 14:57:02.314 21953-2195 ... I/TryAsync: onPreExecute [ Android ]
08-04 14:57:04.285 21953-23233 ... I/TryAsync: doInBackground [ A ][ END ]
08-04 14:57:04.286 21953-23233 ... I/TryAsync: doInBackground [ Andr ][ START ]
08-04 14:57:04.286 21953-21953 ... I/TryAsync: onPostExecute [ A ]
08-04 14:57:05.204 21953-23250 ... I/TryAsync: doInBackground [ An ][ END ]
08-04 14:57:05.204 21953-21953 ... I/TryAsync: onPostExecute [ An ]
08-04 14:57:05.205 21953-23250 ... I/TryAsync: doInBackground [ Andro ][ START ]
08-04 14:57:05.784 21953-23281 ... I/TryAsync: doInBackground [ And ][ END ]
08-04 14:57:05.785 21953-23281 ... I/TryAsync: doInBackground [ Androi ][ START ]
08-04 14:57:05.786 21953-21953 ... I/TryAsync: onPostExecute [ And ]
08-04 14:57:09.286 21953-23233 ... I/TryAsync: doInBackground [ Andr ][ END ]
08-04 14:57:09.287 21953-21953 ... I/TryAsync: onPostExecute [ Andr ]
08-04 14:57:09.287 21953-23233 ... I/TryAsync: doInBackground [ Android ][ START ]
08-04 14:57:10.205 21953-23250 ... I/TryAsync: doInBackground [ Andro ][ END ]
08-04 14:57:10.206 21953-21953 ... I/TryAsync: onPostExecute [ Andro ]
08-04 14:57:10.786 21953-23281 ... I/TryAsync: doInBackground [ Androi ][ END ]
08-04 14:57:10.787 21953-2195 ... I/TryAsync: onPostExecute [ Androi ]
08-04 14:57:14.288 21953-23233 ... I/TryAsync: doInBackground [ Android ][ END ]
08-04 14:57:14.290 21953-2195 ... I/TryAsync: onPostExecute [ Android ]
让我们根据任务中执行的方法来过滤日志。我们先来看看onPreExecute
方法的过滤器:
08-04 14:56:59.283 21953-21953 ... I/TryAsync: onPreExecute [ A ]
08-04 14:57:00.202 21953-21953 ... I/TryAsync: onPreExecute [ An ]
08-04 14:57:00.783 21953-21953 ... I/TryAsync: onPreExecute [ And ]
08-04 14:57:01.001 21953-21953 ... I/TryAsync: onPreExecute [ Andr ]
08-04 14:57:01.669 21953-21953 ... I/TryAsync: onPreExecute [ Andro ]
08-04 14:57:01.934 21953-21953 ... I/TryAsync: onPreExecute [ Androi ]
08-04 14:57:02.314 21953-21953 ... I/TryAsync: onPreExecute [ Android ]
对每个方法都这样做,并关注方法执行的时间。为了给你的代码更多的挑战,改变doInBackground()
方法实现来做一些更严肃和密集的工作。然后,通过键入更长的标题来激发更多的任务,例如,整个句子。过滤并分析您的日志。
了解安卓 Looper
我们来解释一下Looper
课。我们在前面的例子中使用了它,但没有详细解释。
Looper
表示用于执行队列中messages
或runnable
实例的类。普通线程不像Looper
类那样有任何队列。
我们可以在哪里使用Looper
类?要执行多个messages
或runnable
实例,需要Looper
!使用的一个例子是在任务处理操作运行的同时,向队列中添加新任务。
准备活套
要使用Looper
类,首先要调用prepare()
方法。当Looper
准备好后,我们可以使用loop()
方法。该方法用于在当前线程中创建message
循环。我们将给你一个简短的例子:
class LooperHandler : Handler() {
override fun handleMessage(message: Message) {
...
}
}
class LooperThread : Thread() {
var handler: Handler? = null
override fun run() {
Looper.prepare()
handler = LooperHandler()
Looper.loop()
}
}
在这个例子中,我们演示了编程一个Looper
类的基本步骤。别忘了去你的Looper
班,否则你会得到一个例外,你的应用可能会崩溃!
延迟执行
这一章还有一件重要的事情要给你看。我们将向您展示安卓系统中的一些延迟执行。我们会给你一些延迟操作应用到我们的用户界面的例子。打开ItemsFragment
并进行以下更改:
class ItemsFragment : BaseFragment() {
...
override fun onResume() {
super.onResume()
...
val items = view?.findViewById<ListView>(R.id.items)
items?.let {
items.postDelayed({
if (!activity.isFinishing) {
items.setBackgroundColor(R.color.grey_text_middle)
}
}, 3000)
}
}
...
}
三秒钟后,如果我们不关闭此屏幕,背景颜色将更改为稍深的灰色调。运行您的应用并亲自查看。现在,让我们用不同的方式做同样的事情:
class ItemsFragment : BaseFragment() {
...
override fun onResume() {
super.onResume()
...
val items = view?.findViewById<ListView>(R.id.items)
items?.let {
Handler().postDelayed({
if (!activity.isFinishing) {
items.setBackgroundColor(R.color.grey_text_middle)
}
}, 3000)
}
}
}
...
}
这次我们使用Handler
类进行延迟修改。
摘要
在本章中,您已经了解了安卓并发。我们解释了每个部分,并为您提供了示例。在深入安卓服务之前,这是一个很好的介绍。安卓服务是安卓必须提供的最强大的并发功能,正如您将看到的,它可以用作您应用的大脑。
版权属于:月萌API www.moonapi.com,转载请注明出处