Android如何让应用在待机休眠的情况下维持心跳连接

Android省电机制1. 如何让CPU不休眠通过WakeLock 唤醒锁通过AlarmManager进行定时唤醒并解锁屏幕AlarmManager的使用AlarmManager的唤醒类型存在的问题为啥不用Timer或者handler执行定时任务

通过JobScheduler定时唤醒JobScheduler使用存在的问题

避免电池优化白名单申请通过WifiLock锁定Wi-Fi 来保持设备的 Wi-Fi 连接处于活动状态华为平板最终使用的方案参考

Android省电机制

从 Android 6.0(API 级别 23)开始,Android 引入了两项省电功能,通过管理应用在设备未连接至电源时的行为方式,帮助用户延长电池寿命。当用户长时间未使用设备时,低电耗模式会延迟应用的后台 CPU 和网络活动,从而降低耗电量。应用待机模式会延迟用户近期未与之交互的应用的后台网络活动。

CPU 休眠:设备的中央处理器 (CPU) 进入休眠状态,停止运行大部分任务和进程。这包括暂停应用程序的代码执行、系统服务的运行以及大部分的设备硬件操作。屏幕关闭:设备的屏幕会关闭,进入省电模式。这有助于降低屏幕背光和其他相关硬件的能耗。网络断开:通常情况下,当设备进入休眠状态时,会断开与移动网络和 Wi-Fi 网络的连接,以减少网络访问的功耗。

1. 如何让CPU不休眠

通过WakeLock 唤醒锁

WakeLock 是一种 Android 中的机制,允许应用程序保持设备唤醒状态,以防止 CPU 和屏幕进入休眠。WakeLock 主要用于在需要时确保设备保持唤醒状态,以执行特定的任务。以下是 WakeLock 的使用方法:

PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);

PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");

wakeLock.acquire();

wakeLock.release();

WakeLock 类提供了不同类型的锁,以满足不同的需求和场景。在 Android 中,常见的 WakeLock 类型包括以下几种:

PARTIAL_WAKE_LOCK:部分唤醒锁

使用 PowerManager.PARTIAL_WAKE_LOCK 标志创建。当你希望保持 CPU 唤醒而同时允许屏幕关闭时,可以使用这种类型的锁。 SCREEN_DIM_WAKE_LOCK:屏幕暗亮唤醒锁

使用 PowerManager.SCREEN_DIM_WAKE_LOCK 标志创建。当你需要保持屏幕处于暗亮状态(比全亮稍暗)同时保持 CPU 唤醒时,可以使用这种类型的锁。 SCREEN_BRIGHT_WAKE_LOCK:屏幕全亮唤醒锁

使用 PowerManager.SCREEN_BRIGHT_WAKE_LOCK 标志创建。当你需要保持屏幕全亮同时保持 CPU 唤醒时,可以使用这种类型的锁。 FULL_WAKE_LOCK:全唤醒锁

使用 PowerManager.FULL_WAKE_LOCK 标志创建。当你希望保持 CPU 和屏幕都保持唤醒状态时,可以使用这种类型的锁。 PROXIMITY_SCREEN_OFF_WAKE_LOCK:靠近感应器唤醒锁

使用 PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK 标志创建。当你希望在设备靠近感应器时关闭屏幕,同时保持 CPU 唤醒时,可以使用这种类型的锁。 当我们想要应用在锁屏待机状态继续运行时可以使用 PowerManager.PARTIAL_WAKE_LOCK 来保持设备唤醒状态

通过AlarmManager进行定时唤醒并解锁屏幕

AlarmManager的使用

AlarmManager 是 Android 中用于在指定时间触发后台操作的类。它允许你安排在未来的某个时间点或以固定间隔触发某个操作。以下是使用 AlarmManager 的基本步骤:

获取 AlarmManager 对象:

AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

通过调用 getSystemService() 方法,传入 Context.ALARM_SERVICE 参数获取 AlarmManager 的实例。

创建 PendingIntent:

Intent intent = new Intent(this, MyReceiver.class);

PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

在上述代码中,我们创建了一个 Intent 对象,指定了接收闹钟触发事件的广播接收器(MyReceiver)。然后,通过调用 PendingIntent.getBroadcast() 方法创建 PendingIntent。PendingIntent.FLAG_UPDATE_CURRENT 参数表示如果已经存在相同的 PendingIntent,那么更新它的 Extra 数据。

