Сегодня в выпуске: отключаем и удаляем предустановленный софт без root, разбираемся с форматом распространения приложений App Bundle, запускаем короутины в основном потоке без задержек, разбираемся в многопоточном программировании, учимся держать код в чистоте и порядке, изучаем новый механизм hot reload в Android Studio и, конечно же, берем на вооружение свежие библиотеки.
 

Почитать

 

Удаляем/отключаем любые предустановленные приложения без root

How to disable any pre-installed system app bloatware on Android without root — несколько баянистая, но оттого не менее полезная статья о том, как отключить любое предустановленное на устройство приложение, не имея прав root.

Для начала необходимо активировать ADB в настройках разработчика на устройстве (как это сделать, написано на каждом углу) и установить ADB на комп. Затем с помощью любой утилиты для управления приложениями нужно узнать точное имя пакета этого приложения. Автор статьи рекомендует использовать для этого App Inspector.

Далее открываем терминал на ПК и выполняем такую команду:

$ adb shell pm disable-user --user 0 <имя_отключаемого_пакета>

В случае необходимости приложение можно включить снова:

$ adb shell pm enable <имя_отключенного_пакета>

А с помощью такой команды можно просмотреть список отключенных приложений:

$ adb shell pm list packages -d

Кроме того, приложение можно полностью удалить с устройства:

$ adb pm uninstall -k --user 0 <имя_пакета>
Список отключенных пакетов
Список отключенных пакетов
 

Разработчику

 

Все, что нужно знать об App Bundle

Android App Bundles: Getting Started — большая статья о новом формате распространения приложений App Bundle, позволяющем существенно сократить размер приложения, которое пользователям придется скачивать и устанавливать.

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

Частично эту проблему можно решить самостоятельно, разделив приложение на несколько разных версий с помощью директив buildTypes и splits системы сборки Gradle. Однако в этом случае их все приходится заливать в Google Play по отдельности, а это может превратиться в кошмар, если в результате разделения получится десяток различных версий.

App Bundle позволяет упаковать код и данные всех версий в один пакет с расширением .aab и оставить его разделение для разных смартфонов на усмотрение гуглу.

Также статья содержит другую интересную информацию:

  • App Bundle — это обычный ZIP-архив. Его можно распаковать любым архиватором, поддерживающим формат ZIP.
  • Google не просто создает из App Bundle несколько вариантов APK для разных смартфонов. Он дробит их на мелкие части, которые могут быть скачаны по мере необходимости. Например, при смене языка смартфон автоматически докачает и установит APK с нужным языковым пакетом.
  • Google уже работает над функцией Dynamic delivery, которая позволит дробить App Bundle на еще более мелкие части, разделяя не только ресурсы и нативные библиотеки, но и код самого приложения. В этом случае сначала пользователи будут устанавливать только «самую используемую» часть приложения, а дополнительные компоненты будут докачиваться при необходимости (точно так же работают Instant Apps).
  • Расщепить App Bundle на набор APK можно и самостоятельно. Для этого необходимо скачать и установить утилиту BundleTool. Использовать так (все APK будут упакованы в ZIP-архив app.apks):
$ java -jar bundletool.jar build-apks --bundle=app.aab
--output=app.apks

Или так, если ты хочешь установить их на смартфон:

$ java -jar bundletool.jar build-apks --bundle=app.aab
--output=app.apks
--ks=keystore.jks
--ks-pass=<путь_до_keystore>
--ks-key-alias=<key_alias>
--key-pass=<пароль>

Чтобы сразу установить приложение на смартфон, используй такую команду:

$ java -jar bundletool.jar install-apks --apks=app.apks
App Bundle после разделения на APK
App Bundle после разделения на APK
 

Мгновенный запуск короутин в основном потоке

Launching a Kotlin Coroutine for immediate execution on the Main thread — интересное исследование, почему короутины Kotlin не запускаются сразу, если они используют Disaptcher.Main.

Представим такой код:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    launch(Dispatchers.Main) {
        log("A")
    }

    log("B")
}

Вопреки интуиции, он выведет на экран «B A» вместо «A B». Причина такого поведения заключается в том, что, хотя оба вызова функции log должны быть выполнены в основном потоке приложения, вызов log("A") все равно будет поставлен в очередь и, таким образом, будет выполнен уже после log("B").

Этого можно избежать, если использовать Dispatchers.Main.immediate вместо Dispatchers.Main. Но разработчики Kotlin настоятельно не рекомендуют этого делать, так как в этом случае ты можешь столкнуться с багами, которые очень трудно поймать.

 

Неожиданная причина использовать устаревший API анимации

One still surprisingly valid reason to use the old Animation (API 1+) — интересная заметка о том, почему устаревший и давно никем не используемый API анимации из древних версий Android еще может быть полезным для программистов.

