Статьи

Понимание параллелизма на Android с помощью HaMeR

Каждый, кто пробует разработку под Android, обнаруживает важность параллелизма . Единственный способ создать отзывчивое приложение — оставить поток пользовательского интерфейса настолько свободным, насколько это возможно, позволяя выполнять всю тяжелую работу асинхронно фоновыми потоками.

Из-за дизайна Android управление потоками с использованием только пакетов java.lang.thread и java.util.concurrent может быть очень сложным. Использование низкоуровневых потоковых пакетов с Android означает, что вам нужно беспокоиться о сложной синхронизации, чтобы избежать условий гонки. К счастью, сотрудники Google проделали тяжелую работу и создали несколько отличных инструментов, облегчающих нашу работу: AsyncTask , IntentService , Loader , AsyncQueryHandler и CursorLoader , а также классы HaMeR Handler , Message и Runnable . Есть много отличных вариантов для выбора, каждый со своими плюсами и минусами.

Много было сказано об объекте AsyncTask , и многие люди используют его в качестве решения серебряной пули для параллелизма на Android. Он чрезвычайно полезен для коротких операций, прост в реализации и, вероятно, является наиболее популярным подходом к параллелизму на Android. Если вы хотите узнать больше об AsyncTask , ознакомьтесь со следующими сообщениями AsyncTask +.

  • Android с нуля: фоновые операции

  • Понимание значений AsyncTask за 60 секунд

Однако AsyncTask не должен быть единственным инструментом на вашем поясе инструментов.

Для длительных операций, для сложных проблем параллелизма или для достижения большей эффективности в некоторых ситуациях вы должны выбрать другое решение. Если вам нужно больше гибкости или эффективности, чем обеспечивает AsyncTask , вы можете использовать платформу HaMeR ( Handler , Message & Runnable ).   В этом руководстве мы рассмотрим структуру HaMeR, одну из самых мощных моделей параллелизма, доступных на Android, и узнаем, когда и как ее использовать. В следующем уроке я покажу вам, как кодировать приложение, чтобы опробовать некоторые возможности HaMeR.

В следующем разделе будет представлена ​​важность фоновых потоков для системы Android. Если вы знакомы с этой концепцией, не стесняйтесь пропустить ее и перейдите непосредственно к обсуждению структуры HaMeR в разделе 3.

Когда приложение Android запускается, первый поток, созданный его процессом, является основным потоком, также известным как поток пользовательского интерфейса, который отвечает за обработку всей логики пользовательского интерфейса. Это самая важная тема приложения. Он отвечает за обработку всего взаимодействия с пользователем, а также за «связывание» движущихся частей приложения. Android относится к этому очень серьезно, и если ваш поток пользовательского интерфейса застревает, работая над задачей дольше нескольких секунд, приложение вылетает.

[Поток пользовательского интерфейса] очень важен, потому что он отвечает за отправку событий в соответствующие виджеты пользовательского интерфейса, включая события рисования. Это также поток, в котором ваше приложение взаимодействует с компонентами из набора инструментов Android UI (компоненты из пакетов android.widget и android.view ). Таким образом, основной поток также иногда называют потоком пользовательского интерфейса. Процессы и потоки , Руководство разработчика Android

Проблема в том, что почти весь код в приложении Android будет по умолчанию выполняться в потоке пользовательского интерфейса. Поскольку задачи в потоке выполняются последовательно, это означает, что ваш пользовательский интерфейс может «зависнуть» и перестать отвечать на запросы при обработке какой-либо другой работы.

Оставьте поток пользовательского интерфейса максимально свободным, используя фоновые потоки.

Длительные задачи, вызываемые в пользовательском интерфейсе, вероятно, будут фатальными для вашего приложения, и появится диалоговое окно ANR (приложение не отвечает). Даже небольшие задачи могут поставить под угрозу взаимодействие с пользователем, поэтому правильный подход — удалить как можно больше работы из потока пользовательского интерфейса с помощью фоновых потоков. Как уже было сказано, есть много способов решить эту проблему, и мы исследуем инфраструктуру HaMeR, одно из основных решений, предоставляемых Android для решения этой проблемы.

Платформа HaMeR позволяет фоновым потокам отправлять сообщения или публиковать исполняемые объекты в поток пользовательского интерфейса и в MessageQueue любого другого потока через обработчики. HaMeR относится к Handler , Message и выполнению. Есть также некоторые другие важные классы, которые работают вместе с HaMeR: Looper и MessageQueue . Вместе эти объекты отвечают за облегчение управления потоками в Android, заботятся о синхронизации и предоставляют простые методы для фоновых потоков для взаимодействия с пользовательским интерфейсом и другими потоками.

