九、安卓系统中的并发

在本章中,我们将解释 Android 中的并发性。我们将给出例子和建议,并将并发应用到我们的 Journaler 应用中。通过演示AsyncTask类的使用,我们已经触及了一些基础知识,但现在我们将深入挖掘。

在本章中,我们将涵盖以下主题:

  • 处理程序和线程
  • AsyncTask
  • 安卓 Looper
  • 延迟执行

安卓并发介绍

我们应用的默认执行是在主应用线程上执行的。这种执行必须是有效的!如果某个东西运行太久,我们就会得到 ANR——一个安卓应用没有响应的消息。为了避免 ANRs,我们在后台运行代码。安卓提供了一些机制,因此我们可以高效地做到这一点。异步运行操作不仅带来了良好的性能,还带来了出色的用户体验。

主流中泓线

所有用户界面更新都是从一个线程执行的。这是主线。所有事件都收集在一个队列中,并由Looper类实例处理。

下图解释了相关类之间的关系:

需要注意的是,主线程更新都是你看到的 UI。但是,也可以从其他线程中完成。直接从其他线程执行此操作会导致异常,应用可能会崩溃。为了避免这种情况,通过从当前活动上下文中调用runOnUiThread()方法,在主线程上执行所有与线程相关的代码。

处理程序和线程

在安卓系统中,线程可以通过使用线程以标准方式执行。不建议在没有任何控制的情况下直接激发裸线程。所以,为了这个目的,你可以使用ThreadPoolsExecutor类。

为了演示这一点,我们将更新我们的应用。用名为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 作为开发人员的选项之一。处理程序不是线程的替代品,而是一种补充!处理程序实例向其父线程注册自己。它代表了一种向特定线程发送数据的机制。我们可以发送MessageRunnable类的实例。让我们用一个例子来说明它的用法。如果一切正常,我们将使用绿色指示器更新“备注”屏幕。如果数据库持久化失败,它将是红色的。它的默认颜色是灰色。打开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表示用于执行队列中messagesrunnable实例的类。普通线程不像Looper类那样有任何队列。

我们可以在哪里使用Looper类?要执行多个messagesrunnable实例,需要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类进行延迟修改。

摘要

在本章中,您已经了解了安卓并发。我们解释了每个部分,并为您提供了示例。在深入安卓服务之前,这是一个很好的介绍。安卓服务是安卓必须提供的最强大的并发功能,正如您将看到的,它可以用作您应用的大脑。