Adam Šimek: Optimalizace skrolování, RecyclerView

Post on 16-Feb-2017

34 views 1 download

transcript

Pohádka o plynulém skrolování

ADAM ŠIMEK

Umět seznamy je důležité

Kulmanův test

Jakto?

¯\_(ツ)_/¯

16

16ms

“A to máte: user input, networking, data processing, database/disk I/O, view inflation, layout, drawing,… Jsou tam taky další aplikace, procesory jsou pomalý, flashky taky, všechno to strasně dlouho trvá. A ještě ke všemu jedeme na baterku.”

16ms?

Tak jdeme na to…

ListView

ListView

RecyclerView

Architektura RecyclerView

ViewHolder

ViewHolder

Best practice z ListViewTakový lightweight controller k jednotlivým položkámRecyclerView si jich drží omezený počet a recykluje jeJeden ViewHolder je použit pro víc položek (ne najednou)Má svůj životní cyklus

ViewHolder Lifecycle

@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {}

@Override public void onBindViewHolder(ViewHolder holder, int position) {}

@Override public void onViewAttachedToWindow(ViewHolder holder) {}

@Override public void onViewDetachedFromWindow(ViewHolder holder) {}

@Override public void onViewRecycled(ViewHolder holder) {}

@Override public boolean onFailedToRecycleView(ViewHolder holder) {}

ViewHolder Lifecycle

@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {}

@Override public void onBindViewHolder(ViewHolder holder, int position) {}

@Override public void onViewAttachedToWindow(ViewHolder holder) {}

@Override public void onViewDetachedFromWindow(ViewHolder holder) {}

@Override public void onViewRecycled(ViewHolder holder) {}

@Override public boolean onFailedToRecycleView(ViewHolder holder) {}

“Nejrychlejší kód je kód, který se nevolá.” Sokrates

Co nedělat?

Disk I/O - SQLite, SharedPreferences, Content Providersfor (Comment comment : item.getComments()) {}

Parsování textu, JSONu,…, složité formátování,..Html.fromHtml()

Složité layouty 🙂

“Když to běží na u mě plynule, tak v pohodě.”Kritický kód může být schovaný v abstrakci

Co dělat?

Složité operace musí bežet na pozadí (vše z předchozího slajdu)…,nebo už musí být předpřipravené

Není potřeba vše vykreslovat hned → placeholder na chvíli stačívoid onScrollStateChanged(…, int newState)

mHandler.postDelayed(deferredBind, DELAY }

☝ důležitá je vyvážennost

Testovat na různých zařízeních (low-end, tablety)…a taky ruzných datechStrictMode

Co je doma, to se počítá

Co je špatně?

//ViewHolder code called in onBindViewHolder()

@Override public void bindData(Object data, final int position) { mImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mClickHandler.onItemClicked(v, position, photo); } }); }

Co je špatně?

//ViewHolder code called in onBindViewHolder()

@Override public void bindData(Object data, final int position) { mImageView.setOnClickListener(mClickListener); // use member var instead }

Co je špatně?

//ViewHolder code called in onBindViewHolder()

@Override public void bindData(Object data, final int position, ItemClickListener listener) { mImageView.setOnClickListener(listener); // even better.. }

Co je špatně?

//ViewHolder code called in onBindViewHolder()

@Override public void bindData(Object data, final int position) { mImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mClickHandler.onItemClicked(v, position, photo); } }); }

Co je špatně?

//ViewHolder code called in onBindViewHolder()

@Override public void bindData(Object data, final int position) { mImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mClickHandler.onItemClicked(v, getAdapterPosition(), photo); } }); }

Dvakrát měř, jednou řež.

AsyncTask

AsyncTask

Standardně běží tasky sériově → všechno trváHm…executeOnExecutor(THREAD_POOL_EXECUTOR); Je tam celkem velký overheadTasky se docela blbě přerušují

💥

java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@36e83ee rejected from java.util.concurrent.ThreadPoolExecutor@a54e08f[Running, pool size = 13, active threads = 13, queued tasks = 128, completed tasks = 1055]

DIY

Vlastní “taskmanager”

… mDecodeThreadPool = new ThreadPoolExecutor( NUMBER_OF_CORES, // Initial pool size NUMBER_OF_CORES, // Max pool size KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, mDecodeWorkQueue); // 128 size in AsyncTask …

mDecodeThreadPool.execute(DownloadTask); // execute task …

Další záludnosti

Další záludnosti

View.requestLayout() ——————— RecyclerView.setHasFixedSize( boolean hasFixedSize

) ——————— TextView.setAllCaps(boolean allCaps) ——————— notifyDatasetChanged()

View.requestLayout()

View.requestLayout() + hasFixedSize

void triggerUpdateProcessor() { if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); } else { mAdapterUpdateDuringMeasure = true; requestLayout(); } }

Další záludnosti

View.requestLayout() ——————— RecyclerView.setHasFixedSize( boolean hasFixedSize

) ——————— TextView.setAllCaps(boolean allCaps) ——————— notifyDatasetChanged()

TextView.setAllCaps()

public void setAllCaps(boolean allCaps) { if (allCaps) { setTransformationMethod(new AllCapsTransformationMethod(getContext())); } else { setTransformationMethod(null); } }

Další záludnosti

View.requestLayout() ——————— RecyclerView.setHasFixedSize( boolean hasFixedSize

) ——————— TextView.setAllCaps(boolean allCaps) ——————— notifyDatasetChanged()

“Today I will do what others won't, so tomorrow I can accomplish what

others can’t"

Prefetch

Systrace

Systrace - missed frame

Prefetch OFF

Prefetch ON

Prefetch

create a bind “do zásoby”odhaduje jak dlouho ty operace trvají (aby se to stihlo)Lollipop+ (RenderThread), support lib 25.1setItemPrefetchEnabled(true) podporováno ve výchozích LayoutManagerech (API pro custom)pozor na onBindViewHolder - žádné animace,…setInitialPrefetchItemCount(N) rozdíl je fakt vidět

Díky! @simekadam

strava.com/athletes/simekadam

@simekadam strava.com/athletes/simekadam