Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

That’s My App - Running in Your Background - Draining Your Battery

6,115 views

Published on

You have seen the ads where Android based devices like to brag about how awesome their multitasking is and now even the iPhone claims to have multitasking. Unfortunately it’s pseudo-multitasking borrowed from Android, but fear not. Android has “real” multitasking as well. It’s easy to do, but even easier to screw up. In this talk you’ll learn how to do it right, and how to do it without killing a phone’s battery. We’ll discuss the dreaded “P” word (polling), as well as alternatives such as Android’s cloud to device messaging and persistent connections.

  • Be the first to comment

That’s My App - Running in Your Background - Draining Your Battery

  1. 1. THAT’S MY APP ~ RUNNING IN YOUR BACKGROUND ~ DRAINING YOUR BATTERY
  2. 2. WHO AM I? Michael Galpin Mobile Architect, eBay Author, Android in Practice @michaelg
  3. 3. AGENDA Motivation Strategies Death Resurrection Advanced
  4. 4. TRUE MULTI-TASKING Multiple applications, executing simultaneously Consuming memory CPU cycles (battery) Using I/O But not like Desktop
  5. 5. DATA SYNC Apps don’t live in a vacuum Users use other apps / website Other users interact Keep data sync Improve user experience
  6. 6. LOOK MA, NO HANDS Start your app without the user launching it Extend app execution after app is closed (long running tasks) Interact with other apps
  7. 7. STRATEGIES
  8. 8. SERVICES 101 <manifest> <application android:icon="@drawable/icon" android:label="@string/app_name"> <service android:process=":stocks_background" android:name=".PortfolioManagerService" android:icon="@drawable/icon" android:label="@string/service_name"/> <!-- ... --> </manifest>
  9. 9. SERVICES 101 public class PortfolioManagerService extends Service { @Override public void onCreate() { } @Override public int onStartCommand(Intent intent, int flags, int startId) { } @Override public IBinder onBind(Intent intent) { } }
  10. 10. POLLING @Override public void onCreate() { super.onCreate(); final long fiveMinutes = 5*60*1000; final Handler handler = new Handler(); handler.postDelayed(new Runnable(){ public void run(){ updateStockData(); handler.postDelayed(this, fiveMinutes); } }, fiveMinutes); }
  11. 11. NOTIFICATIONS private void createHighPriceNotification(Stock stock) { // Get the system service NotificationManager mgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // Create the ticker int dollarBill = R.drawable.dollar_icon; String shortMsg = "High price alert: " + stock.getSymbol(); long time = System.currentTimeMillis(); Notification n = new Notification(dollarBill, shortMsg, time); // Create expanded info and what happens when tapped String title = stock.getName(); String msg = "Current price $" + stock.getCurrentPrice() + " is high"; Intent i = new Intent(this, NotificationDetails.class); i.putExtra("stock", stock); PendingIntent pi = PendingIntent.getActivity(this, 0, i, 0); n.setLatestEventInfo(this, title, msg, pi); // Sound! n.defaults |= Notification.DEFAULT_SOUND; // Vibrate!! long[] steps = {0, 500, 100, 200, 100, 200}; n.vibrate = steps; // Flashing LED lights !!! n.ledARGB = 0x80009500; n.ledOnMS = 250; n.ledOffMS = 500; n.flags |= Notification.FLAG_SHOW_LIGHTS; // Post the Notification mgr.notify(HIGH_PRICE_NOTIFICATION, n); }
  12. 12. START ME UP! <receiver android:name="PortfolioStartupReceiver" android:process=":stocks_background"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> public class PortfolioStartupReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent stockService = new Intent(context, PortfolioManagerService.class); context.startService(stockService); } }
  13. 13. DATA MANAGEMENT Service and app use the same data Use the Service for any access Service can cache data Pre-emptive retrieval
  14. 14. ACTIVITY -> SERVICE COMMUNICATION public class Stock implements Parcelable{ // fields, getter, setters private Stock(Parcel parcel){ this.readFromParcel(parcel); } public static final Parcelable.Creator<Stock> CREATOR = new Parcelable.Creator<Stock>() { public Stock createFromParcel(Parcel source) { return new Stock(source); Stock.aidl } public Stock[] newArray(int size) { package com.flexware.stocks; return new Stock[size]; } parcelable Stock; }; public int describeContents() { return 0; // nothing special here! IStockService.aidl } public void writeToParcel(Parcel parcel, int flags) { package com.flexware.stocks.service; parcel.writeString(symbol); parcel.writeDouble(maxPrice); import com.flexware.stocks.Stock; parcel.writeDouble(minPrice); ... interface IStockService{ } Stock addToPortfolio(in Stock stock); public void readFromParcel(Parcel parcel){ symbol = parcel.readString(); List<Stock> getPortfolio(); maxPrice = parcel.readDouble(); } minPrice = parcel.readDouble(); ... } }
  15. 15. ACTIVITY -> SERVICE COMMUNICATION Generated public interface IStockService extends IInterface{ /** Local-side IPC implementation stub class. */ public static abstract class Stub extends Binder implements IStockService{ } } public class PortfolioManagerService extends Service { @Override public IBinder onBind(Intent intent) { return new IStockService.Stub() { public Stock addToPortfolio(Stock stock) throws RemoteException { ... } public List<Stock> getPortfolio() throws RemoteException { ... } }; } }
  16. 16. ACTIVITY -> SERVICE COMMUNICATION public class ViewStocks extends ListActivity { private IStockService stockService; private ServiceConnection connection = new ServiceConnection(){ public void onServiceConnected(ComponentName className, IBinder service) { stockService = IStockService.Stub.asInterface(service); } public void onServiceDisconnected(ComponentName className) { stockService = null; } }; @Override public void onCreate(Bundle savedInstanceState) { ... bindService(new Intent(IStockService.class.getName()), connection, Context.BIND_AUTO_CREATE); new AsyncTask<Void,Void,List<Stock>>(){ @Override protected List<Stock> doInBackground(Void... params) { return stockService.getPortfolio(); } @Override protected void onPostExecute(List<Stock> dbStocks){ // update UI } }
  17. 17. DEATH OF A SERVICE
  18. 18. PSYCHOPATHIC USERS Settings -> Applications -> Running Services -> KILL! 3rd Party “Task Managers” FUD (Thanks Apple)
  19. 19. MANAGE SERVICES
  20. 20. PROCESS PRIORITIZATION Foreground Visible Service Background Empty
  21. 21. PROCESS PRIORITIZATION Foreground Visible Service Background Empty
  22. 22. PROCESS PRIORITIZATION Foreground Visible Service Background Empty
  23. 23. PROCESS PRIORITIZATION Foreground Visible Service Background Empty
  24. 24. PROCESS PRIORITIZATION Foreground Visible Service Background Empty
  25. 25. PROCESS PRIORITIZATION Foreground Visible Service Background Empty
  26. 26. RESURRECTION
  27. 27. CONCEPT: USE THE OS
  28. 28. HELLO ALARMMANAGER! public class PortfolioStartupReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent i = new Intent(context, AlarmReceiver.class); PendingIntent sender = PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_CANCEL_CURRENT); Calendar now = Calendar.getInstance(); now.add(Calendar.MINUTE, 2); long fifteenMinutesInMillis = 15*60*1000; mgr.setRepeating(AlarmManager.RTC_WAKEUP, now.getTimeInMillis(), fifteenMinutesInMillis, sender); } }
  29. 29. SLEEP IS FOR THE WEAK! public class AlarmReceiver extends BroadcastReceiver { private static PowerManager.WakeLock wakeLock = null; private static final String LOCK_TAG = "com.flexware.stocks"; public static synchronized void acquireLock(Context ctx){ if (wakeLock == null){ PowerManager mgr = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE); wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOCK_TAG); wakeLock.setReferenceCounted(true); } wakeLock.acquire(); } public static synchronized void releaseLock(){ if (wakeLock != null){ wakeLock.release(); } } @Override public void onReceive(Context context, Intent intent) { acquireLock(context); Intent stockService = new Intent(context, PortfolioManagerService.class); context.startService(stockService); } }
  30. 30. SHARING A WAKELOCK public class PortfolioManagerService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { updateStockData(); return Service.START_STICKY; } private void updateStockData(){ try{ ... } finally { AlarmReceiver.releaseLock(); stopSelf(); } } }
  31. 31. STRATEGY: THE SHORT- LIVED SERVICE Start at device boot Use AlarmManager Start early and often Stop Service when work is done Fly under the radar
  32. 32. ADVANCED
  33. 33. DIY PUSH Persistent TCP connection Long-lived service Frequent alarms Keep-alives, re- connect Always in-sync
  34. 34. CLOUD TO DEVICE MESSAGING (C2DM) Requires Android 2.2 And a lot of permissions Send Intents directly to device Awkward authN/authZ
  35. 35. C2DM <receiver android:name=".PushReceiver" android:permission="com.google.android.c2dm.permission.SEND"> <intent-filter> <action android:name= "com.google.android.c2dm.intent.RECEIVE" /> <category android:name="com.flexware.stocks" /> </intent-filter> <intent-filter> <action android:name= "com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="com.flexware.stocks" /> </intent-filter> </receiver> ... <uses-sdk android:minSdkVersion="8" /> <uses-permission android:name="android.permission.INTERNET"/> <permission android:name="com.flexware.stocks.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="com.flexware.stocks.permission.C2D_MESSAGE"/> <uses-permission android:name= "com.google.android.c2dm.permission.RECEIVE"/> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
  36. 36. C2DM public class PushReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { AlarmReceiver.acquireLock(context); if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) { onRegistration(context, intent); } else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) { onMessage(context, intent); } } private void onRegistration(Context context, Intent intent) { final String regId = intent.getStringExtra("registration_id"); if (regId != null) { AccountManager mgr = AccountManager.get(context); Account googleAccount = mgr.getAccountsByType("com.google")[0]; mgr.getAuthToken(googleAccount, "ah", true, new AccountManagerCallback<Bundle>(){ public void run(AccountManagerFuture<Bundle> future) { Bundle bundle = future.getResult(); String authToken = bundle.get(AccountManager.KEY_AUTHTOKEN).toString(); sendToServer(regId,authToken); } },null); } } private void sendToServer(String regId, String authToken) { ... release lock! } private void onMessage(Context context, Intent intent){ } }
  37. 37. STRATEGY: REMOTE RESURRECTION Use C2DM Send minimal Intents Start (short lived) Service Retrieve/sync data Notifications Activity only use cache

×