С самых первых версий в Android был API анимации android.view.animation. По сравнению с современными ValueAnimator и ObjectAnimator его возможности были сильно ограниченны, к тому же он страдал от бага, который заставлял систему думать, что анимируемый объект находится на своем месте, даже если визуально он в другом. Например, если сделать эффект выезда элемента интерфейса из-за экрана, во время анимации система будет предполагать, что он уже на своем финальном месте, поэтому клик по текущему положению элемента ничего не даст, но клик по его ожидаемому положению будет обработан, как будто он уже там. В новых API баг был исправлен.

Однажды у автора статьи возникла проблема. Он хотел, чтобы пользователи могли работать с его приложением максимально быстро и кликать на элементы интерфейса, не дожидаясь окончания их анимации. И как раз здесь возникла проблема с новым API, который правильно рассчитывает текущее положение элементов интерфейса: клик по тому месту, где пользователь ожидал увидеть элемент интерфейса, просто не проходил, потому что элемент еще не «доехал» до него (анимация не закончилась). А вот старый API с его багом работал в данной ситуации отлично.

Для примера. Анимация с использованием нового API:

appearingGroup.setTranslationY(offset);
appearingGroup.setAlpha(0f);
appearingGroup
    .animate()
    .translationY(0f)
    .alpha(1f)
    .setDuration(100L)
    .setStartDelay(35L)
    .setInterpolator(interpolator)
.start();

С использованием старого API:

AnimationSet animSet = new AnimationSet(true);
animSet.addAnimation(new TranslateAnimation(0f, 0f, offset, 0f));
animSet.addAnimation(new AlphaAnimation(0f, 1f));
animSet.setDuration(100L);
animSet.setStartOffset(35L);
animSet.setInterpolator(interpolator);

appearingGroup.startAnimation(animSet);
 

Как Android Studio 3.5 ускорит повторный запуск приложения при отладке

Android Studio Project Marble: Apply Changes — статья разработчиков Android Studio о функции Apply Changes, которая должна ускорить повторный запуск приложения в эмуляторе или на реальном устройстве.

Apply Changes должна прийти на смену плохо зарекомендовавшему себя механизму Intsant Run, который часто работал неверно и был несовместим со многими типами приложения. Смысл функции остался прежний — попытаться избежать перезапуска приложения при небольших изменениях кода и ресурсов и таким образом ускорить продуктивность тестирования и отладки. Однако детали реализации изменились:

  • В этот раз разработчики решили сделать упор на стабильность работы механизма, даже если для этого приходится жертвовать скоростью.
  • Apply Changes работает только на Android 8 и выше.
  • Если в новой сборке приложения изменились только ресурсы, Android Studio перезапускает только активности приложения.
  • Если изменился код, Android Studio подменяет код новым прямо в памяти без перезапуска приложения. Однако эта функция работает только в том случае, если изменился код методов, но не их сигнатуры.
  • Если были изменены AndroidManifest.xml или нативные библиотеки, Android Studio придется перезапустить приложение.
Принцип работы Apply Changes
Принцип работы Apply Changes
 

Разница между пулами потоков: CPU-bound vs. I/O-bound

Understanding CPU- and I/O-bound for asynchronous operations — статья о многопоточном программировании, посвященная разнице между потоками, которые предназначены для выполнения расчетов (CPU-bound), и потоками, в которых должны быть выполнены операции ввода-вывода (I/O-bound).

Все мы знаем, что тяжелые расчеты и сетевые операции необходимо выносить в отдельные потоки. Также все мы знаем, что создание нового потока требует много ресурсов. Именно поэтому современные системы не заставляют программиста создавать новые потоки на каждый чих, а предлагают использовать так называемые пулы потоков (thread pool): если нужно вынести какую-то работу в отдельный поток, ты достаешь поток из пула и отдаешь эту работу ему.

Так работают и RxJava, и Kotlin Coroutines. Они предлагают два вида пулов: I/O-bound и CPU-bound. Чтобы понять, в чем их различия, нужно разобраться, почему те или иные операции необходимо выносить в отдельные потоки.

Операции ввода-вывода, такие как сетевые запросы и чтение больших файлов, следует выносить в отдельный поток просто потому, что они блокируют его: поток отправляет сетевой запрос и до тех пор, пока не получит ответ, блокируется без возможности делать что-либо еще. Если выполнить сетевой запрос в основном потоке приложения, оно просто зависнет до получения ответа.

Приложения могут отправлять множество сетевых запросов во время работы, поэтому пул I/O-потоков должен быть большим. Таким большим, чтобы в любой момент приложение могло запросить еще один поток и не заблокироваться из-за того, что они закончились.

