GPS定位系統(tǒng)(二)——Android端

前言

GPS系列——Android端,github項(xiàng)目地址 tag: gps_mine

Android移動(dòng)端,主要是使用高德地圖定位,后臺(tái)上傳定位信息,然后就是想辦法盡量?;?。

包括兩個(gè)小功能:1、上傳定位信息 2、模擬定位信息

都是練手實(shí)踐,去深入了解其原理。通篇代碼較多,慎入。

大家盡可以去查看源碼,各取所需。

GPS定位系統(tǒng)系列

GPS定位系統(tǒng)(一)——介紹

GPS定位系統(tǒng)(二)——Android端

GPS定位系統(tǒng)(三)——Java后端

GPS定位系統(tǒng)(四)——Vue前端

GPS定位系統(tǒng)(五)——Docker

[TOC]

收獲

學(xué)習(xí)完這篇文章你將收獲:

  • 高德地圖、定位使用
  • 高德坐標(biāo)系轉(zhuǎn)換(官方只有其他坐標(biāo)系轉(zhuǎn)高德,沒有高德轉(zhuǎn)gps)
  • 模擬定位(打卡)
  • 卸載重裝也不變的uuid|imei
  • 保活策略和原理

一、地圖

地圖使用的是高德地圖,注冊(cè)申請(qǐng)appkey的話,請(qǐng)移步官網(wǎng)網(wǎng)站。

地圖界面功能很簡單,跟著官方文檔來就行

 private void initMap() {
        MyLocationStyle myLocationStyle;
        myLocationStyle = new MyLocationStyle();//初始化定位藍(lán)點(diǎn)樣式類myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);//連續(xù)定位、且將視角移動(dòng)到地圖中心點(diǎn),定位點(diǎn)依照設(shè)備方向旋轉(zhuǎn),并且會(huì)跟隨設(shè)備移動(dòng)。(1秒1次定位)如果不設(shè)置myLocationType,默認(rèn)也會(huì)執(zhí)行此種模式。
        myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW);
        myLocationStyle.interval(10000); //設(shè)置連續(xù)定位模式下的定位間隔,只在連續(xù)定位模式下生效,單次定位模式下不會(huì)生效。單位為毫秒。
        AMap map = mMapView.getMap();
        map.setMyLocationStyle(myLocationStyle);//設(shè)置定位藍(lán)點(diǎn)的Style
        map.setMyLocationEnabled(true);// 設(shè)置為true表示啟動(dòng)顯示定位藍(lán)點(diǎn),false表示隱藏定位藍(lán)點(diǎn)并不進(jìn)行定位,默認(rèn)是false。
        map.getUiSettings().setMyLocationButtonEnabled(true); //顯示默認(rèn)的定位按鈕
        map.setMyLocationEnabled(true);// 可觸發(fā)定位并顯示當(dāng)前位置
        map.moveCamera(CameraUpdateFactory.zoomTo(16));
        map.setOnMapClickListener(latLng -> {
            Log.d(TAG, "mapCLick:" + latLng.latitude + "\t" + latLng.longitude);
            mMockLat = latLng.latitude;
            mMockLng = latLng.longitude;
            if (mMarker != null) {
                mMarker.remove();
            }
            mMarker = map.addMarker(new MarkerOptions().position(latLng).title("模擬位置").snippet("default"));
        });
        map.setOnMyLocationChangeListener(location -> Log.d(TAG, "onMyLocationChange:" + location.getLatitude() + "\t" + location.getLongitude()));
    }

注意:地圖選點(diǎn)的話,使用map.setOnMapClickListener來設(shè)置監(jiān)聽。

gps和高德地圖 經(jīng)緯度 互轉(zhuǎn)

注意:就是gps和高德的坐標(biāo)體系的互轉(zhuǎn),模擬定位模擬的gps定位,需要選好模擬點(diǎn)之后,轉(zhuǎn)成gps的定位進(jìn)行模擬。這里寫了一個(gè)工具類。

