Slide 1

Slide 1 text

OPTIMIZING ANDROID UI PRO TIPS FOR CREATING SMOOTH AND RESPONSIVE APPS

Slide 2

Slide 2 text

@CYRILMOTTIER

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

GET TO KNOW JAVA

Slide 5

Slide 5 text

DON’T USE BOXED TYPES UNNECESSARILY

Slide 6

Slide 6 text

HashMap hashMap = new HashMap(); hashMap.put(665, "Android"); hashMap.put(666, "rocks"); hashMap.get(666);

Slide 7

Slide 7 text

HashMap< , String> hashMap = new HashMap< , String>(); hashMap.put(665, "Android"); hashMap.put(666, "rocks"); hashMap.get(666); Integer Integer Integer is not int

Slide 8

Slide 8 text

new Integer(666) REALITY BECOMES 666 666 DREAMS

Slide 9

Slide 9 text

QUESTION Would the result have been the same if we had been trying to add ″Android″ with 42 as key?

Slide 10

Slide 10 text

QUESTION Would the result have been the same if we had been trying to add ″Android″ with 42 as key? ANSWER Yes 42 is the answer to the Java universe

Slide 11

Slide 11 text

QUESTION Would the result have been the same if we had been trying to add ″Android″ with 42 as key? ANSWER Yes 42 is the anwser to the Java universe I AM JOKING

Slide 12

Slide 12 text

QUESTION Would the result have been the same if we had been trying to add ″Android″ with 42 as key? ANSWER No Integer has an internal cache for [-128, 127]

Slide 13

Slide 13 text

SparseArray: SparseArrays map integers to Objects. Unlike a normal array of Objects, there can be gaps in the indices. It is intended to be more efficient than using a HashMap to map Integers to Objects.

Slide 14

Slide 14 text

SparseArray sparseArray = new SparseArray(); sparseArray.put(665, "Android"); sparseArray.put(666, "rocks"); sparseArray.get(666);

Slide 15

Slide 15 text

SparseArray sparseArray = new SparseArray(); sparseArray.put(665, "Android"); sparseArray.put(666, "rocks"); sparseArray.get(666); ints as keys, Strings as values

Slide 16

Slide 16 text

SparseArray sparseArray = new SparseArray(); sparseArray.put(665, "Android"); sparseArray.put(666, "rocks"); sparseArray.get(666); no autoboxing, at all ints as keys, Strings as values

Slide 17

Slide 17 text

REUSE AS MUCH AS POSSIBLE

Slide 18

Slide 18 text

@Override public View getView(int position, View convertView, ViewGroup parent) { final View itemView = mInflater.inflate( android.R.layout.two_line_list_item, // resource parent, // parent false); // attach ((TextView) itemView.findViewById(android.R.id.text1)) .setText(TITLES.get(position)); ((TextView) itemView.findViewById(android.R.id.text2)) .setText(SUBTITLES.get(position)); return itemView; } NEVER DO THIS !!!

Slide 19

Slide 19 text

@Override public View getView(int position, View convertView, ViewGroup parent) { ((TextView) itemView.findViewById(android.R.id.text1)) .setText(TITLES.get(position)); ((TextView) itemView.findViewById(android.R.id.text2)) .setText(SUBTITLES.get(position)); return itemView; } NEVER DO THIS !!! android.R.layout.two_line_list_item, // resource false); // attach final View itemView = mInflater.inflate( parent, // parent inflate a new View at every getView call

Slide 20

Slide 20 text

convertView: The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view.

Slide 21

Slide 21 text

(RE)USE THE CONVERTVIEW @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate( android.R.layout.two_line_list_item, // resource parent, // parent false); // attach } ((TextView) convertView.findViewById(android.R.id.text1)) .setText(TITLES.get(position)); ((TextView) convertView.findViewById(android.R.id.text2)) .setText(SUBTITLES.get(position)); return convertView; }

Slide 22

Slide 22 text

(RE)USE THE CONVERTVIEW @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate( android.R.layout.two_line_list_item, // resource parent, // parent false); // attach } ((TextView) convertView.findViewById(android.R.id.text1)) .setText(TITLES.get(position)); ((TextView) convertView.findViewById(android.R.id.text2)) .setText(SUBTITLES.get(position)); return convertView; } check for a convertView

Slide 23

Slide 23 text

(RE)USE THE CONVERTVIEW @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate( android.R.layout.two_line_list_item, // resource parent, // parent false); // attach } ((TextView) convertView.findViewById(android.R.id.text1)) .setText(TITLES.get(position)); ((TextView) convertView.findViewById(android.R.id.text2)) .setText(SUBTITLES.get(position)); return convertView; } create a view only when necessary check for a convertView

Slide 24

Slide 24 text

PREFER STATIC FACTORY METHODS TO CONSTRUCTORS

Slide 25

Slide 25 text

private static final int MSG_ANIMATION_FRAME = 0xcafe; public void sendMessage(Handler handler, Object userInfo) { final Message message = new Message(); message.what = MSG_ANIMATION_FRAME; message.obj = userInfo; handler.sendMessage(message); }

Slide 26

Slide 26 text

private static final int MSG_ANIMATION_FRAME = 0xcafe; public void sendMessage(Handler handler, Object userInfo) { final Message message = Message(); message.what = MSG_ANIMATION_FRAME; message.obj = userInfo; handler.sendMessage(message); } new creation of a new Message instance

Slide 27

Slide 27 text

private static final int MSG_ANIMATION_FRAME = 0xcafe; public void sendMessage(Handler handler, Object userInfo) { final Message message = Message.obtain(); message.what = MSG_ANIMATION_FRAME; message.obj = userInfo; handler.sendMessage(message); }

Slide 28

Slide 28 text

private static final int MSG_ANIMATION_FRAME = 0xcafe; public void sendMessage(Handler handler, Object userInfo) { final Message message = Message.obtain(); message.what = MSG_ANIMATION_FRAME; message.obj = userInfo; handler.sendMessage(message); } try to reuse Message instances Message.obtain(): Gets an object from a pool or create a new one

Slide 29

Slide 29 text

Want this in your app? Go to github.com/android/platform_frameworks_base Find android.util Copy PoolableManager, Pool, Poolable, Pools, FinitePool, etc. Paste in your project Enjoy the easy-to-use object pooling mechanism

Slide 30

Slide 30 text

PREFER STATIC VARIABLES TO TEMPORARY VARIABLES

Slide 31

Slide 31 text

@Override public boolean onInterceptTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final Rect frame = new Rect(); // Are we touching mHost ? mHost.getHitRect(frame); if (!frame.contains(x, y)) { return false; } return true; }

Slide 32

Slide 32 text

@Override public boolean onInterceptTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final Rect frame = Rect(); // Are we touching mHost ? mHost.getHitRect(frame); if (!frame.contains(x, y)) { return false; } return true; } creation of a new Rect instance new

Slide 33

Slide 33 text

private final Rect mFrameRect = new Rect(); @Override public boolean onInterceptTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final Rect frame = mFrameRect; // Are we touching mHost ? mHost.getHitRect(frame); if (!frame.contains(x, y)) { return false; } return true; }

Slide 34

Slide 34 text

private final Rect mFrameRect = new Rect(); @Override public boolean onInterceptTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final Rect frame = mFrameRect; // Are we touching mHost ? mHost.getHitRect(frame); if (!frame.contains(x, y)) { return false; } return true; } create a single Rect

Slide 35

Slide 35 text

private final Rect mFrameRect = new Rect(); @Override public boolean onInterceptTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final Rect frame = mFrameRect; // Are we touching mHost ? mHost.getHitRect(frame); if (!frame.contains(x, y)) { return false; } return true; } create a single Rect always use the same instance of Rect

Slide 36

Slide 36 text

REMEMBER STRINGS ARE ELLIGIBLE TO GARBAGE COLLECTION

Slide 37

Slide 37 text

public class CharArrayBufferAdapter extends CursorAdapter { private interface DataQuery { int TITLE = 0; int SUBTITLE = 1; } public CharArrayBufferAdapter(Context context, Cursor c) { super(context, c); } @Override public void bindView(View view, Context context, Cursor cursor) { final TwoLineListItem item = (TwoLineListItem) view; item.getText1().setText(cursor.getString(DataQuery.TITLE)); item.getText2().setText(cursor.getString(DataQuery.SUBTITLE)); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return LayoutInflater.from(context).inflate( android.R.layout.two_line_list_item, parent, false); } }

Slide 38

Slide 38 text

public class CharArrayBufferAdapter extends CursorAdapter { private interface DataQuery { int TITLE = 0; int SUBTITLE = 1; } public CharArrayBufferAdapter(Context context, Cursor c) { super(context, c); } @Override public void bindView(View view, Context context, Cursor cursor) { final TwoLineListItem item = (TwoLineListItem) view; item.getText1().setText( ); item.getText2().setText( ); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return LayoutInflater.from(context).inflate( android.R.layout.two_line_list_item, parent, false); } } cursor.getString(DataQuery.TITLE) cursor.getString(DataQuery.SUBTITLE) getString means new String

Slide 39

Slide 39 text

private static class ViewHolder { CharArrayBuffer titleBuffer = new CharArrayBuffer(128); CharArrayBuffer subtitleBuffer = new CharArrayBuffer(128); } @Override public void bindView(View view, Context context, Cursor cursor) { final TwoLineListItem item = (TwoLineListItem) view; final ViewHolder holder = (ViewHolder) view.getTag(); final CharArrayBuffer titleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.TITLE, titleBuffer); item.getText1().setText(titleBuffer.data, 0, titleBuffer.sizeCopied); final CharArrayBuffer subtitleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.SUBTITLE, subtitleBuffer); item.getText2().setText(subtitleBuffer.data, 0, subtitleBuffer.sizeCopied); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { final View v = LayoutInflater.from(context).inflate( android.R.layout.two_line_list_item, parent, false); v.setTag(new ViewHolder()); return v; }

Slide 40

Slide 40 text

private static class ViewHolder { CharArrayBuffer titleBuffer = new CharArrayBuffer(128); CharArrayBuffer subtitleBuffer = new CharArrayBuffer(128); } @Override public void bindView(View view, Context context, Cursor cursor) { final TwoLineListItem item = (TwoLineListItem) view; final ViewHolder holder = (ViewHolder) view.getTag(); final CharArrayBuffer titleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.TITLE, titleBuffer); item.getText1().setText(titleBuffer.data, 0, titleBuffer.sizeCopied); final CharArrayBuffer subtitleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.SUBTITLE, subtitleBuffer); item.getText2().setText(subtitleBuffer.data, 0, subtitleBuffer.sizeCopied); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { final View v = LayoutInflater.from(context).inflate( android.R.layout.two_line_list_item, parent, false); v.setTag(new ViewHolder()); return v; } attach buffers to the itemview

Slide 41

Slide 41 text

private static class ViewHolder { CharArrayBuffer titleBuffer = new CharArrayBuffer(128); CharArrayBuffer subtitleBuffer = new CharArrayBuffer(128); } @Override public void bindView(View view, Context context, Cursor cursor) { final TwoLineListItem item = (TwoLineListItem) view; final ViewHolder holder = (ViewHolder) view.getTag(); final CharArrayBuffer titleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.TITLE, titleBuffer); item.getText1().setText(titleBuffer.data, 0, titleBuffer.sizeCopied); final CharArrayBuffer subtitleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.SUBTITLE, subtitleBuffer); item.getText2().setText(subtitleBuffer.data, 0, subtitleBuffer.sizeCopied); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { final View v = LayoutInflater.from(context).inflate( android.R.layout.two_line_list_item, parent, false); v.setTag(new ViewHolder()); return v; } attach buffers to the itemview get the buffers

Slide 42

Slide 42 text

private static class ViewHolder { CharArrayBuffer titleBuffer = new CharArrayBuffer(128); CharArrayBuffer subtitleBuffer = new CharArrayBuffer(128); } @Override public void bindView(View view, Context context, Cursor cursor) { final TwoLineListItem item = (TwoLineListItem) view; final ViewHolder holder = (ViewHolder) view.getTag(); final CharArrayBuffer titleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.TITLE, titleBuffer); item.getText1().setText(titleBuffer.data, 0, titleBuffer.sizeCopied); final CharArrayBuffer subtitleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.SUBTITLE, subtitleBuffer); item.getText2().setText(subtitleBuffer.data, 0, subtitleBuffer.sizeCopied); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { final View v = LayoutInflater.from(context).inflate( android.R.layout.two_line_list_item, parent, false); v.setTag(new ViewHolder()); return v; } attach buffers to the itemview get the buffers copy column content to buffer

Slide 43

Slide 43 text

private static class ViewHolder { CharArrayBuffer titleBuffer = new CharArrayBuffer(128); CharArrayBuffer subtitleBuffer = new CharArrayBuffer(128); } @Override public void bindView(View view, Context context, Cursor cursor) { final TwoLineListItem item = (TwoLineListItem) view; final ViewHolder holder = (ViewHolder) view.getTag(); final CharArrayBuffer titleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.TITLE, titleBuffer); item.getText1().setText(titleBuffer.data, 0, titleBuffer.sizeCopied); final CharArrayBuffer subtitleBuffer = holder.titleBuffer; cursor.copyStringToBuffer(DataQuery.SUBTITLE, subtitleBuffer); item.getText2().setText(subtitleBuffer.data, 0, subtitleBuffer.sizeCopied); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { final View v = LayoutInflater.from(context).inflate( android.R.layout.two_line_list_item, parent, false); v.setTag(new ViewHolder()); return v; } attach buffers to the itemview get the buffers set the buffer to the TextView copy column content to buffer

Slide 44

Slide 44 text

FLATTEN VIEW HIERARCHIES

Slide 45

Slide 45 text

PERFECTLY MASTER THE ANDROID UI TOOLKIT

Slide 46

Slide 46 text

1 2 3 GridLayout RelativeLayout

Slide 47

Slide 47 text

1GridLayout A Layout that places its children in a rectangular grid

Slide 48

Slide 48 text

1GridLayout 12 VIEWS

Slide 49

Slide 49 text

1GridLayout 12 VIEWS 9 VIEWS

Slide 50

Slide 50 text

2 RelativeLayout A Layout where the positions of the children can be described in relation to each other or to the parent.

Slide 51

Slide 51 text

2 RelativeLayout A Layout where the positions of the children can be described in relation to each other or to the parent. Flatten hierarchy

Slide 52

Slide 52 text

3

Slide 53

Slide 53 text

3 INVALID XML DOCUMENT

Slide 54

Slide 54 text

3

Slide 55

Slide 55 text

3 useless ViewGroup

Slide 56

Slide 56 text

3

Slide 57

Slide 57 text

COOK YOUR OWN VIEWS WHENEVER NECESSARY

Slide 58

Slide 58 text

goo.gl/ZQIZ4 AVélov beta

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

ListView’s padding

Slide 63

Slide 63 text

QUESTION What are the available techniques to create such a a great ListView header?

Slide 64

Slide 64 text

public class UnderlinedTextView extends TextView { private final Paint mPaint = new Paint(); private int mUnderlineHeight; public UnderlinedTextView(Context context) { super(context); } public UnderlinedTextView(Context context, AttributeSet attrs) { super(context, attrs); } public UnderlinedTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } }

Slide 65

Slide 65 text

public class UnderlinedTextView extends TextView { private final Paint mPaint = new Paint(); private int mUnderlineHeight; public UnderlinedTextView(Context context) { super(context); } public UnderlinedTextView(Context context, AttributeSet attrs) { super(context, attrs); } public UnderlinedTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } } extends TextView

Slide 66

Slide 66 text

@Override public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, mUnderlineHeight + bottom); } public void setUnderlineHeight(int underlineHeight) { if (underlineHeight < 0) underlineHeight = 0; if (underlineHeight != mUnderlineHeight) { mUnderlineHeight = underlineHeight; setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom() + mUnderlineHeight); } } public void setUnderlineColor(int underlineColor) { if (mPaint.getColor() != underlineColor) { mPaint.setColor(underlineColor); invalidate(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawRect(0, getHeight() - mUnderlineHeight, getWidth(), getHeight(), mPaint); }

Slide 67

Slide 67 text

@Override public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, mUnderlineHeight + bottom); } public void setUnderlineHeight(int underlineHeight) { if (underlineHeight < 0) underlineHeight = 0; if (underlineHeight != mUnderlineHeight) { mUnderlineHeight = underlineHeight; setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom() + mUnderlineHeight); } } public void setUnderlineColor(int underlineColor) { if (mPaint.getColor() != underlineColor) { mPaint.setColor(underlineColor); invalidate(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawRect(0, getHeight() - mUnderlineHeight, getWidth(), getHeight(), mPaint); } use padding to avoid measurements use padding to avoid measurements

Slide 68

Slide 68 text

@Override public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, mUnderlineHeight + bottom); } public void setUnderlineHeight(int underlineHeight) { if (underlineHeight < 0) underlineHeight = 0; if (underlineHeight != mUnderlineHeight) { mUnderlineHeight = underlineHeight; setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom() + mUnderlineHeight); } } public void setUnderlineColor(int underlineColor) { if (mPaint.getColor() != underlineColor) { mPaint.setColor(underlineColor); invalidate(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawRect(0, getHeight() - mUnderlineHeight, getWidth(), getHeight(), mPaint); } extend TextView default behavior use padding to avoid measurements use padding to avoid measurements

Slide 69

Slide 69 text

DEVELOPERS TEND TO BE «NEVER SATISFIED» PEOPLE THAT ARE ALWAYS ASKING FOR MORE ASSERTION Yep, this is the exact same definition of «being French»

Slide 70

Slide 70 text

VIEW & have been designed to be easily extended via inheritance VIEWGROUP

Slide 71

Slide 71 text

GET OFF THE MAIN THREAD

Slide 72

Slide 72 text

DON’T BLOCK THE MAIN THREAD

Slide 73

Slide 73 text

THE MAIN THREAD IS WHERE ALL EVENTS ARE DISPATCHED TRUE FACT

Slide 74

Slide 74 text

EVENT QUEUE EVENT LOOP

Slide 75

Slide 75 text

EVENT QUEUE EVENT LOOP

Slide 76

Slide 76 text

onMeasure onLayout onDrawn onTouchEvent BE MINDFUL WITH CRITICAL METHODS

Slide 77

Slide 77 text

3 disc network hardware MAIN OPERATIONS to perform in background

Slide 78

Slide 78 text

MOVE WORK OFF THE MAIN THREAD

Slide 79

Slide 79 text

THE JAVA WAY... Plain old Java java.util.concurrent

Slide 80

Slide 80 text

THE JAVA WAY... Plain old Java java.util.concurrent FOR BEARDED MEN ONLY

Slide 81

Slide 81 text

YOU ARE MORE THIS KIND OF GUY?

Slide 82

Slide 82 text

THE ANDROID WAY... Handler AsyncTask Loader IntentService

Slide 83

Slide 83 text

DO LESS AND APPROPRIATELY

Slide 84

Slide 84 text

CACHE VALUES NOT TO OVER COMPUTE THEM

Slide 85

Slide 85 text

@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate( android.R.layout.two_line_list_item, // resource parent, // parent false); // attach } ((TextView) convertView.findViewById(android.R.id.text1)) .setText(TITLES.get(position)); ((TextView) convertView.findViewById(android.R.id.text2)) .setText(SUBTITLES.get(position)); return convertView; } DO YOU REMEMBER THIS?

Slide 86

Slide 86 text

QUESTION How to avoid having findViewById executed at every call to getView?

Slide 87

Slide 87 text

QUESTION How to avoid having findViewById executed at every call to getView? ANSWER Caching it!

Slide 88

Slide 88 text

static class ViewHolder { TextView text1; TextView text2; } VIEW HOLDER

Slide 89

Slide 89 text

@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate( android.R.layout.two_line_list_item, parent, false); holder = new ViewHolder(); holder.text1 = (TextView) convertView.findViewById(android.R.id.text1); holder.text2 = (TextView) convertView.findViewById(android.R.id.text2); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.text1.setText(STRINGS.get(position)); holder.text2.setText(STRINGS.get(position)); return convertView; } AVOID REPETITIVE EXECUTIONS

Slide 90

Slide 90 text

@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate( android.R.layout.two_line_list_item, parent, false); holder = new ViewHolder(); holder.text1 = (TextView) convertView.findViewById(android.R.id.text1); holder.text2 = (TextView) convertView.findViewById(android.R.id.text2); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.text1.setText(STRINGS.get(position)); holder.text2.setText(STRINGS.get(position)); return convertView; } AVOID REPETITIVE EXECUTIONS cache refs to the children

Slide 91

Slide 91 text

@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate( android.R.layout.two_line_list_item, parent, false); holder = new ViewHolder(); holder.text1 = (TextView) convertView.findViewById(android.R.id.text1); holder.text2 = (TextView) convertView.findViewById(android.R.id.text2); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.text1.setText(STRINGS.get(position)); holder.text2.setText(STRINGS.get(position)); return convertView; } AVOID REPETITIVE EXECUTIONS cache refs to the children retrieve cached refs

Slide 92

Slide 92 text

@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate( android.R.layout.two_line_list_item, parent, false); holder = new ViewHolder(); holder.text1 = (TextView) convertView.findViewById(android.R.id.text1); holder.text2 = (TextView) convertView.findViewById(android.R.id.text2); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.text1.setText(STRINGS.get(position)); holder.text2.setText(STRINGS.get(position)); return convertView; } AVOID REPETITIVE EXECUTIONS cache refs to the children retrieve cached refs use cached refs

Slide 93

Slide 93 text

GIVE PRORITITY TO YOUR THREADS

Slide 94

Slide 94 text

private static final int MSG_LOAD = 0xbeef; private Handler mHandler = new Handler(Looper.getMainLooper()); public void loadUrl(final String url) { new Thread(new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); InputStream i = null; Bitmap b = null; try { i = new URL(url).openStream(); b = BitmapFactory.decodeStream(i); } catch (Exception e) { } finally { if (i != null) try { i.close(); } catch (IOException e) {} } Message.obtain(mHandler, MSG_LOAD, b).sendToTarget(); } }).start(); }

Slide 95

Slide 95 text

private static final int MSG_LOAD = 0xbeef; private Handler mHandler = new Handler(Looper.getMainLooper()); public void loadUrl(final String url) { new Thread(new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); InputStream i = null; Bitmap b = null; try { i = new URL(url).openStream(); b = BitmapFactory.decodeStream(i); } catch (Exception e) { } finally { if (i != null) try { i.close(); } catch (IOException e) {} } Message.obtain(mHandler, MSG_LOAD, b).sendToTarget(); } }).start(); } set a low priority

Slide 96

Slide 96 text

private static final int MSG_LOAD = 0xbeef; private Handler mHandler = new Handler(Looper.getMainLooper()); public void loadUrl(final String url) { new Thread(new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); InputStream i = null; Bitmap b = null; try { i = new URL(url).openStream(); b = BitmapFactory.decodeStream(i); } catch (Exception e) { } finally { if (i != null) try { i.close(); } catch (IOException e) {} } Message.obtain(mHandler, MSG_LOAD, b).sendToTarget(); } }).start(); } set a low priority post a Message to callback the UI Thread

Slide 97

Slide 97 text

FAVOR UI TO BACKGROUND COMPUTATIONS

Slide 98

Slide 98 text

SCROLL STATES 3 ListView can have The current ListView’s scroll state can be obtained using an OnScrollListener

Slide 99

Slide 99 text

1 2 3 IDLE TOUCH SCROLL FLING

Slide 100

Slide 100 text

1IDLE The view is not scrolling

Slide 101

Slide 101 text

2TOUCH SCROLL The user is scrolling using touch, and their finger is still on the screen

Slide 102

Slide 102 text

3FLING The user had previously been scrolling using touch and had performed a fling. The animation is now coasting to a stop Avoid blocking the animation pause worker Threads

Slide 103

Slide 103 text

getListView().setOnScrollListener(mScrollListener); private OnScrollListener mScrollListener = new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { ImageLoader imageLoader = ImageLoader.get(getContext()); imageLoader.setPaused(scrollState == OnScrollListener.SCROLL_STATE_FLING); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // Nothing to do } };

Slide 104

Slide 104 text

getListView().setOnScrollListener(mScrollListener); private OnScrollListener mScrollListener = new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { ImageLoader imageLoader = ImageLoader.get(getContext()); imageLoader.setPaused(scrollState == OnScrollListener.SCROLL_STATE_FLING); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // Nothing to do } }; set the listener

Slide 105

Slide 105 text

getListView().setOnScrollListener(mScrollListener); private OnScrollListener mScrollListener = new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { ImageLoader imageLoader = ImageLoader.get(getContext()); imageLoader.setPaused(scrollState == OnScrollListener.SCROLL_STATE_FLING); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // Nothing to do } }; (un)pause ImageLoader set the listener

Slide 106

Slide 106 text

CONCLUSION

Slide 107

Slide 107 text

DO NOT BLOCK THE MAIN THREAD

Slide 108

Slide 108 text

FLATTEN YOUR VIEW HIERARCHY

Slide 109

Slide 109 text

LAZY LOAD AND REUSE WHENEVER POSSIBLE

Slide 110

Slide 110 text

PRIORITIZE TASKS: UI ALWAYS COMES FIRST

Slide 111

Slide 111 text

CYRIL MOTTIER @cyrilmottier android.cyrilmottier.com