Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Hemî bikarhêner di serîlêdanên mobîl de destpêkirina bilez û UI-ya bersivdar ji bo destûr digirin. Ger serîlêdan demek dirêj dest pê dike, bikarhêner dest bi xemgîn û hêrsbûnê dike. Berî ku ew dest bi karanîna serîlêdanê bike, hûn dikarin bi hêsanî ezmûna xerîdar xera bikin an bikarhêner bi tevahî winda bikin.

Carekê me kifş kir ku sepana Dodo Pizza bi navînî 3 saniye digire, û ji bo hin "bextan" jî 15-20 saniye digire.

Li jêr birîn çîrokek bi dawîhatinek dilxweş heye: li ser mezinbûna databasa Realm, lekeyek bîranînê, me çawa tiştên hêlîn berhev kir, û dûv re xwe kişand hev û her tiştî rast kir.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Çîroka ka çawa jêbirina cascade li Realm dest pê kir
Nivîskarê gotarê: Maxim Kachinkin - Pêşdebirê Android-ê li Dodo Pizza.

Sê çirkeyan ji tikandina li ser îkonê serîlêdanê heya li ser Resume() ya çalakiya yekem bêdawî ye. Û ji bo hin bikarhêneran, dema destpêkirinê gihîşt 15-20 saniyeyan. Ev jî çawa gengaz e?

Kurteyek pir kurt ji bo kesên ku wextê xwendinê tune
Databasa me ya Realm bêdawî mezin bû. Hin tiştên hêlîn nehatin jêbirin, lê bi berdewamî hatin berhev kirin. Dema destpêkirina serîlêdanê gav bi gav zêde bû. Dûv re me ew rast kir, û dema destpêkirinê hat armanc - ew ji 1 çirkeyê kêmtir bû û nema zêde bû. Gotar analîzek rewşê û du çareseriyê dihewîne - yek zû û yek normal.

Lêgerîn û analîzkirina pirsgirêkê

Îro, her serîlêdana mobîl divê zû dest pê bike û bersivdar be. Lê ew ne tenê li ser sepana mobîl e. Tecrûbeya bikarhêner a danûstendina bi karûbar û pargîdaniyek re tiştek tevlihev e. Mînakî, di doza me de, leza radestkirinê yek ji nîşanên sereke yên karûbarê pizza ye. Ger radestkirin bilez be, dê pîzza germ bibe, û xerîdarê ku niha bixwaze bixwe, ne hewce ye ku demek dirêj li bendê bimîne. Ji bo serîlêdanê, di encamê de, girîng e ku meriv hestek karûbarê bilez biafirîne, ji ber ku ger serîlêdan tenê 20 saniye bide destpêkirin, wê hingê hûn ê kengî li benda pîzayê bisekinin?

Di destpêkê de, em bixwe bi vê rastiyê re rû bi rû bûn ku carinan serlêdan çend saniyeyan dest pê kir, û dûv re me dest bi bihîstina giliyên hevkarên din kir ka ew çiqas dirêj girt. Lê me nekarî bi berdewamî vê rewşê dubare bikin.

Çiqas e? Ligor Belgekirina Google, heke destpêkek sar a serîlêdanê kêmtirî 5 çirkeyan bigire, wê hingê ev "wek normal" tête hesibandin. Serlêdana Dodo Pizza Android-ê hate destpêkirin (li gorî pîvanên Firebase _app_destpêk) li destpêka sar bi navînî di 3 hûrdeman de - "Ne mezin, ne tirsnak," wekî ku ew dibêjin.

