KEMBAR78
Android programming -_pushing_the_limits | PDF
Pushing the Limits
Erik Hellman, Spotify
google.com/+ErikHellman
@ErikHellman
Android
Programming
2
wiley.com/go/ptl/androidprogramming
3
Improved memory management
Efficient multi-threading and -processing
Using Android components correctly
Improved memory management
4
Use a static factory method whenever possible
5
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.”
Message.java
6
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++;	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  }	
  
}	
  
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>
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>
How to get more memory - Part 3
9
data	
  =	
  malloc(sizeof(data_struct));
10
Improved memory management
Efficient multi-threading and -processing
Using Android components correctly
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();	
  
}
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;	
  
}	
  
Why Handler?
13
•Scheduling of messages

•Cancelling of messages

•Reduces GC calls
Using the process attribute
14
Why a separate process?
15
•Better isolation from crashes

•Binder transactions uses a thread pool

•Double the memory!
16
Improved memory management
Efficient multi-threading and -processing
Using Android components correctly
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>
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...	
  
	
  	
  	
  	
  }	
  
}	
  
Application Context limitations
19
•Inflating layout uses default theme

•Starting Activity creates a new task
20
Activity - saving state
Activity.onSaveInstanceState()
Activity.onPause()
or
21
onSaveInstanceState() is not called…
•…when user presses back

•…when you call Activity.finish()
22
Fragment
23
android.app.Fragment
android.support.v4.app.Fragment
or
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'])	
  
}	
  
Typical Fragment crash
25
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;	
  
	
  	
  	
  	
  }	
  
}
Typical Fragment crash
26
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
27
Use a Loader whenever possible and
cancel remaining requests in onPause()/onDetach()!
Binding to Services
28
@Override	
  
public	
  IBinder	
  onBind(Intent	
  intent)	
  {	
  
	
   return	
  null;	
  //	
  NEVER	
  do	
  this	
  
}
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!");	
  
	
   }	
  
}	
  
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!:");	
  
	
   }	
  
}	
  
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
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;	
  
}
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;	
  
	
  	
  	
  	
  }	
  
}
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;	
  
}	
  
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>	
  
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>	
  
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

Android programming -_pushing_the_limits

  • 1.
    Pushing the Limits ErikHellman, Spotify google.com/+ErikHellman @ErikHellman Android Programming
  • 2.
  • 3.
    3 Improved memory management Efficientmulti-threading and -processing Using Android components correctly
  • 4.
  • 5.
    Use a staticfactory method whenever possible 5 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.”
  • 6.
    Message.java 6 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++;                          }                  }          }   }  
  • 7.
    How to getmore 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>
  • 8.
    How to getmore 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>
  • 9.
    How to getmore memory - Part 3 9 data  =  malloc(sizeof(data_struct));
  • 10.
    10 Improved memory management Efficientmulti-threading and -processing Using Android components correctly
  • 11.
    11 Always use aHandler!* * 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();   }
  • 12.
    12 Always use aHandler!* * 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;   }  
  • 13.
    Why Handler? 13 •Scheduling ofmessages
 •Cancelling of messages
 •Reduces GC calls
  • 14.
    Using the processattribute 14
  • 15.
    Why a separateprocess? 15 •Better isolation from crashes
 •Binder transactions uses a thread pool
 •Double the memory!
  • 16.
    16 Improved memory management Efficientmulti-threading and -processing Using Android components correctly
  • 17.
    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>
  • 18.
    Android Singletons usingContext 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...          }   }  
  • 19.
    Application Context limitations 19 •Inflatinglayout uses default theme
 •Starting Activity creates a new task
  • 20.
    20 Activity - savingstate Activity.onSaveInstanceState() Activity.onPause() or
  • 21.
    21 onSaveInstanceState() is notcalled… •…when user presses back
 •…when you call Activity.finish()
  • 22.
  • 23.
  • 24.
    Support library canbe upgraded! 24 dependencies  {          compile  'com.android.support:appcompat-­‐v7:+'          compile  'com.android.support:support-­‐v4:19.1.+'          compile  fileTree(dir:  'libs',  include:  ['*.jar'])   }  
  • 25.
    Typical Fragment crash 25 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;          }   }
  • 26.
    Typical Fragment crash 26 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
  • 27.
    27 Use a Loaderwhenever possible and cancel remaining requests in onPause()/onDetach()!
  • 28.
    Binding to Services 28 @Override   public  IBinder  onBind(Intent  intent)  {     return  null;  //  NEVER  do  this   }
  • 29.
    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!");     }   }  
  • 30.
    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!:");     }   }  
  • 31.
    Service considerations 31 •A binderIntent 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
  • 32.
    Most common ContentProvidermistake :) 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;   }
  • 33.
    Most common ContentProvidermistake :) 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;          }   }
  • 34.
    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;   }  
  • 35.
    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>  
  • 36.
    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>  
  • 37.
    37 “Many problems withAndroid apps can be fixed with 
 a proper use of the Handler class.” - Erik Hellman Get my book at wiley.com/go/ptl/androidprogramming