正如documentation 所说:
在打盹模式下,系统会尝试通过限制
应用程序对网络和 CPU 密集型服务的访问。 它还可以防止
应用程序访问网络并推迟其作业、同步和
标准警报。
系统会定期退出 Doze 短暂的时间以让应用
完成他们推迟的活动。在此维护窗口期间,
系统运行所有待处理的同步、作业和警报,并允许应用
访问网络。
简而言之,在打盹模式下,系统暂停网络访问,忽略唤醒锁,停止从传感器获取数据,将 AlarmManager 作业推迟到下一个打盹维护窗口(调用频率逐渐降低),还有 WiFi扫描、JobScheduler 作业和同步适配器不运行。
对于每个应用,setAndAllowWhileIdle() 和 setExactAndAllowWhileIdle() 都不能每 9 (?) 分钟触发一次以上的警报。
而且前台服务似乎也参与了这场“打瞌睡剧”,至少在 MarshMellow (M) 中。
要在这种情况下生存下来,至少需要重新审核大量应用程序。你能想象一个简单的 mp3 播放器在设备进入打盹模式时停止播放音乐吗?
打盹模式自动启动,当设备从电源上拔下并留在桌子上大约 1 小时左右,甚至更早,当用户单击电源按钮关闭屏幕时,但我认为这可能也取决于设备制造商。
我尝试了很多对策,其中一些真的很搞笑。
在我的测试结束时,我找到了一个可能的解决方案:
即使主机设备处于打盹模式,让您的应用程序运行的一种可能(并且可能是唯一)方法基本上是拥有一个ForegroundService(即使是假的,根本不做任何工作)在another process 中运行并获得partial WakeLock。
你需要做的基本上如下(你可以创建一个简单的项目来测试它):
- 1 - 在您的新项目中,创建一个扩展 Application (myApp) 的新类,或使用
新项目的主要活动。
- 2 - 在 myApp onCreate() 中启动一个服务 (myAntiDozeService)
- 3 - 在 myAntiDozeService onStartCommand() 中,创建通知
需要将服务作为前台服务启动,启动
使用 startForeground(id, notification) 服务并获取
部分唤醒锁。
记住!这会起作用,但这只是一个起点,因为您必须小心这种方法会产生的“副作用”:
好的,我们开始吧。
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,例如一些repeating alarm(它是
通常在设备进入打盹模式时停止),或成熟
网络访问,或播放定时铃声,或....任何你想要的。
- 请记住,主要活动执行的作业必须运行
永远因为要测试这个 AntiDoze 你需要等待至少 1
小时以确保设备进入打盹模式。
要进入打盹模式,设备必须保持安静并拔下电源,所以
调试时无法对其进行测试。首先调试您的应用程序,
检查一切都在运行然后停止它,拔下电源,重新启动
再次应用,然后将设备单独放在桌面上并保持安静。
文档向simulate Doze
and StandBy modes 建议的adb 命令可以也不能给您正确的结果
(我想这取决于设备制造商,驱动程序,bla
布拉)。请以真实的行为进行测试。
在我的第一个测试中,我使用 AlarmManager 和音调发生器每 10 分钟播放一次音调,以了解我的应用程序仍然处于活动状态。
而且它仍然从大约 18 小时开始运行,每 10 分钟就用一次响亮的音调打破我的耳朵。 :-)
编码愉快!