GPS positioning system (2) - Android side

foreword

GPS series - Android side, github project address tag: gps_mine

The Android mobile terminal mainly uses AutoNavi map positioning, uploads positioning information in the background, and then tries to keep alive as much as possible.

Including two small functions: 1. Upload positioning information 2. Simulate positioning information

It's all hands-on practice to gain a deep understanding of its principles. There is a lot of code throughout, so be careful.

You can check the source code as much as you can, and get what you need.

GPS positioning system series

GPS Positioning System (1) - Introduction

GPS positioning system (2) - Android side

GPS Positioning System (3) - Java Backend

GPS positioning system (4) - Vue front end

GPS positioning system (5) - Docker

[TOC]

reward

After studying this article you will gain:

  • AutoNavi map, positioning use
  • AutoNavi coordinate system conversion (officially only other coordinate systems are converted to AutoNavi, no AutoNavi to GPS)
  • Simulate positioning (punch in)
  • Uninstall and reinstall the same uuid|imei
  • Keep Alive Strategy and Principles

1. Map

The map uses AutoNavi map , if you register to apply for appkey, please go to the official website.

The map interface function is very simple, just follow the official documentation

 private void initMap() {
        MyLocationStyle myLocationStyle;
        myLocationStyle = new MyLocationStyle();//初始化定位蓝点样式类myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);//连续定位、且将视角移动到地图中心点,定位点依照设备方向旋转,并且会跟随设备移动。(1秒1次定位)如果不设置myLocationType,默认也会执行此种模式。
        myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW);
        myLocationStyle.interval(10000); //设置连续定位模式下的定位间隔,只在连续定位模式下生效,单次定位模式下不会生效。单位为毫秒。
        AMap map = mMapView.getMap();
        map.setMyLocationStyle(myLocationStyle);//设置定位蓝点的Style
        map.setMyLocationEnabled(true);// 设置为true表示启动显示定位蓝点,false表示隐藏定位蓝点并不进行定位,默认是false。
        map.getUiSettings().setMyLocationButtonEnabled(true); //显示默认的定位按钮
        map.setMyLocationEnabled(true);// 可触发定位并显示当前位置
        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()));
    }

Note : If the map is selected, use map.setOnMapClickListenerto set the monitor.

GPS and Gaode map latitude and longitude conversion

Note : It is the inter-rotation of the coordinate system of GPS and Gaode. To simulate the GPS positioning of the positioning simulation, you need to select the simulation point and convert it to the GPS positioning for simulation. A utility class is written here.

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转高德
        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  高德转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;
        }
 
}

2. Background keep-alive positioning

Daycare:

It's nice to use a framework here to keep alive, HelloDaemon

Keep alive ideas:

  1. Set Service as foreground service without showing notification

  2. Return START_STICKY in the onStartCommand method of the Service

  3. Override the onDestroy/onTaskRemoved method of the Service, save the data to disk, and then restart the service

  4. Monitor 8 system broadcasts

  5. Start the daemon service: regularly check whether the service is running, and pull it up if it is not running

  6. Guard the enabled state of the Service component so that it is not disabled by tools such as MAT

In addition, there are also setting interfaces such as [Battery Optimization] [Auto-Start Settings] [White List] and other settings for the ROMs of various mobile phone manufacturers.

 IntentWrapper.whiteListMatters(this, "为了更好的实时定位,最好把应用加入您手机的白名单");

The keep-alive service inherits AbsWorkService and implements its abstract method.

/**
 * 是否 任务完成, 不再需要服务运行?
 * @return 应当停止服务, true; 应当启动服务, false; 无法判断, null.
 */
Boolean shouldStopService();

/**
 * 任务是否正在运行?
 * @return 任务正在运行, true; 任务当前不在运行, false; 无法判断, null.
 */
Boolean isWorkRunning();

void startWork();

void stopWork();

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

//服务被杀时调用, 可以在这里面保存数据.
void onServiceKilled();

About keep alive, security, privacy

In fact, with the maturity of Android, the ecology is healthier, safer, more privacy-oriented, and user-oriented. Many "black technologies" are no longer available. Now the maintenance of life is not like the previous bells and whistles. I am interested in learning about some old technologies. If you have a version of the keep-alive strategy, you can learn it from these links:

Android process resident (2) ---- detail the use of the android system mechanism to keep alive means

D-clock / AndroidDaemonService

Unlike the practices of major manufacturers, various mutual pulls and whitelists of mobile phone manufacturers; the current "civilian" keep-alive ideas are basically to guide users to add whitelists, optimize power without restrictions, and lock applications .

No one can say that it can survive 100% in the background. Even if it can be kept alive, it is triggered by certain broadcasts or user behaviors. The rom performance of major mobile phone manufacturers varies, and some will still be killed. , there is no way. However, on my Xiaomi Mi 6 mobile phone, after the actual measurement, after the whitelist and power optimization settings, I can upload gps information every minute, constantly, but it still consumes a lot of power, to be honest. Some other mobile phones will not work, such as Huawei, Huawei's ecology and security are really great.

