Сегодня в выпуске: отключаем и удаляем предустановленный софт без root, разбираемся с форматом распространения приложений App Bundle, запускаем короутины в основном потоке без задержек, разбираемся в многопоточном программировании, учимся держать код в чистоте и порядке, изучаем новый механизм hot reload в Android Studio и, конечно же, берем на вооружение свежие библиотеки.
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 <имя_пакета>
Android App Bundles: Getting Started — большая статья о новом формате распространения приложений App Bundle, позволяющем существенно сократить размер приложения, которое пользователям придется скачивать и устанавливать.
Идея App Bundle достаточно проста. Android-смартфоны могут быть очень разными, поэтому разработчикам необходимо заботиться о таких вещах, как разные размеры иконок для разных разрешений экрана, переводы на другие языки и сборки нативных библиотек для разных архитектур процессоров. В результате пользователям приходится скачивать громоздкое приложение, включающее код и данные, которые на их конкретном телефоне не нужны.
Частично эту проблему можно решить самостоятельно, разделив приложение на несколько разных версий с помощью директив buildTypes
и splits
системы сборки Gradle. Однако в этом случае их все приходится заливать в Google Play по отдельности, а это может превратиться в кошмар, если в результате разделения получится десяток различных версий.
App Bundle позволяет упаковать код и данные всех версий в один пакет с расширением .aab и оставить его разделение для разных смартфонов на усмотрение гуглу.
Также статья содержит другую интересную информацию:
$ 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
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 настоятельно не рекомендуют этого делать, так как в этом случае ты можешь столкнуться с багами, которые очень трудно поймать.
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 Project Marble: Apply Changes — статья разработчиков Android Studio о функции Apply Changes, которая должна ускорить повторный запуск приложения в эмуляторе или на реальном устройстве.
Apply Changes должна прийти на смену плохо зарекомендовавшему себя механизму Intsant Run, который часто работал неверно и был несовместим со многими типами приложения. Смысл функции остался прежний — попытаться избежать перезапуска приложения при небольших изменениях кода и ресурсов и таким образом ускорить продуктивность тестирования и отладки. Однако детали реализации изменились:
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 — несколько советов о том, как поддерживать свой код в чистоте и порядке. Кому-то они покажутся банальными, но для новичков могут стать откровением.
Читайте также
Последние новости