前段时间因为项目需求,通过百度定位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中的代码,通过注释可以理解思路流程.
[java] view
plaincopyprint?
- 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 (方便程序在后台实时更新经纬度)
[java] view
plaincopyprint?
- 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
[java] view
plaincopyprint?
- 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
[java] view
plaincopyprint?
- 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数据到对应变量,以实体类作为参数更新数据库)
[java] view
plaincopyprint?
- 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
[java] view
plaincopyprint?
- package app.model;
- public class GpsLocation {
- public double lat;//纬度
- public double lng;//经度
- }
将从百度定位中获得的经纬度转换为精准的GPS数据 BDLocation2GpsUtil.java
[java] view
plaincopyprint?
- 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.较快的消耗电量
android中使用百度定位sdk实时的计算移动距离