设置闹钟:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);

} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);

} else {

alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), WakeUpBroadcastReceiver.TIME_INTERVAL, pendingIntent);

}

在上述代码中,我们使用 System.currentTimeMillis() 获取当前时间,并在其基础上加上 5000 毫秒(5 秒)来计算触发时间。然后,通过调用 alarmManager.set() 方法,传入触发类型(AlarmManager.ELAPSED_REALTIME_WAKEUP 表示使用实时时钟触发,同时唤醒设备),触发时间和 PendingIntent 对象来设置闹钟。

创建广播接收器:

public class MyReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

// 处理闹钟触发事件

Intent intent1 = new Intent(context, MyReceiver.class);

PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent1, PendingIntent.FLAG_UPDATE_CURRENT);

AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);

// 重复定时任务

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);

} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);

}

}

}

AlarmManager的唤醒类型

AlarmManager的四个唤醒类型,它可以使用以下四个常量: 1、AlarmManager.ELAPSED_REALTIME:使用相对时间,可以通过SystemClock.elapsedRealtime()获取(从开机到现在的毫秒数,包括手机的睡眠时间),设备休眠时并不会唤醒设备。 2、AlarmManager.ELAPSED_REALTIME_WAKEUP:与ELAPSED_REALTIME基本功能一样,只是会在设备休眠时唤醒设备。 3、AlarmManager.RTC:使用绝对时间,可以通过System.currentTimeMillis()获取,设备休眠时并不会唤醒设备。 4、AlarmManager.RTC_WAKEUP:与RTC基本功能一样,只是会在设备休眠时唤醒设备。

相对时间:设备boot后到当前经历的时间,SystemClock.elapsedRealtime()获取到的是相对时间。 绝对时间:1970年1月1日到当前经历的时间,System.currentTimeMillis()和Calendar.getTimeInMillis()获取到的都是绝对时间。

如果是相对时间,那么计算triggerAtMillis就需要使用SystemClock.elapsedRealtime(); 如果是绝对时间,那么计算triggerAtMillis就需要使用System.currentTimeMillis()或者calendar.getTimeInMillis()。

以上是使用 AlarmManager 在不同Android版本使用的适配,这样可以实现一个定时任务。

存在的问题

但是在华为平板上测试存在的问题: 1.当手机连接usb电源时,息屏不会造成定时器暂停运行,而且时间间隔准确 2.当手机未连接usb电源时,息屏会造成定时器时间间隔不准,设置为30秒的间隔可能会4分钟或者10分钟再执行,并在再次点亮屏幕时定时器又正常。

为啥不用Timer或者handler执行定时任务

可以参考下这个Handler休眠失效

通过JobScheduler定时唤醒

JobScheduler是在Android 5.0添加的,它可以检测网络状态、设备是否充电中、低电量、低存储等状态,当所有条件都满足时就会触发执行对应的JobService来完成任务。

JobScheduler使用

创建 JobService 类:

public class MyJobService extends JobService {

@Override

public boolean onStartJob(JobParameters params) {

// 在这里执行后台任务

return true; // 如果任务是异步的,则返回 true;如果任务是同步的,则返回 false

}

@Override

public boolean onStopJob(JobParameters params) {

// 在这里处理任务停止的逻辑(例如重试机制)

return true; // 返回 true 以重新安排该作业,返回 false 以放弃该作业

}

}

在上述代码中,我们创建了一个继承自 JobService 的类(MyJobService),并重写了 onStartJob() 和 onStopJob() 方法。在 onStartJob() 方法中,你可以执行后台任务的逻辑。如果任务是异步的,记得返回 true,以指示系统该任务正在进行中。如果任务是同步的,则返回 false。在 onStopJob() 方法中,你可以处理任务停止的逻辑(例如重试机制)。

创建 JobInfo 对象:

JobInfo.Builder builder = new JobInfo.Builder(jobId, new ComponentName(this, MyJobService.class));

builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // 设置所需的网络类型

builder.setPersisted(true); // 设置任务持久化,即在设备重启后仍然有效

//android7.0 及以上系统

