Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android Programming: Pushing the limits - DroidCon DE 2014

Android Programming: Pushing the limits - DroidCon DE 2014

Advanced tips'n'tricks for Android developers based on the book "Android Programming: Pushing the limits"

Erik Hellman

May 08, 2014
Tweet

More Decks by Erik Hellman

Other Decks in Programming

Transcript

  1. 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.”
  2. 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++;                          }                  }          }   }  
  3. 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>
  4. 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>
  5. How to get more memory - Part 3 9 data

     =  malloc(sizeof(data_struct));
  6. 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();   }
  7. 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;   }  
  8. Why a separate process? 15 •Better isolation from crashes
 •Binder

    transactions uses a thread pool
 •Double the memory!
  9. 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>
  10. 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...          }   }  
  11. 22

  12. 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'])   }  
  13. 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;          }   }
  14. 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
  15. Binding to Services 28 @Override   public  IBinder  onBind(Intent  intent)

     {     return  null;  //  NEVER  do  this   }
  16. 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!");     }   }  
  17. 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!:");     }   }  
  18. 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
  19. 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;   }
  20. 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;          }   }
  21. 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;   }  
  22. 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>  
  23. 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>  
  24. 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