public class ConvertUtil {
        private final static double a = 6378245.0;
        private final static double pi = 3.14159265358979324;
        private final static double ee = 0.00669342162296594323;

        // WGS-84 to GCJ-02  gps轉(zhuǎn)高德
        public static LatLng toGCJ02Point(double latitude, double longitude) {
            LatLng dev = calDev(latitude, longitude);
            double retLat = latitude + dev.latitude;
            double retLon = longitude + dev.longitude;
            return new LatLng(retLat, retLon);
        }

        // GCJ-02 to WGS-84  高德轉(zhuǎn)gps
        public static LatLng toWGS84Point(double latitude, double longitude) {
            LatLng dev = calDev(latitude, longitude);
            double retLat = latitude - dev.latitude;
            double retLon = longitude - dev.longitude;
            dev = calDev(retLat, retLon);
            retLat = latitude - dev.latitude;
            retLon = longitude - dev.longitude;
            return new LatLng(retLat, retLon);
        }

        private static LatLng calDev(double wgLat, double wgLon) {
            if (isOutOfChina(wgLat, wgLon)) {
                return new LatLng(0, 0);
            }
            double dLat = calLat(wgLon - 105.0, wgLat - 35.0);
            double dLon = calLon(wgLon - 105.0, wgLat - 35.0);
            double radLat = wgLat / 180.0 * pi;
            double magic = Math.sin(radLat);
            magic = 1 - ee * magic * magic;
            double sqrtMagic = Math.sqrt(magic);
            dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
            dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
            return new LatLng(dLat, dLon);
        }

        private static boolean isOutOfChina(double lat, double lon) {
            if (lon < 72.004 || lon > 137.8347)
                return true;
            if (lat < 0.8293 || lat > 55.8271)
                return true;
            return false;
        }

        private static double calLat(double x, double y) {
            double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2
                    * Math.sqrt(Math.abs(x));
            ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
            ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
            ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
            return ret;
        }

        private static double calLon(double x, double y) {
            double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
            ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
            ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
            ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
            return ret;
        }
 
}

二、后臺(tái)保活定位

保活:

?;钸@里使用一個(gè)框架很不錯(cuò),HelloDaemon

?;钏悸罚?/p>

  1. 將Service設(shè)置為前臺(tái)服務(wù)而不顯示通知

  2. 在 Service 的 onStartCommand 方法里返回 START_STICKY

  3. 覆蓋 Service 的 onDestroy/onTaskRemoved 方法, 保存數(shù)據(jù)到磁盤, 然后重新拉起服務(wù)

  4. 監(jiān)聽 8 種系統(tǒng)廣播

  5. 開啟守護(hù)服務(wù) : 定時(shí)檢查服務(wù)是否在運(yùn)行,如果不在運(yùn)行就拉起來

  6. 守護(hù) Service 組件的啟用狀態(tài), 使其不被 MAT 等工具禁用

并且,還有適配各種手機(jī)廠商rom的intent跳轉(zhuǎn)【電量優(yōu)化】【自啟設(shè)置】【白名單】等設(shè)置界面

 IntentWrapper.whiteListMatters(this, "為了更好的實(shí)時(shí)定位,最好把應(yīng)用加入您手機(jī)的白名單");

保活service繼承AbsWorkService,實(shí)現(xiàn)其抽象方法即可

/**
 * 是否 任務(wù)完成, 不再需要服務(wù)運(yùn)行?
 * @return 應(yīng)當(dāng)停止服務(wù), true; 應(yīng)當(dāng)啟動(dòng)服務(wù), false; 無法判斷, null.
 */
Boolean shouldStopService();

/**
 * 任務(wù)是否正在運(yùn)行?
 * @return 任務(wù)正在運(yùn)行, true; 任務(wù)當(dāng)前不在運(yùn)行, false; 無法判斷, null.
 */
Boolean isWorkRunning();

void startWork();

