• úvod
  • témata
  • události
  • tržiště
  • diskuze
  • nástěnka
  • přihlásit
    registrace
    ztracené heslo?
    LWEEKAndroid development
    JOHNY_G
    JOHNY_G --- ---
    LWEEK: Pokud je to FirebaseMessagingService, kde zachytáváš onMessageReceived, tak to je IntentService, a nespouští se jako foreground service (která by normálně jela na main threadu), ale na nově vytvořeném worker threadu mimo hlavní vlákno. Pokud máš kromě ní ještě nějakou skutečnou foreground service (tedy s viditelnou notifikací v liště), jejímž účelem je udržet aplikaci při životě, zatímco dělá něco na pozadí, tak ta nemá přímý vliv na životnost jakékoli jiné služby. Ale může ti držet při životě aplikační context. Takže v praxi pokud máš dejme tomu nějaký usecase singleton v aplikačním scopu, který ti tu blocking práci udělá (stáhne data, uloží do DB), tak ho jen provoláš v onMessageReceived a další život té služby, která ho zavolala, tě nemusí zajímat, dokud zůstane naživu aplikace jako taková. U jednoho REST callu se toho ale v zásadě nemusíš bát, ten by měla přežít služba i sama o sobě :-). Předpokládám ale celou dobu nějaké menší nárazové balíčky dat, jejichž ztráta není pro běh aplikace problematická. Android ti totiž nikdy nedokáže garantovat, že proces nechcípne, zejména při zhasnutém displeji. Pokud jde o synchronizaci nějakých větších databází, tak už je samozřejmě WorkManager na místě. I v něm ale můžeš použít CoroutineWorker, popř. spustit coroutine jako runBlocking v normálním doWork. Worker ti vždycky někdy proběhne, akorát úplně nevíš kdy (i u Immediate/Expedited to mohou být řádově minuty, když je systém v doze mode) :-)). Takže tldr; krátké nárazové a postradatelné operace v Coroutině, kterou si vyrobíš v runtime a provedou se hned, dlouhá kontinuální práce ve WorkerManageru, a provede se až systém uzná za vhodné :-).
    DRIZDIK
    DRIZDIK --- ---
    LWEEK: Tím myslí ukončení procesu, kde WorkManager je persistovatelný, protože parametry k workrequestu předáváš jako serializovatelné Parcelable nejspíš. Takže si je systém uloží a i když vypneš úplně mobil (reboot) tak při dalším startu ti to wrokrequest obnoví, řekne si to workmanagerfactory o workera a vykoná.
    To s prací na pozadí nemá nic společného a pokud si vystačíš s tím, že ti nemusí refresh dat někdy doběhnout, protože servicu může něco zabít, tak bych si život s workmanagerem nekomplikoval a dělal to rovnou z té servicy.
    LWEEK
    LWEEK --- ---
    Respektive ten IO dispatch stejně jako Main dispatch blokuje servise usnout? Já totiž ani nevím na jakém dispatchi jede ta servisa, ale tipl bych si, že foreground servisa pojede na Mainu?
    LWEEK
    LWEEK --- ---
    JOHNY_G: Tzn ta foreground servisa se neuspí když tam běží corutina?
    JOHNY_G
    JOHNY_G --- ---
    LWEEK: Jednorázové asynchronní operace dělej normálně přes coroutines, měli jsme to i na REST cally s pětiminutovým timeoutem bez problémů. Můžeš je i nestovat podle dispatcherů. Třeba IO scope je elastický a vytváří paralelní vlákna pro každou novou coroutine. Main je naopak vázaný na UI thread a coroutines se řadí do fronty. Takže můžeš udělat třeba tohle, a nemusíš se jebat s workerama, kteří slouží úplně jinému účelu :-).

    CoroutineScope(Dispatchers.IO).launch {
        doTheSlowBlockingStuff()
    
        CoroutineScope(Dispatchers.Main).launch {
    	updateViewsOnUIThread()
        }
    }
    LWEEK
    LWEEK --- ---
    DRIZDIK: V dokumentaci jsem se dočetl, že:

    Coroutines are the standard means of leaving the main thread in Kotlin. However, they leave memory once the app closes. For persistent work, use WorkManager.

    Ale je pravda, že nevím jestli tím close myslí jako killnutí apky nebo jenom to že je suspendovaná.

    Můj problém je, že jsem iOS vývojář, takže dost často uvažuju v kontextu iOS a na Androidu to bývá kolikrát dost jinak. Možná bude lepší vysvětlit o co mi jde.

    Mám firebase messaging foreground servisu. Jakmile přijde silent zpráva určitého formátu, tak se mají ze serveru stáhnout nová data a uložit do DB.

    Business logiku mám v repozitáři. Vím, že by tam měl ještě před tím voláním být usecase. Ten samo vyrobím. Takže to co potřebuju je dostat se k tomu usecase (skrze dagger componenty v application) a provolat funkci, která může v extrémním případě chvíli trvat. Volat to přes corutiny mě trochu děsí, protože nevím kolik času ta servisa má než ji systém utne a pokud bych volal asynchronně tak předpokládám, že se apka uspí jakmile projede lifecyklus té servisy.
    DRIZDIK
    DRIZDIK --- ---
    LWEEK: Samozřejmě umí: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
    Co přesně jsi načetl o korutinách, že neběží v pozadí? Můžou běžet klidně i na stejném threadu.
    LWEEK
    LWEEK --- ---
    Možná něco takového? Ale nevím jak moc to je "deadlock" odolné. Ideální by bylo mít možnost ten job cancelnout když se canceluje worker, ale takovou metodu Worker nejspíš nemá.

    class ActivitiesUpdateWorker @Inject constructor(
    ctx: Context,
    params: WorkerParameters,
    private val repository: ActivitiesRepository
    ) : Worker(ctx, params) {

    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.IO + job)

    override fun doWork(): Result {
    scope.launch {
    repository.getAllScheduledActivities(true)
    }

    while (!job.complete()) { continue }

    return Result.success()
    }
    }
    LWEEK
    LWEEK --- ---
    DRIZDIK: No, tahle. Tvůrce týhle aplikace ji napsal kompletně s corutinama. Jestli jsem si načetl problematiku správně, tak Corutiny neumí běžet v pozadí když je apka uspaná. A chápu že nemohu volat ve workeru něco asynchroně, ale přeci nebudu duplikovat model jenom proto abych měl jednu synchronní a jednu asynchronní verzi? Neexistuje možnost volat suspendovanou funkci synchronně?
    DRIZDIK
    DRIZDIK --- ---
    LWEEK: Neděláš snad ve workerovi něco asynchronně? :-)
    DRIZDIK
    DRIZDIK --- ---
    LWEEK: Určitě nechceš injektovat uvnitř a nejlépe injektovat konstruktorem, aby instance vždy byla správně inicializovaná a dobře testovatelná.
    A než to řešit s Daggerem 2, tak by raději refactoroval na Hilt :-D
    LWEEK
    LWEEK --- ---
    Našel jsem si ale článek který zmiňuje použití workera s Daggerem, tak půjdu podle něj. Ale pořád ještě musím zjistit jak vyřešit blamáž s corutinou.
    LWEEK
    LWEEK --- ---
    Dagger 2 .)

    Ještě mě napadlo injektovat uvnitř workeru.

    class ActivitiesUpdateWorker(
    ctx: Context,
    params: WorkerParameters,
    ) : Worker(ctx, params) {

    @Inject
    lateinit var repository: ActivitiesRepository

    override fun doWork(): Result {
    (applicationContext as? App)?.appComponent?.inject(this)

    //repository.getAllScheduledActivities(true)

    return Result.failure()
    }
    }

    Nicméně nevím jestli applicationContext je opravdu instance App a druhá věc je že ty metody v repozitáři jsou psaný pro corutiny. Takže si nejsem ani jistý jak je z workeru zavolat.

    Worker používám protože potřebuju aby to pracovalo když je apka v pozadí.
    DRIZDIK
    DRIZDIK --- ---
    LWEEK: To je samozřejmě dost blbost :-D Protože ti jen použije class z té instance, kterou pak zahodí :-D
    Co používáš jako DI container? Hilt? Koin? Ale v základu je to o vytvoření WorkerFactory, která ti ty workery vytváří a injektuje. Oba nástroje na to mají už hotový tooling, kterým buď anotuješ Workery nebo vytváříš worker factory v Koinu .

    WorkManager | Koin
    https://insert-koin.io/docs/reference/koin-android/workmanager/
    https://developer.android.com/reference/androidx/hilt/work/HiltWorker
    LWEEK
    LWEEK --- ---
    Ahoj, snažím se zjistit jak si injectnout repozitář do Workera. ChatGPT mi navrhnul tohle:

    val myWorker = MyWorker(context, workerParams, myComponent.provideMyModel())
    myComponent.inject(myWorker)

    val workRequest = OneTimeWorkRequest.Builder(myWorker::class.java).build()
    WorkManager.getInstance(context).enqueue(workRequest)

    To mi ale přijde jako blbost. :) Nebo se mýlím? Každopádně ví někdo jak injectovat do Workera?
    LWEEK
    LWEEK --- ---
    DRIZDIK: Já si říkal, že to nemůže být až tak složité. Respektive jsem si říkal proč by to mělo být tak složité a na StackOverflow žádné info. Mělo mě to trknout. :) Díky moc!
    DRIZDIK
    DRIZDIK --- ---
    LWEEK: Ano dostaneš, je to ta stejná instance, kterou budeš mít ve foreground appce. Lifecycle bys měl obsluhovat velmi podobně jako v aktivitě, service může service kdykoliv zabít, stejně jako aktivitu. Doporučuji logovat pomocí Timberu, pokud už nepoužíváš.
    LWEEK
    LWEEK --- ---
    DRIZDIK: To je zvláštní protože mi přišlo, že když jsem logoval ze servisy tak to nešlo do konzole, ale v logcatu to bylo. Takže teoreticky bych si mohl v servise i nějak dostat k Application instanci?
    DRIZDIK
    DRIZDIK --- ---
    LWEEK: Service běží na Androidu defaultně ve stejném procesu a vykonává se na Main Threadu, pokud nedefinuješ explicitně "android:process". Separátní proces běžně není třeba a vystačíš si s multithreadingem/coroutinama, pokud neřešíš problémy s pamětí, GC a možnost přežít crash hlavní appky.
    LWEEK
    LWEEK --- ---
    DRIZDIK: Respektive, asi se blbě ptám. Je nějak možnost olvivnit jestli servisa běží na stejném procesu nebo vlastním? V manifestu to mám takto:
    <service
        android:name=".utils.TMFirebaseMessagingService"
        android:exported="false">
        <!-- // android:directBootAware="true" -->
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>
    Kliknutím sem můžete změnit nastavení reklam