View Lifecycle — Android foydalanuvchi interfeysi elementi (View) ni ekranda ko'rsatish va qayta ko'rsatish uchun chaqiradigan metodlar ketma-ketligidir. Activity yoki Fragmentdan farqli o'laroq, View kengaytirilgan hayot aylanishiga ega bo'lmagan, ammo qat'iy uch fazali jarayondan o'tadigan yengil komponentdir: onMeasure (o'lchash), onLayout (joylashtirish), onDraw (chizish). View Lifecycle-ni tushunish maxsus View-lar yaratish, ishlashni optimallashtirish va ko'rsatish muammolarini hal qilish uchun zarurdir. Google ma'lumotlariga ko'ra, to'g'ri amalga oshirilganda maxsus View-lar UI-ni 15–40% tezlashtiradi standart ichma-ich ViewGroup kombinatsiyasi bilan solishtirganda. Android-ning maxsus View-lar haqidagi hujjatlari onMeasure, onLayout va onDraw-ni View Lifecycle-ning uch ustuni sifatida tasvirlaydi.
Asosiy ma'lumotlar
View Lifecycle — Android View (va ViewGroup) o'zini ekranda ko'rsatish uchun o'tadigan jarayondir. Activity yoki Fragmentdan farqli o'laroq, View da onStart/onStop/onDestroy mavjud emas — uning „hayoti” o'lchash, joylashtirish va chizishning davriy jarayonidan iborat. Bu tsikl View har safar ko'rsatilishi yoki qayta chizilishi kerak bo'lganda ishga tushadi.
View Lifecycle-ning uch fazasi:
View Lifecycle-ning to'liq tsikli, shuningdek, View-ning oynaga biriktirilishi bilan bog'liq metodlarni o'z ichiga oladi: onAttachedToWindow (View oynaga biriktirilgan, HW acceleration mavjud) va onDetachedFromWindow (View ajratilgan, resurslar bo'shatiladi). Bu metodlar View hayoti davomida bir marta chaqiriladi va animatsiyalar, sensorlarni ro'yxatdan o'tkazish/bekor qilish uchun muhimdir.
Android Performance Blog ma'lumotlariga ko'ra, UI ishlash muammolarining (jank, kadr tushirish) 65% onMeasure va onDraw-ning noto'g'ri amalga oshirilishi bilan bog'liq: haddan tashqari override, keraksiz requestLayout() chaqirish, onDraw-da obyekt yaratish.
onMeasure — View Lifecycle-ning eng muhim va eng murakkab fazasi. Bu bosqichda Android View ekranda qancha joy egallashini aniqlaydi. Tizim rejim va o'lchamdan iborat int-ga yig'ilgan ko'rsatmalar — MeasureSpec-ni uzatadi.
MeasureSpec-ning uch rejimi:
| Rejim | Doimiy | Ma'nosi | Misol |
|---|---|---|---|
| EXACTLY | MeasureSpec.EXACTLY | Ota tomonidan belgilangan aniq o'lcham (match_parent yoki qat'iy kenglik) | width=400dp → MeasureSpec(400, EXACTLY) |
| AT_MOST | MeasureSpec.AT_MOST | View belgilangan maksimalgacha o'lchamda bo'lishi mumkin (wrap_content) | width ≤ 400dp → MeasureSpec(400, AT_MOST) |
| UNSPECIFIED | MeasureSpec.UNSPECIFIED | Cheklovsiz — View istalgan o'lchamda bo'lishi mumkin (ScrollView, RecyclerView) | kenglik cheklanmagan → MeasureSpec(0, UNSPECIFIED) |
onMeasure amalga oshirish:
setMeasuredDimension(int width, int height) chaqiriladi.getPaddingLeft() + getPaddingRight() ayiriladi.measureChild() yoki measureChildWithMargins() orqali o'lchanadi.Oddiy xato: wrap_content da MeasureSpec-ni hisobga olmaslik. View wrap_content ga o'rnatilgan bo'lsa, lekin onMeasure AT_MOST-ni qayta ishlamasa va qat'iy o'lcham qaytarsa, View yo kesiladi yoki kerak bo'lganidan ko'proq joy egallaydi.
onLayout — View yoki ViewGroup o'z avlodlarini o'z chegaralari ichida joylashtiradigan faza. Oddiy View (ViewGroup emas) uchun onLayout talab qilinmaydi — tizim ota tomondan uzatilgan parametrlar bilan layout() ni chaqiradi. ViewGroup uchun onLayout majburiydir — usiz bola View-lar joylashtirilmaydi.
onLayout imzosi:
@Override
protected void onLayout(boolean changed,
int left, int top,
int right, int bottom) {
// bola View-larni joylashtirish
}
changed parametri View-ning pozitsiyasi yoki o'lchami oldingi layout bilan solishtirganda o'zgarganligini ko'rsatadi. Agar false bo'lsa — View optimallashtirish uchun avlodlar pozitsiyalarini qayta hisoblashni o'tkazib yuborishi mumkin.
ViewGroup uchun onLayout:
getChildCount() va getChildAt(i) orqali ko'rib chiqadi.child.layout(l, t, r, b) chaqiriladi.onLayout onMeasure dan keyin chaqiriladi — o'lchangan o'lchamlar getMeasuredWidth()/getMeasuredHeight() orqali mavjud. Agar bola View layout() dan keyin boshqa haqiqiy o'lchamlarga ega bo'lsa, qayta o'lchash uchun requestLayout() chaqiriladi. Bu „layout pass” deb ataladi va qayta hisoblashlarning zanjirli reaktsiyasiga olib kelishi mumkin.
onDraw — View o'zini Canvas-da chizadigan faza. Bu onMeasure va onLayoutsiz bir necha marta chaqirilishi mumkin bo'lgan yagona fazadir — agar View invalidate() deb belgilangan bo'lsa. Canvas chizish uchun API taqdim etadi: drawLine, drawRect, drawCircle, drawText, drawBitmap va drawPath.
onDraw qoidalari:
canvas.clipRect() dan foydalaning.ViewGroup da chizish tartibi: fon (setBackgroundDrawable) → onDraw (mazmun) → dispatchDraw (bola View-lar) → onDrawForeground (old fon). dispatchDraw har bir avlodning onDraw-ni chaqiradi. dispatchDraw-ni override qilish bola elementlar ustiga effektlar qo'llash uchun ishlatiladi.
Android Vitals statistikasiga ko'ra, onDraw da kadr tushishining eng keng tarqalgan sabablari — metod ichida obyekt yaratish (48%), decodeResource chaqiruvi (22%) va keshsiz Path bilan murakkab operatsiyalar (15%).
Invalidation — View-ni qayta chizishni ishga tushiradigan mexanizm. invalidate() chaqiruvi View-ni „iflos” deb belgilaydi va keyingi chizish siklida onDraw chaqiruvini rejalashtiradi. requestLayout() chaqiruvi — og'irroq operatsiya bo'lib, to'liq tsiklni ishga tushiradi: onMeasure → onLayout → onDraw.
| Metod | Nima qiladi | Qachon ishlatish kerak |
|---|---|---|
| invalidate() | onMeasure/onLayoutsiz onDraw chaqiradi | Faqat tashqi ko'rinish o'zgargan (rang, matn, jarayon) |
| invalidate(Rect) | Faqat ko'rsatilgan maydonni qayta chizadi | View qismi o'zgargan — animatsiya, tanlash |
| postInvalidate() | UI bo'lmagan thread dan invalidate chaqiradi | Fon thread-i chizish uchun ma'lumotlarni yangilagan |
| requestLayout() | onMeasure → onLayout → onDraw ishga tushiradi | Mazmun o'lchami o'zgargan (matn, tasvir) |
| forceLayout() | View-ni majburiy qayta o'lchash uchun belgilaydi | Ichki holat o'zgargan, o'lcham o'zgargan bo'lishi mumkin |
Animatsiyalar va View Lifecycle: ViewPropertyAnimator va ValueAnimator animatsiyaning har bir kadrida invalidate() chaqiradi. ObjectAnimator View da setter chaqiradi, agar setter o'lchamni (width/height) o'zgartirsa, avtomatik ravishda requestLayout() chaqiradi. Bu murakkab ViewGroup lar uchun qimmatga tushishi mumkin: har bir requestLayout ildiz view-gacha to'liq iyerarxiyani ishga tushiradi.
Optimallashtirish qoidasi: requestLayout() o'rniga invalidate() faqat tashqi ko'rinish o'zgarganda (rang, shaffoflik, o'lchamsiz aylantirish). requestLayout faqat o'lchamlar yoki o'lchamga ta'sir qiluvchi mazmun o'zgarganda ishlating.
Maxsus View-lar — noyob UI yaratish uchun kuchli vosita, ammo ular ishlash qoidalariga qat'iy rioya qilishni talab qiladi. View Lifecycle optimallashtirish bo'yicha Google-ning asosiy tavsiyalari.
setLayerType(LAYER_TYPE_HARDWARE) va tugagandan so'ng setLayerType(LAYER_TYPE_NONE) ishlating.onMeasure, onDraw va invalidate ning to'g'ri amalga oshirilishi bilan oddiy dumaloq jarayon ko'rsatkichi.
class CircularProgressView constructor(
context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {
private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
style = Paint.Style.STROKE
strokeWidth = 8f
strokeCap = Paint.Cap.ROUND
}
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.LTGRAY
style = Paint.Style.STROKE
strokeWidth = 8f
}
private var progress = 0f
private var viewWidth = 0
private var viewHeight = 0
fun setProgress(value: Float) {
progress = value.coerceIn(0f, 100f)
invalidate()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val desiredSize = 100 * resources.displayMetrics.density.toInt()
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
val size = minOf(width, height).coerceAtLeast(desiredSize)
setMeasuredDimension(size, size)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val padding = progressPaint.strokeWidth / 2
val radius = (minOf(viewWidth, viewHeight) - padding) / 2
val cx = viewWidth / 2f
val cy = viewHeight / 2f
canvas.drawCircle(cx, cy, radius, backgroundPaint)
val sweepAngle = (progress / 100f) * 360f
canvas.drawArc(cx - radius, cy - radius, cx + radius, cy + radius,
-90f, sweepAngle, false, progressPaint)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w
viewHeight = h
}
}
Dumaloq jarayon chizig'i: onMeasure MeasureSpec asosida kvadrat o'lcham qaytaradi, onSizeChanged o'lchamlarni eslab qoladi, onDraw fon va jarayon yoyini chizadi. Invalidate jarayon o'zgarganda chaqiriladi — onMeasure/onLayout ta'sirlanmaydi. Paint konstruktorda bir marta yaratiladi, onDraw da emas.
Bola View-larni qatorlarga joylashtiradigan maxsus ViewGroup (Flexbox wrap kabi).
class FlowLayout constructor(
context: Context, attrs: AttributeSet? = null
) : ViewGroup(context, attrs) {
private val horizontalSpacing = 8.dpToPx(resources)
private val verticalSpacing = 8.dpToPx(resources)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = MeasureSpec.getSize(widthMeasureSpec)
var totalHeight = paddingTop + paddingBottom
var rowWidth = paddingLeft
var rowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight)
if (rowWidth + child.measuredWidth > width - paddingRight) {
totalHeight += rowHeight + verticalSpacing
rowWidth = paddingLeft
rowHeight = 0
}
rowWidth += child.measuredWidth + horizontalSpacing
rowHeight = maxOf(rowHeight, child.measuredHeight)
}
totalHeight += rowHeight
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec)
)
}
override fun onLayout(changed: Boolean,
l: Int, t: Int, r: Int, b: Int) {
var rowTop = paddingTop
var rowLeft = paddingLeft
var rowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
if (rowLeft + child.measuredWidth > r - paddingRight) {
rowTop += rowHeight + verticalSpacing
rowLeft = paddingLeft
rowHeight = 0
}
child.layout(rowLeft, rowTop, rowLeft + child.measuredWidth, rowTop + child.measuredHeight)
rowLeft += child.measuredWidth + horizontalSpacing
rowHeight = maxOf(rowHeight, child.measuredHeight)
}
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
}
FlowLayout onMeasure-ni override qiladi: har bir avlodni o'lchaydi, kenglik oshib ketganda yangi qatorga o'tkazadi, umumiy balandlikni hisoblaydi. onLayout bolalarni qator o'tishlari bilan koordinatalar bo'yicha joylashtiradi. generateLayoutParams bola View-larda margin qo'llab-quvvatlash uchun MarginLayoutParams qaytaradi.
Maxsus View Path-ni oldindan hisoblab va keshlab, silliq Bezier egri chizig'ini chizadi.
class WaveView constructor(
context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {
private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.parseColor("#4A90D9")
style = Paint.Style.FILL
}
private val wavePath = Path()
private var isPathDirty = true
private var viewWidth = 0
private var viewHeight = 0
fun refreshWave() {
isPathDirty = true
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w
viewHeight = h
isPathDirty = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (isPathDirty) {
wavePath.reset()
val amplitude = viewHeight * 0.1f
wavePath.moveTo(0f, viewHeight * 0.5f)
for (x in 0..viewWidth step 4) {
val y = viewHeight * 0.5f + amplitude * Math.sin(x * 2 * Math.PI / viewWidth).toFloat()
wavePath.lineTo(x.toFloat(), y)
}
wavePath.lineTo(viewWidth.toFloat(), viewHeight.toFloat())
wavePath.lineTo(0f, viewHeight.toFloat())
wavePath.close()
isPathDirty = false
}
canvas.drawPath(wavePath, wavePaint)
}
}
Path keshlash: isPathDirty = true faqat View o'lchamlari o'zgarganda yoki refreshWave() chaqirilganda. onDraw da Path faqat „iflos” bo'lsa qayta hisoblanadi. Bu har bir animatsiya kadrida Bezier egri chizig'ini qayta hisoblashning oldini oladi, CPU-ni tejaydi.
Tez-tez so'raladigan savollar
View Lifecycle — Activity yaratilishi/yo'q qilinishiga bog'liq bo'lmagan davriy chizish jarayoni (onMeasure → onLayout → onDraw). View da onStart/onStop yo'q — u yo ko'rinadi (oynaga biriktirilgan) yoki yo'q. Activity Lifecycle ilova komponentining holatini boshqaradi, View Lifecycle — UI chizilishini.
requestLayout() ildizdan butun View daraxti uchun to'liq tsiklni ishga tushiradi: onMeasure → onLayout → onDraw. Agar requestLayout() tez-tez chaqirilsa (masalan, animatsiyaning har bir kadrida), bu jank va kadr tushishiga olib keladi. Google ma'lumotlariga ko'ra, bitta requestLayout 10 elementli ViewGroup da o'rtacha 2–5 ms davom etadi. Animatsiyalar uchun invalidate() dan foydalaning.
onAttachedToWindow View oynaga (Window) biriktirilganda — ko'rinadigan iyerarxiyaning bir qismiga aylanganda chaqiriladi. Shu payt View HW acceleration va Window resurslariga (WindowManager, Display) kirish huquqini oladi. onAttachedToWindow animatsiya tinglovchilarini va View ko'rinadigan vaqt davomida yashovchi BroadcastReceiver-ni ro'yxatdan o'tkazish uchun to'g'ri joydir.
Overdraw — piksel bir kadrda bir necha marta chiziladigan holat. Har bir qo'shimcha o'tish GPU vaqtini yo'qotishdir. Kamaytirish usullari: theme da windowBackground ni o'rnating (layout da fon chizmang), canvas.clipRect() dan foydalaning, ichma-ich fonlarni birlashtirishdan saqlaning, ichma-ich LinearLayout o'rniga ConstraintLayout qo'llang. Android Studio → Profile GPU Rendering → Overdraw overdraw rang xaritasini ko'rsatadi (ko'k = 1x, qizil = 3x+).
Ha, agar View fon (background) ga ega bo'lsa. super.onDraw() View fonini chizadi. Agar maxsus View ning foni bo'lmasa yoki o'z foningizni chizsangiz, super.onDraw() chaqirilmasligi mumkin — bu bitta chizish o'tishini tejaydi. ViewGroup uchun super.dispatchDraw() majburiydir — bola View-larni chizadi.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.