產(chǎn)生問題的原因:
手機(jī)鎖屏后财饥,Android系統(tǒng)為了省電以及減少CPU消耗案铺,在一段時(shí)間后會將手機(jī)進(jìn)入休眠狀態(tài)。此時(shí)的服務(wù)以及線程等都會停止闸迷。
最近就這個(gè)問題,閱讀了很多代碼以及官方文檔俘枫,下面就說下最近都嘗試過的方式腥沽,可能其中有些您實(shí)現(xiàn)了,我這邊沒實(shí)現(xiàn)鸠蚪,望見諒今阳。本文采用的高德定位。
一茅信、PowerManager.WakeLock
(1)直接強(qiáng)制當(dāng)前頁面cpu運(yùn)行
<uses-permission android:name="android.permission.WAKE_LOCK"/>
private PowerManager pm;
private PowerManager.WakeLock wakeLock;
@Override
public void onCreate() {
super.onCreate();
//創(chuàng)建PowerManager對象
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
//保持cpu一直運(yùn)行盾舌,不管屏幕是否黑屏
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CPUKeepRunning");
wakeLock.acquire();
}
@Override
public void onDestroy() {
wakeLock.release();
super.onDestroy();
}
這個(gè)寫法我表示并沒有什么用,并不能強(qiáng)制cpu持續(xù)運(yùn)行汹押。
(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);
}
}
<receiver android:name=".WLWakefulReceiver" >
<intent-filter>
<action android:name="startlocation" />
</intent-filter>
</receiver>
WakefulBroadcastReceiver 內(nèi)部的原理也是PowerManager矿筝,注冊廣播時(shí)8.0的請動(dòng)態(tài)注冊,靜態(tài)沒有用棚贾。廣播注冊完了之后窖维,寫一個(gè)服務(wù)用來與廣播互動(dòng)。
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
protected void onHandleIntent(@Nullable final Intent intent) {
//子線程中執(zhí)行
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("定位結(jié)果", aMapLocation.getAddress()+"");
定位結(jié)果操作妙痹,我這邊是把定位的時(shí)候保存到數(shù)據(jù)庫里面铸史。
}
});
}
}).start();
Log.i("MyIntentService", "onHandleIntent:"+extra);
//調(diào)用completeWakefulIntent來釋放喚醒鎖。
// WLWakefulReceiver.completeWakefulIntent(intent);
}
}
<service android:name=".MyIntentService"></service>
注冊
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();
//設(shè)置定位模式為AMapLocationMode.Hight_Accuracy耿芹,高精度模式崭篡。
mLocationOption.setLocationMode(AMapLocationMode.Hight_Accuracy);
//設(shè)置定位間隔,單位毫秒,默認(rèn)為2000ms
mLocationOption.setInterval(5000);
//設(shè)置是否只定位一次,默認(rèn)為false
mLocationOption.setOnceLocation(false);
//返回最近3s內(nèi)精度最高的一次定位結(jié)果。
mLocationOption.setOnceLocationLatest(false);
//設(shè)置是否返回地址信息(默認(rèn)返回地址信息)
mLocationOption.setNeedAddress(true);
//單位是毫秒吧秕,默認(rèn)30000毫秒琉闪,建議超時(shí)時(shí)間不要低于8000毫秒。
mLocationOption.setHttpTimeOut(20000);
//關(guān)閉緩存機(jī)制
mLocationOption.setLocationCacheEnable(false);
//給定位客戶端對象設(shè)置定位參數(shù)
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();//關(guān)閉
mLocationClient.onDestroy();//銷毀
mLocationClient = null;
Log.e("開始定位","開始定位");
}
@Override
public void onLocationChanged(AMapLocation aMapLocation) {
Log.e("定位到當(dāng)前位置: " , 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;
}
}
}
使用 記得把權(quán)限加上砸彬。
Intent intent = new Intent("startlocation");
intent.putExtra("msg", "定位定位定位");
sendBroadcast(intent);
就這個(gè)寫法而言颠毙,我拿demo測試的時(shí)候,小米砂碉,oppo蛀蜜,高精度gps都可以,gps需要室外定位增蹭,室內(nèi)沒有信號滴某。在適配華為手機(jī)的時(shí)候,有點(diǎn)坑。華為并不行壮池,只支持亮屏偏瓤,之后我用的雙服務(wù)喚醒通訊方式實(shí)現(xiàn)的華為手機(jī),適配8.0椰憋,但其實(shí)我覺得就是因?yàn)橐粋€(gè)電量管理中保持鎖屏后繼續(xù)運(yùn)行權(quán)限導(dǎo)致的厅克, 但這個(gè)我沒有測試過開了權(quán)限這個(gè)方式行不行,小伙伴測試的時(shí)候橙依,可以給我反饋一下证舟,謝謝。
二窗骑、AlarmManager
這個(gè)我一開始看的時(shí)候女责,覺得不錯(cuò),設(shè)置重復(fù)的鬧鐘喚醒服務(wù)创译,讓服務(wù)持續(xù)抵知。鬧鐘設(shè)置重復(fù)定時(shí)的方法變化還是蠻大的,閱讀官方api的時(shí)候软族,不同的版本基本都有不同的設(shè)置方式刷喜。
使用的服務(wù):
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 ");
}
}
注冊
<service android:name=".PollingService">
<intent-filter>
<action android:name="com.hdsx.service.PollingService"/>
</intent-filter>
</service>
服務(wù)里面起個(gè)線程,就是為了測試是否能持續(xù)的log輸出日志立砸。這塊提醒一下掖疮,調(diào)試狀態(tài)的時(shí)候手機(jī)是處于充電狀態(tài)的,cpu不會休眠颗祝,所以會一直log浊闪,不過經(jīng)過我后面添加代碼測試,是可行的螺戳。 我這塊只是提供了原始的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);
這種方式倔幼,也可以實(shí)現(xiàn)盖腿, 但是測試的過程中有個(gè)問題,就是鬧鐘重復(fù)定時(shí)它的間隔有時(shí)候并不能完全按照我設(shè)置的時(shí)間來運(yùn)行凤藏,周期感覺有點(diǎn)不太確定,而且大多時(shí)間返回間隔較長堕伪。最主要的是揖庄,這個(gè)我華為手機(jī)并不行。欠雌。蹄梢。。。
三禁炒、JobScheduler執(zhí)行任務(wù)調(diào)度倍兀活
它的宗旨是把一些不是特別緊急的任務(wù)放到更合適的時(shí)機(jī)批量處理,這樣可以有效的節(jié)省電量幕袱。這個(gè)我自己也寫了個(gè)demo啥的測試暴备,應(yīng)該是我寫的有問題,也可能是別的们豌,我覺得這個(gè)不太適合定位這個(gè)事涯捻,最終沒有實(shí)現(xiàn)。有實(shí)現(xiàn)的小伙伴可以分享一下望迎,謝謝障癌。
四、WorkManager
一開始看這個(gè)的時(shí)候辩尊,我表示這個(gè)的描述有點(diǎn)牛X而且還是新特性
不管是斷網(wǎng)涛浙,還是將進(jìn)程殺死,他都會執(zhí)行摄欲。有一些需求用這個(gè)來實(shí)現(xiàn)是相當(dāng)?shù)牟诲e(cuò)轿亮,比如 手機(jī)端向服務(wù)器請求數(shù)據(jù),當(dāng)沒有網(wǎng)絡(luò)時(shí)蒿涎,不要請求哀托,有網(wǎng)絡(luò)時(shí)自動(dòng)恢復(fù)請求。如果網(wǎng)絡(luò)處于斷開狀態(tài)劳秋,則將請求保存在隊(duì)列當(dāng)中仓手,監(jiān)聽網(wǎng)絡(luò)狀態(tài),一旦網(wǎng)絡(luò)連接上玻淑,則將隊(duì)列當(dāng)中的請求一個(gè)一個(gè)的執(zhí)行嗽冒。 很強(qiáng),主要是這個(gè)可以設(shè)置重復(fù)的任務(wù)补履,我主要看這塊添坊。
簡單介紹:WorkManager 可以輕松地讓異步任務(wù)延遲執(zhí)行以及何時(shí)運(yùn)行它們,API需要我們創(chuàng)建個(gè)worker任務(wù)并將其交給WorkManager箫锤,用來控制在什么時(shí)間運(yùn)行這個(gè)任務(wù)贬蛙。
.setRequiredNetworkType(NetworkType.CONNECTED) // 網(wǎng)絡(luò)狀態(tài)
.setRequiresBatteryNotLow(true) // 不在電量不足時(shí)執(zhí)行
.setRequiresCharging(true) // 在充電時(shí)執(zhí)行
.setRequiresStorageNotLow(true) // 不在存儲容量不足時(shí)執(zhí)行
.setRequiresDeviceIdle(true) // 在待機(jī)狀態(tài)下執(zhí)行,需要 API 23
我就不做深究了谚攒, 有興趣的朋友可以自己研究阳准, 最終我沒有用這個(gè)來實(shí)現(xiàn)我的需求,因?yàn)橹貜?fù)的任務(wù)執(zhí)行時(shí)間間隔最少15分鐘馏臭。
五野蝇、實(shí)現(xiàn)了的方式。雙服務(wù)喚醒
經(jīng)過測試,小米绕沈,oppo锐想,華為等手機(jī) 6.0 - 8.0版本沒問題,可完美實(shí)現(xiàn)乍狐。
先把AIDL寫了:
interface ILocationHelperServiceAIDL {
/**
* 定位service綁定完畢后通知helperservice自己綁定的notiId
* @param notiId 定位service的notiId
*/
void onFinishBind(int notiId);
}
interface ILocationServiceAIDL {
/** 當(dāng)其他服務(wù)已經(jīng)綁定時(shí)調(diào)起 */
void onFinishBind();
}
一共兩個(gè)赠摇。
定義的回調(diào)接口,也先寫了澜躺。
/**
* 代理類蝉稳,用于處理息屏造成wifi被關(guān)掉時(shí)再重新點(diǎn)亮屏幕的邏輯
*/
public interface IWifiAutoCloseDelegate {
/**
* 判斷在該機(jī)型下此邏輯是否有效。目前已知的系統(tǒng)是小米系統(tǒng)存在(用戶自助設(shè)置的)息屏斷掉wifi的功能掘鄙。
*
* @param context
* @return
*/
boolean isUseful(Context context);
/**
* 點(diǎn)亮屏幕的服務(wù)有可能被重啟耘戚。此處進(jìn)行初始化
*
* @param context
* @return
*/
void initOnServiceStarted(Context context);
/**
* 定位成功時(shí),如果移動(dòng)網(wǎng)絡(luò)無法訪問操漠,而且屏幕是點(diǎn)亮狀態(tài)收津,則對狀態(tài)進(jìn)行保存
*/
void onLocateSuccess(Context context, boolean isScreenOn, boolean isMobileable);
/**
* 對定位失敗情況的處理
*/
void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable);
}
1.定位服務(wù)
/**
*
* 后臺服務(wù)定位
*
* 1. 只有在由息屏造成的網(wǎng)絡(luò)斷開造成的定位失敗時(shí)才點(diǎn)亮屏幕
* 2. 利用notification機(jī)制增加進(jìn)程優(yōu)先級
*
*/
public class LocationService extends NotificationService {
private AMapLocationClient mLocationClient;
private AMapLocationClientOption mLocationOption;
/**
* 處理息屏關(guān)掉wifi的delegate類
*/
private IWifiAutoCloseDelegate mWifiAutoCloseDelegate = new WifiAutoCloseDelegate();
/**
* 記錄是否需要對息屏關(guān)掉wifi的情況進(jìn)行處理
*/
private boolean mIsWifiCloseable = false;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
applyNotiKeepMech(); //開啟利用notification提高進(jìn)程優(yōu)先級的機(jī)制
if (mWifiAutoCloseDelegate.isUseful(getApplicationContext())) {
mIsWifiCloseable = true;
mWifiAutoCloseDelegate.initOnServiceStarted(getApplicationContext());
}
startLocation();
return START_STICKY;
}
@Override
public void onDestroy() {
unApplyNotiKeepMech();
stopLocation();
super.onDestroy();
}
/**
* 啟動(dòng)定位
*/
void startLocation() {
stopLocation();
if (null == mLocationClient) {
mLocationClient = new AMapLocationClient(this.getApplicationContext());
}
mLocationOption = new AMapLocationClientOption();
// 使用連續(xù)
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) {
//發(fā)送結(jié)果的通知
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);
}
}
};
}
定位服務(wù)的基類
/**
* 利用雙service進(jìn)行notification綁定,進(jìn)而將Service的OOM_ADJ提高到1
* 同時(shí)利用LocationHelperService充當(dāng)守護(hù)進(jìn)程浊伙,在NotificationService被關(guān)閉后撞秋,重啟他。
* 如果LocationHelperService被停止嚣鄙,NotificationService不負(fù)責(zé)喚醒
*/
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", "遠(yuǎn)程服務(wù)調(diào)用成功");
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";
/**
* 觸發(fā)利用notification增加進(jìn)程優(yōu)先級
*/
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;
}
}
另外一個(gè)服務(wù):
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;
}
}
雙服務(wù)進(jìn)行捆綁吻贿,
之前測試的過程中,發(fā)現(xiàn)這個(gè)powermanager配合服務(wù)他是可實(shí)現(xiàn)部分手機(jī)的哑子,這邊也做一個(gè)power的封裝舅列。
/**
* 獲得PARTIAL_WAKE_LOCK , 保證在息屏狀體下卧蜓,CPU可以正常運(yùn)行
*/
public class PowerManagerUtil {
private static class Holder {
public static PowerManagerUtil instance = new PowerManagerUtil();
}
private PowerManager pm = null;
private PowerManager.WakeLock pmLock = null;
/**
* 上次喚醒屏幕的觸發(fā)時(shí)間
*/
private long mLastWakupTime = System.currentTimeMillis();
/**
* 最小的喚醒時(shí)間間隔帐要,防止頻繁喚醒。默認(rèn)5分鐘
*/
private long mMinWakupInterval = 10 * 1000;
private InnerThreadFactory mInnerThreadFactory = null;
public static PowerManagerUtil getInstance() {
return Holder.instance;
}
/**
* 判斷屏幕是否處于點(diǎn)亮狀態(tài)
*
* @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;
}
}
/**
* 根據(jù)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);
}
}
}
整體的邏輯呢就是說, 啟動(dòng)LocationService開啟定位盛霎,定位成功后在手機(jī)添加一個(gè)前臺的通知赠橙,讓通知的優(yōu)先級盡可能的提高。在鎖屏后愤炸,powermanager判斷獲取如果定位失敗喚醒服務(wù)期揪。
public void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {
//如果屏幕點(diǎn)亮情況下,因?yàn)閿嗑W(wǎng)失敗摇幻,則表示不是屏幕點(diǎn)亮造成的斷網(wǎng)失敗横侦,并修改參照值
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);
}
代碼有點(diǎn)多,就不一一介紹了绰姻,下面把我包含的工具類都發(fā)出來枉侧。
- LocationStatusManager
/**
* 在定位失敗的情況下,用于判斷當(dāng)前定位錯(cuò)誤是否是由于息屏導(dǎo)致的網(wǎng)絡(luò)關(guān)閉引起的狂芋。
* 判斷邏輯僅限于處理設(shè)備僅有wifi信號的情況下
*/
public class LocationStatusManager {
/**
* 上一次的定位是否成功
*/
private boolean mPriorSuccLocated = false;
/**
* 屏幕亮?xí)r可以定位
*/
private boolean mPirorLocatableOnScreen = false;
static class Holder {
public static LocationStatusManager instance = new LocationStatusManager();
}
public static LocationStatusManager getInstance() {
return Holder.instance;
}
/**
* 由于僅僅處理只有wifi連接的情況下榨馁,如果用戶手機(jī)網(wǎng)絡(luò)可連接,那么忽略帜矾。
* 定位成功時(shí)翼虫,重置為定位成功的狀態(tài)
*
* @param isScreenOn 當(dāng)前屏幕是否為點(diǎn)亮狀態(tài)
* @param isMobileable 是否有手機(jī)信號
*/
public void onLocationSuccess(Context context, boolean isScreenOn, boolean isMobileable) {
if (isMobileable) {
return;
}
mPriorSuccLocated = true;
if (isScreenOn) {
mPirorLocatableOnScreen = true;
saveStateInner(context, true);
}
}
/**
* reset到默認(rèn)狀態(tài)
*
* @param context
*/
public void resetToInit(Context context) {
this.mPirorLocatableOnScreen = false;
this.mPriorSuccLocated = false;
saveStateInner(context, false);
}
/**
* 由preference初始化。特別是在定位服務(wù)重啟的時(shí)候會進(jìn)行初始化
*/
public void initStateFromPreference(Context context) {
if (!isLocableOnScreenOn(context)) {
return;
}
this.mPriorSuccLocated = true;
this.mPirorLocatableOnScreen = true;
}
/**
* 判斷是否由屏幕關(guān)閉導(dǎo)致的定位失敗屡萤。
* 只有在 網(wǎng)絡(luò)可訪問&&errorCode==4&&(priorLocated&&locatableOnScreen) && !isScreenOn 才認(rèn)為是有息屏引起的定位失敗
* 如果判斷條件較為嚴(yán)格珍剑,請按需要適當(dāng)修改
*
* @param errorCode 定位錯(cuò)誤碼, 0=成功, 4=因?yàn)榫W(wǎng)絡(luò)原因造成的失敗
* @param isScreenOn 當(dāng)前屏幕是否為點(diǎn)亮狀態(tài)
*/
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 的過期時(shí)間
*/
private String LOCALBLE_KEY_EXPIRE_TIME_KEY = "localble_key_expire_time_key";
/**
* 過期時(shí)間為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死陆,則存入正確的過期時(shí)間招拙,否則存默認(rèn)值
*
* @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讀取,判斷是否存在網(wǎng)絡(luò)狀況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
做最后定位的結(jié)果,保存到數(shù)據(jù)庫里
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("定位數(shù)據(jù)", "經(jīng)度:" + 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
用于判斷設(shè)備是否可以訪問網(wǎng)絡(luò)领虹。
public class NetUtil {
private static class Holder {
public static NetUtil instance = new NetUtil();
}
public static NetUtil getInstance() {
return Holder.instance;
}
/**
* 是否手機(jī)信號可連接
* @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";
/**
* 根據(jù)定位結(jié)果返回定位信息的字符串
*
* @param location
* @return
*/
public synchronized static String getLocationStr(AMapLocation location) {
if (null == location) {
return null;
}
StringBuffer sb = new StringBuffer();
//errCode等于0代表定位成功规哪,其他的為定位失敗,具體的可以參照官網(wǎng)定位錯(cuò)誤碼說明
if (location.getErrorCode() == 0) {
sb.append("定位成功" + "\n");
sb.append("定位類型: " + location.getLocationType() + "\n");
sb.append("經(jīng) 度 : " + 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時(shí)才會有
// 獲取當(dāng)前提供定位服務(wù)的衛(wèi)星個(gè)數(shù)
sb.append("星 數(shù) : "
+ 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("區(qū) : " + location.getDistrict() + "\n");
sb.append("區(qū)域 碼 : " + location.getAdCode() + "\n");
sb.append("地 址 : " + location.getAddress() + "\n");
sb.append("興趣點(diǎn) : " + location.getPoiName() + "\n");
//定位完成的時(shí)間
sb.append("定位時(shí)間: " + formatUTC(location.getTime(), "yyyy-MM-dd HH:mm:ss") + "\n");
} else {
//定位失敗
sb.append("定位失敗" + "\n");
sb.append("錯(cuò)誤碼:" + location.getErrorCode() + "\n");
sb.append("錯(cuò)誤信息:" + location.getErrorInfo() + "\n");
sb.append("錯(cuò)誤描述:" + location.getLocationDetail() + "\n");
}
//定位之后的回調(diào)時(shí)間
sb.append("回調(diào)時(shí)間: " + 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> 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
接口實(shí)現(xiàn)類塌衰,回調(diào)一下結(jié)果
public class WifiAutoCloseDelegate implements IWifiAutoCloseDelegate {
/**
* 請根據(jù)后臺數(shù)據(jù)自行添加诉稍。此處只針對小米手機(jī)
* @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) {
//如果屏幕點(diǎn)亮情況下,因?yàn)閿嗑W(wǎng)失敗猾蒂,則表示不是屏幕點(diǎn)亮造成的斷網(wǎng)失敗均唉,并修改參照值
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
附張效果圖
上述圖片僅用設(shè)備GPS定位的层扶。
demo鏈接如下:
https://download.csdn.net/download/binbinxiaoz/10892551
https://download.csdn.net/download/binbinxiaoz/10890248
測試前,請檢查手機(jī) 對app的電量管理權(quán)限以及限制后臺運(yùn)行權(quán)限及鎖屏后針對app的權(quán)限烙荷。若無可手動(dòng)更改請忽略镜会。
測試時(shí),請室外測試终抽,室內(nèi)gps信號差戳表。若室內(nèi)測試請切換別的定位模式桶至,保證可以定位但不保證定位的精準(zhǔn)度,本身流量等定位偏差就相當(dāng)?shù)拇蟆?br>
demo中只需關(guān)注養(yǎng)護(hù)巡查模塊即可匾旭,別的模塊練手加的镣屹,無關(guān)聯(lián)。
android studio版本3.1.4
下載demo后价涝,評個(gè)分謝謝女蜈。
小米9.0手機(jī)處理: 禁止電池優(yōu)化, 華為手機(jī)會因?yàn)楹碾娺^強(qiáng)直接終止進(jìn)程色瘩,
別的手機(jī)暫時(shí)還為測試伪窖。 推薦接推送喚醒·
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);