前言
GPS系列——Android端,github項(xiàng)目地址 tag: gps_mine
Android移動(dòng)端,主要是使用高德地圖定位,后臺(tái)上傳定位信息,然后就是想辦法盡量?;?。
包括兩個(gè)小功能:1、上傳定位信息 2、模擬定位信息
都是練手實(shí)踐,去深入了解其原理。通篇代碼較多,慎入。
大家盡可以去查看源碼,各取所需。
GPS定位系統(tǒng)系列
[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>
將Service設(shè)置為前臺(tái)服務(wù)而不顯示通知
在 Service 的 onStartCommand 方法里返回 START_STICKY
覆蓋 Service 的 onDestroy/onTaskRemoved 方法, 保存數(shù)據(jù)到磁盤, 然后重新拉起服務(wù)
監(jiān)聽 8 種系統(tǒng)廣播
開啟守護(hù)服務(wù) : 定時(shí)檢查服務(wù)是否在運(yùn)行,如果不在運(yùn)行就拉起來
守護(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