一、简介
之前公司在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及上实现,如果适配9.0需要对上面的代码做部分改动)