前段時間因為項目需求,通過百度定位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]
- 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]
- 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{
- 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 +) +
- (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+ +數據庫中保存的距離+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]
- 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]
- 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]
- 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]
- 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
Android中計算地圖上兩點距離的算法
項目中目前尚有部分不健全的地方,如:
1.在行駛等待時間較長後,使用TimerTask 、Handler刷新界面是偶爾會出現卡住的現象,車仍在行駛,
但是數據不動了,通過改善目前測試近7次未出現此問題。
2.較快的消耗電量
源碼下載地址