Lê paşê gilî dest pê kir ku serîlêdanê pir, pir, pir dirêj dest pê kir! Ji bo destpêkê, me biryar da ku em bipîvin ka "pir, pir, pir dirêj" çi ye. Û me ji bo vê yekê şopa Firebase bikar anî Şopa destpêka sepanê.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Ev şopa standard dema di navbera gava ku bikarhêner serîlêdanê vedike û dema ku çalakiya yekem a onResume () tê meşandin, dipîve. Di Konsola Firebase de ji vê metrikê re _app_start tê gotin. Derket holê ku:

  • Demên destpêkirinê ji bo bikarhênerên li jor ji sedî 95-an nêzîkê 20 saniyeyan in (hinek jî dirêjtir), tevî ku dema destpêkirina sar a navîn ji 5 çirkeyan kêmtir be.
  • Dema destpêkirinê ne nirxek domdar e, lê bi demê re mezin dibe. Lê carinan dilop hene. Me ev nimûne dît dema ku me pîvana analîzê 90 rojan zêde kir.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Du fikir hatin bîra min:

  1. Tiştek diherike.
  2. Ev "tiştek" piştî berdanê ji nû ve tê sererast kirin û dûv re dîsa diherike.

"Dibe ku tiştek bi databasê re," me fikirîn, û em rast bûn. Pêşîn, em databasê wekî cache bikar tînin; di dema koçberiyê de em wê paqij dikin. Ya duyemîn, dema ku serîlêdanê dest pê dike databas tê barkirin. Her tişt li hev dikeve.

Di databasa Realm de çi xelet e

Me dest bi kontrolê kir ka naveroka databasê çawa di jiyana serîlêdanê de diguhezîne, ji sazkirina yekem û bêtir di dema karanîna çalak de. Hûn dikarin naveroka databasa Realm bi rê ve bibînin steto an jî bi berfirehî û zelal bi vekirina pelê bi rêya Realm Studio. Ji bo dîtina naveroka databasê bi ADB-ê, pelê databasa Realm kopî bikin:

adb exec-out run-as ${PACKAGE_NAME} cat files/${DB_NAME}

Dema ku di demên cihêreng de li naveroka databasê mêze kir, me dît ku hejmara tiştên cûreyek diyar bi domdarî zêde dibe.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir
Wêne parçeyek Realm Studio ji bo du pelan nîşan dide: li milê çepê - bingeha serîlêdanê çend demek piştî sazkirinê, li rastê - piştî karanîna çalak. Tê dîtin ku hejmara tiştan ImageEntity и MoneyType bi girîngî mezin bûye (dîmenê hejmara tiştên her cûre nîşan dide).

Têkiliya di navbera mezinbûna databasê û dema destpêkirinê de

Mezinbûna databasa bêkontrol pir xirab e. Lê ev çawa bandorê li dema destpêkirina serîlêdanê dike? Pîvandina vê bi navgîniya ActivityManager pir hêsan e. Ji Android 4.4-ê ve, logcat têketinê bi rêzika Nîşankirî û demjimêrê nîşan dide. Ev dem bi navberê ji dema destpêkirina serîlêdanê heya dawiya pêşkêşkirina çalakiyê wekhev e. Di vê demê de bûyerên jêrîn çêdibin:

  • Pêvajoyê dest pê bikin.
  • Destpêkkirina tiştan.
  • Afirandin û destpêkirina çalakiyan.
  • Çêkirina layout.
  • Pêşkêşkirina serîlêdanê.

Li gorî me. Ger hûn ADB-ê bi alayên -S û -W dimeşînin, hûn dikarin bi dema destpêkirinê re hilberek dirêjtir bistînin:

adb shell am start -S -W ru.dodopizza.app/.MainActivity -c android.intent.category.LAUNCHER -a android.intent.action.MAIN

Ger hûn wê ji wir bigirin grep -i WaitTime dem, hûn dikarin berhevkirina vê metrikê otomatîk bikin û bi dîtbarî li encaman binêrin. Grafika jêrîn girêdayîbûna dema destpêkirina serîlêdanê li ser hejmara destpêka sar a serîlêdanê nîşan dide.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Di heman demê de, heman cewherê pêwendiya di navbera mezinahî û mezinbûna databasê de hebû, ku ji 4 MB berbi 15 MB ve bû. Bi tevahî, derdikeve holê ku bi demê re (bi mezinbûna destpêka sar re), hem dema destpêkirina serîlêdanê û hem jî mezinahiya databasê zêde bû. Di destê me de hîpotezek heye. Niha ya ku mabû piştrastkirina girêdayîbûnê bû. Ji ber vê yekê, me biryar da ku "leaks" rakin û bibînin ka ev ê destpêkirinê bilez bike.

