- How Does Android App Work?
- Step 1: Building the APK File
- 1. Code Compilation
- 2. Conversion into Dalvik bytecodes
- 3. Generating .apk file
- 4. App Distribution
- Step 2: Deploy the Application
- 1. Establish the ADB Server
- 2. Transfer .apk file to the Device
- Step 3: Run the Application
- 1. App launch request
- 2. Conversion of the .dex code to native OAT format
- Фоновая работа в Android: обзор возможностей WorkManager
- 1) Описание и добавление задачи
- 2) Критерии запуска задачи
- 3) Цепочки задач
- 4) Уникальная цепочка задач
- 5) Отмена задачи
- 6) Статусы задач
- 7) Входные и выходные данные задачи
- 8) Передача данных между задачами
- 9) InputMerger
- 10) Кастомная конфигурация WorkManager
- 11) Тестирование
- Заключение
How Does Android App Work?
Developing an android application involves several processes that happen in a sequential manner. After writing the source code files, when developers click the Run button on the Android studio, plenty of operations and process starts at the backend. Every operation happening in the background is a crucial step and are interdependent. The IDE builds all the application files, make them device compatible in order to deploy, and assure that the application runs successfully on the device. This article broadly explains each and every critical step involved in the journey of an android app, from the IDE files to a working device application.
Step 1: Building the APK File
1. Code Compilation
The android application source files are written in either Java(*.java files) or Kotlin(*.kt files) programming languages. Syntax of writing the code in these 2 languages are different but their compilation process is almost the same. Both programming languages generate code that can be compiled to Java byte-code which is executable on JVM(Java Virtual Machine). In an android environment, the process begins with the compilation of Java/Kotlin source code into the Java class file. The class files have the extension *.class and it contains the java byte-code(represents Java assembly). This compilation task is carried out by the javac and kotlinc compilers for the Java and Kotlin language code respectively.
2. Conversion into Dalvik bytecodes
The generated Java class(*.class) file in the previous step is executable on Java Virtual Machine(JVM) as it contains the standard Oracle JVM Java byte-codes. However, this code format is not suitable for Android devices and thus Android has its own unique byte-code format known as Dalvik byte-code. Dex compiler translates the Java byte-code into the Dalvik byte-code that are machine-code instructions for a theoretical processor. During the compilation process, dx command ties up all the .class files as well as .jar files together and creates a single classes.dex file that is written in Dalvik byte-code format. This file is now executable on the virtual machine in the Android operating system known as Android Runtime(or Dalvik Virtual Machine(DVM) for android version older than Kitkat(4.4)).
public int addTwoNumbers(int a, int b) <
Equivalent Java byte-code:
public int addTwoNumbers(int, int);
Equivalent Dalvik byte-code:
.method public addTwoNumbers(II)I
add-int v0, p1, p2
3. Generating .apk file
Resource files of the android application like images, fonts, XML layouts, etc. are transformed into a single compiled resource unit by the Android Asset Packaging Tool(aapt). The aapt tool is also responsible for creating the R.java file of an android application. Further, the compiled resource unit along with the classes.dex file is compressed by the apkbuilder tool and a zip-like file is created that is termed as Android Package(.apk file). The generated .apk file contains all necessary data to run the Android application.
4. App Distribution
The .apk file generated in the previous step is a ready-to-use application package and developers can use this file for the purpose of app distribution. However, to distribute and publish the application through Google Play Store, developers need to sign it. Android applications are required to be digitally signed with a certificate so that they can be installed by the users. The certificate is self-signed, and the Android uses it to identify the author of the application. The app developer/author holds the private key of the certificate and these all details are stored as an additional file in the android package(.apk file).
Oracle Java Development Kit(JDK) provides the jarsigner tool to sign the .jar files and .apk files. Further, the compressed parts of the signed .apk file are required to line up on byte-boundaries in such a manner so that Android OS can read them without uncompressing the file. The byte alignment of files is assured by running the signed .apk file through the zipalign tool.
Step 2: Deploy the Application
1. Establish the ADB Server
Android Debug Bridge(ADB) deploys an application to Android devices. It is a command-line tool that acts as an interface and facilitates developers to communicate with an android device. To start the deployment, the ADB client will first check whether the ADB server process is already running on the device. If there isn’t, the server process starts with the ADB command. The ADB server starts and binds with local TCP port 5037. All communication and commands are transferred from the ADB server to ADB clients using port 5037. Further, the server sets up a connection with all running devices. It scans all the ports and when the server detects an ADB daemon(adbd: a background process on emulator or device instance), it set up a connection to that port. The adbd process that matches on the android device can communicate with applications, debug them, and collect their log output.
2. Transfer .apk file to the Device
The ADB command transfers the .apk file into the local file system of the target Android device. The app location in the device file system is defined by its package name. For example, if the application package is com.example.sampleapp, then its .apk file will be located in the path /data/app/com.example.sampleapp.
Step 3: Run the Application
1. App launch request
Zygote process is the parent to all Android apps and it launches an application when a user makes the request to do so. The zygote is a special kind of Android OS process which enables code sharing between different instances that run across Android virtual devices(Dalvik/Android Runtime). Those resources, classes, and code libraries which possibly required by any application at runtime are preloaded in the memory space of the zygote process. Whenever the process gets a request to launch a new application, it forks itself(creates a copy) using the fork system call(android is a Linux system) and starts the new app. The preloaded libraries and resources are the reason for efficient and fast app launch in android.
2. Conversion of the .dex code to native OAT format
When a new application is installed, the Android optimize the app data and generates a corresponding OAT file. This file is created by the Android OS to accelerate the application loading time. The process to generate the OAT file starts with the extraction of classes.dex file present inside the .apk file of the application. The classes.dex file is placed in a separate directory and Android compiles the Dalvik byte-code with ahead-of-time(AOT, also abbreviated as OAT) compilation into native machine code. Android system uses this native OAT file to enhance the user experience by loading the application quickly and smoothly.
Before AOT came into the picture, dexopt tool is used to convert the .dex files into .odex file(optimized DEX) that holds the optimized byte-code. With the introduction of AOT in Android, dex2oat tool converts and optimize the .dex file into an OAT file format that holds machine code written in ELF format(Executable and Linkable Format). This native library is then mapped into the memory of the application process. OAT files are generally saved in the Android device in the directory: /data/dalvik-cache/
After following all these steps and procedures, the application will finally start and the initial activity of the app will appear on the device screen.
Источник
Фоновая работа в Android: обзор возможностей WorkManager
В мобильных приложениях широко востребованы различные виды фоновой работы. Зачастую нужно поддерживать работу в офлайне, планировать какие-либо долгие и повторяющиеся задачи на определенное время, выполнять «тяжелые» задачи без привязки к сценариям пользовательского взаимодействия.
Например, в ритейле мерчендайзерам бывает необходимо в конце каждого рабочего дня отправлять фотоотчеты на сервер и удалять их из памяти телефона, чтобы не занимать место. А для работы онлайн-кассы требуется в фоновом режиме загружать актуальный справочник товаров. В этой статье мы рассмотрим один из самых популярных инструментов для реализации фоновой работы – WorkManager из Android Jetpack.
Для фоновой работы в Android существует множество изначально реализованных нативных решений, таких как AlarmManager, Handler, IntentService, SyncAdapter, Loader. Однако, их судьба складывается по-разному:
Handler до сих пор повсеместно используется, но, в основном, для посылки эвентов в очередь событий главного потока.
На действия AlarmManager система Android накладывает все больше ограничений, также он имеет довольно раздутое API для работы.
IntentService, используемый для обработки операций в рабочем потоке, начиная с Android API 30 стал deprecated.
Loader имеет привязку к жизненному циклу Activity/Fragment и, с появлением более новых, удобных инструментов, позволяющих решать схожие задачи, морально устарел.
SyncAdapter также морально устарел, не имеет возможности задавать условия запуска задач, создавать цепочки задач.
Начиная с Android 5.0 появился JobScheduler, позволяющий задавать условия запуска задач (устройство на зарядке, подключено к wi-fi и т.д.). Его работа основана на Service, и, чтобы обработка прошла асинхронно, необходимо самостоятельно запустить рабочий поток, а также вызвать необходимые методы JobService для избежания утечек. Все описанное повышает вероятность ошибок и доступно только с api 21.
Учитывая перечисленные ограничения, разработчики столкнулись с потребностью в таком инструменте, который может инкапсулировать работу по избежанию утечек и обращению с потоками, а также предоставить удобное API для запуска асинхронных, повторяющихся, откладываемых задач. В результате в 2018 году был выпущен Android Jetpack, частью которого стал WorkManager (познакомиться с ним подробнее можно, в частности, здесь).
Далее рассмотрим подробнее особенности работы.
WorkManager предоставляет удобные инструменты для описанных выше задач, совместим с корутинами, RxJava2, другими Jetpack библиотеками, может работать в мультипроцессном режиме. Доступен он начиная с API 14 за счет использования под капотом уже знакомых инструментов.
1) Описание и добавление задачи
Для описания задачи необходимо унаследоваться от класса Worker и определить метод doWork():
Код внутри метода doWork() будет выполнен в рабочем потоке WorkManager’a.
Далее задачу можно сделать разовой с помощью OneTimeWorkRequestBuilder.
Либо ее можно сделать периодической с помощью PeriodicWorkRequestBuilder.
В обоих случаях мы передали в качестве generic-параметра класс Worker’a, определенный нами.
В случае периодической задачи мы дополнительно определили интервал ее выполнения — 30 минут (минимально доступный интервал составляет 15 минут; если мы поставим интервал меньше 15 минут, то WorkManager повысит его до 15). А также параметр flex — 25 минут. Этот параметр ограничивает окно запуска задачи: она будет запущена не в любой момент интервала, а между 25 и 30 минутами.
Также для задачи можно установить начальную задержку и тег, по которому задача однозначно будет найдена в списке запланированных задач.
После создания задачи мы добавляем ее в очередь WorkManager’a.
2) Критерии запуска задачи
Мы можем задать необходимые условия для запуска задачи:
После этого констрейнты добавляются в билдере work request’a.
Рассмотрим перечисленные условия запуска:
setRequiresCharging (boolean requiresCharging) — критерий: зарядное устройство должно быть подключено.
setRequiresBatteryNotLow (boolean requiresBatteryNotLow) — критерий: уровень батареи не ниже критического (задача начинает выполняться при уровне заряда больше 20, а останавливается при значении меньше 16).
setRequiredNetworkType (NetworkType networkType) — критерий: наличие интернета. Мы можем указать, какой именно тип сети интернет (NetworkType) должен быть при запуске задачи. Тип соединения с сетью может быть:
CONNECTED — WiFi или Mobile Data
UNMETERD — только WiFi
METERED — только Mobile Data
NOT_ROAMING — интернет не должен быть роуминговым;
NOT_REQUIRED — интернет не нужен.
setRequiresDeviceIdle (boolean requiresDeviceIdle) — критерий: девайс не используется какое-то время и ушел “в спячку”. Работает на API 23 и выше.
setRequiresStorageNotLow (boolean requiresStorageNotLow) — критерий: на девайсе должно быть свободное место, не меньше критического порога.
3) Цепочки задач
WorkManager может выполнять несколько разовых задач последовательно друг за другом. При этом задача, для которой не выполнены критерии запуска, будет ожидать их соблюдения вместе с теми, что идут после нее. Если порядок задач не важен, можно запустить их параллельно.
Периодические задачи ставить в цепочку нельзя.
В данном примере мы параллельно запускаем задачи myWorkRequest1, myWorkRequest2. После их выполнения будут параллельно выполняться задачи myWorkRequest3, myWorkRequest4. Затем — задача myWorkRequest5. Данный пример можно переписать, выделив параллельные запросы в цепочки. После чего две получившиеся цепочки можно передать в метод combine() класса WorkContinuation для параллельного исполнения:
4) Уникальная цепочка задач
Мы можем сделать последовательность задач уникальной. Для этого нужно начать последовательность методом beginUniqueWork():
В этот метод мы передали название уникальной цепочки, стратегию действий при коллизии цепочек с одинаковым именем и саму задачу (можно передать список задач).
Стратегий может быть несколько:
REPLACE – остановка последовательности с таким же именем, и запуск новой;
KEEP – оставит в работе текущую выполняемую последовательность, а новая будет проигнорирована;
APPEND – запустит новую последовательность после выполнения текущей.
5) Отмена задачи
Для отмены задачи у класса WorkManager есть следующие методы:
cancelAllWork() — отменяет все запланированные задачи (и не только вашим приложением);
cancelAllWorkByTag(String tag) — отменяет все задачи с указанным тегом;
cancelUniqueWork(String uniqueWorkName) — отменяет уникальную цепочку задач с указанным именем;
cancelWorkById(UUID id) — отменяет задачу по указанному id.
6) Статусы задач
Статусы и жизненный цикл задачи может быть различным, в зависимости от того, разовая она или нет. Возможны следующие статусы задач:
ENQUEUED – задача запланирована;
RUNNING – задача выполняется;
SUCCEEDED (SUCCESS) – задача выполнена, терминальное состояние;
FAILED (FAILURE) – задача не была выполнена, не повторять, терминальное состояние;
RETRY – задача не была выполнена, повторить через некоторое время;
BLOCKED – задача включена в цепочку, и ее очередь выполнения еще не наступила;
CANCELLED – задача отменена, терминальное состояние.
Для разовой задачи возможен следующий флоу:
Если одна из разовых задач включена в цепочку и завершилась с состоянием FAILED, следующие за ней по цепочке задачи будут отменены.
Для периодической задачи существует другой флоу:
Как мы видим, для периодической задачи существует одно терминальное состояние: CANCELLED.
Так как WorkManager является частью Jetpack, получить информацию о задаче можно в виде LiveData:
7) Входные и выходные данные задачи
Мы можем задать входные данные, необходимые для ее работы.
Когда задача будет запущена, мы сможем получить эти данные внутри нее с помощью:
После выполнения мы можем в Result.success() или Result.failure() передать выходные данные задачи.
8) Передача данных между задачами
При создании цепочки задач выходные данные одной задачи будут передаваться во входные данные другой. Рассмотрим такой пример. Пусть задачи myWorkRequest1 и myWorkRequest2 выполняются параллельно, затем выполняется myWorkRequest3. В результате выходные данные из первой и второй задач попадут в третью.
Так как мы задали одинаковые ключи, невозможно предположить, какое именно значение попадет в третью задачу, ведь первая и вторая задача выполняются параллельно. Важно об этом помнить.
9) InputMerger
Для преобразования нескольких выходных результатов в один входной используются реализации класса InputMerger. По умолчанию используется OverwritingInputMerger, перезаписывающий значение по уже существующему ключу. Это было видно на предыдущем примере с двумя параллельными задачами. Если же нам нужно при совпадении ключа записать все пришедшие значения, следует использовать ArrayCreatingInputMerger.
InputMerger можно задать при создании задачи. Добавим ArrayCreatingInputMerger для myWorkRequest3 из предыдущего примера.
Теперь по ключу keyA мы получим не последнее записанное значение, а массив [«value1», «value2»]. Также и для ключа keyB — [1, 2].
10) Кастомная конфигурация WorkManager
WorkManager имеет собственный провайдер, WorkManagerInitializer, который мержится в манифест приложения. Он создает пул потоков, необходимый для работы, фабрики для создания InputMerger’ов и WorkerFactory (создает объекты Worker’ов, полезна в случаях, когда класс Worker’a получил новое имя, и WorkManager должен соотнести действия, запланированные на старое имя, с новым). При необходимости эти параметры можно изменить.
Для начала нужно отключить стандартный провайдер WorkManager’a. Это делается в манифесте приложения путем объявления.
После этого нужно сделать класс нашего приложения реализацией интерфейса Configuration.Provider. И в переопределенном методе изменить нужные нам параметры. Например, заменим стандартный Executor, в потоках которых выполняется код Worker’ов, и изменим минимальный уровень логирования:
11) Тестирование
Для тестирования Worker’ов нужно подключить в проект зависимость
В этом пакете есть классы, которые помогают в тестовой среде.
Пусть у нас есть Worker, который складывает 2 числа и выдает результат в качестве выходных данных. Протестируем его:
Дополнительно можно проверить, что код Worker’a выполняется только тогда, когда выполнены все условия его запуска. А также проверить статус работы.
Пусть у нас есть тот же Worker, выполняющий сложение 2 чисел. Зададим ему условия старта и протестируем.
Заключение
WorkManager предоставляет широкие возможности для асинхронной работы, которая должна выполняться в определенном состоянии устройства, откладывать ее, планировать, выполнять параллельно, перезапускать, запускать периодически, работать в многопроцессном режиме. Также этот инструмент гарантирует выполнение задачи даже после закрытия приложения и перезагрузки устройства. А его доступность, начиная с API 14, делает его must-have инструментом для подобного рода задач.
Спасибо за внимание! Надеемся, что этот материал был полезен для вас.
Источник