Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Omnes usores utentes launch et responsivam UI celeriter in applicationibus mobilibus pro concesso accipiunt. Si applicationis longum tempus immittendi, user incipit contristari et irasci. Lorem experientiam facile corrumpere potes vel usorem omnino amittere etiam antequam applicationis usus incipit.

Semel invenimus Dodo Pizza app 3 secundas res in medium deducere, et aliquot "felix" secundas 15-20 capit.

Infra rescissa fabula est cum felici fine: de incremento Regni datorum, de memoria Leak, quomodo res congessimus nidos, et nos simul trahimus et omnia fixa.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?
Articulus auctor: Maximus Kachinkin β€” Elit Android ad Dodo Pizza.

Tres secundae strepitando in applicatione iconis ad Resume() actionis primae infinitum est. Et aliquot utentes, initium temporis pervenit 15-20 secundis. Quomodo hoc vel fieri potest?

Brevissima summa eorum qui non habent tempus legendi
Regnum nostrum database sine fine crevit. Quaedam objecta nidificata non deleta sunt, sed constanter congesta sunt. Applicatio satus temporis paulatim aucta est. Tunc illud destinavimus, et initium temporis ad scopum venimus, secunda minus quam 1 facta, nec amplius aucta sunt. Articulus analysin continet condicionem et duas solutiones, unam vivam et unam normalem.

Exquire et analysis quaestionis

Hodie, quaelibet applicatio mobilis cito mittere debet et respondeat. Sed non solum de app mobili. Usus usoris commercii cum servitio et societate res implicata est. Exempli causa, in casu nostro, celeritas partus unum e clavis indicibus ad ministerium pizza. Si traditio ieiunium est, pizza calida erit, et emptorem qui nunc edere vult, diu non exspectandum erit. Applicatio, vicissim, refert ad affectum festinationis officium, quia si applicationis tantum XX secundis ad deducendum accipit, quousque expectandum erit pizza?

Primum nos ipsi adversus hoc quod interdum applicatione duobus secundis ad deducendum sumpsit, et tunc de aliis collegis querelis audire coepimus quousque tulit. Sed constanter hanc condicionem repetere non potuimus.

Quam diu est? Secundum Documenta Googlesi frigidum initium applicationis minus quam 5 secundis accipit, hoc "sicut normale" consideratur. Dodo Pizza Android app launched (secundum Firebase metrics _app_start) at* frigus satus in mediocris in 3 secundis - "Non magnus, non terribilis," ut aiunt.

Sed tunc querelae apparere coeperunt quod applicationis valde sumpsit, ipsum, ipsum diu launch! In primis, quid "ipsum, valde, diutissime" metiri decrevimus. Et vestigia Firebase huius usi sumus Satus vestigium app.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Haec norma vestigium mensurat tempus inter momentum utentis applicationem aperit et momentum in Resume primae actionis effectum est. In Firebase Console haec metrica appellatur _app_start. Contigit hoc:

  • Satumi tempora utentes supra 95th cent, fere XX secundis sunt (quaedam etiam longiora), licet mediana frigoris initium temporis minus quam 20 secundis esse.
  • Tempus startup valorem constantem non est, sed in tempore crescit. Sed interdum guttae sunt. Hanc formam invenimus cum scalam analyseos ad 90 dies auximus.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Duae cogitationes in mentem venerunt;

  1. Aliquid portasse.
  2. Hoc "aliquid" est reset post remissionem et deinde effluo iterum.

"Verisimile aliquid cum datorum" putavimus, et recte fuimus. In primis utimur ut cache database, per migrationem purgamus. Secundo oneratur datorum applicatio cum incipit. Omnia convenit in unum.

Quod mali regnum database

Incipere incepimus quomodo contenta mutationis datorum super vitam applicationis, a prima institutione et ulteriore in usu activo. Potes videre contenta regni database via stetho aut planius et apertius aperiendo tabella via Regnum Studio. Ut videre contenta datorum via ADB, effingo Regnum database lima:

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

Respectis contentorum datorum diversis temporibus, numerum objectorum cuiusdam generis constanter augere invenimus.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?
Pictura exhibet fragmentum Regni Studio duorum imaginum: in sinistro - basis applicationis aliquanto post institutionem, in dextro - post usum activum. Ex his videri potest quod numerus rerum ImageEntity ΠΈ MoneyType increvit signanter (promptura ostendit numerum obiecti cuiusque generis).

Relatio inter database incrementum et satus tempus

Intemperantia database incrementum valde malum est. Sed quomodo hoc tempus afficit applicationis satus? Hoc satis facile est per ActivityManager metiri. Cum Android 4.4, logcat trabem cum chorda ostentat et tempus ostendit. Hoc tempus aequale est intervallo, ex quo applicatio educitur usque ad finem actionis reddentis. Hoc tempore sequuntur eventa.

  • Incipit processus.
  • Initialization obiecti.
  • Creatio et initialisatio actionum.
  • Cras at arcu.
  • Applicatio reddendi.

