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.
6. TRUE MULTI-TASKING
Multiple applications,
executing
simultaneously
Consuming memory
CPU cycles (battery)
Using I/O
But not like Desktop
7. DATA SYNC
Apps don’t live in a
vacuum
Users use other
apps / website
Other users interact
Keep data sync
Improve user experience
8. 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
11. 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) {
}
}
12. 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);
}
13. 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);
}
14. 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);
}
}
16. DATA MANAGEMENT
Service and app use the
same data
Use the Service for any
access
Service can cache data
Pre-emptive retrieval
17. 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();
...
}
}
18. 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 {
...
}
};
}
}
19. 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);
stocks = stockService.getPortfolio();
refreshUi();
}
public void onServiceDisconnected(ComponentName className) {
stockService = null;
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
...
bindService(new Intent(IStockService.class.getName()), connection,
Context.BIND_AUTO_CREATE);
}
20.
21. SOMETIMES AIDL IS
OVERKILL
Service binding only
needed for if you need
data from Service
Activity -> Service
Service -> Service
Otherwise consider
IntentService
22. DON’T BUILD THIS
YOURSELF!
Task
Task
Task
AIDL ? Service Task
Task
Task
IO (Network, Disk)
23. DON’T BUILD THIS
YOURSELF!
Task
Intent
Task
Task
Intent AIDL ? Service Task
Task
Task
IO (Network, Disk)
24. USING AN
INTENTSERVICE
public static class StockActivity extends Activity{ public class AddStockService extends IntentService {
private Stock stock;
@Override private static final String NAME = "AddStockService";
public void onCreate(Bundle savedInstance){ public static final String EXTRA_STOCK = "Stock";
Button button = (Button) findViewById(R.id.btn);
button.setOnClickListener(new OnClickListener(){ public AddStockService(){
super(NAME);
@Override }
public void onClick(View v) {
Intent request = @Override
new Intent(StockActivity.this, protected void onHandleIntent(Intent request) {
AddStockService.class); Stock stock =
request.putExtra(EXTRA_STOCK, stock); request.getParcelableExtra(EXTRA_STOCK);
startService(request); StocksDb db = new StocksDb(this);
} db.addStock(stock);
}
}); }
}
}
36. 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);
}
}
37. 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);
}
}
38. 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();
}
}
}
39. 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
Use IntentService?
44. 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) {
String regId = intent.getStringExtra("registration_id");
if (regId != null) {
Intent i = new Intent(context, SendC2dmRegistrationService.class);
i.putExtra("regId", regId);
context.startService(i);
}
}
private void onMessage(Context context, Intent intent){
Intent stockService =
new Intent(context, PortfolioManagerService.class);
// copy any data sent from your server
stockService.putExtras(intent);
context.startService(stockService);
}
}
45. C2DM REGISTRATION
Device
Broadcast registration C2DM
2
Intent Service
1 3
C2DM service sends
request to Google MyService
C2DM Servers
Receive registration
4
response (Intent)
Send registration ID to
My Servers
your servers
46. C2DM RUNTIME EVENTS
Device
2
Send data to Google C2DM
Service
Good ‘ol HTTP 3
Pushed from C2DM MyService
server to device service C2DM Servers
over persistent conn
Broadcasts Intent to
your Service My Servers
1
47. STRATEGY: REMOTE
RESURRECTION
Use C2DM
Send minimal Intents
Start (short
lived)Service
(IntentService?)
Retrieve/sync data
Notifications
Activity only use cache
48. RESOURCES
Android in Practice
http://www.manning.com/collins
http://code.google.com/p/android-in-practice/
CommonsWare http://commonsware.com/
AndTutorials/