void stopWork();

//Service.onBind(Intent intent)
@Nullable IBinder onBind(Intent intent, Void unused);

//服務(wù)被殺時(shí)調(diào)用, 可以在這里面保存數(shù)據(jù).
void onServiceKilled();

關(guān)于保活、安全、隱私

其實(shí)隨著Android的日趨成熟,生態(tài)更健康、安全、更注重隱私、以用戶為本,很多“黑科技”已經(jīng)都不行了,現(xiàn)在的保活已經(jīng)不像以前各種花里胡哨,感興趣了解一些舊版本的保活策略的話可以這些鏈接學(xué)習(xí)一下:

Android 進(jìn)程常駐(2)----細(xì)數(shù)利用android系統(tǒng)機(jī)制的?;钍侄?/a>

D-clock / AndroidDaemonService

不像大廠們的做法,各種互拉,手機(jī)廠商的白名單;現(xiàn)在的“民間”?;钏悸坊径际牵M量引導(dǎo)用戶添加白名單、電量優(yōu)化無限制、鎖住應(yīng)用等。

保活沒有誰能夠說能100%,一直在后臺(tái)存活的,就算能?;?,也是在某些廣播或者用戶行為下,觸發(fā)拉活,各大手機(jī)廠商的rom表現(xiàn)不一,有些還是會(huì)被殺,沒有辦法。不過我小米6手機(jī),實(shí)測(cè),白名單、電量優(yōu)化設(shè)置后,能每分鐘都上傳gps信息,不斷,但是還是挺耗電的,說實(shí)話。有些其他的手機(jī)就不行了,例如華為,華為生態(tài)和安全的確是很棒啊。

UploadGpsService實(shí)現(xiàn):

public class UploadGpsService extends AbsWorkService {
    private static final String TAG = UploadGpsService.class.getSimpleName();
    //是否 任務(wù)完成, 不再需要服務(wù)運(yùn)行?
    public static boolean sShouldStopService;

    int shouldCount;
    int actualCount;


    @Override
    public void onCreate() {
        super.onCreate();
        initGps();
    }

    //聲明AMapLocationClient類對(duì)象
    public AMapLocationClient mLocationClient = null;
    //聲明定位回調(diào)監(jiān)聽器
    public AMapLocationListener mLocationListener = amapLocation -> {
        if (amapLocation != null) {
            if (amapLocation.getErrorCode() == 0) {
//可在其中解析amapLocation獲取相應(yīng)內(nèi)容。
                Log.d("mapLocation", amapLocation.toString());
                //獲取定位時(shí)間
                SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date date = new Date();
                df.format(date);
                Log.d(TAG, String.format("經(jīng)度:%s\t緯度:%s\t地址:%s\n%s\n應(yīng)上傳次數(shù)%d\n實(shí)上傳次數(shù)%d", amapLocation.getLongitude(), amapLocation.getLatitude(), amapLocation.getAddress(), df.format(date), shouldCount, actualCount));
                upload(amapLocation.getLongitude(), amapLocation.getLatitude());
            } else {
                //定位失敗時(shí),可通過ErrCode(錯(cuò)誤碼)信息來確定失敗的原因,errInfo是錯(cuò)誤信息,詳見錯(cuò)誤碼表。
                Log.e("AmapError", "location Error, ErrCode:"
                        + amapLocation.getErrorCode() + ", errInfo:"
                        + amapLocation.getErrorInfo());
            }
        }
    };

