Upload
open-rnd
View
537
Download
1
Embed Size (px)
Citation preview
Optymalizacja hierarchii widoków na
platformie Android
Michał Włodarczyk, Mobilization 2015, Łódź
Jak tworzyć wydajne aplikacje?
“Layout Traversals on Android”
Lucas RochaFacebook Inc.
DroidCon 2015, Berlin
Pozycjonowanie
Rysowanie
Interakcje ze zdarzeniami
Czym jest UI dla programisty?
Początki Android’a były trudne ...
Przed Jelly Bean
ViewRoot.java
public final class ViewRoot extends Handler ... {
...
public void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; sendEmptyMessage(DO_TRAVERSAL);
} } ... public void handleMessage(Message msg) {
... case DO_TRAVERSAL: performTraversals(); ...
} }
Odświeżanie ekranu w przewidywalnych odstępach czasu
Od Jelly Bean - Choreographer
... f1 f2 f3 f4 f5 ...
Resize view
Redrawview
InputEvents
Choreographer.java
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { ... scheduleVsync(); ...
}
...
void doFrame(long frameTimeNanos, int frame) { ... if (!mFrameScheduled) {
return; } ... doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); ...
}
Choreographer
ViewRootlmpl
Łączy zarządcę okien z hierarchią widoków
SurfaceFlinger
ViewRootImpl
Hierarchia widoków
Metody związane z widokami
Measure + Layout + Draw
measureHierarchy(...)
measure(int, int) → onMeasure(int, int)
M M M
M M
M
*
Measure
Lazy Measure
Multi-MeasureSpec cache
View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
... int cacheIndex = mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else { long value = mMeasureCache.valueAt(cacheIndex); setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} ...
}
Lazy Measure
View.java
public void layout(int l, int t, int r, int b) { if ((flags & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0 ) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} ...
}
Lazy Measure
RelativeLayout
LinearLayout Button
LinearLayout TextView
RelativeLayout RelativeLayout
TextView TextViewTextView TextView TextView TextView
requestLayout()
Multi-MeasureSpace cache
18 19
6.5 ms 0.7 ms
72 14
SDK
czas mierzenia
ilość wywołań
Multi-MeasureSpace cache
layout(int, int, int, int) → onLayout(boolean, int, int, int, int)
M L M L M L
M L M L
M L
*
Layout
performLayout()
performDraw()
draw(Canvas) → onDraw(Canvas)M L D
*
M L D M L DM L D
M L D M L DDraw
View.java
private DisplayList getDisplayList(...) { ... final HardwareCanvas canvas = displayList.start(width, height); ... draw(canvas); ... displayList.end(); ...
}
Display List
Kiedy wykonywany jest layout(), to na pewno zrobiony jest measure()
Kiedy wykonywany jest draw(), to na pewno zrobiony jest layout()
Pamiętaj!
1. Wywołanie getMeasured*() poza onLayout()
2. Alokacja:● onLayout(): Akceptowalna● onMeasure(): Unikać● onDraw(): Nigdy
Unikaj!
Zmiany rozmiaru elementów, odświeżenie stanu, animacje
... f1 f2 f3 f4 f5 ...
Metoda requestLayout()
*
requestLayout()
View.java
public void requestLayout() { ... if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } ...
}
ViewRootImpl.java
void scheduleTraversals() { ... mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ...
}
Metoda requestLayout()
Metoda invalidate()
... f1 f2 f3 f4 f5 ...
*
invalidate(...)
Metoda invalidate()
View.java
public void invalidateInternal(...) { ... mPrivateFlags |= PFLAG_INVALIDATED; ... if (mParent != null && mAttachInfo != null && l < r && t < b) {
final Rect damage = mAttachInfo.mTmpInvalRect; damage.set(l, t, r, b); mParent.invalidateChild(this, damage);
} ...
}
boolean draw(...) { ... mRecreateDisplayList = (
mPrivateFlags & PFLAG_INVALIDATED) == PFLAG_INVALIDATED; ...
}
Metoda postOnAnimation()
...... f1 f2 f3 f4 f5 ...
*postOnAmination()valueAnimator...
Metoda postOnAnimation()
View.java
public void postOnAnimation(Runnable action) { ... attachInfo.mViewRootImpl.mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, action, null);...
}
Choreographer.java
void doFrame(long frameTimeNanos, int frame) { ... doCallbacks(Choreographer.CALLBACK_INPUT,frameTimeNanos); doCallbacks(Choreographer.CALLBACK_ANIMATION,frameTimeNanos); doCallbacks(Choreographer.CALLBACK_TRAVERSAL,frameTimeNanos); ...
}
Używajmy OnPreDrawListener!
Tree Observer
OnPreDrawListener
// 1. Save layout state and wait for next frame.
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {@Override public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
// 2. Restore original layout state. // 3. Trigger animators towards new layout state.
} }
Nie wywołuj layout() podczas layout
Nie wywołuj layout() podczas animacji
Wywołuj invalidate() tylko na tych elementach, które chcesz przerysować
Pamiętaj!
1. Prosta hierarchia widoków2. Multi pass layout - unikajmy
Wydajność
Własne widoki
1. Composite Views2. Custom Composite Views3. Flat Custom Views
Własne widoki
Przydatne narzędzia
Hierarchy Viewer
źródło: http://cfile10.uf.tistory.com/image/110412354E5A61652D1F2C
Stereovision Image Calculator
Stereovision Image Calculator
1. Menu zmienione z RelativeLayout na FrameLayout
2. Uproszczenie hierarchii widoków3. Wykres jako Custom Flat View4. Kolejkowanie odświeżania
Stereovision Image Calculator Optymalizacja
Nexus 7
Nexus 7 po optymalizacja
Samsung Tab 2
Samsung tab 2 po optymalizacja
Stereovision Image CalculatorWyniki optymalizacji
299.03
50.54
478.42
65.92
Czas renderowania [ms]
Po co tworzyć wydajne aplikacje?
QUESTIONS ? THOUGHTS ? COMMENTS ?
Feel free to contact us!
WWW.OPEN-RND.PL
Thank you
Michał WłodarczykSENIOR SOFTWARE DEVELOPER of Open-RnD