UploadGpsService implementation:

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

    int shouldCount;
    int actualCount;


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

    //声明AMapLocationClient类对象
    public AMapLocationClient mLocationClient = null;
    //声明定位回调监听器
    public AMapLocationListener mLocationListener = amapLocation -> {
        if (amapLocation != null) {
            if (amapLocation.getErrorCode() == 0) {
//可在其中解析amapLocation获取相应内容。
                Log.d("mapLocation", amapLocation.toString());
                //获取定位时间
                SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date date = new Date();
                df.format(date);
                Log.d(TAG, String.format("经度:%s\t纬度:%s\t地址:%s\n%s\n应上传次数%d\n实上传次数%d", amapLocation.getLongitude(), amapLocation.getLatitude(), amapLocation.getAddress(), df.format(date), shouldCount, actualCount));
                upload(amapLocation.getLongitude(), amapLocation.getLatitude());
            } else {
                //定位失败时,可通过ErrCode(错误码)信息来确定失败的原因,errInfo是错误信息,详见错误码表。
                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对象
    public AMapLocationClientOption mLocationOption = null;


    private void initGps() {
//初始化定位
        mLocationClient = new AMapLocationClient(getApplicationContext());
//设置定位回调监听
        mLocationClient.setLocationListener(mLocationListener);

        //初始化AMapLocationClientOption对象
        mLocationOption = new AMapLocationClientOption();
        /**
         * 设置定位场景,目前支持三种场景(签到、出行、运动,默认无场景)
         */
        mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.Sport);

        //设置定位模式为AMapLocationMode.Hight_Accuracy,高精度模式。
        mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);

        //设置定位间隔,单位毫秒,默认为2000ms,最低1000ms。
        mLocationOption.setInterval(5000);

        mLocationClient.setLocationOption(mLocationOption);

        //启动定位
        mLocationClient.startLocation();
    }


    public static void stopService() {
        //我们现在不再需要服务运行了, 将标志位置为 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) {
        //若还没有取消订阅, 就说明任务仍在运行.
        return null;
    }

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

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

upload api

Uploading the api design is very simple, just upload the latitude, longitude and time

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;
}

About imei

The Android ecosystem is getting healthier and more secure, and many users' private information cannot be obtained. For example, imei, mobile phone number, sim card number, etc.

However, if you want to ensure uniqueness, ime is the best choice, followed by uuid or other self-organized code. However, there is a problem involved. If the localization is not handled properly, there will be no uninstallation and reloading. Therefore, there is a tool class for uuid. The principle is that uuid is stored in the sdcard instead of the sandbox directory.

But 29 (Android 10), due to the file partition, cannot be accessed directly. Need to use api to access. android:requestLegacyExternalStorage="true"Can also be used for compatibility.

Tools:

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, "文件夹创建失败: " + 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, "文件创建失败:" + file.getPath());
                    }
                } catch (IOException e) {
                    Log.e(TAG, "文件创建失败:" + 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;
        }
    }
}

3. Simulated positioning

Now simulate GPS positioning, to achieve simulated positioning, position opening, etc., most of them also use the method of [developer-simulated positioning application]. This method can still be used on some APPs that have not done special anti-simulation positioning processing, such as Baidu Maps. After the simulation, you can still see where you are simulating. However, for example, DingTalk punch card, WeChat punch card, WeChat positioning and other big factory APPs have been processed by anti-simulation, and they still cannot be used.

The realization here is also to practice hands and understand the principle.

See module: mocklocationlib source code

Implementation steps:

1. Guide the user to open the developer mode, select the simulated positioning application, and add its own application

2. Use system api, LocationManager to add test simulation location information

 public boolean getUseMockPosition(Context context) {
        // Android 6.0以下,通过Setting.Secure.ALLOW_MOCK_LOCATION判断
        // Android 6.0及以上,需要【选择模拟位置信息应用】,未找到方法,因此通过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 包括网络、gps、卫星等
                    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)) { //模拟网络定位信息
                            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();
                        // 防止用户在软件运行过程中关闭模拟位置或选择其他应用
                        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;
    }

Code analysis:

  • First obtain the test provider, including modules such as network, gps satellite, etc.
  • Start the thread, add the simulated positioning information to the provider regularly, and simulate

After encapsulating the lib, it is very simple to use, open the thread, and set the location to be simulated.

    @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);
        }
    }

Use: select a point on the map, and then simulate is OK

Summarize

The Android side is just a small beginning. Without the support of the background interface, it is useless to upload the data. Therefore, we need to build a java server later and write a few interfaces to meet our needs.

Please move GPS positioning system (3) - Java backend

About the author

The author is a programmer who loves learning, open source, sharing, spreading positive energy, likes to play basketball, and has a lot of hair. -

Warmly welcome everyone to follow, like, comment and exchange!

Short Book: Jafir - Short Book

github:https://github.com/fly7632785

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

Nuggets: https://juejin.im/user/5efd8d205188252e58582dc7/posts



Reprinted at: https://www.jianshu.com/p/e463b1ea6b7d
 

Guess you like

Origin blog.csdn.net/weixin_42602900/article/details/123089606