    private void upload(double longitude, double latitude) {
        String userId = PrefManager.getInstance(this).userId();
        String token = PrefManager.getInstance(this).getToken();
//        if (TextUtils.isEmpty(userId)) {
//            return;
//        }
        shouldCount++;
        RequestModel requestModel = new RequestModel();
        requestModel.setTime(System.currentTimeMillis() / 1000);
        requestModel.setLat(latitude);
        requestModel.setLng(longitude);
        RetrofitManager.getInstance()
                .mainService()
                .gps(token, requestModel)
                .compose(ReactivexCompat.singleThreadSchedule())
                .subscribe(result -> {
                    if (result.getCode() == 200) {
                        long interval = result.getData();
                        mLocationOption.setInterval(interval);
                        mLocationClient.setLocationOption(mLocationOption);
                        Log.d(TAG, "service upload success:" + new Gson().toJson(result));
                        actualCount++;
                    }
                }, e -> {
                    Log.e(TAG, "service upload err:" + e.getMessage());
                });
    }


    //聲明AMapLocationClientOption對(duì)象
    public AMapLocationClientOption mLocationOption = null;


    private void initGps() {
//初始化定位
        mLocationClient = new AMapLocationClient(getApplicationContext());
//設(shè)置定位回調(diào)監(jiān)聽
        mLocationClient.setLocationListener(mLocationListener);

        //初始化AMapLocationClientOption對(duì)象
        mLocationOption = new AMapLocationClientOption();
        /**
         * 設(shè)置定位場(chǎng)景,目前支持三種場(chǎng)景(簽到、出行、運(yùn)動(dòng),默認(rèn)無場(chǎng)景)
         */
        mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.Sport);

        //設(shè)置定位模式為AMapLocationMode.Hight_Accuracy,高精度模式。
        mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);

        //設(shè)置定位間隔,單位毫秒,默認(rèn)為2000ms,最低1000ms。
        mLocationOption.setInterval(5000);

        mLocationClient.setLocationOption(mLocationOption);

        //啟動(dòng)定位
        mLocationClient.startLocation();
    }


    public static void stopService() {
        //我們現(xiàn)在不再需要服務(wù)運(yùn)行了, 將標(biāo)志位置為 true
        sShouldStopService = true;
        //取消 Job / Alarm / Subscription
        cancelJobAlarmSub();
    }

    @Override
    public Boolean shouldStopService(Intent intent, int flags, int startId) {
        return sShouldStopService;
    }

    @Override
    public void startWork(Intent intent, int flags, int startId) {
        Log.i(TAG, "startWork");
        String userId = PrefManager.getInstance(this).userId();
        if (!TextUtils.isEmpty(userId)) {
            if (mLocationClient != null && !mLocationClient.isStarted()) {
                Log.i(TAG, "startLocation");
                mLocationClient.startLocation();
            } else if (mLocationClient == null) {
                initGps();
            }
        } else {
            if (mLocationClient != null) {
                mLocationClient.stopLocation();
            }
        }

    }

    @Override
    public void stopWork(Intent intent, int flags, int startId) {
        Log.i(TAG, "stopWork");
        stopService();
        if (mLocationClient != null) {
            mLocationClient.stopLocation();
            mLocationClient.onDestroy();
        }
    }

    @Override
    public Boolean isWorkRunning(Intent intent, int flags, int startId) {
        //若還沒有取消訂閱, 就說明任務(wù)仍在運(yùn)行.
        return null;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent, Void alwaysNull) {
        return null;
    }

    @Override
    public void onServiceKilled(Intent rootIntent) {
        Log.i(TAG, "onServiceKilled");
    }
}

上傳api

上傳api設(shè)計(jì)就很簡單了,上傳經(jīng)緯度、時(shí)間就行

public interface MainService {

    @POST("/login")
    @FormUrlEncoded
    Single<LoginResult> login(@Field("username") String username, @Field("password") String password);


    @POST("/gps")
    Single<UploadResult> gps(@Header ("token")String token, @Body RequestModel model);
}

public class RequestModel
{
    private Double lat;
    private Double lng;
    private Long time;
}

關(guān)于imei

Android的生態(tài)越來越健康,也越來越安全,很多用戶隱私信息都獲取不到了。例如,imei、手機(jī)號(hào)、sim卡編號(hào)等等。

