Android如何让应用在待机休眠的情况下维持心跳连接
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() 来检查它当前是否在豁免白名单中。
- 添加权限
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
- 判断并申请加入白名单
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锁屏自动断开的开关(这比较重要)