一、簡介
之前公司在app中以android5.0爲適用版本添加了軌跡功能用於記錄用戶健走路線。由於現在用戶手機系統普遍更新到android8.0甚至更高。有一些問題就凸顯出來,最突出的問題便是android系統現有版本在鎖屏條件下無法繼續定位。用戶如果使用必須要保持手機前臺亮屏。這樣用戶體驗就非常差了。所以新一輪的優化也很有必然了。
道長看過一些方案後,最後選擇使用的雙服務喚醒方法實現。道長這篇博客重點是雙服務喚醒的實現以及一些需要注意的地方。大佬的博客列舉了幾種方案以及實現方法,十分詳細,希望可以轉發擴散,讓更多小夥伴解決自己的問題:https://www.jianshu.com/p/956cbba64c53
二、實現步驟
2.1創建AIDL文件
首先這兩個AIDL文件必須是創建的(copy是沒有靈魂的),然後創建位置要對。如下圖所示:
內容如下:
interface ILocationHelperServiceAIDL {
/**
* 定位service綁定完畢後通知helperservice自己綁定的notiId
* @param notiId 定位service的notiId
*/
void onFinishBind(int notiId);
}
interface ILocationServiceAIDL {
/** 當其他服務已經綁定時調起 */
void onFinishBind();
}
2.2回調接口
public interface IWifiAutoCloseDelegate {
boolean isUseful(Context context);
void initOnServiceStarted(Context context);
/**
* 定位成功時,如果移動網絡無法訪問,而且屏幕是點亮狀態,則對狀態進行保存
*/
void onLocateSuccess(Context context, boolean isScreenOn, boolean isMobileable);
/**
* 對定位失敗情況的處理
*/
void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable);
}
2.3定位服務實現
實現的邏輯大概爲:啓動LocationService開啓定位,定位成功後在手機添加一個前臺的通知,讓通知的優先級儘可能的提高。在鎖屏後,powermanager判斷獲取如果定位失敗喚醒服務。
1.定位服務基類,代碼如下:
public class NotificationService extends Service {
private static int NOTICE_ID = 12138;
private final String mHelperServiceName = "com.yushan.background.locationservice.LocationHelperService";
private Utils.CloseServiceReceiver mCloseReceiver;
public Binder mBinder;
private ILocationHelperServiceAIDL mHelperAIDL;
private ServiceConnection connection;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
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();
}
/**
* 觸發利用notification增加進程優先級
*/
protected void applyNotiKeepMech() {
startForeground(NOTICE_ID, Utils.buildNotification(getBaseContext()));
startBindHelperService();
}
public void unApplyNotiKeepMech() {
stopForeground(true);
}
public class LocationServiceBinder extends ILocationServiceAIDL.Stub {
@Override
public void onFinishBind(){
}
}
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(NOTICE_ID);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
Intent intent = new Intent();
intent.setAction(mHelperServiceName);
intent.setPackage("com.wanbu.dascom");
Intent explicitIntent = Utils.getExplicitIntent(getApplicationContext(), intent);
bindService(explicitIntent, connection, Service.BIND_AUTO_CREATE);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (mBinder == null) {
mBinder = new LocationServiceBinder();
}
return mBinder;
}
}
2.定位服務類,代碼如下:
public class LocationService extends NotificationService {
private AMapLocationClient mLocationClient;
private AMapLocationClientOption mLocationOption;
private IWifiAutoCloseDelegate mWifiAutoCloseDelegate = new WifiAutoCloseDelegate();
private boolean mIsWifiCloseable = false;
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);
}
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
//開啓利用notification提高進程優先級的機制
applyNotiKeepMech();
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.setLocationMode(AMapLocationClientOption.AMapLocationMode.Device_Sensors);
// 使用連續
mLocationOption.setOnceLocation(false);
mLocationOption.setLocationCacheEnable(false);
// 每2秒定位一次
mLocationOption.setInterval(2 * 1000);
// 地址信息
mLocationOption.setNeedAddress(true);
mLocationClient.setLocationOption(mLocationOption);
mLocationClient.setLocationListener(locationListener);
mLocationClient.startLocation();
}
void stopLocation() {
if (null != mLocationClient) {
mLocationClient.stopLocation();
}
}
}
3.定位守護類,代碼如下
public class LocationHelperService extends Service {
private Utils.CloseServiceReceiver mCloseReceiver;
private HelperBinder mBinder;
private ServiceConnection mInnerConnection;
@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 void startBind() {
final String locationServiceName = "com.yushan.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);
intent.setPackage("com.wanbu.dascom");
bindService(Utils.getExplicitIntent(getApplicationContext(), intent), mInnerConnection, Service.BIND_AUTO_CREATE);
}
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;
}
}
4.添加全局廣播
由於道長公司app是組件化框架,在當前模塊的Application註冊全局廣播就可以實現喚醒服務。
public class HealthApplication extends BaseApplication {
private static LocationChangBroadcastReceiver locationChangBroadcastReceiver;
public HealthApplication(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
public static LocationChangBroadcastReceiver getlocationChangeBoardcase() {
if (null == locationChangBroadcastReceiver) {
locationChangBroadcastReceiver = new LocationChangBroadcastReceiver();
}
Log.e("yushan","一切有我");
return locationChangBroadcastReceiver;
}
}
這個方案由道長親測可以。小米手機後臺運行鎖屏可能會被殺死,但是打開自啓動後可以長時間運行,但是重新喚醒期間不能定位,期間會丟失部分數據。華爲手機沒有問題。其他機型小夥伴們可以測試一下。
小米手機(6.0.1)app後臺息屏運行時間30s(未打開自啓動)
app後臺息屏運行時間不確定(5小時)(打開自啓動)
app前臺息屏運行時間超長(大於5個小時,具體未測出)(未打開自啓動)
華爲手機(8.0.0)app後臺息屏運行時間大於20小時(未上軟件鎖)
app後臺息屏運行時間(打開軟件鎖)
app前臺息屏運行時間(未上軟件鎖)
由於道長是在公司app上實現的,代碼無法分享。如果有人需要的話可以去上面大佬博客下載Demo,如果還是不明白的話可以留言。希望這篇博客給小夥伴們解決問題的思路。(這個方案暫時無法在android9.0及上實現)