knock, knock, who is there? doze
TRANSCRIPT
Yonatan LevinGoogle Developer Expert
levin.yonatanparahall
72 Cities > 20M usersRuby, Go, Python, Microservices
~ 1600 members Largest Android Active Community
:)
The story about the shy guy
Doze Mode
Doze, the bro :)
No network access
No jobs/ No syncs
No wakelocks
No alarms
No GPS / Wifi Scans
Doze Mode on Marshmallow
Not shy anymore
Doze Mode on Nougat
When?
All intervals are configurable by Google/Manufacturer.
DeviceIdleControler
PowerManagerService
NetworkPolicyManagerService
BatteryStatsServicedeviceidle.xml
Statistics on OnePlus 3 (Marshmallow)
Statistics on Nexus 6P (Nougat)
Time
5m
5m
5s
10m
7s
14m
12s
14m
12s
11m 57 minutes
51s
2 hours
30s
3 hours 44 minutes
Maintenance Light Doze Doze
Statistics on Nexus 6P (Nougat)
Doze, sweet, Doze,Let me go...
Charge it...
AlarmManager
setAndAllowWhileIdle() or setExactAndAllowWhileIdle().
Could be triggered only once in every 15 minutes
But...
No network access
GCM/FCM
Use FCM with High priority - but treat it with special care
{
"to" : "...",
"priority" : "high",
"notification" : {
...
},
"data" : {
...
}
}
WhiteList
● An app can fire the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent to take the user directly to the Battery Optimization, where they can add the app.
Note: Google Play policies prohibit apps from requesting direct exemption from Power
Management features in Android 6.0+ (Doze and App Standby) unless the core function of
the app is adversely affected.
And it’s not all, folks
CONNECTIVITY_CHANGES
My shiny new app
git clone [email protected]:parahall/final_battle_doze.git
public class FinalBattleActivity
public void onClick(View v) {
switch (v.getId()) {
case R.id.kill_button:
StarWarsUtils.makeDecision(
LukeDecision.LUKE_KILL_DARTH_VADER, this);
break;
case R.id.light_side_button:
StarWarsUtils.makeDecision(
LukeDecision.STAY_ON_LIGHT_SIDE, this);
Break;
}
}
public class StarWarsUtils {
public static void makeDecision(LukeDecision decision,Context
context) {
Intent nowIntent = new Intent(context, NowIntentService.class);
nowIntent.putExtra(..., decision);
context.startService(nowIntent);
}
public class NowIntentService extends IntentService {
protected void onHandleIntent(Intent intent) {
LukeDecision decision =
(LukeDecision)intent.getSerializableExtra(...);
if (decision != null) {
makeNetworkCall(decision);
}
...
}
public class NowIntentService extends IntentService {
private void makeNetworkCall(LukeDecision decision) {
boolean isCompleted = false;
if(StarWarsUtils.isNetworkActive()) {
isCompleted = doingNetworkCommunication();
}
...
}
public class NowIntentService extends IntentService {
private void makeNetworkCall(LukeDecision decision) {
...
if (!isCompleted) {
StarWarsUtils.addRetryTask(decision, this);
return;
}
...
}
How we will retry the request if it’s failed?
Connectivity Manager
AndroidManifest.xml
<receiver android:name=".ConnectivityChangeReceiver">
<intent-filter>
<action
android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
public void onReceive(Context context, Intent intent) {
LukeDecision decisionToRetry =
StarWarsUtils.getDecisionToRetry(context);
StarWarsUtils.makeDecision(decisionToRetry, context);
}
public class ConnectivityChangeReceiver extends WakefulBroadcastReceiver
Han Solo reporting to Rebels
public class FinalBattleActivity extends AppCompatActivity
protected void onCreate(Bundle savedInstanceState) {
...
scheduleHanSoloReport();
...
}
public class FinalBattleActivity extends AppCompatActivity
private void scheduleHanSoloReport() {
AlarmManager alarmManager = (AlarmManager)
getSystemService(ALARM_SERVICE);
PendingIntent broadcast = getPendingIntent();
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
System.currentTimeMillis(),
ONE_MINUTE,
broadcast);
}
public class HanSoloReceiver
public void onReceive(Context context, Intent intent) {
LocationManager locationService = (LocationManager)
context.getSystemService(Context.LOCATION_SERVICE);
PendingIntent pendingIntent = getPendingIntent(context);
String provider = getLocationProvider(locationService);
locationService.requestSingleUpdate(provider, pendingIntent);
}
public class NowIntentService extends IntentService {
@Override protected void onHandleIntent(Intent intent) {
...
Location HanSoloLocation =
intent.getParcelableExtra(LocationManager.KEY_LOCATION_CHANGED);
if (HanSoloLocation != null) {
checkIfRebelsReady(HanSoloLocation);
}
}
RebelService
public class RebelService extends Service implements Handler.Callback
public void onCreate() {
handlerThread = new HandlerThread("RebelServiceHandlerThread");
handlerThread.start();
Looper looper = handlerThread.getLooper();
handler = new Handler(looper, this);
handler.sendEmptyMessage(WHAT_MAKE_NETWORK_REQUEST);
}
public class RebelService extends Service implements Handler.Callback
@Override public boolean handleMessage(Message msg) {
StarWarsUtils.doingNetworkCommunication();
handler.sendEmptyMessageDelayed(
WHAT_MAKE_NETWORK_REQUEST,
DELAY_MILLIS);
return true;
}
So what is affected by Doze my app?
What is affected
- Pending network transactions will never be fired...- Han Solo Alarms will be postponed.- No location updates
But...
There is still some place for...
JobSchedulerGCMNetworkManager
Firebase JobDispatcher
What?
Schedule the task to execute it when certain conditions met.
(charging, idle, connected to a network or connected to an unmetered network)
What’s the difference?
JobScheduler was introduced in API >= 21 (Lollipop).
GCMNetworkManager - is part of GCM package. When using on devices >= 21, use JobScheduler underneath.
build.gradledependencies {
...
compile 'com.google.android.gms:play-services-gcm:9.6.1'
...
}
public class StarWarsUtilities
void addRetryTask(LukeDecision decision,Context context){
GcmNetworkManager gcmNetworkManager =
GcmNetworkManager.getInstance(context);
…
}
public class StarWarsUtilities
Task task = new
OneoffTask.Builder().setService(BestTimeService.class)
.setExecutionWindow(0, 30)
.setTag(BestTimeService.LUKE_DECISION)
.setUpdateCurrent(false)
.setRequiredNetwork(Task.NETWORK_STATE_CONNECTED)
.setRequiresCharging(false)
.setExtras(bundle)
.build();
public class StarWarsUtilities
void addRetryTask(LukeDecision decision,Context context){
...
gcmNetworkManager.schedule(task);
}
AndroidManifest.xml<service
android:name=".BestTimeService" android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE"android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.gcm.ACTION_TASK_READY"/>
</intent-filter>
</service>
BestTimeService.java/**
* Task run by GcmNetworkManager when all the
requirements of the scheduled
* task are met.
*/
public class BestTimeService extends GcmTaskService {
...
}
BestTimeService.java@Override public int onRunTask(TaskParams taskParams) {
switch (taskParams.getTag()) {
case LUKE_DECISION:
...
return GcmNetworkManager.RESULT_SUCCESS;
case HAN_SOLO_LOCATION:
...
return GcmNetworkManager.RESULT_RESCHEDULE;
default:
return GcmNetworkManager.RESULT_FAILURE;
}
}
BestTimeService.java
public int onRunTask(TaskParams taskParams) {
…
case LUKE_DECISION:
Bundle extras = taskParams.getExtras();
LukeDecision decision = (LukeDecision)
extras.getSerializable(...);
StarWarsUtils.makeDecision(decision, this);
return GcmNetworkManager.RESULT_SUCCESS;
…
}
}
Han Solo Recurring Location task?
Task task = new PeriodicTask.Builder()
.setService(BestTimeService.class)
.setPeriod(SIXTY_SEC)
.setFlex(10)
.setTag(BestTimeService.HAN_SOLO_LOCATION)
.setPersisted(true)
.build();
public class FinalBattleActivity
BestTimeService.javapublic int onRunTask(TaskParams taskParams) {
…
case HAN_SOLO_LOCATION:
.. request location...
return GcmNetworkManager.RESULT_SUCCESS;
…
}
Canceling TaskmGcmNetworkManager.cancelAllTasks(BestTimeService.class);
mGcmNetworkManager.cancelTask(
TAG,
BestTimeService.class
);
There is a problem hiding here
Not all devices shipped with Play Servicesint resultCode =
GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode == ConnectionResult.SUCCESS) {
mGcmNetworkManager.schedule(task);
} else {
// Deal with this networking task some other way
}
When Google Play updated it removes all scheduled periodic tasks
public class BestTimeService extends GcmTaskService {
@Override
public void onInitializeTasks() {
super.onInitializeTasks();
// Reschedule removed tasks here
}
}
Can we do better?
FCM/GCM with High Priority
{
"to" : "...",
"priority" : "high",
"notification" : {
...
},
"data" : {
...
}
}
AndroidManifest.xml
<service
android:name=".RebelsMessagingService">
<intent-filter>
<action
android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
public class RebelsMessagingService extends FirebaseMessagingService
@Override public void onMessageReceived(RemoteMessage remoteMessage) { Intent intent = new Intent(HanSoloReceiver.ACTION);
LocalBroadcastManager. getInstance(this). sendBroadcast(intent); }
Some dirty tricks. Be aware when using it…
public class RebelService extends Service implements Handler.Callback
public boolean handleMessage(Message msg) {
StarWarsUtils.doingNetworkCommunication());
handler.sendEmptyMessageDelayed(
WHAT_MAKE_NETWORK_REQUEST,
DELAY_MILLIS);
return true;
}
public class RebelService extends Service implements Handler.Callback
public void onCreate() {
Notification notification =
...
startForeground(101, notification);
}
When doze mode is kicked inmPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mDozeBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { boolean isDeviceIdleMode = mPowerManager.isDeviceIdleMode(); } };
registerReceiver(mDozeBroadcastReceiver, new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));}
Yonatan LevinGoogle Developer Expert
levin.yonatanparahall
Examples of daily usage apps
File explorer over wifi
Long Running Upload/Download