if (Build.VERSION.SDK_INT >= 24) {

builder.setMinimumLatency(3000) //至少3s 才会执行

//builder.setOverrideDeadline(3000) //3s后一定会执行

//builder.setMinimumLatency(3000)

//默认是10s,如果小于10s,也会按照10s来依次增加

//builder.setBackoffCriteria(10000, JobInfo.BACKOFF_POLICY_LINEAR);

} else {

builder.setPeriodic(5000)

}

// 设置其他参数和约束条件

JobInfo jobInfo = builder.build();

在上述代码中,我们使用 JobInfo.Builder 创建一个 JobInfo 对象。jobId 是唯一标识作业的整数值。new ComponentName(this, MyJobService.class) 指定了执行作业的 JobService 类。通过 setRequiredNetworkType() 方法,我们可以设置作业所需的网络类型(如 JobInfo.NETWORK_TYPE_ANY 表示任何网络都可以)。setPersisted(true) 设置任务持久化,即在设备重启后仍然有效。

你还可以使用其他方法设置作业的约束条件,例如 setRequiresCharging()(要求设备在充电时才执行作业)或 setRequiresDeviceIdle()(要求设备处于空闲状态时才执行作业)等。 如果想要周期执行的话7.0 及以上系统通过setMinimumLatency方法并在执行onStartJob重新创建job,其他则使用setPeriodic方法。

调度作业:JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

int result = jobScheduler.schedule(jobInfo);

if (result == JobScheduler.RESULT_SUCCESS) {

// 作业调度成功

}

存在的问题

在华为平板测试时发现: 1.setPeriodic最低时间间隔为15分钟,低于15分钟不起作用 2.锁屏休眠时JobScheduler 不执行

避免电池优化白名单申请

在低电耗模式和应用待机模式期间,列入白名单的应用可以使用网络并保留部分唤醒锁定。不过,列入白名单的应用仍会受到其他限制,就像其他应用一样。例如,列入白名单的应用的作业和同步会延迟(在搭载 API 级别 23 及更低级别的设备上),并且其常规 AlarmManager 闹钟不会触发。应用可以调用 isIgnoringBatteryOptimizations() 来检查它当前是否在豁免白名单中。

添加权限

判断并申请加入白名单

public static void isIgnoreBatteryOption(Context context) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

try {

Intent intent = new Intent();

String packageName = context.getPackageName();

PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

if (!pm.isIgnoringBatteryOptimizations(packageName)) {

intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);

intent.setData(Uri.parse("package:" + packageName));

context.startActivity(intent);

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

通过WifiLock锁定Wi-Fi 来保持设备的 Wi-Fi 连接处于活动状态

在 Android 设备上,默认情况下,当设备进入休眠状态时,网络连接会自动断开以节省电池。然而,如果你需要在设备休眠期间保持网络连接,可以尝试以下方法: 在 Android 设备上,默认情况下,当设备进入休眠状态时,网络连接会自动断开以节省电池。然而,如果你需要在设备休眠期间保持网络连接,可以尝试以下方法:

使用 Wi-Fi 锁定来保持设备的 Wi-Fi 连接处于活动状态。这样可以防止设备在休眠时断开 Wi-Fi 连接。

WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);

WifiManager.WifiLock wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "MyWifiLockTag");

wifiLock.acquire();

创建一个后台服务,在其中保持网络连接活动。后台服务可以在设备休眠时保持网络连接,以便持续进行网络操作。

public class MyBackgroundService extends Service {

private WifiManager.WifiLock wifiLock;

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);

wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "MyWifiLockTag");

wifiLock.acquire();

return START_STICKY;

}

@Override

public void onDestroy() {

super.onDestroy();

if (wifiLock != null && wifiLock.isHeld()) {

wifiLock.release();

wifiLock = null;

}

}

@Nullable

@Override

public IBinder onBind(Intent intent) {

return null;

}

}

具体效果未测试

华为平板最终使用的方案

使用场景,需求是在设备休眠时可以继续访问网络,并维持一个与服务器的长连接。 1.通过WackLock获取PARTIAL_WAKE_LOCK锁,让应用在锁屏时CPU不休眠。 2.通过AlarmManager定时检测长连接是否断 3.避免电池优化白名单申请 4. 通过WifiLock锁定Wi-Fi 来保持设备的 Wi-Fi 连接处于活动状态 5. 去掉华为的自动管理改成手动管理,打开wlan锁屏自动断开的开关(这比较重要)

参考

AlarmManager使用 针对低电耗模式和应用待机模式进行优化 Android休眠机制