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

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

</content></entry><entry><id>secundans:TDUU9o72huy</id><link rel="alternate" type="text/html" href="https://teletype.in/@secundans/TDUU9o72huy?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=secundans"></link><title>Layout Inspector, который не лагает</title><published>2024-09-23T20:44:17.783Z</published><updated>2024-09-24T05:50:31.003Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/f7/5b/f75bee20-6f90-4f15-a155-2987f14b7da8.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/db/41/db4155f0-5ebb-4474-a05a-a954a13c9523.png&quot;&gt;Довольно давно пользуюсь Flipper Layout Plugin. Это мощный инструмент, который может отображать семантическое дерево и даже позволяет редактировать параметры View. C ним было удобно найти разного рода баги, проследить какая ViewGroup отвечает за размеры контента, как прокидываются паддинги, как применяются insents и прочее.</summary><content type="html">
  &lt;p id=&quot;Fuxn&quot;&gt;Довольно давно пользуюсь &lt;a href=&quot;https://fbflipper.com/docs/features/plugins/inspector/&quot; target=&quot;_blank&quot;&gt;Flipper Layout Plugin&lt;/a&gt;. Это мощный инструмент, который может отображать семантическое дерево и даже позволяет редактировать параметры View. C ним было удобно найти разного рода баги, проследить какая ViewGroup отвечает за размеры контента, как прокидываются паддинги, как применяются insents и прочее.&lt;/p&gt;
  &lt;p id=&quot;5YcC&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;hpwI&quot;&gt;И вот в один день в мае пришел ко мне коллега, которому я до этого рекомендовал Layout Plugin, с новостями о том, что появился плагин для Compose. Оказалось, что они сделали его еще в прошлом году. Я бросился изучать как именно и что они сделали. В общем не увидел какого-то Rocket Science, а скорее код, который ранее уже видел в исходниках Layout Inspector в Android Studio. Вот например, &lt;a href=&quot;https://github.com/facebook/flipper/blob/main/android/plugins/jetpack-compose/src/main/java/facebook/internal/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt#L85&quot; target=&quot;_blank&quot;&gt;LayoutInspectorThree&lt;/a&gt;, где они вытаскиваю &lt;code&gt;SemanticsNode&lt;/code&gt;. И &lt;a href=&quot;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&quot; target=&quot;_blank&quot;&gt;тот же класс&lt;/a&gt; в коде AS. Или, например, &lt;a href=&quot;https://github.com/facebook/flipper/blob/2b3184f48ad4bd88cc2c706396f36edc6c40fdbf/android/plugins/jetpack-compose/src/main/java/facebook/internal/androidx/compose/ui/inspection/RecompositionHandler.kt#L28&quot; target=&quot;_blank&quot;&gt;RecompositionHandler&lt;/a&gt;, который хукает виртуальную машину ART для подсчета количества рекомпозиций и пропусков. &lt;a href=&quot;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&quot; target=&quot;_blank&quot;&gt;Вот он в исходниках&lt;/a&gt; AS.&lt;/p&gt;
  &lt;p id=&quot;bXgC&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;xT2Q&quot;&gt;В любом случае, все выглядело круто. Даже если код скопирован, есть шансы на то, что этот инструмент гибче и его можно сколько угодно допиливать, так как под рукой все &lt;code&gt;SemanticsNode&lt;/code&gt;, а там можно и все &lt;code&gt;Modifier.Node&lt;/code&gt; вытащить. Поэтому надо пробовать запустить. Но, к сожалению, первая попытка не увенчалась успехом:&lt;/p&gt;
  &lt;figure id=&quot;gyTc&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ef/63/ef6313d2-6f93-4aba-8e2c-a9a80b2e1027.png&quot; width=&quot;1105&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PqcC&quot;&gt;Видимо, часть кода они скопировали, а часть вынесли в виде &lt;code&gt;aar&lt;/code&gt;. Я так понял, что это локально собранная версия &lt;a href=&quot;https://mvnrepository.com/artifact/androidx.inspection/inspection/1.0.0&quot; target=&quot;_blank&quot;&gt;этой библиотеки&lt;/a&gt;, но поновее, так как, если заменить библиотеку механизмами Gradle, то при сборке не найдутся некоторые классы. Я не перый, кто пытался запустить этот плагин, но с тех пор, как его сделали, во Flipper не очень-то и хотели его развивать. Ну так может показаться по &lt;a href=&quot;https://github.com/facebook/flipper/issues/5635&quot; target=&quot;_blank&quot;&gt;issues&lt;/a&gt;, которые висят, и по отсутствию новых коммитов.&lt;/p&gt;
  &lt;p id=&quot;PcZe&quot;&gt;В тот раз остановился на этом месте и мне было лень дальше пытаться запустить его :) Да, надо было просто опубликовать нормальную версию библиотеки inspection или скачать &lt;code&gt;aar&lt;/code&gt; из их репозитория и подключить так. Но, с учетом того, что я пропал на несколько месяцев, времени для этой авантюры не нашлось...&lt;/p&gt;
  &lt;p id=&quot;G0l4&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;njrs&quot;&gt;И вот недавно тестировал еще один инструмент, о котором напишу отдельно, и решил проверить его тут. Опять же, для этого надо было просто опубликовать недостающий &lt;code&gt;aar&lt;/code&gt; и наконец запустить, ибо Layout Inspector уже невозможно терпеть. Честно сказать, был приятно удивлен тому, как это быстро работает и не лагает. &lt;/p&gt;
  &lt;figure id=&quot;PvW7&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/db/41/db4155f0-5ebb-4474-a05a-a954a13c9523.png&quot; width=&quot;3018&quot; /&gt;
    &lt;figcaption&gt;https://github.com/flipperdevices/Flipper-Android-App&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;46vW&quot;&gt;Рекомендую самим попробовать. Держите &lt;a href=&quot;https://github.com/0xera/inspection-lib&quot; target=&quot;_blank&quot;&gt;инструкцию&lt;/a&gt; как подключить плагин и там же пример проекта с ним.&lt;/p&gt;
  &lt;p id=&quot;pepK&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Htq7&quot;&gt;Опубликовано в &lt;a href=&quot;https://t.me/int_ax&quot; target=&quot;_blank&quot;&gt;Полуночные Зарисовки&lt;/a&gt;&lt;/p&gt;

</content></entry></feed>