Sedemên mezinbûna databasa bêdawî

Berî rakirina "leaks", hêja ye ku meriv fêm bike ka çima ew di rêza yekem de xuya bûn. Ji bo vê yekê, em bînin bîra xwe ku Realm çi ye.

Realm databasek ne-girêdayî ye. Ew dihêle hûn têkiliyên di navbera tiştan de bi rengek mîna çend databasên pêwendiya ORM-ê yên li ser Android-ê têne rave kirin. Di heman demê de, Realm bi hindiktirîn veguhertin û nexşeyan tiştan rasterast di bîranînê de hilîne. Ev dihêle hûn daneyên ji dîskê pir zû bixwînin, ku hêza Realm e û çima jê tê hezkirin.

(Ji bo mebestên vê gotarê, ev ravekirin dê têra me bike. Hûn dikarin li ser Realm bêtir bixwînin belgekirin an di wan de danişga).

Gelek pêşdebiran fêr bûne ku bêtir bi databasên pêwendiyê re bixebitin (mînakî, databasên ORM yên bi SQL di bin hoodê de). Û tiştên wekî jêbirina daneya cascading bi gelemperî wekî diyariyek xuya dikin. Lê ne li Serêkaniyê.

Bi awayê, taybetmendiya jêbirina cascade ji demek dirêj ve tê xwestin. Ev nûxwestin и yekî din, bi wê ve girêdayî, bi awayekî çalak hate nîqaş kirin. Hestek hebû ku ew ê di demek nêzîk de were kirin. Lê paşê her tişt di danasîna girêdanên bihêz û qels de hate wergerandin, ku dê bixweber jî vê pirsgirêkê çareser bike. Di vî karî de pir jîndar û çalak bû daxwaza kişandinê, ku niha ji ber aloziyên navxweyî hatiye rawestandin.

Daneyên bêyî jêbirina kaskadî diherikin

Ger hûn xwe bispêrin jêbirinek kaskad a neheyî bi rastî daneyên çawa diherikin? Ger we hêmanên Realm hêlîn hene, wê hingê divê ew werin jêbirin.
Ka em li mînakek (hema hema) rastîn binêrin. Tiştek me heye CartItemEntity:

@RealmClass
class CartItemEntity(
 @PrimaryKey
 override var id: String? = null,
 ...
 var name: String = "",
 var description: String = "",
 var image: ImageEntity? = null,
 var category: String = MENU_CATEGORY_UNKNOWN_ID,
 var customizationEntity: CustomizationEntity? = null,
 var cartComboProducts: RealmList<CartProductEntity> = RealmList(),
 ...
) : RealmObject()

Hilbera di selikê de qadên cûda hene, tevî wêneyek ImageEntity, malzemeyên xwerû CustomizationEntity. Di heman demê de, hilbera di selikê de dikare bi komek hilberên xwe re tevliheviyek be RealmList (CartProductEntity). Hemî qadên navnîşkirî tiştên Realm in. Ger em babetek nû (copyToRealm() / copyToRealmOrUpdate()) bi heman id-ê têxin nav xwe, wê hingê ev tişt dê bi tevahî were nivîsandin. Lê hemî tiştên hundurîn (wêne, xwerûkirinEntity û cartComboProducts) dê pêwendiya bi dêûbav re winda bikin û di databasê de bimînin.

Ji ber ku pêwendiya bi wan re winda dibe, em êdî wan naxwînin an jê nabin (heta ku em bi eşkere xwe bigihînin wan an tevahiya "maseyê" paqij nekin). Me ji vê re digot "leaksên bîranînê".

Dema ku em bi Realm re dixebitin, divê em bi eşkereyî hemî hêmanan derbas bikin û berî operasyonên weha her tiştî bi eşkere jêbirin. Ev dikare were kirin, bo nimûne, bi vî rengî:

