- Потоки
- Использование фоновых потоков
- Плохое приложение
- Запуск потока
- Усыпить поток
- Приоритет потоков
- Отмена выполнения потока
- Android Threading: All You Need to Know
- Threading in Android
- Threading Components that Attach to an Activity/Fragment
- AsyncTask
- Loaders
- Threading Components that Don’t Attach to an Activity/Fragment
- Service
- IntentService
- Seven Threading Patterns in Android
- Use Case No. 1: Making a request over network without requiring a response from the server
- Option 1: AsyncTask or loaders
- Option 2: Service
- Option 3: IntentService
- Use Case No. 2: Making a network call, and getting the response from the server
- Option 1: Service or IntentService
- Option 2: AsyncTask or loaders
- Option 3: RxJava
- Use Case No. 3: Chaining network calls
- Option 1: AsyncTask or loaders
- Option 2: RxJava using flatMap
- Use Case No. 4: Communicate with the UI thread from another thread
- Option 1: RxJava inside the service
- Option 2: BroadcastReceiver
- Option 3: Using Handler
- Option 3: Using EventBus
- Use Case No. 5: Two-way communication between threads based on user actions
- Option 1: Using EventBus
- Option 2: Using BoundService
- Use Case No. 6: Executing actions in parallel and getting results
- Option 1: Using RxJava
- Option 2: Using native Java components
- Use Case #7: Querying local SQLite database
- Option 1: Using RxJava
- Option 2: Using CursorLoader + ContentProvider
- There’s no Silver Bullet Solution to Threading in Android
Потоки
Потоки позволяют выполнять несколько задач одновременно, не мешая друг другу, что даёт возможность эффективно использовать системные ресурсы. Потоки используются в тех случаях, когда одно долгоиграющее действие не должно мешать другим действиям. Например, у нас есть музыкальный проигрыватель с кнопками воспроизведения и паузы. Если вы нажимаете кнопку воспроизведения и у вас запускается музыкальный файл в отдельном потоке, то вы не можете нажать на кнопку паузы, пока файл не воспроизведётся полностью. С помощью потоков вы можете обойти данное ограничение.
Использование фоновых потоков
Чтобы быть уверенным, что ваше приложение не теряет отзывчивости, хорошим решением станет перемещение всех медленных, трудоёмких операций из главного потока приложения в дочерний.
Применение фоновых потоков — необходимое условие, если вы хотите избежать появления диалогового окна для принудительного закрытия приложения. Когда активность в Android на протяжении 5 секунд не отвечает на события пользовательского ввода (например, нажатие кнопки) или приёмник широковещательных намерений не завершает работу обработчика onReceive() в течение 10 секунд, считается, что приложение зависло. Подобные ситуации следует избегать любой ценой. Используйте фоновые потоки для всех трудоёмких операций, включая работу с файлами, сетевые запросы, транзакции в базах данных и сложные вычисления.
Android предоставляет несколько механизмов перемещения функциональности в фоновый режим.
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
- Handlers
- AsyncTask
Класс AsyncTask позволяет определить операции, которые будут выполняться в фоне, вы также будете иметь доступ к обработчику событий, что позволит отслеживать прогресс выполнения задач и выводить результаты в контексте главного графического потока. Подробнее об этом классе в отдельной статье.
Хотя использование AsyncTask — хорошее решение, случается, что для работы в фоновом режиме приходится создавать собственные потоки и управлять ими.
В Java есть стандартный класс Thread, который вы можете использовать следующим образом:
Данный способ подходит только для операций, связанных с временем. Но вы не сможете обновлять графический интерфейс программы.
Если вам нужно обновлять интерфейс программы, то нужно использовать AsyncTask, о котором говорилось выше, или вы можете реализовать ваш собственный класс, наследованный от Thread, используя объект Handler из пакета android.os для синхронизации с потоком GUI перед обновлением пользовательского интерфейса.
Вы можете создавать дочерние потоки и управлять ими с помощью класса Handler, а также классов, доступных в пространстве имён java.lang.Thread. Ниже показан простой каркас для переноса операций в дочерний поток.
Плохое приложение
Напишем «плохое» приложение, неправильно использующее основной поток. Однажды мы писали программу для подсчёта ворон. На этот раз будем считать чёрных котов, которые перебегают нам дорогу. Зачем они это делают — молчит наука. Может быть собранная статистика поможет разгадать тайну. Добавим на экран активности кнопки и текстовую метку. Код для щелчка кнопки.
Для имитации тяжёлой работы программа делает паузу на двадцать секунд, а потом выводит текст с подсчётом котов. Если нажать на кнопку один раз и подождать двадцать секунд, то программа отработает как положено. Но представьте себе, что вы нажали на кнопку один раз. Программа запустила паузу. Вы, не дожидаясь окончания паузы, снова нажали на кнопку. Программа должна выполнить вашу команду, но предыдущая команда ещё не отработала и наступает конфликт. Попробуйте нажать на кнопку несколько раз с небольшими перерывами. В какой-то момент приложение зависнет и выведет системное диалоговое окно:
В реальных приложениях такое окно может разозлить пользователя и он поставит низкую оценку вашему приложению.
В данном случае ошибку вызывает не сам вывод текста в текстовой метке, который, к слову, тоже выполняется в основном потоке, а сам щелчок кнопки. Если вы закомментируете последние две строчки кода, связанные с TextView, то ошибка сохранится.
Вам необходимо перенести трудоёмкую задачу в отдельный поток. Для этого создаётся экземпляр класса Runnable, у которого есть метод run(). Далее создаётся объект Thread, в конструкторе у которого указывается созданный Runnable. После этого можно запускать новый поток с помощью метода start(). Перепишем пример.
Весь код мы перенесли в метод run(). Теперь вы можете безостановочно щёлкать по кнопке. На этот раз приложение сохранит свою работоспособность. Чтобы в этом убедиться, в код добавлено протоколирование логов Log.i(). При каждом нажатии создаётся новый поток, в котором выполняется код. Потоки друг другу не мешают и дожидаются своей очереди, когда система позволит им отработать.
Основной поток также называют UI-потоком. Имено в главном потоке можно обновить текст у текстовой метки. В создаваемых нами потоках это делать нельзя. Если вы уберёте комментарии с последнего примера и запустите проект, то получите сообщение об ошибке.
Нужен некий посредник между создаваемыми потоками и основным UI-потоком. В роли такого посредника служит класс Handler (полное название класса android.os.Handler, не перепутайте). Вам нужно создать экземпляр класса и указать код, который нужно выполнить.
После строчки кода с Log.i() добавьте вызов метода посредника.
Поток вызывает посредника, который в свою очередь обновляет интерфейс. В нашем случае посредник посылает пустое сообщение от потока.
Но бывает так, что от потока требуется получить информацию для обработки. Ниже упрощённый пример.
Запуск потока
Предположим, мы разрабатываем собственный проигрыватель. У нас есть кнопка Play, которая вызывает метод play() для воспроизведения музыки:
Теперь запустим метод в другом потоке. Сначала создаётся новый поток. Далее описывается объект Runnable в конструкторе потока. А внутри созданного потока вызываем наш метод play(). И, наконец, запускаем поток.
Усыпить поток
Иногда требуется временно приостановить поток («усыпить»):
Приоритет потоков
Для установки приоритета процесса используется метод setPriority(), который вызывается до запуска потока. Значение приоритета может варьироваться от Thread.MIN_PRIORITY (1) до Thread.MAX_PRIORITY (10):
Отмена выполнения потока
У потока есть метод stop(), но использовать его не рекомендуется, поскольку он оставляет приложение в неопределённом состоянии. Обычно используют такой подход:
Существует и другой способ, когда все запускаемые потоки объявляются демонами. В этом случае все запущенные потоки будут автоматически завершены при завершении основного потока приложения:
Источник
Android Threading: All You Need to Know
Android provides many ways of creating and managing threads, and third-party libraries exist to make that even easier. However, with so many options, choosing the right approach can be quite confusing. In this article, Toptal Freelance Software Engineer Eliran Goshen discusses some common scenarios in Android development that involve threading and how each of the scenarios can be dealt with.
Every Android developer, at one point or another, needs to deal with threads in their application.
When an application is launched in Android, it creates the first thread of execution, known as the “main” thread. The main thread is responsible for dispatching events to the appropriate user interface widgets as well as communicating with components from the Android UI toolkit.
To keep your application responsive, it is essential to avoid using the main thread to perform any operation that may end up keeping it blocked.
Network operations and database calls, as well as loading of certain components, are common examples of operations that one should avoid in the main thread. When they are called in the main thread, they are called synchronously, which means that the UI will remain completely unresponsive until the operation completes. For this reason, they are usually performed in separate threads, which thereby avoids blocking the UI while they are being performed (i.e., they are performed asynchronously from the UI).
Android provides many ways of creating and managing threads, and many third-party libraries exist that make thread management a lot more pleasant. However, with so many different approaches at hand, choosing the right one can be quite confusing.
In this article, you will learn about some common scenarios in Android development where threading becomes essential and some simple solutions that can be applied to those scenarios and more.
Threading in Android
In Android, you can categorize all threading components into two basic categories:
- Threads that are attached to an activity/fragment: These threads are tied to the lifecycle of the activity/fragment and are terminated as soon as the activity/fragment is destroyed.
- Threads that are not attached to any activity/fragment: These threads can continue to run beyond the lifetime of the activity/fragment (if any) from which they were spawned.
Threading Components that Attach to an Activity/Fragment
AsyncTask
AsyncTask is the most basic Android component for threading. It’s simple to use and can be good for basic scenarios.
AsyncTask , however, falls short if you need your deferred task to run beyond the lifetime of the activity/fragment. It is worth noting that even something as simple as screen rotation can cause the activity to be destroyed.
Loaders
Loaders are the solution for the problem mentioned above. Loaders can automatically stop when the activity is destroyed, and can also restart themselves after the activity is recreated.
There are mainly two types of loaders: AsyncTaskLoader and CursorLoader . You will learn more about CursorLoader later in this article.
AsyncTaskLoader is similar to AsyncTask , but a bit more complicated.
Threading Components that Don’t Attach to an Activity/Fragment
Service
Service is a component that is useful for performing long (or potentially long) operations without any UI.
Service runs in the main thread of its hosting process; the service does not create its own thread and does not run in a separate process unless you specify otherwise.
With Service , it is your responsibility to stop it when its work is complete by calling either the stopSelf() or the stopService() method.
IntentService
Like Service , IntentService runs on a separate thread, and stops itself automatically after it completes its work.
IntentService is usually used for short tasks that don’t need to be attached to any UI.
Seven Threading Patterns in Android
Use Case No. 1: Making a request over network without requiring a response from the server
Sometimes you may want to send an API request to a server without needing to worry about its response. For example, you may be sending a push registration token to your application’s back-end.
Since this involves making a request over the network, you should do it from a thread other than the main thread.
Option 1: AsyncTask or loaders
You can use AsyncTask or loaders for making the call, and it will work.
However, AsyncTask and loaders are both dependent on the lifecycle of the activity. This means you will either need to wait for the call to execute and try to prevent the user from leaving the activity, or hope that it will execute before the activity is destroyed.
Option 2: Service
Service may be a better fit for this use case since it isn’t attached to any activity. It will therefore be able to continue with the network call even after the activity is destroyed. Plus, since the response from the server is not needed, a service wouldn’t be limiting here, either.
However, since a service will begin running on the UI thread, you will still need to manage threading yourself. You will also need to make sure that the service is stopped once the network call is complete.
This would require more effort than should be necessary for such a simple action.
Option 3: IntentService
This, in my opinion, would be the best option.
Since IntentService doesn’t attach to any activity and it runs on a non-UI thread, it serves our needs perfectly here. Moreover, IntentService stops itself automatically, so there is no need to manually manage it, either.
Use Case No. 2: Making a network call, and getting the response from the server
This use case is probably a bit more common. For example, you may want to invoke an API in the back-end and use its response to populate fields on the screen.
Option 1: Service or IntentService
Although a Service or an IntentService fared well for the previous use case, using them here wouldn’t be a good idea. Trying to get data out of a Service or an IntentService into the main UI thread would make things very complex.
Option 2: AsyncTask or loaders
At first blush, AsyncTask or loaders would appear to be the obvious solution here. They are easy to use—simple and straightforward.
However, when using AsyncTask or loaders, you’ll notice that there is a need to write some boilerplate code. Moreover, error handling becomes a major chore with these components. Even with a simple networking call, you need to be aware of potential exceptions, catch them, and act accordingly. This forces us to wrap our response in a custom class containing the data, with possible error information, and a flag indicates if the action was successful or not.
That’s quite a lot of work to do for every single call. Fortunately, there is now a much better and simpler solution available: RxJava.
Option 3: RxJava
You may heard about RxJava, the library developed by Netflix. It’s almost magic in Java.
RxAndroid lets you use RxJava in Android, and makes dealing with asynchronous tasks a breeze. You can learn more about RxJava on Android here.
RxJava provides two components: Observer and Subscriber .
An observer is a component that contains some action. It performs that action and returns the result if it succeeds or an error if it fails.
A subscriber, on the other hand, is a component that can receive the result (or error) from an observable, by subscribing to it.
With RxJava, you first create an observable:
Once the observable has been created, you can subscribe to it.
With the RxAndroid library, you can control the thread in which you want to execute the action in the observable, and the thread in which you want to get the response (i.e., the result or error).
You chain on the observable with these two functions:
Schedulers are components that execute the action in a certain thread. AndroidSchedulers.mainThread() is the scheduler associated with the main thread.
Given that our API call is mRestApi.getData() and it returns a Data object, the basic call can look like this:
Without even going into other benefits of using RxJava, you can already see how RxJava allows us to write more mature code by abstracting away the complexity of threading.
Use Case No. 3: Chaining network calls
For network calls that need to be performed in sequence (i.e., where each operation depends upon the response/result of the previous operation), you need to be particularly careful about generating spaghetti code.
For example, you may have to make an API call with a token that you need to fetch first through another API call.
Option 1: AsyncTask or loaders
Using AsyncTask or loaders will almost definitely lead to spaghetti code. The overall functionality will be difficult to get right and will require a tremendous amount of redundant boilerplate code throughout your project.
Option 2: RxJava using flatMap
In RxJava, the flatMap operator takes an emitted value from the source observable and returns another observable. You can create an observable, and then create another observable using the emitted value from the first one, which will basically chain them.
Step 1. Create the observable that fetches the token:
Step 2. Create the observable that gets the data using the token:
Step 3. Chain the two observables together and subscribe:
Note that use of this approach is not limited to network calls; it can work with any set of actions that needs to be run in a sequence but on separate threads.
All of the use cases above are quite simple. Switching between threads only happened after each finished its task. More advanced scenarios—for example, where two or more threads need to actively communicate with each other—can be supported by this approach as well.
Use Case No. 4: Communicate with the UI thread from another thread
Consider a scenario where you would like to upload a file and update the user interface once it is complete.
Since uploading a file can take a long time, there is no need to keep the user waiting. You could use a service, and probably IntentService , for implementing the functionality here.
In this case, however, the bigger challenge is being able to invoke a method on the UI thread after the file upload (which was performed in a separate thread) is complete.
Option 1: RxJava inside the service
RxJava, either on its own or inside an IntentService , may not be ideal. You will need to use a callback-based mechanism when subscribing to an Observable , and IntentService is built to do simple synchronous calls, not callbacks.
On the other hand, with a Service , you will need to manually stop the service, which requires more work.
Option 2: BroadcastReceiver
Android provides this component, which can listen to global events (e.g., battery events, network events, etc.) as well as custom events. You can use this component to create a custom event that is triggered when the upload is complete.
To do this, you need to create a custom class that extends BroadcastReceiver , register it in the manifest, and use Intent and IntentFilter to create the custom event. To trigger the event, you will need the sendBroadcast method.
This approach is a viable option. But as you have noticed, it involves some work, and too many broadcasts can slow things down.
Option 3: Using Handler
A Handler is a component that can be attached to a thread and then made to perform some action on that thread via simple messages or Runnable tasks. It works in conjunction with another component, Looper , which is in charge of message processing in a particular thread.
When a Handler is created, it can get a Looper object in the constructor, which indicates which thread the handler is attached to. If you want to use a handler attached to the main thread, you need to use the looper associated with the main thread by calling Looper.getMainLooper() .
In this case, to update the UI from a background thread, you can create a handler attached to the UI thread, and then post an action as a Runnable :
This approach is a lot better than the first one, but there is an even simpler way to do this…
Option 3: Using EventBus
EventBus , a popular library by GreenRobot, enables components to safely communicate with one another. Since our use case is one where we only want to update the UI, this can be the simplest and safest choice.
Step 1. Create an event class. e.g., UIEvent .
Step 2. Subscribe to the event.
Step 3. Post the event: EventBus.getDefault().post(new UIEvent());
With the ThreadMode parameter in the annotation, you are specifying the thread on which you would like to subscribe for this event. In our example here, we are choosing the main thread, since we will want the receiver of the event to be able to update the UI.
You can structure your UIEvent class to contain additional information as necessary.
In the activity/fragment:
Using the EventBus library , communicating between threads becomes much simpler.
Use Case No. 5: Two-way communication between threads based on user actions
Suppose you are building a media player and you want it to be able to continue playing music even when the application screen is closed. In this scenario, you will want the UI to be able to communicate with the media thread (e.g., play, pause, and other actions) and will also want the media thread to update the UI based on certain events (e.g. error, buffering status, etc).
A full media player example is beyond the scope of this article. You can, however, find good tutorials here and here.
Option 1: Using EventBus
You could use EventBus here. However, it is generally unsafe to post an event from the UI thread and receive it in a service. This is because you have no way of knowing whether the service is running when you have sent the message.
Option 2: Using BoundService
A BoundService is a Service that is bound to an activity/fragment. This means that the activity/fragment always knows if the service is running or not and, in addition, it gets access to the service’s public methods.
To implement it, you need to create a custom Binder inside the service and create a method that returns the service.
To bind the activity to the service, you need to implement ServiceConnection , which is the class monitoring the service status, and use the method bindService to make the binding:
You can find a full implementation example here.
To communicate with the service when the user taps on the Play or Pause button, you can bind to the service and then call the relevant public method on the service.
When there is a media event and you want to communicate that back to the activity/fragment, you can use one of the earlier techniques (e.g., BroadcastReceiver , Handler , or EventBus ).
Use Case No. 6: Executing actions in parallel and getting results
Let’s say you are building a tourist app and you want to show attractions on a map fetched from multiple sources (different data providers). Since not all of the sources may be reliable, you may want to ignore the ones that have failed and continue to render the map anyway.
To parallelize the process, each API call must take place in a different thread.
Option 1: Using RxJava
In RxJava, you can combine multiple observables into one using the merge() or concat() operators. You can then subscribe on the “merged” observable and wait for all results.
This approach, however, won’t work as expected. If one API call fails, the merged observable will report an overall failure.
Option 2: Using native Java components
The ExecutorService in Java creates a fixed (configurable) number of threads and executes tasks on them concurrently. The service returns a Future object that eventually returns all results via the invokeAll() method.
Each task you send to the ExecutorService should be contained in Callable interface, which is an interface for creating a task that can throw an exception.
Once you get the results from invokeAll() , you can check every result and proceed accordingly.
Let’s say, for example, that you have three attraction types coming in from three different endpoints and you want to make three parallel calls:
This way, you are running all of the actions in parallel. You can, therefore, check for errors in each action separately and ignore individual failures as appropriate.
This approach is easier than using RxJava. It is simpler, shorter, and doesn’t fail all actions because of one exception.
Use Case #7: Querying local SQLite database
When dealing with a local SQLite database, it is recommended that the database be used from a background thread, since database calls (especially with large databases or complex queries) can be time consuming, resulting in the UI freezing.
When querying for SQLite data, you get a Cursor object that can then be used to fetch the actual data.
Option 1: Using RxJava
You can use RxJava and get the data from the database, just as we’re getting data from our back-end:
You can use the observable returned by getLocalDataObservable() as follows:
While this is certainly a good approach, there is one that is even better, since there is a component that is built just for this very scenario.
Option 2: Using CursorLoader + ContentProvider
Android provides CursorLoader , a native component for loading SQLite data and managing the corresponding thread. It’s a Loader that returns a Cursor , which we can use to get the data by calling simple methods such as getString() , getLong() , etc.
CursorLoader works with the ContentProvider component. This component provides a plethora of real-time database features (e.g., change notifications, triggers, etc.) that enables developers to implement a better user experience much more easily.
There’s no Silver Bullet Solution to Threading in Android
Android provides many ways to handle and manage threads, but none of them are silver bullets.
Choosing the right threading approach, depending on your use case, can make all the difference in making the overall solution easy to implement and understand. The native components fit well for some cases, but not for all. The same applies for fancy third-party solutions.
I hope you will find this article useful when working on your next Android project. Share with us your experience of threading in Android or any use case where the above solutions work well—or don’t, for that matter—in the comments below.
Источник