Вот как классы в платформе HaMeR сочетаются друг с другом.

Рамки HaMeR
  • Looper запускает цикл сообщений в потоке, используя MessageQueue .
  • MessageQueue содержит список сообщений, которые должны быть отправлены Looper .
  • Handler позволяет отправлять и обрабатывать Message и Runnable в MessageQueue . Может использоваться для отправки и обработки сообщений между потоками.
  • Message содержит описание и данные, которые могут быть отправлены обработчику.
  • Runnable представляет задачу для выполнения.

С помощью инфраструктуры HaMeR потоки могут отправлять сообщения или публиковать выполняемые объекты либо себе, либо потоку пользовательского интерфейса. HaMeR также способствует взаимодействию фоновых потоков через Handler .

Handler — рабочая лошадка HaMeR. Он отвечает за отправку объектов Message (сообщение данных) и публикацию объектов Runnable (сообщение задачи) в MessageQueue связанный с MessageQueue . После доставки задач в очередь, обработчик получает объекты из Looper и обрабатывает сообщения в соответствующее время, используя связанный с ним Handler .

Handler может использоваться для отправки или публикации объектов Message и Runnable между потоками, если такие потоки совместно используют один и тот же процесс. В противном случае будет необходимо создать межпроцессное взаимодействие (IPC) , методологию, которая выходит за рамки данного руководства.

Handler всегда должен быть связан с Looper , и это соединение должно быть установлено во время его создания. Если вы не предоставите Looper Handler , он будет привязан к Looper текущего потока.

1
2
3
4
5
// Handler uses current Thread’s Looper
Handler handler = new Handler();
 
// Handler uses the Looper provides
Handler handler = new Handler(Looper);

Имейте в виду, что Handler всегда связан с Looper , и это соединение является постоянным и не может быть изменено после установления. Однако поток Looper может иметь ассоциации с несколькими Handler . Также важно отметить, что Looper должен быть активным до того, как он будет связан с Handler .

Совместная работа между Looper и MessageQueue в потоке Java создает цикл задач, которые обрабатываются последовательно. Такой цикл будет поддерживать поток, пока он ждет, чтобы получить больше задач. С потоком может быть связан только один Looper и один MessageQueue ; однако для каждого потока может быть несколько обработчиков. Обработчики отвечают за обработку задач в очереди, и каждая задача знает, какой обработчик отвечает за ее обработку.

Пользовательский интерфейс или основной поток является единственным видом потока, который по умолчанию уже имеет Handler , Looper и MessageQueue . Другие потоки должны быть подготовлены с этими объектами, прежде чем они смогут работать с платформой HaMeR. Сначала нам нужно создать Looper который уже включает MessageQueue и прикрепить его к потоку. Вы можете сделать это с подклассом Thread следующим образом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
// Preparing a Thread for HaMeR
class LooperThread extends Thread {
      public Handler mHandler;
      public void run() {
          // adding and preparing the Looper
          Looper.prepare();
          // the Handler instance will be associated with Thread’s Looper
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
          // Starting the message queue loop using the Looper
          Looper.loop();
      }
  }

Однако более просто использовать вспомогательный класс с именем HandlerThread , который имеет Looper и MessageQueue встроенный в MessageQueue Java, и готов получить обработчик.

1
2
3
4
5
6
7
8
// The HandlerThread class includes a working Looper
public class HamerThread extends HandlerThread {
    // you just need to add the Handler
    private Handler handler;
    public HamerThread(String name) {
        super(name);
    }
}

Runnable — это интерфейс Java, который имеет много применений. Это можно понимать как отдельную задачу, выполняемую в Thread . У него есть один метод, который должен быть реализован, Runnable.run() , для выполнения задачи.

1
2
3
4
5
6
7
// Declaring a Runnable
Runnable r = new Runnable() {
     @Override
     public void run() {
          // the task goes here
     }
};

Существует несколько вариантов публикации Runnable на Handler .

  • Handler.post(Runnable r) : добавить Runnable в MessageQueue .
  • Handler.postAtFrontOfQueue(Runnable r) : добавить Runnable в начало MessageQueue .
  • Handler. postAtTime(Runnable r, long timeMillis) Handler. postAtTime(Runnable r, long timeMillis) : добавьте Runnable в MessageQueue который будет вызываться в определенное время.
  • Handler. postDelayed(Runnable r, long delay) Handler. postDelayed(Runnable r, long delay) : добавьте Runnable в очередь, которая будет вызываться по истечении определенного времени.