但是,如果要保證唯一性,ime是最優(yōu)選擇,其次就是uuid或者其他自行組編的code。但是,涉及到一個(gè)問題,如果本地化沒有處理好,卸載重裝就沒有了。所以,這里有個(gè)uuid的工具類,原理是,uuid等存放到sdcard而非沙盒目錄下面。

但是29(Android10),由于文件分區(qū)的關(guān)系,也不能直接訪問了。需要使用api來進(jìn)行訪問。android:requestLegacyExternalStorage="true"也可以用來兼容使用。

工具類:

public final class DeviceUtil {
    private static final String TAG = DeviceUtil.class.getSimpleName();

    private static final String TEMP_DIR = "system_config";
    private static final String TEMP_FILE_NAME = "system_file";
    private static final String TEMP_FILE_NAME_MIME_TYPE = "application/octet-stream";
    private static final String SP_NAME = "device_info";
    private static final String SP_KEY_DEVICE_ID = "device_id";

    public static String getDeviceId(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
        String deviceId = sharedPreferences.getString(SP_KEY_DEVICE_ID, null);
        if (!TextUtils.isEmpty(deviceId)) {
            return deviceId;
        }
        deviceId = getIMEI(context);
        if (TextUtils.isEmpty(deviceId)) {
            deviceId = createUUID(context);
        }
        sharedPreferences.edit()
                .putString(SP_KEY_DEVICE_ID, deviceId)
                .apply();
        return deviceId;
    }

