You are on page 1of 36

THATS MY APP ~ RUNNING IN YOUR BACKGROUND ~ DRAINING YOUR BATTERY

WHO AM I?
Michael Galpin Mobile Architect, eBay Author, Android in Practice @michaelg

AGENDA
Motivation Strategies Death Resurrection Advanced

TRUE MULTI-TASKING
Multiple applications, executing simultaneously Consuming memory CPU cycles (battery) Using I/O But not like Desktop

DATA SYNC
Apps dont live in a vacuum Users use other apps / website Other users interact Keep data sync Improve user experience

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

STRATEGIES

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>

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) { } }

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); }

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); }

NOTIFICATIONS

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); } }

TANGENT

<receiver android:name="InstallReceiver" android:process=":stocks_background"> <intent-filter> <action android:name="com.android.vending.INSTALL_REFERRER"/> </intent-filter> </receiver>

DATA MANAGEMENT
Service and app use the same data Use the Service for any access Service can cache data Pre-emptive retrieval

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); } public Stock[] newArray(int size) { return new Stock[size]; } }; public int describeContents() { return 0; // nothing special here! } public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(symbol); parcel.writeDouble(maxPrice); parcel.writeDouble(minPrice); ... } public void readFromParcel(Parcel parcel){ symbol = parcel.readString(); maxPrice = parcel.readDouble(); minPrice = parcel.readDouble(); ... } }

ACTIVITY -> SERVICE COMMUNICATION


Stock.aidl
package com.flexware.stocks; parcelable Stock;

IStockService.aidl
package com.flexware.stocks.service; import com.flexware.stocks.Stock; interface IStockService{ Stock addToPortfolio(in Stock stock); List<Stock> getPortfolio(); }

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 { ... } }; } }

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 } }

DEATH OF A SERVICE

PSYCHOPATHIC USERS
Settings -> Applications -> Running Services -> KILL! 3rd Party Task Managers FUD (Thanks Apple)

MANAGE SERVICES

PROCESS PRIORITIZATION
Foreground Visible Service Background Empty

RESURRECTION

CONCEPT: USE THE OS

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); } }

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); } }

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(); } } }

STRATEGY: THE SHORTLIVED SERVICE


Start at device boot Use AlarmManager Start early and often Stop Service when work is done Fly under the radar

ADVANCED

DIY PUSH
Persistent TCP connection Long-lived service Frequent alarms Keep-alives, reconnect Always in-sync

CLOUD TO DEVICE MESSAGING (C2DM)


Requires Android 2.2 And a lot of permissions Send Intents directly to device Awkward authN/authZ

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"/>

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){ } }

C2DM

STRATEGY: REMOTE RESURRECTION


Use C2DM Send minimal Intents Start (short lived) Service Retrieve/sync data Notications Activity only use cache

RESOURCES

Android in Practice http://www.manning.com/collins http://code.google.com/p/android-in-practice/ CommonsWare http://commonsware.com/ AndTutorials/

You might also like