前段时间因为项目需求,通过百度定位adk写了一个实时更新距离的程序(类似大家坐的士时,车上的里程表),遇到很多技术点,总结了一下发表出来和大家相互学习。直接要求定位具体的位置应该是不难的,只需要引入百度定位adk,并配置相关参数就可以完成,显示百度地图也类似,但是如果需要不断的实时显示移动距离,GPS定位从一个点,到第二个点,从第二个点,到第三个点,从第三个点……,移动距离是多少呢?不得不说,要实现这种需求的确存在一定的难度。
目标:使用百度定位sdk开发实时移动距离计算功能,根据经纬度的定位,计算行驶公里数并实时刷新界面显示。
大家都知道定位有三种方式:GPS 、Wifi 、 基站 .
误差方面的话,使用GPS误差在10左右,Wifi则在20 - 300左右 ,而使用基站则误差在100 - 300左右的样子,因为在室内GPS是定位不到的,必须在室外,
而我们项目的需求正好需要使用GPS定位,所以我们这里设置GPS优先。车,不可能在室内跑吧。
使用技术点:
1.百度定位sdk
2.sqlite数据库(用于保存经纬度和实时更新的距离)
3.通过经纬度计算距离的算法方式
4.TimerTask 、Handler
大概思路:
1)创建项目,上传应用到百度定位sdk获得应用对应key,并配置定位服务成功。
2)将配置的定位代码块放入service中,使程序在后台不断更新经纬度
3)为应用创建数据库和相应的数据表,编写 增删改查 业务逻辑方法
4)编写界面,通过点击按钮控制是否开始计算距离,并引用数据库,初始化表数据,实时刷新界面
5)在service的定位代码块中计算距离,并将距离和经纬度实时的保存在数据库(注:只要经纬度发生改变,计算出来的距离就要进行保存)
6)界面的刷新显示
以下是MainActivity中的代码,通过注释可以理解思路流程.
package app.ui.activity;
import java.util.Timer;
import java.util.TimerTask;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import app.db.DistanceInfoDao;
import app.model.DistanceInfo;
import app.service.LocationService;
import app.ui.ConfirmDialog;
import app.ui.MyApplication;
import app.ui.R;
import app.utils.ConstantValues;
import app.utils.LogUtil;
import app.utils.Utils;
public class MainActivity extends Activity {
private TextView mTvDistance; //控件
private Button mButton;
private TextView mLng_lat;
private boolean isStart = true; //是否开始计算移动距离
private DistanceInfoDao mDistanceInfoDao; //数据库
private volatile boolean isRefreshUI = true; //是否暂停刷新UI的标识
private static final int REFRESH_TIME = 5000; //5秒刷新一次
private Handler refreshHandler = new Handler(){ //刷新界面的Handler
public void handleMessage(Message msg) {
switch (msg.what) {
case ConstantValues.REFRESH_UI:
if (isRefreshUI) {
LogUtil.info(DistanceComputeActivity.class, "refresh ui");
DistanceInfo mDistanceInfo = mDistanceInfoDao.getById(MyApplication.orderDealInfoId);
LogUtil.info(DistanceComputeActivity.class, "界面刷新---> "+mDistanceInfo);
if (mDistanceInfo != null) {
mTvDistance.setText(String.valueOf(Utils.getValueWith2Suffix(mDistanceInfo.getDistance())));
mLng_lat.setText("经:"+mDistanceInfo.getLongitude()+" 纬:"+mDistanceInfo.getLatitude());
mTvDistance.invalidate();
mLng_lat.invalidate();
}
}
break;
}
super.handleMessage(msg);
}
};
//定时器,每5秒刷新一次UI
private Timer refreshTimer = new Timer(true);
private TimerTask refreshTask = new TimerTask() {
@Override
public void run() {
if (isRefreshUI) {
Message msg = refreshHandler.obtainMessage();
msg.what = ConstantValues.REFRESH_UI;
refreshHandler.sendMessage(msg);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //保持屏幕常亮
setContentView(R.layout.activity_expensecompute);
startService(new Intent(this,LocationService.class)); //启动定位服务
Toast.makeText(this,"已启动定位服务...", 1).show();
init(); //初始化相应控件
}
private void init(){
mTvDistance = (TextView) findViewById(R.id.tv_drive_distance);
mDistanceInfoDao = new DistanceInfoDao(this);
refreshTimer.schedule(refreshTask, 0, REFRESH_TIME);
mButton = (Button)findViewById(R.id.btn_start_drive);
mLng_lat = (TextView)findViewById(R.id.longitude_Latitude);
}
@Override
public void onClick(View v) {
super.onClick(v);
switch (v.getId()) {
case R.id.btn_start_drive: //计算距离按钮
if(isStart)
{
mButton.setBackgroundResource(R.drawable.btn_selected);
mButton.setText("结束计算");
isStart = false;
DistanceInfo mDistanceInfo = new DistanceInfo();
mDistanceInfo.setDistance(0f); //距离初始值
mDistanceInfo.setLongitude(MyApplication.lng); //经度初始值
mDistanceInfo.setLatitude(MyApplication.lat); //纬度初始值
int id = mDistanceInfoDao.insertAndGet(mDistanceInfo); //将值插入数据库,并获得数据库中最大的id
if (id != -1) {
MyApplication.orderDealInfoId = id; //将id赋值到程序全局变量中(注:该id来决定是否计算移动距离)
Toast.makeText(this,"已开始计算...", 0).show();
}else{
Toast.makeText(this,"id is -1,无法执行距离计算代码块", 0).show();
}
}else{
//自定义提示框
ConfirmDialog dialog = new ConfirmDialog(this, R.style.dialogNoFrame){
@Override
public void setDialogContent(TextView content) {
content.setVisibility(View.GONE);
}
@Override
public void setDialogTitle(TextView title) {
title.setText("确认结束计算距离 ?");
}
@Override
public void startMission() {
mButton.setBackgroundResource(R.drawable.btn_noselect);
mButton.setText("开始计算");
isStart = true;
isRefreshUI = false; //停止界面刷新
if (refreshTimer != null) {
refreshTimer.cancel();
refreshTimer = null;
}
mDistanceInfoDao.delete(MyApplication.orderDealInfoId); //删除id对应记录
MyApplication.orderDealInfoId = -1; //停止定位计算
Toast.makeText(DistanceComputeActivity.this,"已停止计算...", 0).show();
}
};
dialog.show();
}
break;
}
}
}
以下是LocationService中的代码,即配置的百度定位sdk代码块,放在继承了service的类中 LocationService.java (方便程序在后台实时更新经纬度)
package app.service;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import app.db.DistanceInfoDao;
import app.model.GpsLocation;
import app.model.DistanceInfo;
import app.ui.MyApplication;
import app.utils.BDLocation2GpsUtil;
import app.utils.FileUtils;
import app.utils.LogUtil;
import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.computedistance.DistanceComputeInterface;
import com.computedistance.impl.DistanceComputeImpl;
public class LocationService extends Service {
public static final String FILE_NAME = "log.txt"; //日志
LocationClient mLocClient;
private Object lock = new Object();
private volatile GpsLocation prevGpsLocation = new GpsLocation(); //定位数据
private volatile GpsLocation currentGpsLocation = new GpsLocation();
private MyLocationListenner myListener = new MyLocationListenner();
private volatile int discard = 1; //Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。
private DistanceInfoDao mDistanceInfoDao;
private ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mDistanceInfoDao = new DistanceInfoDao(this); //初始化数据库
//LogUtil.info(LocationService.class, "Thread id ----------->:" + Thread.currentThread().getId());
mLocClient = new LocationClient(this);
mLocClient.registerLocationListener(myListener);
//定位参数设置
LocationClientOption option = new LocationClientOption();
option.setCoorType("bd09ll"); //返回的定位结果是百度经纬度,默认值gcj02
option.setAddrType("all"); //返回的定位结果包含地址信息
option.setScanSpan(5000); //设置发起定位请求的间隔时间为5000ms
option.disableCache(true); //禁止启用缓存定位
option.setProdName("app.ui.activity");
option.setOpenGps(true);
option.setPriority(LocationClientOption.GpsFirst); //设置GPS优先
mLocClient.setLocOption(option);
mLocClient.start();
mLocClient.requestLocation();
}
@Override
@Deprecated
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
if (null != mLocClient) {
mLocClient.stop();
}
startService(new Intent(this, LocationService.class));
}
private class Task implements Callable<String>{
private BDLocation location;
public Task(BDLocation location){
this.location = location;
}
/**
* 检测是否在原地不动
*
* @param distance
* @return
*/
private boolean noMove(float distance){
if (distance < 0.01) {
return true;
}
return false;
}
/**
* 检测是否在正确的移动
*
* @param distance
* @return
*/
private boolean checkProperMove(float distance){
if(distance <= 0.1 * discard){
return true;
}else{
return false;
}
}
/**
* 检测获取的数据是否是正常的
*
* @param location
* @return
*/
private boolean checkProperLocation(BDLocation location){
if (location != null && location.getLatitude() != 0 && location.getLongitude() != 0){
return true;
}
return false;
}
@Override
public String call() throws Exception {
synchronized (lock) {
if (!checkProperLocation(location)){
LogUtil.info(LocationService.class, "location data is null");
discard++;
return null;
}
if (MyApplication.orderDealInfoId != -1) {
DistanceInfo mDistanceInfo = mDistanceInfoDao.getById(MyApplication.orderDealInfoId); //根据MainActivity中赋值的全局id查询数据库的值
if(mDistanceInfo != null) //不为空则说明车已经开始行使,并可以获得经纬度,计算移动距离
{
LogUtil.info(LocationService.class, "行驶中......");
GpsLocation tempGpsLocation = BDLocation2GpsUtil.convertWithBaiduAPI(location); //位置转换
if (tempGpsLocation != null) {
currentGpsLocation = tempGpsLocation;
}else{
discard ++;
}
//日志
String logMsg = "(plat:--->" + prevGpsLocation.lat + " plgt:--->" + prevGpsLocation.lng +")\n" +
"(clat:--->" + currentGpsLocation.lat + " clgt:--->" + currentGpsLocation.lng + ")";
LogUtil.info(LocationService.class, logMsg);
/** 计算距离 */
float distance = 0.0f;
DistanceComputeInterface distanceComputeInterface = DistanceComputeImpl.getInstance(); //计算距离类对象
distance = (float) distanceComputeInterface.getLongDistance(prevGpsLocation.lat,prevGpsLocation.lng,
currentGpsLocation.lat,currentGpsLocation.lng); //移动距离计算
if (!noMove(distance)) { //是否在移动
if (checkProperMove(distance)) { //合理的移动
float drivedDistance = mDistanceInfo.getDistance();
mDistanceInfo.setDistance(distance + drivedDistance); //拿到数据库原始距离值, 加上当前值
mDistanceInfo.setLongitude(currentGpsLocation.lng); //经度
mDistanceInfo.setLatitude(currentGpsLocation.lat); //纬度
//日志记录
FileUtils.saveToSDCard(FILE_NAME,"移动距离--->:"+distance+drivedDistance+"\n"+"数据库中保存的距离"+mDistanceInfo.getDistance());
mDistanceInfoDao.updateDistance(mDistanceInfo);
discard = 1;
}
}
prevGpsLocation = currentGpsLocation;
}
}
return null;
}
}
}
/**
* 定位SDK监听函数
*/
public class MyLocationListenner implements BDLocationListener {
@Override
public void onReceiveLocation(BDLocation location) {
executor.submit(new Task(location));
LogUtil.info(LocationService.class, "经度:"+location.getLongitude());
LogUtil.info(LocationService.class, "纬度:"+location.getLatitude());
//将经纬度保存于全局变量,在MainActivity中点击按钮时初始化数据库字段
if(MyApplication.lng <=0 && MyApplication.lat <= 0)
{
MyApplication.lng = location.getLongitude();
MyApplication.lat = location.getLatitude();
}
}
public void onReceivePoi(BDLocation poiLocation) {
if (poiLocation == null){
return ;
}
}
}
}
以下是应用中需要使用的DBOpenHelper数据库类 DBOpenHelper.java
package app.db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBOpenHelper extends SQLiteOpenHelper{
private static final int VERSION = 1; //数据库版本号
private static final String DB_NAME = "distance.db"; //数据库名
public DBOpenHelper(Context context){ //创建数据库
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) { //创建数据表
db.execSQL("CREATE TABLE IF NOT EXISTS milestone(id INTEGER PRIMARY KEY AUTOINCREMENT, distance INTEGER,longitude DOUBLE, latitude DOUBLE )");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //版本号发生改变的时
db.execSQL("drop table milestone");
db.execSQL("CREATE TABLE IF NOT EXISTS milestone(id INTEGER PRIMARY KEY AUTOINCREMENT, distance INTEGER,longitude FLOAT, latitude FLOAT )");
}
}
以下是应用中需要使用的数据库业务逻辑封装类 DistanceInfoDao.java
package app.db;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import app.model.DistanceInfo;
import app.utils.LogUtil;
public class DistanceInfoDao {
private DBOpenHelper helper;
private SQLiteDatabase db;
public DistanceInfoDao(Context context) {
helper = new DBOpenHelper(context);
}
public void insert(DistanceInfo mDistanceInfo) {
if (mDistanceInfo == null) {
return;
}
db = helper.getWritableDatabase();
String sql = "INSERT INTO milestone(distance,longitude,latitude) VALUES('"+ mDistanceInfo.getDistance() + "','"+ mDistanceInfo.getLongitude() + "','"+ mDistanceInfo.getLatitude() + "')";
LogUtil.info(DistanceInfoDao.class, sql);
db.execSQL(sql);
db.close();
}
public int getMaxId() {
db = helper.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT MAX(id) as id from milestone",null);
if (cursor.moveToFirst()) {
return cursor.getInt(cursor.getColumnIndex("id"));
}
return -1;
}
/**
* 添加数据
* @param orderDealInfo
* @return
*/
public synchronized int insertAndGet(DistanceInfo mDistanceInfo) {
int result = -1;
insert(mDistanceInfo);
result = getMaxId();
return result;
}
/**
* 根据id获取
* @param id
* @return
*/
public DistanceInfo getById(int id) {
db = helper.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT * from milestone WHERE id = ?",new String[] { String.valueOf(id) });
DistanceInfo mDistanceInfo = null;
if (cursor.moveToFirst()) {
mDistanceInfo = new DistanceInfo();
mDistanceInfo.setId(cursor.getInt(cursor.getColumnIndex("id")));
mDistanceInfo.setDistance(cursor.getFloat(cursor.getColumnIndex("distance")));
mDistanceInfo.setLongitude(cursor.getFloat(cursor.getColumnIndex("longitude")));
mDistanceInfo.setLatitude(cursor.getFloat(cursor.getColumnIndex("latitude")));
}
cursor.close();
db.close();
return mDistanceInfo;
}
/**
* 更新距离
* @param orderDealInfo
*/
public void updateDistance(DistanceInfo mDistanceInfo) {
if (mDistanceInfo == null) {
return;
}
db = helper.getWritableDatabase();
String sql = "update milestone set distance="+ mDistanceInfo.getDistance() +",longitude="+mDistanceInfo.getLongitude()+",latitude="+mDistanceInfo.getLatitude()+" where id = "+ mDistanceInfo.getId();
LogUtil.info(DistanceInfoDao.class, sql);
db.execSQL(sql);
db.close();
}
}
以下是需要使用到的实体类 DistanceInfo.java (set数据到对应变量,以实体类作为参数更新数据库)
package app.model;
public class DistanceInfo {
private int id;
private float distance;
private double longitude;
private double latitude;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public float getDistance() {
return distance;
}
public void setDistance(float distance) {
this.distance = distance;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
@Override
public String toString() {
return "DistanceInfo [id=" + id + ", distance=" + distance
+ ", longitude=" + longitude + ", latitude=" + latitude + "]";
}
}
保存经纬度信息的类 GpsLocation
package app.model;
public class GpsLocation {
public double lat;//纬度
public double lng;//经度
}
将从百度定位中获得的经纬度转换为精准的GPS数据 BDLocation2GpsUtil.java
package app.utils;
import it.sauronsoftware.base64.Base64;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONObject;
import app.model.GpsLocation;
import com.baidu.location.BDLocation;
public class BDLocation2GpsUtil {
static BDLocation tempBDLocation = new BDLocation(); // 临时变量,百度位置
static GpsLocation tempGPSLocation = new GpsLocation(); // 临时变量,gps位置
public static enum Method{
origin, correct;
}
private static final Method method = Method.correct;
/**
* 位置转换
*
* @param lBdLocation 百度位置
* @return GPS位置
*/
public static GpsLocation convertWithBaiduAPI(BDLocation lBdLocation) {
switch (method) {
case origin: //原点
GpsLocation location = new GpsLocation();
location.lat = lBdLocation.getLatitude();
location.lng = lBdLocation.getLongitude();
return location;
case correct: //纠偏
//同一个地址不多次转换
if (tempBDLocation.getLatitude() == lBdLocation.getLatitude() && tempBDLocation.getLongitude() == lBdLocation.getLongitude()) {
return tempGPSLocation;
}
String url = "http://api.map.baidu.com/ag/coord/convert?from=0&to=4&"
+ "x=" + lBdLocation.getLongitude() + "&y="
+ lBdLocation.getLatitude();
String result = executeHttpGet(url);
LogUtil.info(BDLocation2GpsUtil.class, "result:" + result);
if (result != null) {
GpsLocation gpsLocation = new GpsLocation();
try {
JSONObject jsonObj = new JSONObject(result);
String lngString = jsonObj.getString("x");
String latString = jsonObj.getString("y");
// 解码
double lng = Double.parseDouble(new String(Base64.decode(lngString)));
double lat = Double.parseDouble(new String(Base64.decode(latString)));
// 换算
gpsLocation.lng = 2 * lBdLocation.getLongitude() - lng;
gpsLocation.lat = 2 * lBdLocation.getLatitude() - lat;
tempGPSLocation = gpsLocation;
LogUtil.info(BDLocation2GpsUtil.class, "result:" + gpsLocation.lat + "||" + gpsLocation.lng);
} catch (Exception e) {
e.printStackTrace();
return null;
}
tempBDLocation = lBdLocation;
return gpsLocation;
}else{
LogUtil.info(BDLocation2GpsUtil.class, "百度API执行出错,url is:" + url);
return null;
}
}
}
}
需要声明相关权限,且项目中所用到的jar有:
android-support-v4.jar
commons-codec.jar
commons-lang3-3.0-beta.jar
javabase64-1.3.1.jar
locSDK_3.1.jar
项目中目前尚有部分不健全的地方,如:
1.在行驶等待时间较长后,使用TimerTask 、Handler刷新界面是偶尔会出现卡住的现象,车仍在行驶,
但是数据不动了,通过改善目前测试近7次未出现此问题。
2.较大的消耗电量,因为定位服务本身就是个消耗电量的玩意