正如文档所说:
在打盹模式下,系统会尝试通过限制应用程序访问网络和 CPU 密集型服务来节省电池电量。它还可以阻止应用程序访问网络并推迟它们的工作、同步和标准警报。
系统会定期退出 Doze 短暂的时间,以让应用程序完成其延迟活动。在此维护时段内,系统会运行所有挂起的同步、作业和警报,并允许应用程序访问网络。
简而言之,在打盹模式下,系统暂停网络访问、忽略唤醒锁、停止从传感器获取数据、将 AlarmManager 作业推迟到下一个打盹维护窗口(调用频率逐渐降低)、WiFi 扫描、JobScheduler 作业和同步适配器不运行。
对于每个应用,setAndAllowWhileIdle() 和 setExactAndAllowWhileIdle() 都不能每 9 (?) 分钟触发一次以上的警报。
而且前台服务似乎也参与了这场“打瞌睡剧”,至少在 MarshMellow (M) 中。
为了在这种情况下生存,至少需要重新审查大量应用程序。你能想象一个简单的 mp3 播放器在设备进入打盹模式时停止播放音乐吗?
打盹模式会自动启动,当设备从电源上拔下并放在桌子上大约 1 小时左右,或者甚至更早,当用户单击电源按钮关闭屏幕时,但我认为这可能取决于设备制造商也是。
我尝试了很多对策,其中一些真的很搞笑。
在我的测试结束时,我找到了一个可能的解决方案:
即使主机设备处于打盹模式下,让您的应用程序运行的一种可能(也许是唯一)方法基本上是让ForegroundService(甚至是假的,根本不做任何工作)在另一个进程中运行,并获得部分唤醒锁。
您需要做的基本上如下(您可以创建一个简单的项目来测试它):
- 1 - 在您的新项目中,创建一个扩展应用程序(myApp)的新类,或使用新项目的主要活动。
- 2 - 在 myApp onCreate() 中启动一个服务 (myAntiDozeService)
- 3 - 在 myAntiDozeService onStartCommand() 中,创建作为前台服务启动服务所需的通知,使用 startForeground(id, notification) 启动服务并获取部分 WakeLock。
记住!这将起作用,但这只是一个起点,因为您必须小心这种方法将产生的“副作用”:
好的,让我们一起做。
myApp.java
public class myApp extends Application {
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
@Override
public void onCreate() {
super.onCreate();
// start foreground service
startForeService();
}
private void stopForeService() {
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STOPFOREGROUND_ACTION);
stopService(service);
}
private void startForeService(){
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STARTFOREGROUND_ACTION);
startService(service);
}
@Override
public void onTerminate() {
stopForeService();
super.onTerminate();
}
}
myAntiDozeService.java
public class myAntiDozeService extends Service {
private static final String TAG = myAntiDozeService.class.getName();
private static boolean is_service_running = false;
private Context mContext;
private PowerManager.WakeLock mWakeLock;
private static final int NOTIFICATION_ID = 12345678;
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!is_service_running && STARTFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Start Foreground Intent ");
showNotification();
is_service_running = true;
acquireWakeLock();
} else if (is_service_running && STOPFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Stop Foreground Intent");
is_service_running = false;
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
@Override
public void onDestroy() {
releaseWakeLock();
super.onDestroy();
}
private void showNotification(){
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(mContext)
.setContentTitle("myApp")
.setTicker("myApp")
.setContentText("Application is running")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
// starts this service as foreground
startForeground(NOTIFICATION_ID, notification);
}
public void acquireWakeLock() {
final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
releaseWakeLock();
//Acquire new wake lock
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG+"PARTIAL_WAKE_LOCK");
mWakeLock.acquire();
}
public void releaseWakeLock() {
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
mWakeLock = null;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidManifest.xml 更改。
在 AndroidManifest.xml 添加这个权限:
<uses-permission android:name="android.permission.WAKE_LOCK" />
不要忘记在<application>
标签中添加您的应用名称:
<application
....
android:name=".myApp"
....
最后将您的前台服务添加到另一个进程中:
<service
android:name=".myAntiDozeService"
android:process=":MyAntiDozeProcessName">
</service>
一些笔记。
在前面的示例中,创建的通知在单击时会打开测试项目的 ActivityMain 活动。
Intent notificationIntent = new Intent(mContext, ActivityMain.class)
;
但你也可以使用另一种意图。
- 要对其进行测试,您必须在 ActivityMain.java 中添加一些要执行的工作,例如一些重复警报(通常在设备进入打盹模式时停止),或者成熟的网络访问,或者播放定时音,或者....任何你想要的。
- 请记住,主要活动执行的作业必须永远运行,因为要测试此 AntiDoze,您需要等待至少 1 小时以确保设备进入打盹模式。
要进入打盹模式,设备必须保持安静并拔下电源,因此您无法在调试时对其进行测试。首先调试您的应用程序,检查一切是否正在运行,然后停止它,拔下电源,再次重新启动应用程序,然后将设备单独放在桌面上并保持安静。
adb
文档建议的用于模拟 Doze 和 StandBy 模式的命令可以也不能给您正确的结果(我想这取决于设备制造商、驱动程序等)。请以真实的行为进行测试。
在我的第一个测试中,我使用 AlarmManager 和音调发生器每 10 分钟播放一次音调,以了解我的应用程序仍然处于活动状态。而且它仍然从大约 18 小时开始运行,每 10 分钟就用一次响亮的音调打破我的耳朵。:-)
快乐编码!