Pull to refresh
7
0
Андрей Мычка @mychka

User

Send message

Я согласен, что использование Binder выглядит кривовато, искусственно. Но в принципе Binder используется вполне по назначению. Из API к IBinder:


These semantics allow IBinder/Binder objects to be used as a unique identity (to serve as a token or for other purposes) that can be managed across processes.

То, что не передаю Binder в другие процессы — моё дело: хочу — передаю, не хочу — непередаю ) Если вас смущает наследование от Binder, то в этом не вижу ничего плохого, но можно и не наследоваться, а использовать java.util.WeakHashMap.

Как-то не конструктивно с Вашей стороны.

Ой, наверное, меня можно было неправильно понять.


Ранее была такая проблема, но новые инструменты радуют

Под "хромающим правильнописанием" имею ввиду, что, наверное, Андроид — и Jetpack в частности — хорошие. Но я навскидку привёл реальную проблему, типичный юзкейс с навигацией opportunity, для которой хорошего решения не существует. Как оно семь лет назад хромало, так и продолжает хромать. Конечно, наука развивается, работать становится интереснее и приятнее. Но количество дурацких решений в архитектуре Андроида по-прежнему велико.
Моё мнение, что-то у них там в Андроидном подразделении Гугла фундаментально сломано. Как в старом анекдоте: "Всю систему надо менять!" Пока команду не сменят, так и будут лажать. Видимо, не справляются текущие архитекторы.

вполне воспроизводится

Если найдёте способ воспроизвести, это может оказаться очень интересным, иметь мировую значимость.

Да, всё верно. Но как выяснилось, без включённой опции "Don't keep activities" воспроизвести аналогичный сценарий (чтобы вызвался onDestroy()) не удастся.

Тогда сделайте главный экран частью графа и переходите оттуда- для Вас есть Nested navigation graphs.

Конечно, мы и обсуждаем nested navigation graphs. Главный экран никак не может быть частью opportunity графа.


Как видите, решения есть.

Почему-то вспомнился Винни-Пух: "У меня правильнописание хромает. Оно хорошее, но почему-то хромает." )


не хочу тратить на это время

Спасибо вам большое за обсуждение! Надеюсь, оно окажется полезным техническому сообществу.

передать в долгоживущую часть приложения (или даже часть системы) сильную ссылку на объект

Это да, поначалу реально возникает такая проблема, особенно если пытаться передавать всякие Runnable, коллбэки (неявно держащие, например, this на активность или фрагмент), таски, запущенные потоки. With great power comes great responsibility )
Но со временем народ осваивается, вырабатываются паттерны, и проблема теряет актуальность.

Зачастую отдельная обёртка не нужна, имеются естественные, готовые классы для передачи. Например, из AccountsFragment в AccountFragment можно передать класс Account, который у нас уже живёт во ViewModel.
Но если понадобилась обёртка, то всё равно вместо


<fragment . . . >
    <argument
        android:name="fieldA"
        app:argType="integer" />
    <argument
        android:name="fieldB"
        app:argType="string" />
    <argument
        android:name="fieldC"
        app:argType="boolean" />
</fragment>

лучше писать


class BbbFragmentArgs(val fieldA: Int, val fieldB: String, val fieldC: Boolean)

Kotlin DSL лаконичнее, и такой подход естественнее, проще, более type safe.
В принципе я не против кодогенерации, скорее за. Но конкретно в Navigation Safe Args она не имеет смысла. Возможно, когда-нибудь с развитием функционала Navigation смысл появится.

OpportunitiesFragment не должен быть в стеке, при нажатии на Back должны возвращаться обратно на главный экран, откуда перешли на opportunity. Но это не принципиально, можно удалять OpportunitiesFragment из стека вручную.
Но это вариация на тему искусственного routing фрагмента, который не предлагать ) OpportunitiesFragment не должен заниматься маршрутизацией, это вообще задача не View, а ViewModel.


В первой приведённой вами ссылке (на Stack Overflow) предлагается недоделанная реализация варианта с наследованием от NavDestination.


Вторая ссылка не имеет отношения к обсуждению. Вопрос в том, как сгруппировать фрагменты, относящиеся к opportunity, но при этом иметь возможность открывать не только startDestination. Конечно, можно убрать группировку, вынести все фрагменты в корень, получится куча разрозненных фрагментов, о чём я писал выше:


В итоге всё приходит к несвязанным <fragment>
новые инструменты радуют

Недавний пример из реальной жизни. Есть экран OpportunitiesFragment, представляющий собой список. Кликаем по элементу — проваливаемся в экран конкретного "opportunity": AddMobilePhoneFragment, SetupMedicineCabinetFragment, PrescriptionSavingsFragment и т. п.
Выносим это в отдельный граф, всё чудесно:


<navigation
    app:startDestination="@id/opportunities_fragment"
    . . . >

    <fragment
        android:id="@+id/opportunities_fragment"
        . . . />

    <fragment
        android:id="@+id/add_mobile_phone_fragment"
        . . . />

    <fragment
        android:id="@+id/setup_medicine_cabinet_fragment"
        . . . />

    <fragment
        android:id="@+id/prescription_savings_fragment"
        . . . />

    . . .
</navigation>

Затем возникает бизнес-требование попадать на некоторые opportunity сразу из главного экрана, минуя opportunities_fragment. Подскажите способ, как это можно красиво сделать, и я перестану ругаться на Андроид )
Делать наследника от NavDestination, который, в отличие от androidx.navigation.NavGraph, умеет роутить не только на фиксированный (app:startDestination) фрагмент, а более гибко, или вводить для app:startDestination искусственный "routing" фрагмент — не предлагать: это работает, но не характеризует Андроид с хорошей стороны.

