37
Pushing the Limits Erik Hellman, Spotify google.com/+ErikHellman @ErikHellman Android Programming

Android programming -_pushing_the_limits

Embed Size (px)

Citation preview

Page 1: Android programming -_pushing_the_limits

Pushing the Limits Erik Hellman, Spotify google.com/+ErikHellman @ErikHellman

Android Programming

Page 2: Android programming -_pushing_the_limits

2

wiley.com/go/ptl/androidprogramming

Page 3: Android programming -_pushing_the_limits

3

Improved memory management

Efficient multi-threading and -processing

Using Android components correctly

Page 4: Android programming -_pushing_the_limits

Improved memory management4

Page 5: Android programming -_pushing_the_limits

Use a static factory method whenever possible5

Effective Java (2nd ed.): Item 1!“A second advantage of static factory methods is that, unlike constructors, they are not required to create a new object each time they’re invoked.”

Page 6: Android programming -_pushing_the_limits

Message.java6

public  final  class  Message  implements  Parcelable  {          /*package*/  Message  next;  !        private  static  final  Object  sPoolSync  =  new  Object();          private  static  Message  sPool;          private  static  int  sPoolSize  =  0;  !        private  static  final  int  MAX_POOL_SIZE  =  50;  !        public  static  Message  obtain()  {                  synchronized  (sPoolSync)  {                          if  (sPool  !=  null)  {                                  Message  m  =  sPool;                                  sPool  =  m.next;                                  m.next  =  null;                                  sPoolSize-­‐-­‐;                                  return  m;                          }                  }                  return  new  Message();          }

     public  void  recycle()  {                  clearForRecycle();  !                synchronized  (sPoolSync)  {                          if  (sPoolSize  <  MAX_POOL_SIZE)  {                                  next  =  sPool;                                  sPool  =  this;                                  sPoolSize++;                          }                  }          }  }  

Page 7: Android programming -_pushing_the_limits

How to get more memory - Part 1

7

<application          android:allowBackup="true"          android:icon="@drawable/ic_launcher"          android:label="@string/app_name"          android:name=".MyApplication"          android:largeHeap="true"          android:theme="@style/AppTheme"  >          …  </application>

Page 8: Android programming -_pushing_the_limits

How to get more memory - Part 2

8

<service          android:name="se.hellsoft.apptl.app.MyService"          android:enabled="true"          android:exported="false"          android:process="se.hellsoft.apptl.service">  </service>

Page 9: Android programming -_pushing_the_limits

How to get more memory - Part 3

9

data  =  malloc(sizeof(data_struct));

Page 10: Android programming -_pushing_the_limits

10

Improved memory management

Efficient multi-threading and -processing

Using Android components correctly

Page 11: Android programming -_pushing_the_limits

11

Always use a Handler!*

* Except when you shouldn’t…

@Override  protected  void  onCreate(Bundle  savedInstanceState)  {          super.onCreate(savedInstanceState);          HandlerThread  backgroundThread  =  new  HandlerThread("background");          backgroundThread.start();          mBackgroundHandler  =  new  Handler(backgroundThread.getLooper(),  this);          mUiHandler  =  new  Handler(this);  }  !@Override  protected  void  onDestroy()  {          super.onDestroy();          mBackgroundHandler.removeCallbacksAndMessages(null);          mBackgroundHandler.getLooper().quit();  }

Page 12: Android programming -_pushing_the_limits

12

Always use a Handler!*

* Except when you shouldn’t…