val entity = realm.where(CartItemEntity::class.java).equalTo("id", id).findFirst()
if (first != null) {
 deleteFromRealm(first.image)
 deleteFromRealm(first.customizationEntity)
 for(cartProductEntity in first.cartComboProducts) {
   deleteFromRealm(cartProductEntity)
 }
 first.deleteFromRealm()
}
// и потом уже сохраняем

Ger hûn vê yekê bikin, wê hingê her tişt dê wekî ku divê bixebite. Di vê nimûneyê de, em texmîn dikin ku di hundurê wêneyê, Kesayetiya xwerû û cartComboProducts de tiştên din ên hêlîn ên Realm tune ne, ji ber vê yekê lûleyên hêlîn û jêbirinên din tune.

Çareseriya bilez

Yekem tiştê ku me biryar da ku em bikin ev bû ku tiştên ku zû mezin dibin paqij bikin û encaman kontrol bikin da ku bibînin ka ev ê pirsgirêka meya bingehîn çareser bike. Pêşîn, çareseriya herî hêsan û herî berbiçav hate çêkirin, ev e: divê her tişt ji rakirina zarokên xwe berpirsiyar be. Ji bo kirina vê yekê, me navgînek destnîşan kir ku navnîşek tiştên xwe yên hêlîn ên Realm vedigerîne:

interface NestedEntityAware {
 fun getNestedEntities(): Collection<RealmObject?>
}

Û me ew di hêmanên Realm-a xwe de bicîh kir:

@RealmClass
class DataPizzeriaEntity(
 @PrimaryKey
 var id: String? = null,
 var name: String? = null,
 var coordinates: CoordinatesEntity? = null,
 var deliverySchedule: ScheduleEntity? = null,
 var restaurantSchedule: ScheduleEntity? = null,
 ...
) : RealmObject(), NestedEntityAware {

 override fun getNestedEntities(): Collection<RealmObject?> {
   return listOf(
       coordinates,
       deliverySchedule,
       restaurantSchedule
   )
 }
}

В getNestedEntities em hemû zarokan wek lîsteyek dagirtî vedigerînin. Û her tiştê zarokê jî dikare pêwendiya NestedEntityAware bicîh bîne, û destnîşan dike ku ew tiştên hundurîn ên Realm hene ku jêbirin, mînakî ScheduleEntity:

@RealmClass
class ScheduleEntity(
 var monday: DayOfWeekEntity? = null,
 var tuesday: DayOfWeekEntity? = null,
 var wednesday: DayOfWeekEntity? = null,
 var thursday: DayOfWeekEntity? = null,
 var friday: DayOfWeekEntity? = null,
 var saturday: DayOfWeekEntity? = null,
 var sunday: DayOfWeekEntity? = null
) : RealmObject(), NestedEntityAware {

 override fun getNestedEntities(): Collection<RealmObject?> {
   return listOf(
       monday, tuesday, wednesday, thursday, friday, saturday, sunday
   )
 }
}

Û bi vî awayî, hêlîna tiştan dikare dubare bibe.

Dûv re em rêbazek dinivîsin ku bi rengek vegerî hemî tiştên hêlîn jêbirin. Rêbaz (wek dirêjkirinê hatî çêkirin) deleteAllNestedEntities hemî tişt û rêbaza asta jorîn digire deleteNestedRecursively Bi navbeynkariya NestedEntityAware bi vegerî hemî tiştên hêlînkirî radike:

fun <T> Realm.deleteAllNestedEntities(entities: Collection<T>,
 entityClass: Class<out RealmObject>,
 idMapper: (T) -> String,
 idFieldName : String = "id"
 ) {

 val existedObjects = where(entityClass)
     .`in`(idFieldName, entities.map(idMapper).toTypedArray())
     .findAll()

 deleteNestedRecursively(existedObjects)
}

private fun Realm.deleteNestedRecursively(entities: Collection<RealmObject?>) {
 for(entity in entities) {
   entity?.let { realmObject ->
     if (realmObject is NestedEntityAware) {
       deleteNestedRecursively((realmObject as NestedEntityAware).getNestedEntities())
     }
     realmObject.deleteFromRealm()
   }
 }
}

