
В этой статье я собираюсь показать вам Android фичу, представленную в 2012 году, и попробую написать для неё UI на Compose.
Изучая недра Android, я наткнулся на один Service, который привлек моё внимание. Класс, который я обнаружил, не только заинтриговал меня своим названием, но и снова удивил интересными возможностями, скрытыми внутри Android.
Ещё в 2012 году команда Android System UI представила в Android 4.2 новую функцию Daydream, которая позволяет показывать контент пользователю, пока телефон находится в спящем режиме. Вы можете просто отрисовать любой UI или анимацию для пользователя, пока его телефон заряжается. UI-элементы также могут содержать кнопки, так что в целом можно создать полноценный экран для продакшена. К сожалению, эту функцию нельзя включить автоматически. Пользователь должен включить её в настройках дисплея.
Архитектура этой функции похожа на добавление обычного Service. Сначала нужно объявить новый компонент в Manifest:
<service android:name=".MyDreamService" android:exported="true" android:description="@string/dream_service_description" android:icon="@drawable/dream_service_icon" android:label="@string/dream_service_label" android:permission="android.permission.BIND_DREAM_SERVICE"> <intent-filter> <action android:name="android.service.dreams.DreamService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
Простой пример кода из официальной документации:
class MyDreamService : DreamService() { override fun onAttachedToWindow() { super.onAttachedToWindow() isInteractive = false; isFullscreen = true; setContentView(R.layout.dream_service_layout); } }
После этого вы можете увидеть ваш daydream в действии

Затем у меня возник вопрос — могу ли я создать UI на Compose? Давайте попробуем реализовать это.
Поскольку setContentView принимает только View, нам нужно создать ComposeView:
val composeView = ComposeView(this).apply { setContent { DreamServiceTheme { DreamScreen( onOpenSettings = { openSettingsAndExitDream() } ) } } } setContentView(composeView)
А вот тут начинается самое интересное. Android Service на самом деле не работает хорошо с View, и по этой причине у Service отсутствуют некоторые важные для нас API. DreamService не реализует:
ViewTreeLifecycleOwner
SavedStateRegistryOwner
Чтобы решить эту проблему, мы можем расширить наш DreamService, реализовав SavedStateRegistryOwner:
class MyDreamService : DreamService(), SavedStateRegistryOwner
После этого необходимо реализовать lifecycle и savedStateRegistry:
class MyDreamService : DreamService(), SavedStateRegistryOwner { private val lifecycleRegistry = LifecycleRegistry(this) private val savedStateRegistryController = SavedStateRegistryController.create(this) override val lifecycle: Lifecycle get() = lifecycleRegistry override val savedStateRegistry: SavedStateRegistry get() = savedStateRegistryController.savedStateRegistry // ... }
Эти два свойства можно вызывать в каждом колбэке DreamService, чтобы lifecycle был корректно синхронизирован с Compose View. После этого мы можем установить owner и registry для вашего ComposeView и UI заработает с Compose:
val composeView = ComposeView(this).apply { setViewTreeLifecycleOwner(this@MyDreamService) setViewTreeSavedStateRegistryOwner(this@MyDreamService) // ... }
Надеюсь, эта фича обогатит ваш ответ на техническом интервью, ведь теперь вы знаете, что Activity — не единственный компонент, который может представлять UI.