public  void  onStartBackgroundJob(View  view)  {          mBackgroundHandler.obtainMessage(BACKGROUND_JOB).sendToTarget();  }  !@Override  public  boolean  handleMessage(Message  msg)  {          switch  (msg.what)  {                  case  BACKGROUND_JOB:                          Bitmap  result  =  null;                          //  Processing  goes  here...                            mUiHandler.obtainMessage(UPDATE_UI,  result).sendToTarget();                          break;                  case  UPDATE_UI:                          ((ImageView)  findViewById(R.id.photo))                                                                              .setImageBitmap((Bitmap)  msg.obj);                          break;          }          return  true;  }  

Page 13: Android programming -_pushing_the_limits

Why Handler?

13

• Scheduling of messages

• Cancelling of messages

• Reduces GC calls

Page 14: Android programming -_pushing_the_limits

Using the process attribute

14

Page 15: Android programming -_pushing_the_limits

Why a separate process?

15

• Better isolation from crashes

• Binder transactions uses a thread pool

• Double the memory!

Page 16: Android programming -_pushing_the_limits

16

Improved memory management

Efficient multi-threading and -processing

Using Android components correctly

Page 17: Android programming -_pushing_the_limits

Application component

17

<application          android:allowBackup="true"          android:icon="@drawable/ic_launcher"          android:label="@string/app_name"          android:name=“.MyApplication"          android:theme="@style/AppTheme"  >  !                ...  </application>

Page 18: Android programming -_pushing_the_limits

Android Singletons using Context

18

public  class  MySingleton  {          private  static  MySingleton  sInstance;          private  Context  mContext;  !        private  MySingleton(Context  context)  {                  mContext  =  context;          }                    public  MySingleton  getInstance(Context  context)  {                  if(sInstance  ==  null)  {                          sInstance  =  new  MySingleton(context.getApplicationContext());                  }                  return  sInstance;          }                    public  void  doStuffThatRequireContext()  {                  //  Do  stuff  here...          }  }  

Page 19: Android programming -_pushing_the_limits

Application Context limitations

19

• Inflating layout uses default theme

• Starting Activity creates a new task

Page 20: Android programming -_pushing_the_limits

20

Activity - saving state

Activity.onSaveInstanceState()

Activity.onPause()

or

Page 21: Android programming -_pushing_the_limits

21

onSaveInstanceState() is not called…

• …when user presses back

• …when you call Activity.finish()

Page 22: Android programming -_pushing_the_limits

22

Page 23: Android programming -_pushing_the_limits

Fragment23

android.app.Fragment

android.support.v4.app.Fragment

or

Page 24: Android programming -_pushing_the_limits

Support library can be upgraded!24

dependencies  {          compile  'com.android.support:appcompat-­‐v7:+'          compile  'com.android.support:support-­‐v4:19.1.+'          compile  fileTree(dir:  'libs',  include:  ['*.jar'])  }  

Page 25: Android programming -_pushing_the_limits

Typical Fragment crash25

public  static  class  FirstFragment  extends  Fragment  {          private  ImageView  mImageView;  !        @Override          public  View  onCreateView(LayoutInflater  inflater,  ViewGroup  container,                                                            Bundle  savedInstanceState)  {                  View  rootView  =  inflater.inflate(R.layout.fragment_main,  container,  false);                  mImageView  =  (ImageView)  rootView.findViewById(R.id.photo);  !                new  AsyncTask<Void,  Void,  Bitmap>()  {                          @Override                          protected  Bitmap  doInBackground(Void...  params)  {                                  SystemClock.sleep(10000);  //  Fake  long  processing...                                  return  BitmapFactory.decodeResource(getResources(),                                                                                                                                  R.drawable.happy_android);                          }  !                        @Override                          protected  void  onPostExecute(Bitmap  bitmap)  {                                  mImageView.setImageBitmap(bitmap);                          }                  }.execute();  !                return  rootView;          }  }

Page 26: Android programming -_pushing_the_limits

Typical Fragment crash26

E/AndroidRuntime(  1245):  FATAL  EXCEPTION:  AsyncTask  #1  E/AndroidRuntime(  1245):  Process:  se.hellsoft.apptl.app,  PID:  1245  E/AndroidRuntime(  1245):  java.lang.RuntimeException:  An  error  occured  while  executing  doInBackground()  E/AndroidRuntime(  1245):          at  android.os.AsyncTask$3.done(AsyncTask.java:300)  E/AndroidRuntime(  1245):          at  java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)  E/AndroidRuntime(  1245):          at  java.util.concurrent.FutureTask.setException(FutureTask.java:222)  E/AndroidRuntime(  1245):          at  java.util.concurrent.FutureTask.run(FutureTask.java:242)  E/AndroidRuntime(  1245):          at  android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)  E/AndroidRuntime(  1245):          at  java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)  E/AndroidRuntime(  1245):          at  java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)  E/AndroidRuntime(  1245):          at  java.lang.Thread.run(Thread.java:841)  E/AndroidRuntime(  1245):  Caused  by:  java.lang.IllegalStateException:  Fragment  FirstFragment{52836468}  not  attached  to  Activity  E/AndroidRuntime(  1245):          at  android.support.v4.app.Fragment.getResources(Fragment.java:601)  E/AndroidRuntime(  1245):          at  se.hellsoft.apptl.app.MainActivity$FirstFragment$1.doInBackground(MainActivity.java:115)  E/AndroidRuntime(  1245):          at  se.hellsoft.apptl.app.MainActivity$FirstFragment$1.doInBackground(MainActivity.java:109)  E/AndroidRuntime(  1245):          at  android.os.AsyncTask$2.call(AsyncTask.java:288)  E/AndroidRuntime(  1245):          at  java.util.concurrent.FutureTask.run(FutureTask.java:237)  E/AndroidRuntime(  1245):          ...  4  more

Page 27: Android programming -_pushing_the_limits

27

Use a Loader whenever possible and cancel remaining requests in onPause()/onDetach()!

Page 28: Android programming -_pushing_the_limits

Binding to Services

28

@Override  public  IBinder  onBind(Intent  intent)  {     return  null;  //  NEVER  do  this  }

Page 29: Android programming -_pushing_the_limits

Binding to Services

29

@Override  public  IBinder  onBind(Intent  intent)  {     String  action  =  intent.getAction();     if(ACTION_FIRST_BINDER.equals(action))  {       return  mFirstBinder;     }  else  if(ACTION_SECOND_BINDER.equals(action))  {       return  mSecondBinder;     }  else  {       throw  new  RuntimeException("Unexpected  error!");     }  }  

Page 30: Android programming -_pushing_the_limits

Binding to Services

30

@Override  public  IBinder  onBind(Intent  intent)  {     Uri  data  =  intent.getData();     String  param  =  data.getQueryParameter("param");     if(FIRST_BINDER.equals(param))  {       return  mFirstBinder;     }  else  if(SECOND_BINDER.equals(param))  {       return  mSecondBinder;     }  else  {       throw  new  RuntimeException("Unexpected  error!:");     }  }  

Page 31: Android programming -_pushing_the_limits

Service considerations

31

• A binder Intent is identified by the action string and data Uri!

!

•onBind() and onUnbind() only called once per unique Intent

•START_STICKY will give a null  Intent when Service is restarted by the system

• Binder calls run on local thread for process-local service

Page 32: Android programming -_pushing_the_limits

Most common ContentProvider mistake :)

32

@Override  public  Cursor  query(Uri  uri,  String[]  projection,  String  selection,                                          String[]  selectionArgs,  String  sortOrder)  {          SQLiteDatabase  db  =  mDatabaseHelper.getReadableDatabase();          int  match  =  sUriMatcher.match(uri);          Cursor  cursor  =  null;          switch  (match)  {                  case  ALL_ROWS:                          cursor  =  db.query(Contract.TABLE_NAME,  projection,  selection,                                            selectionArgs,  "",  "",  sortOrder);                          break;                  case  SINGLE_ROW:                          String  id  =  uri.getLastPathSegment();                          cursor  =    db.query(Contract.TABLE_NAME,  projection,  "_id  =  ?",                                            new  String[]{id},  "",  "",  sortOrder);                          break;          }          if(cursor  !=  null)  {                  cursor.setNotificationUri(getContext().getContentResolver(),  uri);          }          return  cursor;  }

Page 33: Android programming -_pushing_the_limits

Most common ContentProvider mistake :)

33

@Override  public  Uri  insert(Uri  uri,  ContentValues  values)  {        SQLiteDatabase  db  =  mDatabaseHelper.getWritableDatabase();          int  match  =  sUriMatcher.match(uri);          switch  (match)  {                  case  ALL_ROWS:                          long  newId  =  db.insert(Contract.TABLE_NAME,  "",  values);                          if(newId  !=  -­‐1)  {                                  getContext().getContentResolver().notifyChange(uri,  null);                          }                          return  Uri.withAppendedPath(uri,  String.valueOf(newId));                  default:                          return  null;          }  }

Page 34: Android programming -_pushing_the_limits

Don’t forget bulkInsert() !!!

34

@Override  public  int  bulkInsert(Uri  uri,  ContentValues[]  values)  {          SQLiteDatabase  db  =  mDatabaseHelper.getWritableDatabase();          int  match  =  sUriMatcher.match(uri);          int  inserted  =  0;          switch  (match)  {                  case  TASKS_CODE:                          try  {                                  db.beginTransaction();                                  for  (ContentValues  value  :  values)  {                                          long  id  =  db.insert(Contract.TABLE_NAME,  "",  value);                                          if  (id  <=  0)  throw  new  SQLException("Failed  with  inserting.");                                          inserted++;                                  }                                  db.setTransactionSuccessful();                                  getContext().getContentResolver().notifyChange(uri,  null);                          }  finally  {                                  db.endTransaction();                          }          }          return  inserted;  }  

Page 35: Android programming -_pushing_the_limits

Useful Broadcasts - Auto-starting

35

<receiver  android:name="se.hellsoft.myapp.AutoStartReceiver"  >          <intent-­‐filter>                  <action  android:name="android.intent.action.BOOT_COMPLETED"  />        </intent-­‐filter>  </receiver>  

<receiver  android:name=“se.hellsoft.myapp.AutoStartReceiver"                      android:process=“:autostarter”>          <intent-­‐filter>                  <action  android:name="android.intent.action.USER_PRESENT"  />        </intent-­‐filter>  </receiver>  

Page 36: Android programming -_pushing_the_limits

Useful Broadcasts - Monitoring the network

36

<receiver  android:name=“se.hellsoft.myapp.NetworkMonitor">          <intent-­‐filter>                  <action  android:name="android.net.conn.CONNECTIVITY_CHANGE"  />                  <action  android:name="android.net.wifi.supplicant.CONNECTION_CHANGE"  />                  <action  android:name="android.net.wifi.WIFI_AP_STATE_CHANGED"  />        </intent-­‐filter>  </receiver>  

Page 37: Android programming -_pushing_the_limits

37

“Many problems with Android apps can be fixed with a proper use of the Handler class.” - Erik Hellman

Get my book at wiley.com/go/ptl/androidprogramming