<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Askhar Aydarov</title><generator>teletype.in</generator><description><![CDATA[Askhar Aydarov]]></description><image><url>https://img1.teletype.in/files/84/59/84593d77-e7ee-4971-9c62-3106135f7646.png</url><title>Askhar Aydarov</title><link>https://teletype.in/@secundans</link></image><link>https://teletype.in/@secundans?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=secundans</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/secundans?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/secundans?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Fri, 10 Jul 2026 01:45:15 GMT</pubDate><lastBuildDate>Fri, 10 Jul 2026 01:45:15 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@secundans/philosophers-stone</guid><link>https://teletype.in/@secundans/philosophers-stone?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=secundans</link><comments>https://teletype.in/@secundans/philosophers-stone?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=secundans#comments</comments><dc:creator>secundans</dc:creator><title>Секрет долголетия вашего приложения</title><pubDate>Tue, 29 Oct 2024 21:29:22 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/4f/4a/4f4a3248-a3d9-4ea1-ba4e-9690b98a78f4.png"></media:content><description><![CDATA[<img src="https://img2.teletype.in/files/57/5c/575cf37f-d317-43bb-8ec8-e2332243efa8.jpeg"></img>Закрывая приложение из недавних мы ожидаем, что оно будет убито. И чаще всего так он и было. Но в какой-то момент что-то пошло не так, и многие начали замечать, что настройки, которые должны были примениться после перезапуска приложения, игнорируются. Раньше такого точно не было и этому свидетельствуют жалобы на новое поведение.]]></description><content:encoded><![CDATA[
  <h3 id="dneI">Интро</h3>
  <p id="MWoo">Закрывая приложение из недавних мы ожидаем, что оно будет убито. И чаще всего так он и было. Но в какой-то момент что-то пошло не так, и многие начали замечать, что настройки, которые должны были примениться после перезапуска приложения, игнорируются. Раньше такого точно не было и этому свидетельствуют жалобы на новое поведение.</p>
  <p id="wIed">Оказалось, что процесс приложения продолжает работать. И теперь, чтобы точно перезапустить приложение, нужно останавливать его в настройках. В первую очередь подумал, что это новая секретная оптимизацией недавних обновлений системы, так как при живом процессе <code>onCreate</code> у <code>Application</code> не вызывается повторно, а скорость запуска приложения ускоряется в разы. И оптимизацию скорее всего сделали в Android 14 и в свежих релизах. У меня на руках два устройства: Pixel 7 и Pixel 6 Pro. Оба с Android 14, но на Pixel 6 Pro нет такой оптимизации, — приложение умирает при удалении из недавних — но и сборка там февральская, когда как на Pixel 7 сентябрьская сборка и оптимизация присутствует.</p>
  <p id="6tKU"></p>
  <h3 id="YBbr">Исследуем</h3>
  <p id="Dd2X">Решил проверить не только наше приложение, и оказалось, что оптимизация работает не для всех. Например, сработала для Telegram и ряда банков, но не сработала, например, для Tooba. Для последнего в логах вывелось сообщение <code>Killing 6505:site.tooba.android/u0a410 (adj 905): remove task</code>, которого не было для других. </p>
  <p id="v7hM">Стремясь понять всю цепочку событий начал исследование издалека, а именно с момента смахивания приложения из списка недавних. Происходит это в <a href="https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/RecentsView.java;drc=1b1eb1b13c701c39b380dcb8dc3205f6f99410a5;l=3869" target="_blank">RecentsView</a>, в котором в момент завершения анимации вызывается метод <code>removeTaskInternal</code>. Он делегирует вызов в <code>ActivityManagerWrapper</code>.</p>
  <figure id="ZM8s" class="m_column">
    <img src="https://img2.teletype.in/files/56/b5/56b55d7e-275a-4efc-afae-254e9b97dfb5.png" width="653" />
  </figure>
  <p id="zo72">Делегирование на этом не заканчивается - обертка делегирует вызов в сервис <code>IActivityTaskManager</code>. </p>
  <figure id="4WlX" class="m_column">
    <img src="https://img1.teletype.in/files/c1/6b/c16b84b6-390b-4db6-a9aa-2362a8bae43a.png" width="467" />
  </figure>
  <p id="gI2k">Единственной реализацией этого сервиса является <code>ActivityTaskManagerService</code>, который делегирует вызов в один из методов <code>ActivityTaskSupervisor</code>.</p>
  <figure id="8yQQ" class="m_column">
    <img src="https://img1.teletype.in/files/47/f3/47f3197c-a764-465a-a66f-bf94388d733a.png" width="724" />
  </figure>
  <p id="XgeI">Тут не сильно важно, что вызовется в итоге, — <code>removeTask</code> или <code>removeRootTask</code> — потому что <code>removeRootTask</code> для простого случая начнет в цикле вызывать <code>removeTask</code> для всех листьев. Он же останавливает запущенные <code>Activity</code> и далее вызывает метод <code>cleanUpRemovedTask</code>. В этом методе выполняются три интересных действия:</p>
  <ul id="Idmi">
    <li id="mvCj">Очистка сервисов в <code>cleanUpServices</code></li>
    <li id="o7m3">Проверка, что <code>Activity</code> завершились. Если нет, то убийство откладывается на 1000ms. После чего будет вызван метод <code>killTaskProcessesIfPossible</code></li>
    <li id="1TIa">Убийство, если это возможно, в методе <code>killTaskProcessesIfPossible</code></li>
  </ul>
  <figure id="mkUt" class="m_column">
    <img src="https://img3.teletype.in/files/ed/4d/ed4da121-75c1-48d3-9e73-6d007a48b01d.png" width="724" />
  </figure>
  <p id="9J1Y">В <code>killTaskProcessesIfPossible</code>, происходит несколько проверок, к которым есть описание в виде комментариев и убийство снова делегируется в <code>ActivityManagerInternal</code> путем отправки сообщения.</p>
  <figure id="VIxa" class="m_column">
    <img src="https://img3.teletype.in/files/e0/ec/e0ec61e1-f621-4102-927e-c0ebfdf294ef.png" width="760" />
  </figure>
  <p id="eWK7"><code>ActivityManagerInternal</code> реализует внутренний класс <code>LocalService</code>, который лежит внутри <code>ActivityManagerService</code> (не путать с <code>ActivityTaskManagerService</code>). И вот наконец мы пришли к месту, где начинает формироваться наш лог с сообщением &quot;remove task&quot;.</p>
  <figure id="eAeS" class="m_original">
    <img src="https://img4.teletype.in/files/f0/e3/f0e36a9d-4220-4d7b-aee6-27d67bb1bad2.png" width="714" />
  </figure>
  <p id="7qoy">Но, кроме этого, видно, что процесс будет убит, если выполняются условия:</p>
  <ul id="PmKX">
    <li id="UaBL">Процесс в фоне</li>
    <li id="Jn0d">Нет работающих <code>BroadcastReceiver</code></li>
    <li id="6Em4">Нет работающих <code>Service</code></li>
  </ul>
  <p id="l7aw">В ином случае мы откладываем убийство установкой флага в <code>ProcessRecord</code>. Теперь возникает вопрос: кто убьет приложение позже, так как никакое отложенного сообщение тут не отправляется?</p>
  <p id="tBVQ">Знакомьтесь, <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java;drc=b30a1b4e0e02529181b2877f70b2cb1378596a74;l=3397" target="_blank">OomAdjuster</a>. В задачи OomAdjuster входит определение состояния процесса, оценка oom_adj с предварительной сортировкой в CacheOomRanker, а так же он умеет <a href="https://source.android.com/docs/core/perf/cached-apps-freezer" target="_blank">замораживать и размораживать процессы</a>, используя <code>CachedAppOptimizer</code>. Все эти процессы запускаются по различным <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/ActivityManagerInternal.java;drc=f9030081a47c956c4c2ea38bebce05f47c15996a;l=352" target="_blank">триггерам</a> по типу запуска какого-то компонента или его завершение. Даже смерть процесса - это <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java;drc=16fcdb2ffb1484d35694aa069de6198fa92804d2;l=3551" target="_blank">триггер</a>, который прилетает в <code>OomAdjuster</code> из <code>ActivityManagerService</code> и инициируется в слушателе состояния процесса <code>AppDeathRecipient</code>.</p>
  <figure id="kgT5" class="m_original">
    <img src="https://img1.teletype.in/files/c9/db/c9dbbd27-8b9f-40b8-b55f-fa9a99d7362f.png" width="578" />
  </figure>
  <p id="v6Dp">Убивать <code>OomAdjuster</code> умеет в случаях если приложение долго остается замороженным/закешированным или в случае, когда установлен флаг <code>waitingToKill</code>, который мы ранее видели в <code>ActivityManagerService#LocalService</code>, и выполнены те же самые условия. Поэтому получается, что флаг <code>waitingToKill</code> отложит убийство до возникновения любого триггера у <code>OomAdjuster</code>.</p>
  <figure id="FG28" class="m_original">
    <img src="https://img4.teletype.in/files/fb/db/fbdb3c2b-0da0-4fe1-8d8e-45c5ee9034b5.png" width="681" />
  </figure>
  <p id="VTeW">Теперь осталось понять, что именно изменилось в этих классах за последнее время. Долго искать не пришлось - нашелся такой коммит, который попал в основную ветку в феврале 2024, а сделан был еще в ноябре в 2023.</p>
  <figure id="tLS1" class="m_column">
    <img src="https://img4.teletype.in/files/7a/fc/7afcaf34-4038-41fb-a6d2-086723521a3b.png" width="872" />
  </figure>
  <p id="fiKj">Изменения в коммите минимальные - перед убийством добавили проверку того, что имеются started сервисы в <code>ActivityManagerService#LocalService</code> и <code>OomAdjuster</code>.</p>
  <figure id="E3U0" class="m_column">
    <img src="https://img4.teletype.in/files/bf/42/bf420fe0-c680-4952-ba5d-5e5f19ee636f.png" width="816" />
    <figcaption>Выше - ActivityManagerService#LocalService. Ниже - OomAdjuster</figcaption>
  </figure>
  <p id="n0GF">Забавно, что коммент перед установкой флага ожидания, не обновили.</p>
  <figure id="hLAt" class="m_original">
    <img src="https://img2.teletype.in/files/1a/3a/1a3ad65d-ddaf-4710-aab3-b0c6c2cd35d9.png" width="628" />
  </figure>
  <p id="ks4V">Сначала, изменение показалось странным, ведь ранее мы встречали такой метод как <a href="#mvCj">cleanUpService</a>, где должна была происходить остановка сервисов аналогично тому, что рядом происходит остановка активностей. Оказалось, что метод делегируется в <a href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java;drc=f9030081a47c956c4c2ea38bebce05f47c15996a;l=7121" target="_blank">ActivityServices</a>, который останавливает сервисы только в том случае, если у них в <code>AndroidManifest.xml</code> указан <code>android:stopWithTask=&quot;true&quot;</code>. В ином случае запускается большая цепочка событий определения того, когда стоит перезапустить сервис и с какими аргументами, но остановки сервиса не будет, следовательно, и остановки процесса приложения.</p>
  <figure id="cHPe" class="m_original">
    <img src="https://img3.teletype.in/files/68/8a/688a61dc-ff09-49bf-a453-b8ffbfdc7c80.png" width="709" />
  </figure>
  <p id="BzCy"></p>
  <h3 id="8egp">Эксплуатируем</h3>
  <p id="wGWi">Мы можем проверить гипотезы, попыткой воспроизвести проблему в приложении с одним сервисом. И, о чудо, приложение не умирает. Иногда 2 минуты, а иногда все 10. </p>
  <figure id="awCG" class="m_column">
    <img src="https://img4.teletype.in/files/73/bf/73bf3b22-7cf1-4f6a-bf9a-a53d21a2374e.png" width="860" />
  </figure>
  <p id="jvci">При этом сам сервис <a href="https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java;drc=a90a5a843797ec6c5f011a27d5459c06051fe63f;l=1591" target="_blank">OomAdjuster</a> и <a href="https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java;drc=ba82ea3c5d628f5039fa615de42cb92b3752400b;l=1641" target="_blank">ActivityServices</a> уже убили ввиду его бездействия.</p>
  <figure id="ouLH" class="m_original">
    <img src="https://img1.teletype.in/files/c9/2a/c92a69b6-3922-4833-93dc-4ee87d6c5185.png" width="454" />
  </figure>
  <p id="V7t3">А вот с февральской сборкой на Pixel 6 Pro поведение не получается повторить  - туда этот коммит не попал. Так же не получается повторить с флагом <code>stopWithTask=true</code>.</p>
  <p id="kldB">Теперь стало интересно как долго можно сохранять приложение живым. Для этого сделал приложение, в котором сервис стартует и периодически делает какую-то работу. Но есть еще вариант заставить приложение жить еще дольше, выкинув в <code>onDestroy</code> сервиса ошибку, чтобы процесс завершения сервиса не был успешным никогда. А затем эту ошибку можно поймать с помощью <code>Thread.setDefaultUncaughtExceptionHandler</code>, чтобы у нас не упало приложение. Но есть проблема, и она заключается в том, что из-за того, что мы выкинули ошибку в <code>onDestroy</code>, пользователь через некоторое время увидит диалог об ANR. И, если он не нажмет &quot;Close App&quot;, то мы продолжим жить. Правда он будет получать этот диалог с определенной периодичностью.</p>
  <figure id="rTe2" class="m_column">
    <img src="https://img3.teletype.in/files/eb/a0/eba0d2e2-605b-47ea-942b-f424136f15cb.png" width="915" />
  </figure>
  <p id="y7iO">Кроме этого, если пользователь зайдет в приложение, то оно у него не запустится дальше Splash Screen. Эту проблему можно исправить, перезапустив <code>Looper</code> в Exception Handler, но это все равно не поможет отвязаться от диалога с ANR.</p>
  <figure id="45rU" class="m_original">
    <img src="https://img4.teletype.in/files/33/61/33616faa-94ca-4464-bd0d-d00ef82e931f.png" width="549" />
  </figure>
  <p id="4zmy">Тут можно вспомнить, что кроме <code>Service</code>, который предотвращает убийство на некоторых версиях Android 14, есть еще <code>BroadcastReceiver</code>. Поэтому попробовал отправлять сообщение в моменте закрытия приложения и получил такой же эффект - приложение не будет убито. Кроме этого, мы, конечно, не можем запускать сервис из фона, но никто не запрещал отправлять широковещательное сообщение, поэтому вполне рабочий вариант отправлять новое сообщение в том же самом <code>BroadcastReceiver</code>.</p>
  <p id="CJbf">Но если отправлять сообщения слишком часто, то система в <code>OomAdjuster</code> нас пометит как &quot;excessive processes&quot; и прибьет, потому что мы выполняем слишком много транзакций через <code>Binder</code>(хотя это не так уж часто возникает). И чтобы решить эту проблему, можно делать какую-либо работу или подобрать такой таймаут, чтобы и система нас не прибила, и ANR не вылез.</p>
  <figure id="SC52" class="m_column">
    <img src="https://img3.teletype.in/files/a9/08/a908bd15-f561-44c1-937f-d436dade6838.png" width="664" />
    <figcaption>Осторожно. Псевдокод</figcaption>
  </figure>
  <p id="S6Af">Так же можно поступить для <code>Service</code> в Android 15 - выполнять долгую работу в <code>onDestroy</code> и отправлять в конце сообщение <code>BroadcastReceiver</code>. Но работу в этом случае мы можем выполнять до 200 секунд и не получать ANR.</p>
  <figure id="gUhK" class="m_column">
    <img src="https://img4.teletype.in/files/31/02/3102aded-fb61-4595-bfb3-63a3bf2cecd0.png" width="670" />
    <figcaption>Осторожно. Псевдокод</figcaption>
  </figure>
  <p id="AUpp">Ну и вишенкой будет запуск фонового сервиса при получении сообщения <code>BOOT_COMPLETED</code> в <code>BroadcastReceiver</code>. Экспериментально получилось убедиться, что Android такое позволяет делать.</p>
  <figure id="mqmK" class="m_column">
    <img src="https://img4.teletype.in/files/71/58/71582323-1538-41cc-999e-45f4ef4b5e3b.png" width="751" />
    <figcaption>Осторожно. Псевдокод</figcaption>
  </figure>
  <p id="Exrm"></p>
  <h3 id="9y8G">Итог</h3>
  <p id="1IuT">Изменение, найденное случайным образом, привело к тому, что был найден способ поддерживать живым приложение очень долго. Благодаря этому приложение будет готово к запуску всегда, а его запуск будет приравниваться к запуску обычной <code>Activity</code>. В моем случае, приложение работало несколько часов до момента, пока устройство не ушло в глубокий сон. И все это без <code>Activity</code>, без <code>Foreground Service</code> и без permission&#x27;а на работу в фоне.</p>
  <figure id="pHnr" class="m_column">
    <img src="https://img3.teletype.in/files/e3/69/e369c235-4a2f-4261-aa6b-f6e6499d2c39.png" width="1079" />
  </figure>
  <p id="pbHi">Я вынес код в отдельную библиотеку &quot;<a href="https://github.com/0xera/philosophers-stone" target="_blank">Philosopher&#x27;s Stone</a>&quot; и рядом добавил пример приложения, которое можно скачать в виде <a href="https://github.com/0xera/philosophers-stone/releases/tag/0.1" target="_blank">apk</a> и протестировать сколько оно проживет на вашем устройстве. Для этого запустите приложение, а затем смахните его из списка недавних. Позже, периодически открывая и закрывая активность, вы будете видеть как долго работает приложение. </p>
  <p id="wGST"></p>
  <p id="81cc">Опубликовано в <a href="https://t.me/int_ax" target="_blank">Полуночные Зарисовки</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@secundans/fat-aar</guid><link>https://teletype.in/@secundans/fat-aar?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=secundans</link><comments>https://teletype.in/@secundans/fat-aar?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=secundans#comments</comments><dc:creator>secundans</dc:creator><title>fat aar от Google. Дождались?</title><pubDate>Sat, 12 Oct 2024 18:06:43 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/37/c0/37c08b00-783e-4bc1-9795-af2bfb591a0e.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/22/9e/229e5452-d682-4c15-b568-933634f2bbe2.png"></img>Google начали делать свой инструмент для создания fat-aar, но сильно его не доработали. В Open Source остались сомнительные решения, написанные на Groovy, поэтому сделал свой с блэкджеком, в смысле с shadow-jar.]]></description><content:encoded><![CDATA[
  <h4 id="TLDR;">TL;DR</h4>
  <p id="g44B">Google начали делать свой <a href="https://mvnrepository.com/artifact/com.android.fused-library/com.android.fused-library.gradle.plugin" target="_blank">инструмент для создания fat aar</a>, но сильно его не доработали. В Open Source остались сомнительные решения, написанные на Groovy, поэтому сделал <a href="https://github.com/deepmedia/Grease" target="_blank">свой с блэкджеком</a>, в смысле с <a href="https://github.com/GradleUp/shadow" target="_blank">shadow-jar</a>.</p>
  <figure id="PEB2" class="m_retina">
    <img src="https://img3.teletype.in/files/22/9e/229e5452-d682-4c15-b568-933634f2bbe2.png" width="512" />
    <figcaption><a href="https://medium.com/@vpjigin/create-aar-file-from-android-library-code-ebc81283f211" target="_blank">https://medium.com/@vpjigin/create-aar-file-from-android-library-code-ebc81283f211</a></figcaption>
  </figure>
  <h3 id="BFxp">Интро</h3>
  <p id="G3Na">Думаю многие слышали про такой формат дистрибуции нескольких библиотек или Gradle модулей, запакованных в один артефакт. В любом случае напомню, что по умолчанию каждый Gradle модуль с подключенным плагином <code>com.android.library</code> можно собрать как один aar, но могут быть случаи, когда все же есть необходимость объединить несколько библиотека в одном aar артефакте. Такая идея не нова. Например, в Java мире это называется uber jar или fat jar и готовится он с помощью довольно известных плагинов <a href="https://maven.apache.org/plugins/maven-shade-plugin/" target="_blank">maven-shade-plugin</a> и <a href="https://github.com/GradleUp/shadow" target="_blank">shadow-jar</a> для Gradle.</p>
  <p id="ZreD"></p>
  <h3 id="fMK2">Необходимость</h3>
  <p id="P110">Так ли нам нужен fat aar? В основном, нет. Если правильно организовать многомодульность в проекте и заложить то, что эти модули будут опубликованы как библиотеки, и все будет прекрасно. </p>
  <p id="0Pxl">Необходимость может появиться в ряде случаев. Например, если у вас есть локальные jar и aar файлы, которые нужно опубликовать вместе с библиотекой, но исходники уже утеряны. Другой пример, если не хочется раскрывать идеальную многомодульную архитектуру для пользователей библиотеки. В конце концов, может быть случай, что нужно поделиться куском приложения с другим проектом, а сроки горят.</p>
  <p id="aUAJ">Еще причин почему это может быть нужно, можете найти в <a href="https://issuetracker.google.com/u/0/issues/62121508" target="_blank">issue</a> по этой теме, который висит аж с 2017 года. В комментарии там пришли люди из разных компаний. Например, вот свежий комментарий человека из Netflix:</p>
  <figure id="lNKd" class="m_column">
    <img src="https://img1.teletype.in/files/c3/05/c305974d-bf79-491b-b83a-3bf5b9c29c93.png" width="791" />
    <figcaption>https://issuetracker.google.com/u/0/issues/62121508#comment200</figcaption>
  </figure>
  <p id="Qc6Z">А Google что, не делает? На самом деле начинали делать в 2022 и назвали они свое творение <a href="https://mvnrepository.com/artifact/com.android.fused-library/com.android.fused-library.gradle.plugin" target="_blank">Fused Library</a>. </p>
  <p id="FeHa"></p>
  <h3 id="r7MJ">Fused Library</h3>
  <p id="vhtz">Да, плагин назван так, что его вряд ли кто-то нашёл бы... Но все же он нашёлся. К тому же, появлялись коммиты с обновлениями буквально в дни, когда я его смотрел.</p>
  <figure id="xi9I" class="m_original">
    <img src="https://img1.teletype.in/files/08/1f/081f3243-c156-481f-995a-26756ce32819.png" width="462" />
  </figure>
  <p id="wogl">Это свидетельствует о том, что ребята намерены продолжать разработку инструмента, хоть и был перерыв в два года.</p>
  <p id="9o7o">Я потратил пару вечеров, пытаясь его завести, но лишь расстроился тому, насколько сырой все же инструмент. </p>
  <p id="8Hw3">1. Сразу бросается в глаза то, что он не умеет формировать aar файл... </p>
  <figure id="gWLv" class="m_column">
    <img src="https://img2.teletype.in/files/57/19/5719a36d-9dcc-4127-acb6-4931a7ab4b63.png" width="1276" />
  </figure>
  <p id="1Jog">Он все объединяет в jar. Понятно, что ошибка в том, не выставлено расширение файла при <a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/FusedLibraryBundle.kt;drc=2b528806a1430fa56180e485c0ba6dd264c746ab;l=89" target="_blank">конфигурации задачи</a>, но уже тут можно делать выводы, что решение нерабочее. И все же продолжил исследование инструмента на случай, если надумаю починить и доработать.</p>
  <p id="PNlS">2. По картинке из пункта 1 можно заметить, что нет никаких классов в собранном jar. Все дело в том, что плагин даже это не умел делать, так как там забыли упаковать классы в jar. Благо это <a href="https://cs.android.com/android-studio/platform/tools/base/+/2dbae3aaee02cb2beb6e31ad8b2b5edbafebd511" target="_blank">исправили</a> в июле этого года.</p>
  <figure id="3R3N" class="m_original">
    <img src="https://img4.teletype.in/files/7d/1d/7d1d74ae-66a0-4e52-83ab-4d788c579d33.png" width="694" />
    <figcaption>https://cs.android.com/android-studio/platform/tools/base/+/2dbae3aaee02cb2beb6e31ad8b2b5edbafebd511</figcaption>
  </figure>
  <p id="vQIV">3. Нет поддержки различных build variants. Поддерживает только один build type - debug. С учетом того, что fat aar чаще нужен в релизной сборке, такое нам не подойдет. </p>
  <figure id="7bhO" class="m_column">
    <img src="https://img2.teletype.in/files/d1/46/d1463d11-43b6-49c8-9c51-881026902307.png" width="741" />
    <figcaption>https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/plugins/FusedLibraryPlugin.kt;drc=1e11a7a99f7873f920b1c89eb134dbcc4af0c1af;l=303</figcaption>
  </figure>
  <p id="LDLo">Тут завершу список проблем, потому что их просто нельзя починить. Да, реализация интересная и отличается от всего, что было до этого, но его нельзя доработать без форка всего AGP, так как используется internal api. К тому же, по последним изменениям непонятно, какие планы у разработчиков. Ясно одно, что проект все еще экспериментальный, поэтому велика вероятность того, что решения, которые будут приниматься, не совпадут с моими предложениями. (Как-то у меня была попытка законтрибьютить в AGP - там так же все же предпочли сделать по своему.)</p>
  <p id="37kY">Учитывая все это, прекратил ковырять этот плагин. Сильно сырой все же.</p>
  <p id="7HaL"></p>
  <h3 id="A9a1">Что в Open Source?</h3>
  <p id="2zea">Взяв весь свой пыл куда-нибудь закотрибьютить пошёл искать решения в GitHub. Нашёл пару решений, но <a href="https://github.com/kezong/fat-aar-android?tab=readme-ov-file" target="_blank">самое популярное</a>, написанное с использованием Groovy, уже заброшено. Этот заброшенный плагин был форком других похожих плагинов. Теперь уже его <a href="https://github.com/aasitnikov/fat-aar-android" target="_blank">форкнули</a> для поддержки AGP 8 и нетранзитивных R-классов.</p>
  <p id="s0oi">В актуальном форке хоть и решили проблемы с AGP 8, но остались другие. </p>
  <p id="NO5y">1. Есть <a href="https://github.com/aasitnikov/fat-aar-android/blob/e3c8849e6ecbd3251c785244942c9aa4f8e5ca2f/source/src/main/groovy/com/kezong/fataar/VariantProcessor.groovy#L476" target="_blank">проблема со слиянием ресурсов с одинаковыми id</a>. Такое может быть, потому что плагин соединяет ресурсы только после того, как сформирует конечные артефакты.</p>
  <p id="VhrC">2. Нет возможности выполнять формировать shaded jar, то есть перемещать классы в разные пакеты. Такое, например, делали в компиляторе Kotlin, чтобы зависимость для Idea не конфликтовала с зависимостью для Gradle. Ну и по проекту можно найти <a href="https://github.com/JetBrains/kotlin/blob/9f3c95e2945346e2396de77ad72e71eca3654668/libraries/tools/kotlin-gradle-plugin/build.gradle.kts#L238" target="_blank">разные применения shadow-jar</a>.</p>
  <p id="Stcn">Пошёл искать дальше и обнаружил <a href="https://github.com/deepmedia/Grease/tree/bae9fe0c68222747d8d48b7e269465113117cd63" target="_blank">ещё один плагин</a>. Он полностью написан на Kotlin, имеет еще больше проблем, некоторые вещи недоделаны, а поддерживается только AGP 4. Напомню, что на дворе AGP 8 и не за горами AGP 9. </p>
  <p id="Gu2f">И все же я решил оживить именно этот проект. Потому что, по моему мнению, автор старался делать иначе, чем в других проектах, и так, чтобы это было надежно. Это можно заметить по исследованию задач для формирования aar, результат которых он оставил в виде комментариев в коде. </p>
  <p id="ctEX"></p>
  <h3 id="bWv3">Grease</h3>
  <p id="JCm4">Итак, что было сделано:</p>
  <ul id="Nwdf">
    <li id="RZP8">Переход с AGP 4 на AGP 8.</li>
    <li id="6HK3">Доработал слияние ресурсов, вдохновившись Fused Library. Решение получилось надежнее, чем у fat-aar-android.</li>
    <li id="qNbm">Подключил shadow-jar и научил его трансформировать метаданные Kotlin и AndroidManifest.xml. Кроме того, работу shadow-jar можно конфигурировать.</li>
  </ul>
  <p id="T1mu">Может показаться, что это немного, но по сути плагин был полностью переписан в рамках первого пункта.</p>
  <p id="ngHb">Было большим удивлением то, что после того, как освежил инструмент, у него сразу появились клиенты, которые начали накидывать issues. Количество звезд при этом выросло в два раза. Поэтому, пока Google доделывает Fused Library, есть альтернатива в виде <a href="https://github.com/deepmedia/Grease" target="_blank">Grease</a>.</p>
  <p id="RYzx"></p>
  <p id="Q4MN"></p>
  <p id="Htq7">Опубликовано в <a href="https://t.me/int_ax" target="_blank">Полуночные Зарисовки</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@secundans/TDUU9o72huy</guid><link>https://teletype.in/@secundans/TDUU9o72huy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=secundans</link><comments>https://teletype.in/@secundans/TDUU9o72huy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=secundans#comments</comments><dc:creator>secundans</dc:creator><title>Layout Inspector, который не лагает</title><pubDate>Mon, 23 Sep 2024 20:44:17 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/f7/5b/f75bee20-6f90-4f15-a155-2987f14b7da8.png"></media:content><description><![CDATA[<img src="https://img2.teletype.in/files/db/41/db4155f0-5ebb-4474-a05a-a954a13c9523.png"></img>Довольно давно пользуюсь Flipper Layout Plugin. Это мощный инструмент, который может отображать семантическое дерево и даже позволяет редактировать параметры View. C ним было удобно найти разного рода баги, проследить какая ViewGroup отвечает за размеры контента, как прокидываются паддинги, как применяются insents и прочее.]]></description><content:encoded><![CDATA[
  <p id="Fuxn">Довольно давно пользуюсь <a href="https://fbflipper.com/docs/features/plugins/inspector/" target="_blank">Flipper Layout Plugin</a>. Это мощный инструмент, который может отображать семантическое дерево и даже позволяет редактировать параметры View. C ним было удобно найти разного рода баги, проследить какая ViewGroup отвечает за размеры контента, как прокидываются паддинги, как применяются insents и прочее.</p>
  <p id="5YcC"></p>
  <p id="hpwI">И вот в один день в мае пришел ко мне коллега, которому я до этого рекомендовал Layout Plugin, с новостями о том, что появился плагин для Compose. Оказалось, что они сделали его еще в прошлом году. Я бросился изучать как именно и что они сделали. В общем не увидел какого-то Rocket Science, а скорее код, который ранее уже видел в исходниках Layout Inspector в Android Studio. Вот например, <a href="https://github.com/facebook/flipper/blob/main/android/plugins/jetpack-compose/src/main/java/facebook/internal/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt#L85" target="_blank">LayoutInspectorThree</a>, где они вытаскиваю <code>SemanticsNode</code>. И <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt;drc=0e8c603beb7a99e2a923e38b57fe0249d649c428;l=82" target="_blank">тот же класс</a> в коде AS. Или, например, <a href="https://github.com/facebook/flipper/blob/2b3184f48ad4bd88cc2c706396f36edc6c40fdbf/android/plugins/jetpack-compose/src/main/java/facebook/internal/androidx/compose/ui/inspection/RecompositionHandler.kt#L28" target="_blank">RecompositionHandler</a>, который хукает виртуальную машину ART для подсчета количества рекомпозиций и пропусков. <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/RecompositionHandler.kt;drc=dcaa116fbfda77e64a319e1668056ce3b032469f;l=28" target="_blank">Вот он в исходниках</a> AS.</p>
  <p id="bXgC"></p>
  <p id="xT2Q">В любом случае, все выглядело круто. Даже если код скопирован, есть шансы на то, что этот инструмент гибче и его можно сколько угодно допиливать, так как под рукой все <code>SemanticsNode</code>, а там можно и все <code>Modifier.Node</code> вытащить. Поэтому надо пробовать запустить. Но, к сожалению, первая попытка не увенчалась успехом:</p>
  <figure id="gyTc" class="m_column">
    <img src="https://img3.teletype.in/files/ef/63/ef6313d2-6f93-4aba-8e2c-a9a80b2e1027.png" width="1105" />
  </figure>
  <p id="PqcC">Видимо, часть кода они скопировали, а часть вынесли в виде <code>aar</code>. Я так понял, что это локально собранная версия <a href="https://mvnrepository.com/artifact/androidx.inspection/inspection/1.0.0" target="_blank">этой библиотеки</a>, но поновее, так как, если заменить библиотеку механизмами Gradle, то при сборке не найдутся некоторые классы. Я не перый, кто пытался запустить этот плагин, но с тех пор, как его сделали, во Flipper не очень-то и хотели его развивать. Ну так может показаться по <a href="https://github.com/facebook/flipper/issues/5635" target="_blank">issues</a>, которые висят, и по отсутствию новых коммитов.</p>
  <p id="PcZe">В тот раз остановился на этом месте и мне было лень дальше пытаться запустить его :) Да, надо было просто опубликовать нормальную версию библиотеки inspection или скачать <code>aar</code> из их репозитория и подключить так. Но, с учетом того, что я пропал на несколько месяцев, времени для этой авантюры не нашлось...</p>
  <p id="G0l4"></p>
  <p id="njrs">И вот недавно тестировал еще один инструмент, о котором напишу отдельно, и решил проверить его тут. Опять же, для этого надо было просто опубликовать недостающий <code>aar</code> и наконец запустить, ибо Layout Inspector уже невозможно терпеть. Честно сказать, был приятно удивлен тому, как это быстро работает и не лагает. </p>
  <figure id="PvW7" class="m_column">
    <img src="https://img2.teletype.in/files/db/41/db4155f0-5ebb-4474-a05a-a954a13c9523.png" width="3018" />
    <figcaption>https://github.com/flipperdevices/Flipper-Android-App</figcaption>
  </figure>
  <p id="46vW">Рекомендую самим попробовать. Держите <a href="https://github.com/0xera/inspection-lib" target="_blank">инструкцию</a> как подключить плагин и там же пример проекта с ним.</p>
  <p id="pepK"></p>
  <p id="Htq7">Опубликовано в <a href="https://t.me/int_ax" target="_blank">Полуночные Зарисовки</a></p>

]]></content:encoded></item></channel></rss>