Android开发实现距离位置变化(类似滴滴打车附件的车辆)

前段时间因为项目需求,通过百度定位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.较大的消耗电量,因为定位服务本身就是个消耗电量的玩意

你可能感兴趣的:(代码)