С вычислительными операциями дело обстоит примерно так же. Если необходимо сделать много вычислений, требующих больших процессорных ресурсов, лучше использовать отдельный поток — как и в случае с I/O-операциями, во время вычислений поток будет заблокирован.

Однако есть маленький, но очень важный нюанс: если в случае с потоками ввода-вывода ожидание ничего не стоит и поэтому мы можем спокойно плодить их, то в случае с вычислительными потоками мы упираемся в количество ядер процессора. Размер пула вычислительных потоков должен быть привязан к количеству процессорных ядер, иначе можно «повесить» приложение.

Окей, а как быть с приложениями, которые выполняют сначала операции ввода-вывода, а затем вычислительные операции? Для этого можно переключаться между потоками. Kotlin позволяет делать это легко и удобно:

launch(Dispatchers.IO) {
    val image = api.fetchImageAsync(url).await()
    withContext(Dispatchers.Default) {
        val blurred = image.blur()
        withContext(Dispatchers.Main) {
            displayImage(blurred)
        }
    }
}

Тот же пример с использованием RxJava:

api.fetchImageObservable(url)
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.computation())
    .map { blurImageSync(it) }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { displayImage(it) }
 

Как держать код в чистоте и порядке

Five tips to get your code base in shape — несколько советов о том, как поддерживать свой код в чистоте и порядке. Кому-то они покажутся банальными, но для новичков могут стать откровением.

  1. Не переписывай код. В определенный момент тебе в руки может попасть устаревшее приложение, построенное на старых технологиях и, как тебе кажется, не слишком эффективное. У тебя может возникнуть желание переписать большую часть кода, но не стоит спешить. Скорее всего, этот код до сих пор отлично работает и пользователи в целом довольны приложением. Когда ты возьмешься его переписывать, работа над новыми функциями остановится, ты потратишь уйму времени, чтобы фактически переписать приложение с нуля, и в итоге пользователи, не подозревающие о внутренних изменениях, получат то же самое приложение. Да, часто нельзя смотреть без слез на чужой код, но, может быть, стоит начать с самых ужасных мест и постепенно избавляться от легаси?
  2. Больше релизов. Опять же, имея дело с легаси-кодом, который слабо или совсем не покрыт тестами, ты будешь часто сталкиваться с проблемой «изменил строку кода в одной части приложения — сломал функцию совсем в другом месте». Такая проблема может существенно растянуть релиз-цикл из-за необходимости постоянного ручного тестирования, которое, в свою очередь, может привести к появлению множества веток приложения (работа над новыми функциями не должна останавливаться, пока приложение проходит через тестировщиков) и других недостатков. Но есть решение: выпускать релизы часто. Если вместо одного релиза с множеством изменений ты будешь выпускать множество мелких релизов с одной-двумя новыми функциями, тестирование приложения существенно упростится и ускорится.
  3. Разделяй и властвуй. Самый банальный совет статьи сводится к следующему: стоит разделить приложение на независимые компоненты, которые можно изменять отдельно друг от друга, не боясь, что изменение в одном месте вызовет проблему в другом. Да, писать спагетти-код с кучей зависимостей между компонентами гораздо проще, но в будущем такой код заставит тебя тратить уйму времени на тестирование и сожрет добрую половину нервных клеток.
  4. Изучи свои инструменты. Ты можешь удивиться, сколько отличных функций для автоматизированного рефакторинга включает в себя Android Studio. Многие из этих функций рассматриваются в другой статье автора.
 

Библиотеки

  • InternalAppStore — набор библиотек и инструментов, позволяющий быстро развернуть свой собственный магазин приложений;
  • FlutterExamples — шпаргалка и сборник примеров по Flutter;
  • android-checkout — обертка, позволяющая быстро и без хлопот интегрировать библиотеку биллинга Google в приложение;
  • AndroidRibbon — View, реализующий переливающуюся ленточку поверх изображения;
  • belvedere — диалог выбора изображений;
  • ScratchCardView — реализация скретч-карты, как в лотерейном билете;
  • BubbleGum — библиотека для применения анимированного градиента к любым элементам интерфейса;
  • Thumby — библиотека для интерактивного создания превью из видео;
  • ExpandableHintText — красивая анимированная реализация EditText;
  • Tomo — коллекция эффектов для обработки изображений;
  • StfalconImageViewer — встраиваемый просмотрщик изображений;
  • TimetableLayout — RecyclerView.LayoutManager для показа данных в виде сетки расписания;
  • Boots — библиотека для организации слаженного процесса загрузки приложения;
  • RoundImageView — круглый ImageView, который поддерживает векторные изображения.

Оставить мнение

Check Also

Ghidra vs IDA Pro. На что способен бесплатный тулкит для реверса, созданный в АНБ

В марте 2019 года Агентство национальной безопасности США (NSA) опубликовало инструментари…