Me ev yek bi tiştên ku zûtirîn mezin dibin kir û kontrol kir ka çi qewimî.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Di encamê de, ew tiştên ku me bi vê çareseriyê veşartibûn, mezinbûna xwe rawestand. Û mezinbûna giştî ya bingehê hêdî bû, lê nesekinî.

Çareseriya "normal".

Her çend bingeh hêdî hêdî dest pê kir, lê dîsa jî mezin bû. Ji ber vê yekê me dest bi lêgerîna bêtir kir. Projeya me di Realm de cachkirina daneyê pir çalak bikar tîne. Ji ber vê yekê, nivîsandina hemî tiştên hêlînkirî ji bo her tiştê kedkar e, ji bilî vê metirsiya xeletiyan zêde dibe, ji ber ku hûn dikarin dema ku kodê biguhezînin tiştan diyar bikin.

Min dixwest ez piştrast bikim ku min navbeynkaran bikar neaniye, lê her tişt bi serê xwe dixebitî.

Dema ku em dixwazin tiştek bi serê xwe bixebite, divê em refleksê bikar bînin. Ji bo vê yekê, em dikarin her qada polê derbas bikin û kontrol bikin ka ew tiştek Realm e an navnîşek tiştan e:

RealmModel::class.java.isAssignableFrom(field.type)

RealmList::class.java.isAssignableFrom(field.type)

Ger qad RealmModel an RealmList be, wê hingê tişta vê qadê li navnîşek tiştên hêlînkirî zêde bikin. Her tişt tam eynî wekî me li jor kir, tenê li vir ew ê bi serê xwe were kirin. Rêbaza jêbirina cascade bixwe pir hêsan e û wiha xuya dike:

fun <T : Any> Realm.cascadeDelete(entities: Collection<T?>) {
 if(entities.isEmpty()) {
   return
 }

 entities.filterNotNull().let { notNullEntities ->
   notNullEntities
       .filterRealmObject()
       .flatMap { realmObject -> getNestedRealmObjects(realmObject) }
       .also { realmObjects -> cascadeDelete(realmObjects) }

   notNullEntities
       .forEach { entity ->
         if((entity is RealmObject) && entity.isValid) {
           entity.deleteFromRealm()
         }
       }
 }
}

Dirêjkirinî filterRealmObject tenê tiştên Realm fîlter dike û derbas dike. Awa getNestedRealmObjects bi riya refleksê, ew hemî tiştên Realm hêlînkirî dibîne û wan dixe nav navnîşek rêzik. Dûv re em heman tiştî bi paşvekişandinê dikin. Dema ku jêbirin, hûn hewce ne ku ji bo rastdariya objektê kontrol bikin isValid, ji ber ku dibe ku tiştên dêûbav ên cihêreng dikarin yên wekî hev hêlîn bin. Çêtir e ku meriv ji vê yekê dûr bisekine û dema ku tiştên nû biafirîne bi tenê hilberîna xweser a id-ê bikar bîne.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Pêkanîna tevahî ya rêbaza getNestedRealmObjects

private fun getNestedRealmObjects(realmObject: RealmObject) : List<RealmObject> {
 val nestedObjects = mutableListOf<RealmObject>()
 val fields = realmObject.javaClass.superclass.declaredFields

// Проверяем каждое поле, не является ли оно RealmModel или списком RealmList
 fields.forEach { field ->
   when {
     RealmModel::class.java.isAssignableFrom(field.type) -> {
       try {
         val child = getChildObjectByField(realmObject, field)
         child?.let {
           if (isInstanceOfRealmObject(it)) {
             nestedObjects.add(child as RealmObject)
           }
         }
       } catch (e: Exception) { ... }
     }

     RealmList::class.java.isAssignableFrom(field.type) -> {
       try {
         val childList = getChildObjectByField(realmObject, field)
         childList?.let { list ->
           (list as RealmList<*>).forEach {
             if (isInstanceOfRealmObject(it)) {
               nestedObjects.add(it as RealmObject)
             }
           }
         }
       } catch (e: Exception) { ... }
     }
   }
 }

 return nestedObjects
}

