AlarmManager+ Service + BroadCastReceiver解决持续定位问题
tips: 除了白名单,任何方法都不能保证进程不被系统杀死。本篇文章,也只是尽量不被系统杀死,但是会导致CPU不能休眠,会相当耗电。慎重选择。
- Service部分
定位我就不多说了,bat大厂都提供了。首先我们启动一个Service 注意启动的时候注意8.0权限问题,同时为了保证8.0连续定位问题,我们需要启动前台服务,如下:
创建通知栏
private final String CHANNEL_ONE_ID = "1";
private final String CHANNEL_ONE_NAME = "定位采集";
Intent nfIntent = new Intent(this, MainActivity.class);
Notification.Builder builder = new Notification.Builder(this.getApplicationContext())
.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0)) // 设置PendingIntent
.setSmallIcon(R.mipmap.login_logo) // 设置状态栏内的小图标
.setContentTitle(getResources().getString(R.string.app_name))
.setContentTitle("轨迹采集服务")
.setContentText("定位采集正在运行") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
//---------------- 新增代码 --------------------------------------
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//修改安卓8.1以上系统报错
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ONE_ID, CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_MIN);
notificationChannel.enableLights(false);//如果使用中的设备支持通知灯,则说明此通知通道是否应显示灯
notificationChannel.setShowBadge(false);//是否显示角标
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.createNotificationChannel(notificationChannel);
builder.setChannelId(CHANNEL_ONE_ID);
}
Notification notification;// 获取构建好的Notification
if (android.os.Build.VERSION.SDK_INT >= 16) {
notification = builder.build();
} else {
notification = builder.getNotification();
}
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
startForeground(1001, notification);
启动定位service:
/**
* 启动定位service
*/
private void startLocateService(Context context) {
Intent intent = new Intent(context, LocateService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//android8.0以上通过startForegroundService启动service
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
- AlarmManager
为了保证连续定位,我们选择AlarmManager, 之所以选择它,是因为,他能够在CPU休眠的时候,继续运转,而Timer和Hander 在息屏后,都无法保证继续运转.
AlarmManager概述
AlarmManager是Android的全局定时器。就是在指定时间做一个事情(封装在PendingIntent)。通过PendingIntent的getActivity()、getService()或getBroadcast()来执行。听起来AlarmManager和Timer很类似,但是Timer有可能因为手机休眠而被杀掉服务,但是AlarmManager可以做到唤醒手机。
AlarmManager提供了对系统定时服务的访问接口,使得开发者可以安排在未来的某个时间运行应用。当到达闹铃设定时间,系统就会广播闹铃之前注册的Intent。如果此时目标应用没有被启动,系统还会帮你自动启动目标应用。 即使设备已经进入睡眠已注册的闹铃也会被保持,只有当设备关闭或是重启的时候会被清除.
tips: 具体使用请参考下面两偏文章:
AlarmManager相关API使用一
AlarmManager相关API使用二
怎么实现呢看代码:
alarmManager = (AlarmManager) this.context.getSystemService(Context.ALARM_SERVICE);
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent = new Intent();
//定义广播类型
intent.setAction("com.*****.alarmLoop");
intent.setComponent(new ComponentName(context.getPackageName(), "com.*****.LoopBroadCast"));
/* intent.setComponent(new ComponentName("com.examplehq.forhelp","com.examplehq.forhelp.MyReceiver"));*/
} else {
intent = new Intent(context, LoopBroadCast.class);
intent.setAction("com.*******");
}
sender = PendingIntent.getBroadcast(context, 0, intent, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, sender);
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, sender);
}
} else {
if (flag == 0) { //一次性闹钟
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL, sender);
} else { //重复闹钟
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), INTERVAL, sender);
}
}
这里我要做一个循环的闹铃 ,所以每次铃响,都发送个广播,然后继续开启闹铃.
具体我们看下一部分.需要注意的是,广播,在8.0以后,需要判断处理一下.8.0以上必须要加上/* intent.setComponent(new ComponentName(“com.examplehq.forhelp”,“com.examplehq.forhelp.MyReceiver”));*/
- BroadCastRecevier
这部分主要是监听闹铃响起以后,要做的事情,所以,我们在这里启动服务,还有唤醒屏幕.
class LoopBroadCast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.*******".equals(intent.getAction())) {
Log.w("AlarmMangerUtils", "拉起广播");
AlarmMangerUtils.getInstance().setPendingBroadCast();
startLocateService(context);
boolean contextNull = WakeUpUitls.getInstance().isContextNull();
if (contextNull) {
WakeUpUitls.getInstance().init(context);
}
//获取cpu
WakeUpUitls.getInstance().acquireWakeLock();
//点亮屏幕
WakeUpUitls.getInstance().wakeupAndUnLock();
}
}
}
- WakeUp
这里面我门主要主力CPU, 是为了保证CPU持续运行service
//获取电源锁,保持该服务在屏幕熄灭时仍然获取CPU时,保持运行
public void acquireWakeLock() {
Log.e("LoopBroadCast", "屏幕熄灭时仍然获取CPU");
if (null == keepCpu) {
keepCpu = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, this.getClass().getSimpleName());
if (null != keepCpu) {
keepCpu.acquire();
}
} else {
keepCpu.acquire();
}
}
同时保证CPU休眠的中间,唤醒,打断持续休眠状态
Log.e("LoopBroadCast", "点亮屏幕");
if (null == wakeLock) {
//屏锁管理器
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
KeyguardManager.KeyguardLock kl = km.newKeyguardLock("unLock");
//解锁
kl.disableKeyguard();
//获取PowerManager.WakeLock对象,后面的参数|表示同时传入两个值
wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP |
PowerManager.SCREEN_DIM_WAKE_LOCK, this.getClass().getSimpleName());
//点亮屏幕
if (null != wakeLock) {
wakeLock.acquire(1);
}
} else {
wakeLock.acquire(1);
}
就这样,尽量保持定位service 不被系统杀掉 .
亲测:手里有一部华为荣耀的手机,坚持了一个小时,还在继续定位,但是手里的vivo最多的时候挺到30分钟,还有很多10多分钟就被杀掉的情况.
希望对您有帮助!