Decet nos. Si ADB cum vexillis -S et -W curris, output cum initio temporis protractum accipere potes:

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

Si capto inde grep -i WaitTime tempus, collectionem huius metricae et visibiliter eventus intueri potes. Aliquam lacinia purus infra ostendit dependentiam applicationis startup temporis in numero frigorum principiorum applicationis.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Eodem tempore, eadem natura relationis inter magnitudinem et incrementum datorum, quae crevit ab 4 MB ad 15 MB. In summa, evenit ut supra tempus (cum incremento frigoris incipit) tum applicationis tempus immittendi tum amplitudo datorum augeatur. Hypothesin in manibus habemus. Omnia autem reliqua erant ad confirmandam. Ideo "pinum" removere decrevimus et vide an hoc ad deducendum acceleraretur.

Causas infinitas database incrementum

Priusquam "perfluat", intelligere valet quare primo loco apparuerunt. Hoc facere meminerimus quid sit Regnum.

Regnum datorum non relativorum est. Permittit tibi relationes inter res simili modo describere quot ORM databases relativae in Android describuntur. Eodem tempore, Regnum refert directe in memoria cum minimum mutationum et mappings quantitatem. Hoc permittit tibi celerrime legere notitias orbis, quae vis regni est et cur amatur.

(Ad proposita huius articuli, haec descriptio nobis satis erit. Plura de Regno in frigido legere potes documentum aut in academiae).

Multi tincidunt solent magis cum databases relationibus operari (exempli gratia ORM databases cum SQL sub cucullo). Et omnia sicut lapsus elata deletio saepe quasi data videntur. In at tellus neque.

Viam deletionum cascades diu quaesitum est. Hoc revision ΠΈ aliusadiuncto, agitabatur. Animus erat id propediem faciendum. Sed tunc omnia translata sunt in nexus firmos et debiles introductos, qui etiam sponte hoc problema solvere debent. Satis vivida et efficax ad hoc negotium erat viverra petitionemquae ex difficultatibus internis pro nunc morata est.

Data Leak sine lapsus deletionem

Quam exacte notitia fatiscit, si in non existentibus lapsus delere nitis? Si res regni nidos, tunc deleri debent.
Intueamur exemplum verum (fere) verum. Habemus objectum 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()

Productum in plaustro diversos agros habet, picturam comprehendens ImageEntity, nativus ingredientia CustomizationEntity. Item, productum in plaustro potest esse combo cum suo statuto productorum RealmList (CartProductEntity). Omnes agri enumerantur res regni sunt. Si novum obiectum (copyToRealm() / copyToRealmOrUpdate()) cum eodem id inseramus, hoc obiectum omnino overscriptum erit. Sed omnia interna (imago, customizationEntity et cartComboProducts) nexum cum parente amittent et in database manent.

Cum nexus cum illis amittitur, non iam eas legimus vel delemus (nisi ad eos explicite accedimus vel totam "mensam" purgamus. Hanc "memoriam libero" appellavimus.

Cum opus cum Regno, per omnia elementa explicite ire debemus et ante huiusmodi operationes omnia explicite delere. Qui fit, verbi gratia, sic;

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()
}
// ΠΈ ΠΏΠΎΡ‚ΠΎΠΌ ΡƒΠΆΠ΅ сохраняСм

Si hoc feceris, omnia ut decet operabuntur. In hoc exemplo ponimus quod nullae aliae res in imagine, customizationEntity, et cartComboProducts nidos sunt obiecta, ideo nullae aliae sunt ansulae nidificatae et deletae.

"Vox" solutio

Primum hoc facere decrevimus ut res quam celerrime crescentes mundare et eventus deprime videre an hoc problema originale solveret. Primum solutio simplicissima et intuitiva facta est, scilicet: unumquodque negotium debet liberos removere. Ad hoc efficiendum interfaciendum introduximus, qui indicem rerum obiectorum Regni nidos reddidit:

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

Et nos eam in rebus nostris Regno implevimus:

@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 omnes pueros in torto indice reddimus. Quodque objectum infantis etiam interfaciem NestedEntityAware efficere potest, significans Regnum internum habere obiecta delere, e.g. 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
   )
 }
}

Et sic de obiectorum nidificatione iterari potest.

Deinde scribimus methodum quae omnia objecta frondosa recursively delet. Modus (extensio facta) deleteAllNestedEntities habet omnia summo gradu res et ratio deleteNestedRecursively Recursively removet omnia nested obiecta utens in NestedEntityAware interface:

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()
   }
 }
}