    private static String createUUID(Context context) {
        String uuid = UUID.randomUUID().toString().replace("-", "");

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            Log.d(TAG,"Q");
            Uri externalContentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
            ContentResolver contentResolver = context.getContentResolver();
            String[] projection = new String[]{
                    MediaStore.Downloads._ID
            };
            String selection = MediaStore.Downloads.TITLE + "=?";
            String[] args = new String[]{
                    TEMP_FILE_NAME
            };
            Cursor query = contentResolver.query(externalContentUri, projection, selection, args, null);
            if (query != null && query.moveToFirst()) {
                Log.d(TAG,"moveToFirst");
                Uri uri = ContentUris.withAppendedId(externalContentUri, query.getLong(0));
                query.close();

                InputStream inputStream = null;
                BufferedReader bufferedReader = null;
                try {
                    inputStream = contentResolver.openInputStream(uri);
                    if (inputStream != null) {
                        bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                        uuid = bufferedReader.readLine();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (bufferedReader != null) {
                        try {
                            bufferedReader.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (inputStream != null) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else {
                Log.d(TAG,"ContentValues");
                ContentValues contentValues = new ContentValues();
                contentValues.put(MediaStore.Downloads.TITLE, TEMP_FILE_NAME);
                contentValues.put(MediaStore.Downloads.MIME_TYPE, TEMP_FILE_NAME_MIME_TYPE);
                contentValues.put(MediaStore.Downloads.DISPLAY_NAME, TEMP_FILE_NAME);
                contentValues.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + File.separator + TEMP_DIR);

                Uri insert = contentResolver.insert(externalContentUri, contentValues);
                if (insert != null) {
                    OutputStream outputStream = null;
                    try {
                        outputStream = contentResolver.openOutputStream(insert);
                        if (outputStream == null) {
                            return uuid;
                        }
                        outputStream.write(uuid.getBytes());
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        if (outputStream != null) {
                            try {
                                outputStream.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        } else {
            Log.d(TAG,"DIRECTORY_DOWNLOADS");
            File externalDownloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            File applicationFileDir = new File(externalDownloadsDir, TEMP_DIR);
            if (!applicationFileDir.exists()) {
                if (!applicationFileDir.mkdirs()) {
                    Log.e(TAG, "文件夾創(chuàng)建失敗: " + applicationFileDir.getPath());
                }
            }
            File file = new File(applicationFileDir, TEMP_FILE_NAME);
            if (!file.exists()) {
                Log.d(TAG,"mk DIRECTORY_DOWNLOADS");
                FileWriter fileWriter = null;
                try {
                    if (file.createNewFile()) {
                        fileWriter = new FileWriter(file, false);
                        fileWriter.write(uuid);
                    } else {
                        Log.e(TAG, "文件創(chuàng)建失?。? + file.getPath());
                    }
                } catch (IOException e) {
                    Log.e(TAG, "文件創(chuàng)建失?。? + file.getPath());
                    e.printStackTrace();
                } finally {
                    if (fileWriter != null) {
                        try {
                            fileWriter.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else {
                Log.d(TAG,"read DIRECTORY_DOWNLOADS");
                FileReader fileReader = null;
                BufferedReader bufferedReader = null;
                try {
                    fileReader = new FileReader(file);
                    bufferedReader = new BufferedReader(fileReader);
                    uuid = bufferedReader.readLine();

                    bufferedReader.close();
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (bufferedReader != null) {
                        try {
                            bufferedReader.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    if (fileReader != null) {
                        try {
                            fileReader.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return uuid;
    }

    private static String getIMEI(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            return null;
        }
        try {
            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            if (telephonyManager == null) {
                return null;
            }
            @SuppressLint({"MissingPermission", "HardwareIds"}) String imei = telephonyManager.getDeviceId();
            return imei;
        } catch (Exception e) {
            return null;
        }
    }
}

三、模擬定位

現(xiàn)在模擬gps定位,實(shí)現(xiàn)模擬定位、位置打開等,多數(shù)也是采用【開發(fā)者-模擬定位應(yīng)用】這種方式。這種方式在某些沒有做過特殊反模擬定位處理的APP上還是可以用的,比如百度地圖,模擬之后,依舊能夠看到自己在模擬的地方。但是,例如,釘釘打卡、微信打卡、微信定位等這些大廠APP都是做了反模擬處理,依舊是不能用的。

這里實(shí)現(xiàn),也是為了練練手,了解其中原理。

參看module:mocklocationlib源碼

實(shí)現(xiàn)步驟:

1、引導(dǎo)用戶開啟開發(fā)者模式,選擇模擬定位應(yīng)用,添加自身應(yīng)用

2、利用系統(tǒng)api,LocationManager來添加test模擬位置信息

 public boolean getUseMockPosition(Context context) {
        // Android 6.0以下,通過Setting.Secure.ALLOW_MOCK_LOCATION判斷
        // Android 6.0及以上,需要【選擇模擬位置信息應(yīng)用】,未找到方法,因此通過addTestProvider是否可用判斷
        boolean canMockPosition = (Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0)
                || Build.VERSION.SDK_INT > 22;
        if (canMockPosition && hasAddTestProvider == false) {
            try {
                for (String providerStr : mockProviders) {
                    //獲取所有的provider 包括網(wǎng)絡(luò)、gps、衛(wèi)星等
                    LocationProvider provider = locationManager.getProvider(providerStr);
                    if (provider != null) {
                        locationManager.addTestProvider(
                                provider.getName()
                                , provider.requiresNetwork()
                                , provider.requiresSatellite()
                                , provider.requiresCell()
                                , provider.hasMonetaryCost()
                                , provider.supportsAltitude()
                                , provider.supportsSpeed()
                                , provider.supportsBearing()
                                , provider.getPowerRequirement()
                                , provider.getAccuracy());
                    } else {
                        if (providerStr.equals(LocationManager.GPS_PROVIDER)) {//模擬gps模塊定位信息
                            locationManager.addTestProvider(
                                    providerStr
                                    , true, true, false, false, true, true, true
                                    , Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);
                        } else if (providerStr.equals(LocationManager.NETWORK_PROVIDER)) { //模擬網(wǎng)絡(luò)定位信息
                            locationManager.addTestProvider(
                                    providerStr
                                    , true, false, true, false, false, false, false
                                    , Criteria.POWER_LOW, Criteria.ACCURACY_FINE);
                        } else {
                            locationManager.addTestProvider(
                                    providerStr
                                    , false, false, false, false, true, true, true
                                    , Criteria.POWER_LOW, Criteria.ACCURACY_FINE);
                        }
                    }
                    locationManager.setTestProviderEnabled(providerStr, true);
                    locationManager.setTestProviderStatus(providerStr, LocationProvider.AVAILABLE, null, System.currentTimeMillis());
                }
                hasAddTestProvider = true;  // 模擬位置可用
                canMockPosition = true;
            } catch (SecurityException e) {
                canMockPosition = false;
            }
        }
        if (canMockPosition == false) {
            stopMockLocation();
        }
        return canMockPosition;
    }
 /**
     * 模擬位置線程
     */
    private class RunnableMockLocation implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);

                    if (hasAddTestProvider == false) {
                        continue;
                    }

                    if (bRun == false) {
                        stopMockLocation();
                        continue;
                    }
                    try {
                        // 模擬位置(addTestProvider成功的前提下)
                        for (String providerStr : mockProviders) {
                            Log.d(TAG, "providerStr:" + providerStr);
                            locationManager.setTestProviderLocation(providerStr, generateLocation(latitude, longitude));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        // 防止用戶在軟件運(yùn)行過程中關(guān)閉模擬位置或選擇其他應(yīng)用
                        stopMockLocation();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Location generateLocation(double lat, double lng) {
        Location loc = new Location("gps");
        Log.d(TAG, "mock latitude:" + lat + "\tlongitude:" + lng);

        loc.setAccuracy(2.0F);
        loc.setAltitude(55.0D);
        loc.setBearing(1.0F);
        Bundle bundle = new Bundle();
        bundle.putInt("satellites", 7);
        loc.setExtras(bundle);

        loc.setLatitude(lat);
        loc.setLongitude(lng);
        loc.setTime(System.currentTimeMillis());
        if (Build.VERSION.SDK_INT >= 17) {
            loc.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
        }
        return loc;
    }

代碼解析:

  • 先獲取測(cè)試provider,包括有網(wǎng)絡(luò)、gps衛(wèi)星等模塊
  • 啟動(dòng)線程,定時(shí)往provider里面添加模擬的定位信息,進(jìn)行模擬

封裝好lib之后,使用起來就很簡單了,開啟線程,設(shè)置要模擬的位置即可

    @Override
    public void startWork(Intent intent, int flags, int startId) {
        Log.i(TAG, "startWork");
        if (mMockLocationManager == null) {
            mMockLocationManager = new MockLocationManager();
            mMockLocationManager.initService(getApplicationContext());
            mMockLocationManager.startThread();
        }

        if (mMockLocationManager.getUseMockPosition(getApplicationContext())) {
            startMockLocation();
            double lat = intent.getDoubleExtra(INTENT_KEY_LAT, 0);
            double lng = intent.getDoubleExtra(INTENT_KEY_LNG, 0);
            setMangerLocationData(lat, lng);
        }
    }

使用:在地圖上選點(diǎn),然后模擬就OK

總結(jié)

Android端只是個(gè)小小的開始,沒有后臺(tái)接口的支持,數(shù)據(jù)上傳了也沒有用。所以,后面還需要搭建一下java服務(wù)器,寫幾個(gè)接口來滿足我們的需求。

請(qǐng)移步GPS定位系統(tǒng)(三)——Java后端

關(guān)于作者

作者是一個(gè)熱愛學(xué)習(xí)、開源、分享,傳播正能量,喜歡打籃球、頭發(fā)還很多的程序員-。-

熱烈歡迎大家關(guān)注、點(diǎn)贊、評(píng)論交流!

簡書:http://www.itdecent.cn/u/d234d1569eed

github:https://github.com/fly7632785

CSDN:https://blog.csdn.net/fly7632785

掘金:https://juejin.im/user/5efd8d205188252e58582dc7/posts

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容