1
2
3
4
5
6
7
8
9
// posting a Runnable on a Handler
Handler handler = new Handler();
handler.post(
     new Runnable() {
          @Override
          public void run() {
               // task goes here
          }
     });

Также можно использовать обработчик пользовательского интерфейса по умолчанию для публикации Runnable вызывающего Activity.runOnUiThread() .

1
2
3
4
5
6
7
8
// posting Runnable using the UI Handler
Activity.runOnUiThread(
    new Runnable() {
        @Override
        public void run(){
            // task to perform
        }
    });

Важно помнить некоторые вещи о Runnable s. В отличие от Message , Runnable не может быть переработан — как только его работа выполнена, он мертв. Поскольку он является частью стандартного пакета Java, Runnable не зависит от Handler и может вызываться в стандартном Thread с помощью метода Runnable.run() . Тем не менее, этот подход не имеет ничего общего с платформой HaMeR и не будет разделять ее преимущества.

Объект Message определяет сообщение, содержащее описание и некоторые произвольные данные, которые могут быть отправлены и обработаны с помощью Handler . Message идентифицируется с помощью int определенного в Message.what() . Message может содержать два других аргумента int и Object для хранения данных разных типов.

  • Message.what : int идентифицирующий Message
  • Message.arg1 : произвольный аргумент int
  • Message.arg2 : произвольный аргумент int
  • Message.obj : Object для хранения различных видов данных

Когда вам нужно отправить сообщение, а не создавать его с нуля, рекомендуется использовать извлеченное сообщение непосредственно из глобального пула с помощью Message.obtain() или Handler.obtainMessage() . Существуют несколько различных версий этих методов, которые позволяют вам получить Message соответствии с вашими потребностями.

Обычно Handler.obtainMessage() используется для отправки сообщения в фоновый поток. Вы будете использовать Handler связанный с Looper этого потока, чтобы получить Message и отправить его в фоновый поток, как в примере ниже.

1
2
3
4
5
6
int what = 0;
String hello = «Hello!»;
// Obtaining Message associated with background Thread
Message msg = handlerBGThread.obtainMessage(what, hello);
// Sending the Message to background Thread
handlerBGThread.sendMessage(msg);

В классе Message есть много классных методов, и я советую вам поближе взглянуть на документацию .

Аналогично тому, как мы можем публиковать Runnable , есть несколько вариантов отправки Message :

  • Handler.sendMessage( Message msg ) : добавить Message в MessageQueue .
  • Handler.sendMessageAtFrontOfQueue( Message msg ) : добавить Message в начало MessageQueue .
  • Handler.sendMessageAtTime( Message msg, long timeInMillis ) : добавить Message в очередь в определенное время.
  • Handler.sendMessageDelayed ( Message msg, long timeInMillis ) : добавить Message в очередь по истечении определенного промежутка времени.

Объекты Message отправленные Looper , обрабатываются обработчиком с помощью метода Handler.handleMessage . Все, что вам нужно сделать, это расширить класс Handler и переопределить этот метод для обработки сообщений.

01
02
03
04
05
06
07
08
09
10
11
12
13
public class MessageHandler extends Handler {
       @Override
       public void handleMessage(Message msg) {
           switch (msg.what) {
               // handle ‘Hello’ msg
               case 0:{
                   String hello = (String) msg.obj;
                   System.out.println(hello);
                   break;
               }
           }
       }
   }

Платформа HaMeR может помочь улучшить параллельный код вашего приложения Android. AsyncTask это может показаться странным, если сравнивать с простотой AsyncTask , но открытость HaMeR может быть преимуществом, если используется правильно.

Помнить:

  • Handler.post() используются, когда отправители знают, какие операции нужно выполнить.
  • Handler.sendMessage () используются, когда получатель знает, какую операцию выполнить.

Чтобы узнать больше о многопоточности в Android, вас может заинтересовать книга Андерса Горанссона « Эффективная многопоточность Android : методы асинхронной обработки для приложений Android ».

В следующем уроке мы продолжим изучение инфраструктуры HaMeR с практическим подходом, создав приложение, демонстрирующее различные способы использования этой среды параллелизма Android. Мы создадим это приложение с нуля, пробуя различные возможности, такие как связь между потоками, общение с потоком пользовательского интерфейса, а также отправка сообщений и публикация Runnable с задержками.

До скорого!