Hoc egimus cum celerrimis obiectis crescentibus et quod factum est repressimus.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Quam ob rem ea quae diximus solutione obsita crescere desierunt. Et basis altiore incrementum tardavit, sed non cessavit.

"normalis" solutio

Cum basis tardius crescere coepit, adhuc crevit. Itaque ulterius quaerere coepimus. Propositum nostrum acerrimum usum notitiarum in Regno caching reddit. Ideo scribens omnia objecta nidificata in unumquodque opus intensiva est, plus errorum periculum auget, quia oblivisci potes objecta specificare in codice mutato.

Facere volui me non interfaces utebam, sed omnia per se elaborata sunt.

Cum volumus aliquid operari in se ipso, ad usum reflexionis habemus. Ad hoc efficiendum, per quemque genus campum ire possumus et inspicere utrum Regnum sit obiectum vel index rerum:

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

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

Si ager RealmModel vel RealmList est, obiectum huius campi adde ad indicem rerum nestarum. Omnia prorsus eadem sunt quae supra fecimus, solum hic per se fiet. Ipse methodus cascade deletionis valde simplex est et huius modi similis:

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()
         }
       }
 }
}

Extensio filterRealmObject eliquat et transit solum objecta regni. Methodus getNestedRealmObjects per reflexionem, omnia objecta regni nidos invenit et in linea lineari ponit. Deinde idem facimus recursive. Cum delendo, obiectum validitatis reprehendo isValidpotest enim esse quod diversa parentum genera identitate nidificant. Melius est vitare hoc et simpliciter auto-generatione id uti, cum res novas creant.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Plena ex methodo 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)
}

Quam ob rem in nostro codice clientelam "cascading delere" pro unaquaque operatione notarum modificationis utimur. Verbi gratia, operatio inserta sic spectat;

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 }
 ))
}

Methodus prima getManagedEntities omnia adiecta accipit, et modus cascadeDelete Recursively delet omnia objecta collecta antequam novas scribebat. Accedimus utendo accessu per totam applicationem. Memoria scillam in Regno penitus excidit. Peractae mensurae dependentiae satu eodem tempore in numero frigidorum incipit applicationis, euentu videmus.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Linea viridis ostendit dependentiam applicationis startup tempore in numero frigorum incipit in automatario deletione objectorum nidificatorum.

Proventus et conclusiones

Imperium database in dies crescens applicationem lentissime deducendi causabat. Renovationem cum nostris "cascading delere" de obiectis nidificatis emisimus. Nunc autem monitor et aestimamus quomodo consilium nostrum applicationis startup tempus per _app_incipiendi metricum affectavit.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Pro analysi, tempus XC dierum accipimus et videmus: tempus applicationis immittendi tam mediana quam quae in 90 cento utentium utentium cadit, decrescere coepit et iam non oritur.

Narratio quomodo cascade deletionis in Regno per longam launch, quaesita est?

Si ad septem dies chartulam spectes, _app_start metrica spectat omnino adaequata et minus quam 1 secunda.

Addere etiam valet quod per default, Firebase notificationes emittit si valorem medianum _app_start 5 seconds excedit. Sed hoc, ut perspicimus, non debetis inniti, sed potius intrare et explicite cohibere.

ΠžΡΠΎΠ±Π΅Π½Π½ΠΎΡΡ‚ΡŒ Π±Π°Π·Ρ‹ Π΄Π°Π½Π½Ρ‹Ρ… Realm Π·Π°ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ΡΡ Π² Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ это нСрСляционная Π±Π°Π·Π° Π΄Π°Π½Π½Ρ‹Ρ…. НСсмотря Π½Π° простоС использованиС, ΡΡ…ΠΎΠΆΠ΅ΡΡ‚ΡŒ Ρ€Π°Π±ΠΎΡ‚Ρ‹ с ORM-Ρ€Π΅ΡˆΠ΅Π½ΠΈΡΠΌΠΈ ΠΈ связываниС ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ², Ρƒ Π½Π΅Ρ‘ Π½Π΅Ρ‚ каскадного удалСния.

Si hoc non consideretur, res nidificabunt et cumulabunt et "defluent". Constanter crescet datorum, quod rursus tarditatem vel initium applicationis afficiet.

Experientiam nostram communicavi quam cito cascade deletionem rerum in Regno facere, quod nondum ex cista est, sed diu delatum est. dicens: ΠΈ dicens:. In nostro casu, haec application satus tempore valde concitat.

Quamvis disceptatio de imminenti aspectus huius pluma, absentia cascade deletionis in Regno fit consilio. Si novam applicationem cogitas, hanc rationem habe. Et si Regnum iam uteris, si difficultates habes.

Source: www.habr.com