private fun getChildObjectByField(realmObject: RealmObject, field: Field): Any? {
 val methodName = "get${field.name.capitalize()}"
 val method = realmObject.javaClass.getMethod(methodName)
 return method.invoke(realmObject)
}

Wekî encamek, di koda muwekîlê xwe de em ji bo her operasyona guheztina daneyê "hilweşîna cascading" bikar tînin. Mînakî, ji bo operasyonek têxê ev xuya dike:

override fun <T : Entity> insert(
 entityInformation: EntityInformation,
 entities: Collection<T>): Collection<T> = entities.apply {
 realmInstance.cascadeDelete(getManagedEntities(entityInformation, this))
 realmInstance.copyFromRealm(
     realmInstance
         .copyToRealmOrUpdate(this.map { entity -> entity as RealmModel }
 ))
}

Method yekem getManagedEntities hemî tiştên lêzêdekirî, û paşê rêbazê distîne cascadeDelete Berî ku yên nû binivîsîne, bi paşverû hemî tiştên berhevkirî jêdibe. Em di dawiya serîlêdanê de vê rêbazê bikar tînin. Lezên bîranînê li Realm bi tevahî winda bûne. Piştî ku heman pîvandina girêdayîbûna dema destpêkirinê ya li ser hejmara destpêka sar a serîlêdanê pêk anî, em encamê dibînin.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Xeta kesk girêdayîbûna dema destpêkirina serîlêdanê bi hejmara destpêkirinên sar re di dema jêbirina kaskada otomatîkî ya tiştên hêlîn de nîşan dide.

Encam û encam

Databasa Realm-a her ku diçe mezin dibe sedem ku serîlêdan pir hêdî dest pê bike. Me nûvekirinek bi "jêbirina cascading" ya tiştên hêlînkirî derxist. Naha em çavdêrî û dinirxînin ka biryara me çawa bandor li dema destpêkirina serîlêdanê kir bi navgîniya metrika _app_start.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Ji bo analîzê, em demek ji 90 rojan digirin û dibînin: dema destpêkirina serîlêdanê, hem navîn û hem jî ya ku dikeve ser sedî 95-ê bikarhêneran, dest bi kêmbûnê kir û nema zêde dibe.

Çîroka ka çawa jêbirina cascade li Realm dest pê kir

Ger hûn li nexşeya heft-rojî mêze bikin, metrîka _app_start bi tevahî têr xuya dike û ji 1 saniyeyê kêmtir e.

Di heman demê de hêja ye ku were zêdekirin ku ji hêla xwerû ve, Firebase agahdariyan dişîne ger nirxa navîn ya _app_start ji 5 çirkeyan derbas bibe. Lêbelê, wekî ku em dibînin, divê hûn li ser vê yekê nesekinin, lê ji ber vê yekê biçin hundur û wê bi eşkere kontrol bikin.

Tişta taybetî di derbarê databasa Realm de ev e ku ew databasek ne-girêdayî ye. Tevî karanîna wê hêsan, wekheviya çareseriyên ORM û girêdana objektê, jêbirina cascade tune.

Ger ev yek neyê hesibandin, wê hingê tiştên hêlînkirî dê kom bibin û "biherikin". Database dê bi domdarî mezin bibe, ku di encamê de dê bandorê li hêdîbûn an destpêkirina serîlêdanê bike.

Min serpêhatiya xwe parve kir ka meriv çawa zû jêbirina kaskad a tiştan li Realm, ya ku hîn ji qutîkê dernakeve, lê demek dirêj li ser tê axaftin parve kir. gotin и gotin. Di rewşa me de, vê yekê dema destpêkirina serîlêdanê pir bilez kir.

Tevî nîqaşa li ser xuyabûna nêzîk a vê taybetmendiyê, nebûna jêbirina cascade li Realm ji hêla sêwiranê ve tête kirin. Ger hûn serîlêdanek nû dîzayn dikin, wê hingê vê yekê hesab bikin. Û heke hûn jixwe Realm bikar tînin, kontrol bikin ka we pirsgirêkên weha hene.

Source: www.habr.com

Add a comment