такая реализация не переживет перезапуск

Да, не переживёт. Речь и идёт о возможностях, открывающихся, если отказываемся от восстановления состояния после рестарта процесса.


Про утечки не очень понял, вроде бы ничего не утекает.

Вы показали сторону вызова, а посмотрите на принимающую сторону

На принимающей стороне тоже выглядит красивее, если не используем Safe Args: вместо ненужной обёртки сразу инжектим переданный аргумент. Пример с defaultArg() есть в статье.


class AaaFragment : Fragment() {
    private val myArg: Int by defaultArg()
}
YouTube проверил- перезапускает с сохранением

Откройте, например, экран "History" (история просмотров), сверните и убейте процесс:


adb shell am kill com.google.android.youtube
adb shell "ps -A | grep youtube"

Открывается главный экран, а не "History".

специально для этого придумали Safe Args

Если гуглокодеры предлагают инструмент для решения некой задачи, то это не значит, что необходимо использовать именно этот инструмент. Особенно учитывая низкое качество создаваемых инструментов и вообще принимаемых решений (что касается Андроида).


У меня не было возможности плотно поработать с Jetpack Navigation, поэтому не претендую на авторитетное мнение. Идея соединять фрагменты стрелочками интересная, но боюсь, что в реальных проектах от <action>-ов мало проку из-за, как часто бывает в Андроиде, недоделанности и недопродуманности, в частности из-за ограниченности androidx.navigation.NavGraph. В итоге всё приходит к несвязанным <fragment>.


В Safe Args смысла не вижу. Возьмём пример из документации (https://developer.android.com/guide/navigation/navigation-pass-data).


<fragment
    android:id="@+id/bbbFragment"
    . . . >
    <argument
        android:name="myArg"
        app:argType="integer"/>
</fragment>

findNavController(R.id.nav_host_fragment).navigate(
    R.id.bbbFragment, BbbFragmentArgs(17).toBundle()
)

Вместо возни с XML и получения нетипизированного кода (ничто не мешает вместо BbbFragmentArgs передать СссFragmentArgs) лучше в BbbFragment добавить


fun NavController.navigateToBbb(myArg: Int) {
    this.navigate(R.id.bbbFragment, myArg.bundle)
}

И вызывать


findNavController(R.id.nav_host_fragment).navigate(17)

Аналогично с <action>. Вместо


<fragment ...>
    <action android:id="@+id/startBbbFragment"
            app:destination="@+id/bbbFragment"
            . . . >
        <argument
                android:name="myArg"
                app:argType="integer"/>
    </action>
</fragment>

findNavController(R.id.nav_host_fragment).navigate(
    AaaFragmentDirections.startBbbFragment(17)
)

делаем


fun NavController.navigateToBbb(myArg: Int) {
    this.navigate(AaaFragmentDirections.startBbbFragment().actionId, myArg.bundle)
}

findNavController(R.id.nav_host_fragment).navigate(17)

студия для большинства типов генерирует реализацию Parcelable

По поводу потенциала ) Обнаружил в закромах интерфейс, который делает любой класс Parcelable-ом.


/**
 * Add this marker interface to make any class parcelable.
 */
interface ReferenceParcelable : Parcelable {

    override fun writeToParcel(dest: Parcel, flags: Int) {
        dest.writeStrongBinder(BinderReference(this))
    }

    override fun describeContents(): Int = 0

    companion object {
        @Suppress("unused")
        @JvmField
        val CREATOR = object : Parcelable.Creator<Any> {
            override fun createFromParcel(parcel: Parcel): Any? {
                return (parcel.readStrongBinder() as BinderReference<*>).value
            }

            override fun newArray(size: Int): Array<Any?> {
                return arrayOfNulls(size)
            }
        }
    }
}
Это когда?

Возможно, не очень удачно выразился, поправил в статье. "Раньше" — это в случае, если не восстанавливаем состояние после рестарта процесса.

использованная Вами архитектура (или ее реализация) не удовлетворяет требованиям современных приложений

Спасибо за идею. Добавил в статью:


Для интереса проверил несколько приложений. На 02.03.2020 прозрачно обрабатывают смерть процесса: Facebook, Twitter, WhatsApp, Chrome, Gmail, Yandex.Maps. Перезапускают приложение: Yandex.Navigator, YouTube, мобильный банкинг от Сбербанка и от Альфа-Банка, Skype.

Согласен, если речь идёт о небольшом личном проекте ради развлечения, самообразования, портфолио.


Было бы интересно обсудить выбор наёмного архитектора/девлида, когда перед ним стоит задача разработать основу крупного коммерческого приложения. И выбор небольшой команды ИПшников, начинающей разработку очередной игры с целью заработать денег. В этих случаях рассуждают другими категориями: выгоды, целесообразности.

Похоже, что всё так и есть, большое спасибо за комментарий! Добавил обновление в статью.

Думаю, что вы правы. Большое спасибо, что открыли мне глаза ) Добавил обновление в статью.

Да, конечно.
Чтобы это сэмулировать, в Developer Options есть опция "Don't keep activities".

"The system never kills an activity" — означает, что активность не может просто так без предупреждения исчезнуть, у активности обязательно будут вызваны коллбэки onStop(), onDestroy() (хотя на практике в некоторых случаях onDestroy() не вызывается).
Активность, если не находится внутри [onStart(), onStop()], может быть в любой момент уничтожена системой. В этом случае, если это не configuration change, данные внутри androidx.lifecycle.ViewModel будет потеряны.
Идея с MVVM хорошая, но для гугловой реализации ViewModel сложно найти применение. Поэтому меня удивляет поднятый вокруг этого класса хайп, куча примеров в сети. Вспоминается песня Тараканов "Кто-то из нас двоих" )

1

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity