preface
GPS series - Android terminal, github project address tag: gps_mine
Android mobile terminal mainly uses Gaode map positioning, uploads positioning information in the background, and then tries to keep it alive as much as possible.
It includes two small functions: 1. Upload positioning information; 2. Simulate positioning information
They are all hands-on practice to deeply understand its principle. There are many codes throughout, so be careful.
You can check the source code and get what you need.
GPS positioning system series
GPS positioning system (I) -- Introduction
GPS positioning system (II) - Android terminal
GPS positioning system (III) -- Java back end
GPS positioning system (IV) -- Vue front end
GPS positioning system (V) -- Docker
- Docker nginx secondary domain name has no port to access multiple web projects
- Docker nginx https secondary domain name no port to access multiple web projects
- Continuous deployment - Travis+Docker + alicloud container image
[TOC]
harvest
After learning this article, you will gain:
- Gaode map, positioning and use
- Gaode coordinate system conversion (only other coordinate systems are officially transferred to Gaode, and no Gaode is transferred to gps)
- Analog positioning (clock in)
- Uninstall and reinstall the same uuid|imei
- Keep alive strategy and principle
1, Map
The map uses Gaode map. To register and apply for appkey, please go to the official website.
The map interface function is very simple, just follow the official documents
private void initMap() { MyLocationStyle myLocationStyle; myLocationStyle = new MyLocationStyle();//Initialize the locating blue dot style class mylocationstyle myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);// Continuously locate and move the viewing angle to the center point of the map. The positioning point rotates according to the direction of the device and will follow the device. (positioning once every 1 second) if mylocationtype is not set, this mode will also be executed by default. myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW); myLocationStyle.interval(10000); //Setting the positioning interval in continuous positioning mode is only effective in continuous positioning mode, but not in single positioning mode. The unit is milliseconds. AMap map = mMapView.getMap(); map.setMyLocationStyle(myLocationStyle);//Set the Style to locate the blue dot map.setMyLocationEnabled(true);// Set to true to enable the display of positioning blue dots, false to hide the positioning blue dots without positioning, and the default is false. map.getUiSettings().setMyLocationButtonEnabled(true); //Displays the default positioning button map.setMyLocationEnabled(true);// It can trigger positioning and display the current position 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("Simulated location").snippet("default")); }); map.setOnMyLocationChangeListener(location -> Log.d(TAG, "onMyLocationChange:" + location.getLatitude() + "\t" + location.getLongitude())); }
If you use the map, note: select the point of the map Setonmapcclicklistener to set listening.
gps and Gaode map longitude and latitude rotation
Note: it refers to the mutual transformation of gps and Gaode coordinate system. The simulated gps positioning needs to be transformed into gps positioning for simulation after selecting the simulated points. Here is a tool class.
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 to Gaode 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 Gaode to 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
Keep alive:
It's good to use a framework here, HelloDaemon
Keep alive ideas:
-
Set the Service as a foreground Service without displaying notifications
-
Return start in the onStartCommand method of the Service_ STICKY
-
Overwrite the ondestroy / ontaskeremoved method of the Service, save the data to the disk, and then pull up the Service again
-
Monitor 8 system broadcasts
-
Open the guard service: regularly check whether the service is running. If not, pull it up
-
Guard the enabled state of the Service component so that it is not disabled by tools such as MAT
In addition, there are setting interfaces such as intent jump [power optimization], [auto start setting], [white list] suitable for rom of various mobile phone manufacturers
IntentWrapper.whiteListMatters(this, "For better real-time location, it's best to add the application to your mobile phone's white list");
Keep alive service inherits AbsWorkService and implements its abstract method
/** * Whether the task is completed and the service operation is no longer required? * @return The service should be stopped, true; The service should be started, false; Unable to determine, null */ Boolean shouldStopService(); /** * Is the task running? * @return Task is running, true; The task is not currently running, false; Unable to determine, null */ Boolean isWorkRunning(); void startWork(); void stopWork(); //Service.onBind(Intent intent) @Nullable IBinder onBind(Intent intent, Void unused); //Called when the service is killed, you can save data here void onServiceKilled();
About keeping alive, safe and privacy
In fact, with the growing maturity of Android, the ecology is healthier, safer, more privacy oriented and user-oriented. Many "black technologies" are no longer available. The current preservation is not as fancy as before. If you are interested in understanding some old versions of preservation strategies, you can learn from these links:
D-clock / AndroidDaemonService
Unlike the practices of big manufacturers, all kinds of pull each other and the white list of mobile phone manufacturers; The current "folk" living protection ideas are basically to guide users to add white lists, unlimited power optimization, lock applications, etc.
No one can say that baohuo can survive 100% in the background. Even if baohuo can survive, it is triggered by some broadcast or user behavior. The rom performance of major mobile phone manufacturers is different, and some will still be killed. There is no way. However, my Xiaomi 6 mobile phone can upload gps information every minute after the actual measurement, white list and power optimization settings, but it still consumes a lot of power. To tell the truth. Some other mobile phones don't 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(); //Whether the task is completed and the service operation is no longer required? public static boolean sShouldStopService; int shouldCount; int actualCount; @Override public void onCreate() { super.onCreate(); initGps(); } //Declare AMapLocationClient class object public AMapLocationClient mLocationClient = null; //Declare location callback listener public AMapLocationListener mLocationListener = amapLocation -> { if (amapLocation != null) { if (amapLocation.getErrorCode() == 0) { //You can parse amapLocation to get the corresponding content. Log.d("mapLocation", amapLocation.toString()); //Get location time SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = new Date(); df.format(date); Log.d(TAG, String.format("Longitude:%s\t Latitude:%s\t Address:%s\n%s\n Number of times to upload%d\n Actual upload times%d", amapLocation.getLongitude(), amapLocation.getLatitude(), amapLocation.getAddress(), df.format(date), shouldCount, actualCount)); upload(amapLocation.getLongitude(), amapLocation.getLatitude()); } else { //When positioning fails, the reason for the failure can be determined through ErrCode information. errInfo is the error information. See the error code table for details. 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()); }); } //Declare AMapLocationClientOption object public AMapLocationClientOption mLocationOption = null; private void initGps() { //Initialize positioning mLocationClient = new AMapLocationClient(getApplicationContext()); //Set location callback listening mLocationClient.setLocationListener(mLocationListener); //Initialize the AMapLocationClientOption object mLocationOption = new AMapLocationClientOption(); /** * Set positioning scenarios. Currently, three scenarios are supported (check-in, travel and sports. There is no scenario by default) */ mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.Sport); //Set the location mode to amaplocationmode Hight_ Accuracy, high precision mode. mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy); //Set the positioning interval in milliseconds. The default is 2000ms and the minimum is 1000ms. mLocationOption.setInterval(5000); mLocationClient.setLocationOption(mLocationOption); //Start positioning mLocationClient.startLocation(); } public static void stopService() { //We no longer need the service to run. Set the flag to true sShouldStopService = true; //Cancel 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) { //If you haven't unsubscribed, the task is still running 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 api design is very simple. Just upload longitude, latitude 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 ecosystem of Android is becoming healthier and safer, and many users can't get their privacy information. 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 compiled code s. However, there is a problem involved. If localization is not handled properly, there will be no need to uninstall and reinstall. Therefore, there is a tool class of uuid. The principle is that uuid is stored in sdcard instead of sandbox directory.
However, 29 (Android 10) cannot be accessed directly due to the relationship between file partitions. You need to use an api to access it. 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, "Folder creation failed: " + 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 creation failed:" + file.getPath()); } } catch (IOException e) { Log.e(TAG, "File creation failed:" + 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; } } }
Three, analog positioning
Now simulate gps positioning, realize simulated positioning and position opening, and most of them also adopt 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 map. After simulation, you can still see where you are simulating. However, for example, nail punch, wechat punch, wechat positioning and other large factory apps have done anti simulation processing and are still unusable.
The realization here is also to practice and understand the principle.
See module: mocklocationlib source code
Implementation steps:
1. Guide users to open the developer mode, select the simulated positioning application, and add their own application
2. Use the system api and LocationManager to add test simulation location information
public boolean getUseMockPosition(Context context) { // Below Android 6.0, through setting Secure. ALLOW_ MOCK_ Location judgment // Android 6.0 and above require [Select simulation location information application], and no method is found. Therefore, judge whether addTestProvider is available 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) { //Obtain all provider s, including network, gps, satellite, etc 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)) {//Simulated gps module positioning information locationManager.addTestProvider( providerStr , true, true, false, false, true, true, true , Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); } else if (providerStr.equals(LocationManager.NETWORK_PROVIDER)) { //Analog network location information 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; // Analog location available canMockPosition = true; } catch (SecurityException e) { canMockPosition = false; } } if (canMockPosition == false) { stopMockLocation(); } return canMockPosition; }
/** * Analog location thread */ 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 { // Simulation location (if addTestProvider succeeds) for (String providerStr : mockProviders) { Log.d(TAG, "providerStr: " + providerStr); locationManager.setTestProviderLocation(providerStr, generateLocation(latitude, longitude)); } } catch (Exception e) { e.printStackTrace(); // Prevent the user from closing the simulation position or selecting other applications during the operation of the software 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 network, gps satellite and other modules
- Start the thread and add simulated positioning information to the provider regularly for simulation
After the lib is encapsulated, it is easy to use. Start 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 points on the map, and then simulate it
summary
Android is just a small start, without the support of background interface, and it is useless to upload data. Therefore, we need to build a java server and write several interfaces to meet our needs.
Please move GPS positioning system (III) -- Java back end
Author about
The author is a programmer who loves learning, open source, sharing, spreading positive energy, playing basketball and having a lot of hair --
Warmly welcome your attention, praise, comments and exchanges!
Brief book: Jafir - short book
github: https://github.com/fly7632785
CSDN: https://blog.csdn.net/fly7632785
Nuggets: https://juejin.im/user/5efd8d205188252e58582dc7/posts
Reproduced in: https://www.jianshu.com/p/e463b1ea6b7d