产生问题的原因:
手机锁屏后,Android系统为了省电以及减少CPU消耗,在一段时间后会将手机进入休眠状态。此时的服务以及线程等都会停止。
最近就这个问题,阅读了很多代码以及官方文档,下面就说下最近都尝试过的方式,可能其中有些您实现了,我这边没实现,望见谅。本文采用的高德定位。
一、PowerManager.WakeLock
(1)直接强制当前页面cpu运行
private PowerManager pm;
private PowerManager.WakeLock wakeLock;
@Override
public void onCreate() {
super.onCreate();
//创建PowerManager对象
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
//保持cpu一直运行,不管屏幕是否黑屏
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CPUKeepRunning");
wakeLock.acquire();
}
@Override
public void onDestroy() {
wakeLock.release();
super.onDestroy();
}
这个写法我表示并没有什么用,并不能强制cpu持续运行。
(2)WakefulBroadcastReceiver
public class WLWakefulReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//
String extra = intent.getStringExtra("msg");
Intent serviceIntent = new Intent(context, MyIntentService.class);
serviceIntent.putExtra("msg", extra);
startWakefulService(context, serviceIntent);
}
}
WakefulBroadcastReceiver 内部的原理也是PowerManager,注册广播时8.0的请动态注册,静态没有用。广播注册完了之后,写一个服务用来与广播互动。
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
protected void onHandleIntent(@Nullable final Intent intent) {
//子线程中执行
Log.i("MyIntentService", "onHandleIntent");
String extra = intent.getStringExtra("msg");
new Thread(new Runnable() {
@Override
public void run() {
LocationUtil.getInstance().startLocation(LocationUtil.NULL, new LocationUtil.OnLocationBack() {
@Override
public void back(AMapLocation aMapLocation, String backString) {
Log.e("定位結果", aMapLocation.getAddress()+"");
定位结果操作,我这边是把定位的时候保存到数据库里面。
}
});
}
}).start();
Log.i("MyIntentService", "onHandleIntent:"+extra);
//调用completeWakefulIntent来释放唤醒锁。
// WLWakefulReceiver.completeWakefulIntent(intent);
}
}
注册
LocationUtil.getInstance().startLocation是我封装的高德的定位。保证完整性,我把高德定位的封装也贴上。
public class LocationUtil implements AMapLocationListener {
private static LocationUtil locationUtil;
private AMapLocationClient mLocationClient = null;
private AMapLocationClientOption mLocationOption = null;
private OnLocationBack onLocationBack;
private OnLocationTrain onLocationTrain;
public static final String NULL = "null";
private String differenceFlag = "";
private String latitude, longitude, cityNameString, HotelCityCode;
private LocationUtil() {
}
public static LocationUtil getInstance() {
if (locationUtil == null) {
synchronized (LocationUtil.class) {
if (locationUtil == null) {
locationUtil = new LocationUtil();
}
}
}
return locationUtil;
}
private void init() {
mLocationClient = new AMapLocationClient(HMMyApplication.context);
mLocationOption = new AMapLocationClientOption();
//设置定位模式为AMapLocationMode.Hight_Accuracy,高精度模式。
mLocationOption.setLocationMode(AMapLocationMode.Hight_Accuracy);
//设置定位间隔,单位毫秒,默认为2000ms
mLocationOption.setInterval(5000);
//设置是否只定位一次,默认为false
mLocationOption.setOnceLocation(false);
//返回最近3s内精度最高的一次定位结果。
mLocationOption.setOnceLocationLatest(false);
//设置是否返回地址信息(默认返回地址信息)
mLocationOption.setNeedAddress(true);
//单位是毫秒,默认30000毫秒,建议超时时间不要低于8000毫秒。
mLocationOption.setHttpTimeOut(20000);
//关闭缓存机制
mLocationOption.setLocationCacheEnable(false);
//给定位客户端对象设置定位参数
mLocationClient.setLocationOption(mLocationOption);
mLocationClient.setLocationListener(this);
}
public void startLocation(String differenceFlag, OnLocationBack onLocationBack) {
init();
mLocationClient.startLocation();//开始
this.onLocationBack = onLocationBack;
this.differenceFlag = differenceFlag;
Log.e("开始定位","开始定位");
}
public void startLocationTrain(String differenceFlag, OnLocationTrain onLocationTrain) {
init();
mLocationClient.startLocation();//开始
this.onLocationTrain = onLocationTrain;
this.differenceFlag = differenceFlag;
Log.e("开始定位","开始定位");
}
public void stopLocation() {
if (null == mLocationClient) {
return;
}
mLocationClient.unRegisterLocationListener(this);
mLocationClient.stopLocation();//关闭
mLocationClient.onDestroy();//销毁
mLocationClient = null;
Log.e("开始定位","开始定位");
}
@Override
public void onLocationChanged(AMapLocation aMapLocation) {
Log.e("定位到当前位置: " , aMapLocation.getAddress());
if (aMapLocation == null) {
onLocationTrain.LocationFail("定位失败");
return;
}
if (null != aMapLocation.getCity()
&& !"null".equals(aMapLocation.getCity())
&& !"".equals(aMapLocation.getCity())
&& 0 != aMapLocation.getLatitude()) {
cityNameString = aMapLocation.getCity();
latitude = "" + aMapLocation.getLatitude();
longitude = "" + aMapLocation.getLongitude();
saveLocation(aMapLocation);
} else {
onLocationTrain.LocationFail("定位失败");
return;
}
}
public interface OnLocationBack {
void back(AMapLocation aMapLocation, String backString);
}
public interface OnLocationTrain {
void back(AMapLocation aMapLocation, String backString);
void LocationFail(String error);
}
private void saveLocation(AMapLocation aMapLocation) {
switch (differenceFlag) {
case NULL:
onLocationBack.back(aMapLocation, "返回的是定位到的所有信息");
break;
}
}
}
使用 记得把权限加上。
Intent intent = new Intent("startlocation");
intent.putExtra("msg", "定位定位定位");
sendBroadcast(intent);
就这个写法而言,我拿demo测试的时候,小米,oppo,高精度gps都可以,gps需要室外定位,室内没有信号。在适配华为手机的时候,有点坑。华为并不行,只支持亮屏,之后我用的双服务唤醒通讯方式实现的华为手机,适配8.0,但其实我觉得就是因为一个电量管理中保持锁屏后继续运行权限导致的, 但这个我没有测试过开了权限这个方式行不行,小伙伴测试的时候,可以给我反馈一下,谢谢。
二、AlarmManager
这个我一开始看的时候,觉得不错,设置重复的闹钟唤醒服务,让服务持续。闹钟设置重复定时的方法变化还是蛮大的,阅读官方api的时候,不同的版本基本都有不同的设置方式。
使用的服务:
public class PollingService extends Service {
public static final String ACTION = "com.hdsx.service.PollingService";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
Log.e(TAG, "onCreate:onCreate ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "Service:onStartCommand ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onStart(Intent intent, int startId) {
new PollingThread().start();
}
int count = 0;
class PollingThread extends Thread {
@Override
public void run() {
count ++;
Log.e("polling", count+"" );
}
}
@Override
public void onDestroy() {
super.onDestroy();
startService(new Intent(PollingService.this,PollingService.class));
Log.e(TAG, "Service:onDestroy ");
}
}
注册
服务里面起个线程,就是为了测试是否能持续的log输出日志。这块提醒一下,调试状态的时候手机是处于充电状态的,cpu不会休眠,所以会一直log,不过经过我后面添加代码测试,是可行的。 我这块只是提供了原始的demo,后面的加的代码找不到了。
闹钟:
public static void startPollingService(Context context, int seconds, Class> cls,String action) {
long triggerAtTime = SystemClock.elapsedRealtime();
AlarmManager manager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, cls);
intent.setAction(action);
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
manager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime,
seconds, pendingIntent);
}
使用
PollingUtils.startPollingService(MainActivity.this, 2000, PollingService.class, PollingService.ACTION);
这种方式,也可以实现, 但是测试的过程中有个问题,就是闹钟重复定时它的间隔有时候并不能完全按照我设置的时间来运行,周期感觉有点不太确定,而且大多时间返回间隔较长。最主要的是,这个我华为手机并不行。。。。。
三、JobScheduler执行任务调度保活
它的宗旨是把一些不是特别紧急的任务放到更合适的时机批量处理,这样可以有效的节省电量。这个我自己也写了个demo啥的测试,应该是我写的有问题,也可能是别的,我觉得这个不太适合定位这个事,最终没有实现。有实现的小伙伴可以分享一下,谢谢。
四、WorkManager
一开始看这个的时候,我表示这个的描述有点牛X而且还是新特性
不管是断网,还是将进程杀死,他都会执行。有一些需求用这个来实现是相当的不错,比如 手机端向服务器请求数据,当没有网络时,不要请求,有网络时自动恢复请求。如果网络处于断开状态,则将请求保存在队列当中,监听网络状态,一旦网络连接上,则将队列当中的请求一个一个的执行。 很强,主要是这个可以设置重复的任务,我主要看这块。
简单介绍:WorkManager 可以轻松地让异步任务延迟执行以及何时运行它们,API需要我们创建个worker任务并将其交给WorkManager,用来控制在什么时间运行这个任务。
.setRequiredNetworkType(NetworkType.CONNECTED) // 网络状态
.setRequiresBatteryNotLow(true) // 不在电量不足时执行
.setRequiresCharging(true) // 在充电时执行
.setRequiresStorageNotLow(true) // 不在存储容量不足时执行
.setRequiresDeviceIdle(true) // 在待机状态下执行,需要 API 23
我就不做深究了, 有兴趣的朋友可以自己研究, 最终我没有用这个来实现我的需求,因为重复的任务执行时间间隔最少15分钟。
五、实现了的方式。双服务唤醒
经过测试,小米,oppo,华为等手机 6.0 - 8.0版本没问题,可完美实现。
先把AIDL写了:
interface ILocationHelperServiceAIDL {
/**
* 定位service绑定完毕后通知helperservice自己绑定的notiId
* @param notiId 定位service的notiId
*/
void onFinishBind(int notiId);
}
interface ILocationServiceAIDL {
/** 当其他服务已经绑定时调起 */
void onFinishBind();
}
一共两个。
定义的回调接口,也先写了。
/**
* 代理类,用于处理息屏造成wifi被关掉时再重新点亮屏幕的逻辑
*/
public interface IWifiAutoCloseDelegate {
/**
* 判断在该机型下此逻辑是否有效。目前已知的系统是小米系统存在(用户自助设置的)息屏断掉wifi的功能。
*
* @param context
* @return
*/
boolean isUseful(Context context);
/**
* 点亮屏幕的服务有可能被重启。此处进行初始化
*
* @param context
* @return
*/
void initOnServiceStarted(Context context);
/**
* 定位成功时,如果移动网络无法访问,而且屏幕是点亮状态,则对状态进行保存
*/
void onLocateSuccess(Context context, boolean isScreenOn, boolean isMobileable);
/**
* 对定位失败情况的处理
*/
void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable);
}
1.定位服务
/**
*
* 后台服务定位
*
* 1. 只有在由息屏造成的网络断开造成的定位失败时才点亮屏幕
* 2. 利用notification机制增加进程优先级
*
*/
public class LocationService extends NotificationService {
private AMapLocationClient mLocationClient;
private AMapLocationClientOption mLocationOption;
/**
* 处理息屏关掉wifi的delegate类
*/
private IWifiAutoCloseDelegate mWifiAutoCloseDelegate = new WifiAutoCloseDelegate();
/**
* 记录是否需要对息屏关掉wifi的情况进行处理
*/
private boolean mIsWifiCloseable = false;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
applyNotiKeepMech(); //开启利用notification提高进程优先级的机制
if (mWifiAutoCloseDelegate.isUseful(getApplicationContext())) {
mIsWifiCloseable = true;
mWifiAutoCloseDelegate.initOnServiceStarted(getApplicationContext());
}
startLocation();
return START_STICKY;
}
@Override
public void onDestroy() {
unApplyNotiKeepMech();
stopLocation();
super.onDestroy();
}
/**
* 启动定位
*/
void startLocation() {
stopLocation();
if (null == mLocationClient) {
mLocationClient = new AMapLocationClient(this.getApplicationContext());
}
mLocationOption = new AMapLocationClientOption();
// 使用连续
mLocationOption.setOnceLocation(false);
mLocationOption.setLocationCacheEnable(false);
// 每5秒定位一次
mLocationOption.setInterval(5 * 1000);
// 地址信息
mLocationOption.setNeedAddress(true);
mLocationClient.setLocationOption(mLocationOption);
mLocationClient.setLocationListener(locationListener);
mLocationClient.startLocation();
}
/**
* 停止定位
*/
void stopLocation() {
if (null != mLocationClient) {
mLocationClient.stopLocation();
}
}
AMapLocationListener locationListener = new AMapLocationListener() {
@Override
public void onLocationChanged(AMapLocation aMapLocation) {
//发送结果的通知
sendLocationBroadcast(aMapLocation);
if (!mIsWifiCloseable) {
return;
}
if (aMapLocation.getErrorCode() == AMapLocation.LOCATION_SUCCESS) {
mWifiAutoCloseDelegate.onLocateSuccess(getApplicationContext(), PowerManagerUtil.getInstance().isScreenOn(getApplicationContext()), NetUtil.getInstance().isMobileAva(getApplicationContext()));
} else {
mWifiAutoCloseDelegate.onLocateFail(getApplicationContext() , aMapLocation.getErrorCode() , PowerManagerUtil.getInstance().isScreenOn(getApplicationContext()), NetUtil.getInstance().isWifiCon(getApplicationContext()));
}
}
private void sendLocationBroadcast(AMapLocation aMapLocation) {
if (null != aMapLocation) {
Intent mIntent = new Intent(LocationChangBroadcastReceiver.RECEIVER_ACTION);
mIntent.putExtra(LocationChangBroadcastReceiver.RECEIVER_DATA, aMapLocation);
sendBroadcast(mIntent);
ToastUtils.show("获取到定位信息");
String string = System.currentTimeMillis() + ","+aMapLocation.getLatitude() + "," + aMapLocation.getLongitude();
Utils.saveFile(string, "backlocation.txt", true);
}
}
};
}
定位服务的基类
/**
* 利用双service进行notification绑定,进而将Service的OOM_ADJ提高到1
* 同时利用LocationHelperService充当守护进程,在NotificationService被关闭后,重启他。
* 如果LocationHelperService被停止,NotificationService不负责唤醒
*/
public class NotificationService extends Service {
/**
* startForeground的 noti_id
*/
private static int NOTI_ID = 123321;
private Utils.CloseServiceReceiver mCloseReceiver;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("background location", "远程服务调用成功");
mCloseReceiver = new Utils.CloseServiceReceiver(this);
registerReceiver(mCloseReceiver, Utils.getCloseServiceFilter());
return START_STICKY;
}
@Override
public void onDestroy() {
if (mCloseReceiver != null) {
unregisterReceiver(mCloseReceiver);
mCloseReceiver = null;
}
super.onDestroy();
}
private final String mHelperServiceName = "com.hdsx.background.locationservice.LocationHelperService";
/**
* 触发利用notification增加进程优先级
*/
protected void applyNotiKeepMech() {
startForeground(NOTI_ID, Utils.buildNotification(getBaseContext()));
startBindHelperService();
}
public void unApplyNotiKeepMech() {
stopForeground(true);
}
public Binder mBinder;
public class LocationServiceBinder extends ILocationServiceAIDL.Stub {
@Override
public void onFinishBind(){
}
}
private ILocationHelperServiceAIDL mHelperAIDL;
private void startBindHelperService() {
connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
//doing nothing
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
ILocationHelperServiceAIDL l = ILocationHelperServiceAIDL.Stub.asInterface(service);
mHelperAIDL = l;
try {
l.onFinishBind(NOTI_ID);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
Intent intent = new Intent();
intent.setAction(mHelperServiceName);
bindService(Utils.getExplicitIntent(getApplicationContext(), intent), connection, Service.BIND_AUTO_CREATE);
}
private ServiceConnection connection;
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (mBinder == null) {
mBinder = new LocationServiceBinder();
}
return mBinder;
}
}
另外一个服务:
public class LocationHelperService extends Service {
private Utils.CloseServiceReceiver mCloseReceiver;
@Override
public void onCreate() {
super.onCreate();
startBind();
mCloseReceiver = new Utils.CloseServiceReceiver(this);
registerReceiver(mCloseReceiver, Utils.getCloseServiceFilter());
}
@Override
public void onDestroy() {
if (mInnerConnection != null) {
unbindService(mInnerConnection);
mInnerConnection = null;
}
if (mCloseReceiver != null) {
unregisterReceiver(mCloseReceiver);
mCloseReceiver = null;
}
super.onDestroy();
}
private ServiceConnection mInnerConnection;
private void startBind() {
final String locationServiceName = "com.hdsx.background.locationservice.LocationService";
mInnerConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Intent intent = new Intent();
intent.setAction(locationServiceName);
startService(Utils.getExplicitIntent(getApplicationContext(), intent));
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
ILocationServiceAIDL l = ILocationServiceAIDL.Stub.asInterface(service);
try {
l.onFinishBind();
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
Intent intent = new Intent();
intent.setAction(locationServiceName);
bindService(Utils.getExplicitIntent(getApplicationContext(), intent), mInnerConnection, Service.BIND_AUTO_CREATE);
}
private HelperBinder mBinder;
private class HelperBinder extends ILocationHelperServiceAIDL.Stub {
@Override
public void onFinishBind(int notiId) throws RemoteException {
startForeground(notiId, Utils.buildNotification(LocationHelperService.this.getApplicationContext()));
stopForeground(true);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (mBinder == null) {
mBinder = new HelperBinder();
}
return mBinder;
}
}
双服务进行捆绑,
之前测试的过程中,发现这个powermanager配合服务他是可实现部分手机的,这边也做一个power的封装。
/**
* 获得PARTIAL_WAKE_LOCK , 保证在息屏状体下,CPU可以正常运行
*/
public class PowerManagerUtil {
private static class Holder {
public static PowerManagerUtil instance = new PowerManagerUtil();
}
private PowerManager pm = null;
private PowerManager.WakeLock pmLock = null;
/**
* 上次唤醒屏幕的触发时间
*/
private long mLastWakupTime = System.currentTimeMillis();
/**
* 最小的唤醒时间间隔,防止频繁唤醒。默认5分钟
*/
private long mMinWakupInterval = 10 * 1000;
private InnerThreadFactory mInnerThreadFactory = null;
public static PowerManagerUtil getInstance() {
return Holder.instance;
}
/**
* 判断屏幕是否处于点亮状态
*
* @param context
*/
public boolean isScreenOn(final Context context) {
try {
Method isScreenMethod = PowerManager.class.getMethod("isScreenOn",
new Class[]{});
if (pm == null) {
pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
}
boolean screenState = (Boolean) isScreenMethod.invoke(pm);
return screenState;
} catch (Exception e) {
return true;
}
}
/**
* 唤醒屏幕
*/
public void wakeUpScreen(final Context context) {
try {
acquirePowerLock(context, PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK);
String string = System.currentTimeMillis() +"唤醒";
Utils.saveFile(string, "huanxinglocation.txt", true);
} catch (Exception e) {
throw e;
}
}
/**
* 根据levelAndFlags,获得PowerManager的WaveLock
* 利用worker thread去获得锁,以免阻塞主线程
* @param context
* @param levelAndFlags
*/
private void acquirePowerLock(final Context context, final int levelAndFlags) {
if (context == null) {
throw new NullPointerException("when invoke aquirePowerLock , context is null which is unacceptable");
}
long currentMills = System.currentTimeMillis();
if (currentMills - mLastWakupTime < mMinWakupInterval) {
return;
}
mLastWakupTime = currentMills;
if (mInnerThreadFactory == null) {
mInnerThreadFactory = new InnerThreadFactory();
}
mInnerThreadFactory.newThread(new Runnable() {
@Override
public void run() {
if (pm == null) {
pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
}
if (pmLock != null) {
// release
pmLock.release();
pmLock = null;
}
pmLock = pm.newWakeLock(levelAndFlags, "MyTag");
pmLock.acquire();
pmLock.release();
}
}).start();
}
private class InnerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable);
}
}
}
整体的逻辑呢就是说, 启动LocationService开启定位,定位成功后在手机添加一个前台的通知,让通知的优先级尽可能的提高。在锁屏后,powermanager判断获取如果定位失败唤醒服务。
public void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {
//如果屏幕点亮情况下,因为断网失败,则表示不是屏幕点亮造成的断网失败,并修改参照值
if (isScreenOn && errorCode == AMapLocation.ERROR_CODE_FAILURE_CONNECTION && !isWifiable) {
LocationStatusManager.getInstance().resetToInit(context);
return;
}
if (!LocationStatusManager.getInstance().isFailOnScreenOff(context, errorCode, isScreenOn, isWifiable)) {
return;
}
PowerManagerUtil.getInstance().wakeUpScreen(context);
}
代码有点多,就不一一介绍了,下面把我包含的工具类都发出来。
- LocationStatusManager
/**
* 在定位失败的情况下,用于判断当前定位错误是否是由于息屏导致的网络关闭引起的。
* 判断逻辑仅限于处理设备仅有wifi信号的情况下
*/
public class LocationStatusManager {
/**
* 上一次的定位是否成功
*/
private boolean mPriorSuccLocated = false;
/**
* 屏幕亮时可以定位
*/
private boolean mPirorLocatableOnScreen = false;
static class Holder {
public static LocationStatusManager instance = new LocationStatusManager();
}
public static LocationStatusManager getInstance() {
return Holder.instance;
}
/**
* 由于仅仅处理只有wifi连接的情况下,如果用户手机网络可连接,那么忽略。
* 定位成功时,重置为定位成功的状态
*
* @param isScreenOn 当前屏幕是否为点亮状态
* @param isMobileable 是否有手机信号
*/
public void onLocationSuccess(Context context, boolean isScreenOn, boolean isMobileable) {
if (isMobileable) {
return;
}
mPriorSuccLocated = true;
if (isScreenOn) {
mPirorLocatableOnScreen = true;
saveStateInner(context, true);
}
}
/**
* reset到默认状态
*
* @param context
*/
public void resetToInit(Context context) {
this.mPirorLocatableOnScreen = false;
this.mPriorSuccLocated = false;
saveStateInner(context, false);
}
/**
* 由preference初始化。特别是在定位服务重启的时候会进行初始化
*/
public void initStateFromPreference(Context context) {
if (!isLocableOnScreenOn(context)) {
return;
}
this.mPriorSuccLocated = true;
this.mPirorLocatableOnScreen = true;
}
/**
* 判断是否由屏幕关闭导致的定位失败。
* 只有在 网络可访问&&errorCode==4&&(priorLocated&&locatableOnScreen) && !isScreenOn 才认为是有息屏引起的定位失败
* 如果判断条件较为严格,请按需要适当修改
*
* @param errorCode 定位错误码, 0=成功, 4=因为网络原因造成的失败
* @param isScreenOn 当前屏幕是否为点亮状态
*/
public boolean isFailOnScreenOff(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {
return !isWifiable && errorCode == AMapLocation.ERROR_CODE_FAILURE_CONNECTION && (mPriorSuccLocated && mPirorLocatableOnScreen) && !isScreenOn;
}
/**
* 是否存在屏幕亮而且可以定位的情况的key
*/
private String IS_LOCABLE_KEY = "is_locable_key";
/**
* IS_LOCABLE_KEY 的过期时间
*/
private String LOCALBLE_KEY_EXPIRE_TIME_KEY = "localble_key_expire_time_key";
/**
* 过期时间为10分钟
*/
private static final long MINIMAL_EXPIRE_TIME = 30 * 60 * 1000;
private static final String PREFER_NAME = LocationStatusManager.class.getSimpleName();
private static final long DEF_PRIOR_TIME_VAL = -1;
/**
* 如果isLocable,则存入正确的过期时间,否则存默认值
*
* @param context
* @param isLocable
*/
public void saveStateInner(Context context, boolean isLocable) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFER_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(IS_LOCABLE_KEY, isLocable);
editor.putLong(LOCALBLE_KEY_EXPIRE_TIME_KEY, isLocable ? System.currentTimeMillis() : DEF_PRIOR_TIME_VAL);
editor.commit();
}
/**
* 从preference读取,判断是否存在网络状况ok,而且亮屏情况下,可以定位的情况
*/
public boolean isLocableOnScreenOn(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFER_NAME, MODE_PRIVATE);
boolean res = sharedPreferences.getBoolean(IS_LOCABLE_KEY, false);
long priorTime = sharedPreferences.getLong(LOCALBLE_KEY_EXPIRE_TIME_KEY, DEF_PRIOR_TIME_VAL);
if (System.currentTimeMillis() - priorTime > MINIMAL_EXPIRE_TIME) {
saveStateInner(context, false);
return false;
}
return res;
}
}
- LocationChangBroadcastReceiver
做最后定位的结果,保存到数据库里
public class LocationChangBroadcastReceiver extends BroadcastReceiver {
public static final String RECEIVER_ACTION = "location_in_background";
public static final String RECEIVER_DATA = "location_data";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(RECEIVER_ACTION)) {
AMapLocation location = (AMapLocation) intent.getParcelableExtra(RECEIVER_DATA);
if (null != location) {
String string = System.currentTimeMillis() + ","+location.getLatitude() + "," + location.getLongitude();
Utils.saveFile(string, "broadcastlocation.txt", true);
Log.v("定位数据", "经度:" + location.getLongitude() + " 纬度:" + location.getLatitude());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Date date = new Date(location.getTime());
String tracktime = sdf.format(date);
Map map = new HashMap();
String userid = SpUtils.getString(USER_ID);
map.put("userid", userid);
double[] loc = CoordinateTransformUtil.gcj02towgs84(location.getLongitude(),location.getLatitude());
map.put("tracktime", tracktime);
map.put("latitude", loc[1]);
map.put("lontitude", loc[0]);
Frame.getInstance().getDao().insert("trackbean.insert_track", map);
}
}
}
}
- NetUtil
用于判断设备是否可以访问网络。
public class NetUtil {
private static class Holder {
public static NetUtil instance = new NetUtil();
}
public static NetUtil getInstance() {
return Holder.instance;
}
/**
* 是否手机信号可连接
* @param context
* @return
*/
public boolean isMobileAva(Context context) {
boolean hasMobileCon = false;
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
NetworkInfo[] netInfos = cm.getAllNetworkInfo();
for (NetworkInfo net : netInfos) {
String type = net.getTypeName();
if (type.equalsIgnoreCase("MOBILE")) {
if (net.isConnected()) {
hasMobileCon = true;
}
}
}
return hasMobileCon;
}
/**
* 是否wifi可连接
* @param context
* @return
*/
public boolean isWifiCon(Context context) {
boolean hasWifoCon = false;
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
NetworkInfo[] netInfos = cm.getAllNetworkInfo();
for (NetworkInfo net : netInfos) {
String type = net.getTypeName();
if (type.equalsIgnoreCase("WIFI")) {
if (net.isConnected()) {
hasWifoCon = true;
}
}
}
return hasWifoCon;
}
}
- Utils
辅助工具类
public class Utils {
private static String CLOSE_BRODECAST_INTENT_ACTION_NAME="com.hdsx.background.locationservice.CloseService";
/**
* 开始定位
*/
public final static int MSG_LOCATION_START = 0;
/**
* 定位完成
*/
public final static int MSG_LOCATION_FINISH = 1;
/**
* 停止定位
*/
public final static int MSG_LOCATION_STOP = 2;
public final static String KEY_URL = "URL";
public final static String URL_H5LOCATION = "file:///android_asset/location.html";
private static SimpleDateFormat sdf = null;
private static NotificationManager mNotificationManager;
private final static String PRIMARY_CHANNEL = "default";
/**
* 根据定位结果返回定位信息的字符串
*
* @param location
* @return
*/
public synchronized static String getLocationStr(AMapLocation location) {
if (null == location) {
return null;
}
StringBuffer sb = new StringBuffer();
//errCode等于0代表定位成功,其他的为定位失败,具体的可以参照官网定位错误码说明
if (location.getErrorCode() == 0) {
sb.append("定位成功" + "\n");
sb.append("定位类型: " + location.getLocationType() + "\n");
sb.append("经 度 : " + location.getLongitude() + "\n");
sb.append("纬 度 : " + location.getLatitude() + "\n");
sb.append("精 度 : " + location.getAccuracy() + "米" + "\n");
sb.append("提供者 : " + location.getProvider() + "\n");
sb.append("海 拔 : " + location.getAltitude() + "米" + "\n");
sb.append("速 度 : " + location.getSpeed() + "米/秒" + "\n");
sb.append("角 度 : " + location.getBearing() + "\n");
if (location.getProvider().equalsIgnoreCase(
android.location.LocationManager.GPS_PROVIDER)) {
// 以下信息只有提供者是GPS时才会有
// 获取当前提供定位服务的卫星个数
sb.append("星 数 : "
+ location.getSatellites() + "\n");
}
//逆地理信息
sb.append("国 家 : " + location.getCountry() + "\n");
sb.append("省 : " + location.getProvince() + "\n");
sb.append("市 : " + location.getCity() + "\n");
sb.append("城市编码 : " + location.getCityCode() + "\n");
sb.append("区 : " + location.getDistrict() + "\n");
sb.append("区域 码 : " + location.getAdCode() + "\n");
sb.append("地 址 : " + location.getAddress() + "\n");
sb.append("兴趣点 : " + location.getPoiName() + "\n");
//定位完成的时间
sb.append("定位时间: " + formatUTC(location.getTime(), "yyyy-MM-dd HH:mm:ss") + "\n");
} else {
//定位失败
sb.append("定位失败" + "\n");
sb.append("错误码:" + location.getErrorCode() + "\n");
sb.append("错误信息:" + location.getErrorInfo() + "\n");
sb.append("错误描述:" + location.getLocationDetail() + "\n");
}
//定位之后的回调时间
sb.append("回调时间: " + formatUTC(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss") + "\n");
return sb.toString();
}
public synchronized static String formatUTC(long l, String strPattern) {
if (TextUtils.isEmpty(strPattern)) {
strPattern = "yyyy-MM-dd HH:mm:ss";
}
if (sdf == null) {
try {
sdf = new SimpleDateFormat(strPattern, Locale.CHINA);
} catch (Throwable e) {
}
} else {
sdf.applyPattern(strPattern);
}
return sdf == null ? "NULL" : sdf.format(l);
}
public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
return implicitIntent;
}
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}
public static void saveFile(String toSaveString, String fileName, boolean append) {
try {
String sdCardRoot = Environment.getExternalStorageDirectory()
.getAbsolutePath();
File saveFile = new File(sdCardRoot + "/" + fileName);
if (!saveFile.exists()) {
File dir = new File(saveFile.getParent());
dir.mkdirs();
saveFile.createNewFile();
}
FileOutputStream outStream = new FileOutputStream(saveFile, append);
outStream.write(toSaveString.getBytes());
outStream.write("\r\n".getBytes());
outStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static Notification buildNotification(Context context) {
Notification notification = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(PRIMARY_CHANNEL,
context.getString(R.string.default_channel), NotificationManager.IMPORTANCE_DEFAULT);
channel.setLightColor(Color.GREEN);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
getNotificationManager(context).createNotificationChannel(channel);
Notification.Builder builder = new Notification.Builder(context,
PRIMARY_CHANNEL)
.setContentText("水源地轨迹记录中...")
.setSmallIcon(R.drawable.ic_launcher)
.setAutoCancel(true);
notification = builder.build();
} else {
Notification.Builder builder = new Notification.Builder(context);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentText("水源地轨迹记录中..." )
.setWhen(System.currentTimeMillis());
notification = builder.build();
}
return notification;
}
private static NotificationManager getNotificationManager(Context context) {
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager)context.getSystemService(
Context.NOTIFICATION_SERVICE);
}
return mNotificationManager;
}
public static void startWifi(Context context) {
WifiManager wm = (WifiManager) context
.getSystemService(Context.WIFI_SERVICE);
wm.setWifiEnabled(true);
wm.reconnect();
}
public static boolean isWifiEnabled(Context context) {
WifiManager wm = (WifiManager) context
.getSystemService(Context.WIFI_SERVICE);
return wm.isWifiEnabled();
}
public static String getManufacture(Context context) {
return Build.MANUFACTURER;
}
public static Intent getCloseBrodecastIntent() {
return new Intent(CLOSE_BRODECAST_INTENT_ACTION_NAME);
}
public static IntentFilter getCloseServiceFilter() {
return new IntentFilter(CLOSE_BRODECAST_INTENT_ACTION_NAME);
}
public static class CloseServiceReceiver extends BroadcastReceiver {
Service mService;
public CloseServiceReceiver(Service service) {
this.mService = service;
}
@Override
public void onReceive(Context context, Intent intent) {
if (mService == null) {
return;
}
mService.onDestroy();
}
}
}
- WifiAutoCloseDelegate
接口实现类,回调一下结果
public class WifiAutoCloseDelegate implements IWifiAutoCloseDelegate {
/**
* 请根据后台数据自行添加。此处只针对小米手机
* @param context
* @return
*/
@Override
public boolean isUseful(Context context) {
String manName = Utils.getManufacture(context);
Pattern pattern = Pattern.compile("xiaomi", Pattern.CASE_INSENSITIVE);
Matcher m = pattern.matcher(manName);
return m.find();
}
@Override
public void initOnServiceStarted(Context context) {
LocationStatusManager.getInstance().initStateFromPreference(context);
}
@Override
public void onLocateSuccess(Context context, boolean isScreenOn, boolean isMobileable) {
LocationStatusManager.getInstance().onLocationSuccess(context, isScreenOn, isMobileable);
}
@Override
public void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {
//如果屏幕点亮情况下,因为断网失败,则表示不是屏幕点亮造成的断网失败,并修改参照值
if (isScreenOn && errorCode == AMapLocation.ERROR_CODE_FAILURE_CONNECTION && !isWifiable) {
LocationStatusManager.getInstance().resetToInit(context);
return;
}
if (!LocationStatusManager.getInstance().isFailOnScreenOff(context, errorCode, isScreenOn, isWifiable)) {
return;
}
PowerManagerUtil.getInstance().wakeUpScreen(context);
}
}
基本就这些, 代码比较多, 有兴趣的朋友可以自行阅读,注解基本都有介绍。
记录一下此类问题。
更新:2019-01-03
附张效果图
上述图片仅用设备GPS定位的。
demo链接如下:
https://download.csdn.net/download/binbinxiaoz/10892551
https://download.csdn.net/download/binbinxiaoz/10890248
测试前,请检查手机 对app的电量管理权限以及限制后台运行权限及锁屏后针对app的权限。若无可手动更改请忽略。
测试时,请室外测试,室内gps信号差。若室内测试请切换别的定位模式,保证可以定位但不保证定位的精准度,本身流量等定位偏差就相当的大。
demo中只需关注养护巡查模块即可,别的模块练手加的,无关联。
android studio版本3.1.4
下载demo后,评个分谢谢。
小米9.0手机处理: 禁止电池优化, 华为手机会因为耗电过强直接终止进程,
别的手机暂时还为测试。 推荐接推送唤醒·
Intent intent = new Intent();
String packageName = getActivity().getPackageName();
PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (pm.isIgnoringBatteryOptimizations(packageName))
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
else {
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
}
}
getActivity().startActivity(intent);