目录
一、后台如何持续获取定位
1、后台以及锁屏后持续定位异常的原因以及应对方案探索
2、后台持续获取定位失败的应对方案
二、对坐标点进行加工处理
(1)、为什么要加工处理
(2)、如何加工处理
本文主要是给以下两个问题提供解决方案建议:
1、后台如何持续获取定位,包括处于以下环境:(1)应用已经切换到后台;(2)手机已经锁屏
2、成功获取到的一堆坐标点后应该如何加工处理,让坐标点尽可能准确地描绘手机的移动轨迹
一、后台如何持续获取定位
1、后台以及锁屏后持续定位异常的原因以及应对方案探索
关于App切换到后台并且息屏后,持续定位失效的探索:
(1)、在Android8.0 之后,谷歌已经禁用了后台服务的功能,只允许使用前台服务,意味着app一旦看不见后,不管是主页键还是返回键导致的,黑屏后,后台的功能都会即将或者立即进入冷冻状态,即服务是活着的,但是逻辑却没有正常运行,定位系统就会出现坐标不及时更新甚至不更新的情况。
(2)、针对此类型的系统管制,在参考了网上资料后,主要的有效资料来源于百度和高德的后台定位保活建议处理。百度的保活处理已经在app上得到应用,要提及的关键一点就是系统的电源锁机制。app可以通过获取锁,从影响甚至小程度的控制系统的电源运行情况,从而使自身app免于进入冷冻状态。但该锁在低端机出现明显的兼容问题,就是该保活处理在高端机或者中端机得到较好的成效,但在低端机例如高通骁龙4系的处理器平台上,保活失效,定位系统的更新频率会被系统压制到30秒更新一次,也就是说,app 的定位30秒才更新一次,会对用户带来一定的迷惑。推测原因可能是低端机的电源管控可能更加严格,或者一刀切处理。
2、后台持续获取定位失败的应对方案
为什么说是应对方案了,因为这个方法不能保证一定起作用。方法主要是参考百度地图的鹰眼sdk文档中的服务存活做法建议,这个是百度的链接:百度鹰眼SDK后台保活的做法建议
百度的建议中最强的一点是:启用多媒体锁
什么是多媒体锁?可以自行百度,以下是百度参考建议中直接复制粘贴过来的内容:
为了确保MediaPlayer的承载的服务在系统睡眠的时候继续正常执行下去。Android为我们提供了一种唤醒锁(wake locks)的机制。它能够在系统睡眠时,依旧保持锁定硬件的正常工作。基于这种思路,可以在集成鹰眼SDK的APP中,使用Service继承MediaPlayer,播放一段无声音频文件,达到保活效果。确保在MediaPlayer执行的时候,哪怕系统睡眠了CUP也能正常执行。需要使用MediaPlayer.setWakeMode()为MediaPlayer设定唤醒锁。以下是setWakMode()的定义:setWakeMode(Context context, int mode)第一个參数是当前上下文,第二个參数为须要加锁的状态,被设定为int类型的常量,定义在PowerManager这个final类中。在这里仅仅须要设定为PARTIAL_WAKE_LOCK就可以。
// 设定CUP锁定 mediaPlayer = new MediaPlayer(); mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
一般对于锁而言。锁定了通常须要解锁。可是这里的唤醒锁与MediaPlayer关联,所以仅仅须要在使用完之后release()释放MediaPlayer就可以,无需显式的为其解锁。在使用setWakeMode设定唤醒锁的时候,还必须为应用赋予对应的权限: <uses-permission android:name="android.permission.WAKE_LOCK"/>
在文末云盘分享链接。
示例代码:
/**
* 新建这个播放器,主要是用来获取媒体锁,从而使服务不被系统杀掉,一般对于锁而言。锁定了通常须要解锁。
* 可是这里的唤醒锁与MediaPlayer关联,所以仅仅须要在使用完之后release()释放MediaPlayer就可以,无需显式的为其解锁
*/
private MediaPlayer mMediaPlayer;
//播放音频文件
private void playMuteMusic() {
mMediaPlayer = new MediaPlayer();
setMusicSource();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.start();
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mMediaPlayer.reset();
setMusicSource();
mMediaPlayer.start();
}
});
//一般对于锁而言。锁定了通常须要解锁。
//可是这里的唤醒锁与MediaPlayer关联,所以仅仅须要在使用完之后release()释放MediaPlayer就可以,无需显式的为其解锁
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
}
//设置音频文件路径
private void setMusicSource() {
AssetFileDescriptor fileDescriptor = null;
try {
fileDescriptor = getAssets().openFd("mute.mp3");//播放的是assets下的音频文件
mMediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
mMediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
其中以上代码起关键作用的是开启锁这一行,至于锁的其中缘由,不在本文探索范围内
MediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
以上代码需要放在后台服务类中,启动运行。
二、对坐标点进行加工处理
(1)、为什么要加工处理
坐标点拿回来的时候,会附带有误差值(即定位准确度,数字越小,越准),一般根据项目实际使用情况进行对应的筛选,例如本人负责项目会过滤误差在30米以上的坐标点,也就是大于30米误差的直接扔掉。这是一点,其次是坐标重复冗余的问题。当用户在原地不动的时候,GPS却是时刻保持工作的,但是返回来的坐标点不一定相同,如果这时候也将这些坐标点直接描绘在地图上,轨迹会显示得像马蜂窝那般杂乱无章。最后是视觉体验问题,在不影响轨迹准确性的情况下,将坐标点连接起来后,如何将轨迹显示得圆润些,没那么多直角或者折线。
(2)、如何加工处理
这里主要是个人处理加上高德开源的算法。
个人处理逻辑里,会先在准确度以及速度的前提下,将数据进行先过滤。例如GPS会返回速度,如果速度为0的情况下,坐标点就是无效的。其二,准确度,像刚刚说的那样,根据项目实际需要误差在30米以上的直接扔掉。
然后是依靠高德开源的算法,参考 高德轨迹平滑处理示例
PathSmoothTool
import android.util.Log;
import com.amap.api.maps.AMapUtils;
import com.amap.api.maps.model.LatLng;
import java.util.ArrayList;
import java.util.List;
/**
* 从高德开源工具那里复制过来
* 轨迹优化工具类
* Created by my94493 on 2017/3/31.
* <p>
* 使用方法:
* <p>
* PathSmoothTool pathSmoothTool = new PathSmoothTool();
* pathSmoothTool.setIntensity(2);//设置滤波强度,默认3
* List<LatLng> mList = LatpathSmoothTool.kalmanFilterPath(list);
*/
public class PathSmoothTool {
private int mIntensity = 5; //卡尔曼滤波程度 默认值是3 这东西影响着轨迹地流畅性
private float mThreshhold = 0.1f; //抽稀程度 数字越大 冗余轨迹点越少 轨迹越不圆润 默认值是0.3f
private float mNoiseThreshhold = 10; //去噪程度,轨迹点漂移偏差大小的判断,默认值是10 就是以10为准,两点之间偏差大于10的话,丢弃那个坐标点
public PathSmoothTool(){
}
public int getIntensity() {
return mIntensity;
}
public void setIntensity(int mIntensity) {
this.mIntensity = mIntensity;
}
public float getThreshhold() {
return mThreshhold;
}
public void setThreshhold(float mThreshhold) {
this.mThreshhold = mThreshhold;
}
public void setNoiseThreshhold(float mnoiseThreshhold) {
this.mNoiseThreshhold = mnoiseThreshhold;
}
/**
* 轨迹平滑优化
* @param originlist 原始轨迹list,list.size大于2
* @return 优化后轨迹list
*/
public List<LatLng> pathOptimize(List<LatLng> originlist){
List<LatLng> list = removeNoisePoint(originlist);//去噪
List<LatLng> afterList = kalmanFilterPath(list,mIntensity);//滤波
List<LatLng> pathoptimizeList = reducerVerticalThreshold(afterList,mThreshhold);//抽稀
// Log.i("MY","originlist: "+originlist.size());
// Log.i("MY","list: "+list.size());
// Log.i("MY","afterList: "+afterList.size());
// Log.i("MY","pathoptimizeList: "+pathoptimizeList.size());
return pathoptimizeList;
}
/**
* 轨迹线路滤波
* @param originlist 原始轨迹list,list.size大于2
* @return 滤波处理后的轨迹list
*/
public List<LatLng> kalmanFilterPath(List<LatLng> originlist) {
return kalmanFilterPath(originlist,mIntensity);
}
/**
* 轨迹去噪,删除垂距大于20m的点
* @param originlist 原始轨迹list,list.size大于2
* @return
*/
public List<LatLng> removeNoisePoint(List<LatLng> originlist){
return reduceNoisePoint(originlist,mNoiseThreshhold);
}
/**
* 单点滤波
* @param lastLoc 上次定位点坐标
* @param curLoc 本次定位点坐标
* @return 滤波后本次定位点坐标值
*/
public LatLng kalmanFilterPoint(LatLng lastLoc, LatLng curLoc) {
return kalmanFilterPoint(lastLoc,curLoc,mIntensity);
}
/**
* 轨迹抽稀
* @param inPoints 待抽稀的轨迹list,至少包含两个点,删除垂距小于mThreshhold的点
* @return 抽稀后的轨迹list
*/
public List<LatLng> reducerVerticalThreshold(List<LatLng> inPoints) {
return reducerVerticalThreshold(inPoints,mThreshhold);
}
/********************************************************************************************************/
/**
* 轨迹线路滤波
* @param originlist 原始轨迹list,list.size大于2
* @param intensity 滤波强度(1—5)
* @return
*/
private List<LatLng> kalmanFilterPath(List<LatLng> originlist,int intensity) {
List<LatLng> kalmanFilterList = new ArrayList<LatLng>();
if (originlist == null || originlist.size() <= 2)
return kalmanFilterList;
initial();//初始化滤波参数
LatLng latLng = null;
LatLng lastLoc = originlist.get(0);
kalmanFilterList.add(lastLoc);
for (int i = 1; i < originlist.size(); i++) {
LatLng curLoc = originlist.get(i);
latLng = kalmanFilterPoint(lastLoc,curLoc,intensity);
if (latLng != null) {
kalmanFilterList.add(latLng);
lastLoc = latLng;
}
}
return kalmanFilterList;
}
/**
* 单点滤波
* @param lastLoc 上次定位点坐标
* @param curLoc 本次定位点坐标
* @param intensity 滤波强度(1—5)
* @return 滤波后本次定位点坐标值
*/
private LatLng kalmanFilterPoint(LatLng lastLoc, LatLng curLoc, int intensity) {
if (pdelt_x == 0 || pdelt_y == 0 ){
initial();
}
LatLng kalmanLatlng = null;
if (lastLoc == null || curLoc == null){
return kalmanLatlng;
}
if (intensity < 1){
intensity = 1;
} else if (intensity > 5){
intensity = 5;
}
for (int j = 0; j < intensity; j++){
kalmanLatlng = kalmanFilter(lastLoc.longitude,curLoc.longitude,lastLoc.latitude,curLoc.latitude);
curLoc = kalmanLatlng;
}
return kalmanLatlng;
}
/***************************卡尔曼滤波开始********************************/
private double lastLocation_x; //上次位置
private double currentLocation_x;//这次位置
private double lastLocation_y; //上次位置
private double currentLocation_y;//这次位置
private double estimate_x; //修正后数据
private double estimate_y; //修正后数据
private double pdelt_x; //自预估偏差
private double pdelt_y; //自预估偏差
private double mdelt_x; //上次模型偏差
private double mdelt_y; //上次模型偏差
private double gauss_x; //高斯噪音偏差
private double gauss_y; //高斯噪音偏差
private double kalmanGain_x; //卡尔曼增益
private double kalmanGain_y; //卡尔曼增益
private double m_R= 0;
private double m_Q= 0;
//初始模型
private void initial(){
pdelt_x = 0.001;
pdelt_y = 0.001;
// mdelt_x = 0;
// mdelt_y = 0;
mdelt_x = 5.698402909980532E-4;
mdelt_y = 5.698402909980532E-4;
}
private LatLng kalmanFilter(double oldValue_x, double value_x, double oldValue_y, double value_y){
lastLocation_x = oldValue_x;
currentLocation_x= value_x;
gauss_x = Math.sqrt(pdelt_x * pdelt_x + mdelt_x * mdelt_x)+m_Q; //计算高斯噪音偏差
kalmanGain_x = Math.sqrt((gauss_x * gauss_x)/(gauss_x * gauss_x + pdelt_x * pdelt_x)) +m_R; //计算卡尔曼增益
estimate_x = kalmanGain_x * (currentLocation_x - lastLocation_x) + lastLocation_x; //修正定位点
mdelt_x = Math.sqrt((1-kalmanGain_x) * gauss_x *gauss_x); //修正模型偏差
lastLocation_y = oldValue_y;
currentLocation_y = value_y;
gauss_y = Math.sqrt(pdelt_y * pdelt_y + mdelt_y * mdelt_y)+m_Q; //计算高斯噪音偏差
kalmanGain_y = Math.sqrt((gauss_y * gauss_y)/(gauss_y * gauss_y + pdelt_y * pdelt_y)) +m_R; //计算卡尔曼增益
estimate_y = kalmanGain_y * (currentLocation_y - lastLocation_y) + lastLocation_y; //修正定位点
mdelt_y = Math.sqrt((1-kalmanGain_y) * gauss_y * gauss_y); //修正模型偏差
LatLng latlng = new LatLng(estimate_y,estimate_x);
return latlng;
}
/***************************卡尔曼滤波结束**********************************/
/***************************抽稀算法*************************************/
private List<LatLng> reducerVerticalThreshold(List<LatLng> inPoints,
float threshHold) {
if (inPoints == null) {
return null;
}
if (inPoints.size() <= 2) {
return inPoints;
}
List<LatLng> ret = new ArrayList<LatLng>();
for (int i = 0; i < inPoints.size(); i++) {
LatLng pre = getLastLocation(ret);
LatLng cur = inPoints.get(i);
if (pre == null || i == inPoints.size() - 1) {
ret.add(cur);
continue;
}
LatLng next = inPoints.get(i + 1);
double distance = calculateDistanceFromPoint(cur, pre, next);
if (distance > threshHold){
ret.add(cur);
}
}
return ret;
}
private static LatLng getLastLocation(List<LatLng> oneGraspList) {
if (oneGraspList == null || oneGraspList.size() == 0) {
return null;
}
int locListSize = oneGraspList.size();
LatLng lastLocation = oneGraspList.get(locListSize - 1);
return lastLocation;
}
/**
* 计算当前点到线的垂线距离
* @param p 当前点
* @param lineBegin 线的起点
* @param lineEnd 线的终点
*
*/
private static double calculateDistanceFromPoint(LatLng p, LatLng lineBegin,
LatLng lineEnd) {
double A = p.longitude - lineBegin.longitude;
double B = p.latitude - lineBegin.latitude;
double C = lineEnd.longitude - lineBegin.longitude;
double D = lineEnd.latitude - lineBegin.latitude;
double dot = A * C + B * D;
double len_sq = C * C + D * D;
double param = dot / len_sq;
double xx, yy;
if (param < 0 || (lineBegin.longitude == lineEnd.longitude
&& lineBegin.latitude == lineEnd.latitude)) {
xx = lineBegin.longitude;
yy = lineBegin.latitude;
// return -1;
} else if (param > 1) {
xx = lineEnd.longitude;
yy = lineEnd.latitude;
// return -1;
} else {
xx = lineBegin.longitude + param * C;
yy = lineBegin.latitude + param * D;
}
return AMapUtils.calculateLineDistance(p,new LatLng(yy,xx));
}
/***************************抽稀算法结束*********************************/
private List<LatLng> reduceNoisePoint(List<LatLng> inPoints, float threshHold) {
if (inPoints == null) {
return null;
}
if (inPoints.size() <= 2) {
return inPoints;
}
List<LatLng> ret = new ArrayList<LatLng>();
for (int i = 0; i < inPoints.size(); i++) {
LatLng pre = getLastLocation(ret);
LatLng cur = inPoints.get(i);
if (pre == null || i == inPoints.size() - 1) {
ret.add(cur);
continue;
}
LatLng next = inPoints.get(i + 1);
double distance = calculateDistanceFromPoint(cur, pre, next);
if (distance < threshHold){
ret.add(cur);
}
}
return ret;
}
}
pathOptimize(List<LatLng> originlist)
LatLng 类,但我觉得应该可以使用自定义的因为坐标点传进去,只是用在了高德自身的距离计算方法,该计算方法可以自己网上自行百度,当然,只是推测,大家可以试验下。
最后,pathOptimize 方法处理过后的数据就可以直接描绘在地图上了。
百度云盘链接:
链接:https://pan.baidu.com/s/1QaWASPI9-qaQ-dALVkpZqQ
提取码:26bj