Android tips(十)-->允許模擬位置在Android M下的坑
本文我們將講解允許模擬位置在Android M下的坑。做地圖類應用的同學應該都知道為了避免軟體模擬位置影響正常流程的進行我們一般都會判斷使用者手機是否打開了模擬位置設定,若打開了則終止使用者流程,提醒使用者關閉模擬位置設定。在android系統的開發者選項中有一個模擬位置的選項,其作用是允許使用者通過程式碼模擬裝置的當前位置,比如地圖類應用需要測試在外地的使用情況,通過開啟此項選項可以通過程式碼模擬位置,具體可參考我的:Android中的開發者選項
允許模擬位置的設定選項在手機的開發者選項設定中:
產品實踐:
在我們的產品下單用車中有一個取車的環節,通過手機控制開車門,而這個時候會判斷當前手機是否開啟的模擬位置的功能,若開啟則,提示使用者並關閉該模擬位置的功能。(若是允許使用者開啟模擬位置功能,則惡意使用者可以通過第三方的模擬位置App修改手機的定位資訊,進而影響我們App的定位資訊,當需要使用者在中關村還車時,在十里堡就可以通過模擬位置遮蔽這個操作了)
判斷使用者是否開啟模擬位置的程式碼如下:
/**
* 判斷是否打開了允許虛擬位置,如果打開了 則彈窗讓他去關閉
*/
public static boolean isAllowMockLocation(final Activity context) {
/**
* 判斷使用者是否開啟了模擬位置功能
*/
boolean isOpen = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0 ) != 0;
if (isOpen) {
Config.showTiplDialog(context, null, "定位失敗,需要關閉【允許模擬位置】功能後才能使用友友用車檢視附近的車輛。", "去設定", new View.OnClickListener() {
@Override
public void onClick(View view) {
context.startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS));
}
});
}
return isOpen;
}
在開車門頁面中,點選開車們按鈕,判斷使用者是否打開了模擬位置開關,若開啟則提示使用者關閉:
這時候點選去設定按鈕,則會跳轉到開發者選項中,並允許使用者關閉開發者選項。
出現的問題:
但是在Android M的機型中判斷邏輯出現了問題,部分三星手機開啟車載模式的話,這時候再次點選開車門的話,上述程式碼會判斷出使用者開啟了模擬位置功能,這時候就會阻塞使用者的操作,並指引使用者關閉模擬位置開關。但是Android M手機上已經沒有了允許模擬位置的設定開關了,取而代之的是選擇模擬位置資訊應用設定按鈕。
按道理來說,即便使用者開啟了車載模式這時候通過上述判斷是否開啟模擬位置的程式碼返回值應該是false(沒有開啟模擬位置),但是這時候用於彈出了定位失敗,需要關閉模擬位置的彈窗,說明通過程式碼判斷使用者是否打開了模擬位置返回了true。
後來經過排查得知像這種允許模擬位置等資訊都是儲存在系統底層的一個數據庫中,而我們的判斷程式碼返回了true,則說明使用者底層的允許模擬位置資料庫值為true。
但是這時候Android M中由於已經不存在允許模擬位置取而代之的是選擇模擬位置資訊應用設定,相當於這是兩個設定底層資料庫變數的開關了,而我們的程式碼判斷的是允許模擬位置的資料庫值,在Android M中並沒有更改允許模擬位置的開關,所以這樣就沒辦法更改Android M下的允許模擬位置的值了。但是Android M上不是使用了選擇模擬資訊應用設定麼?這又是什麼鬼呢?
在Android M中已經沒有了允許模擬位置的開發,取而代之的是:選擇模擬位置資訊應用:
在Android M下預設的應用是無法顯示在選擇模擬資訊應用中的,需要經過如下的操作才可以:
- 新增debug-AndroidManifest許可權
這樣經過設定之後我們的應用資訊就可以顯示在模擬位置中了,其中經過測試當為我們的應用設定了模擬位置資訊之後,其只可以影響我們自己應用的定位資訊,而無法影響其他應用的定位資訊。這也算android系統解決的模擬位置資訊的bug吧。
允許模擬位置的BUG:
在Android M之前如果我們為自己的應用選擇了允許模擬位置,則可以通過一個應用的模擬位置操作影響其他應用的定位資訊,而這種操作Google認為是不正確的。模擬位置資訊的初衷是為了方便App的除錯操作,而當這種操作影響其他應用時就可以做一些黑操作了。
比如通過模擬位置,在使用滴滴的時候模擬位置資訊搶單等等。
所以為了解決這個問題,android M中升級了允許模擬位置設定,取而代之的是選擇模擬位置資訊應用設定,通過設定這個選項,只能影響當前應用,而不能影響其他應用的定位資訊。
比如,這時候我們在通過一些App模擬當前手機的定位資訊,這時候就不可以影響滴滴的定位資訊了。
執行Android M下的模擬位置操作:
在debug-AndroidManifest中新增模擬位置的許可權許可權
在開發者選項,選擇模擬位置資訊應用中,選擇自身App
通過程式碼模擬位置
public class RunnableMockLocation implements Runnable {
@Override
public void run() {
try {
// 模擬位置(addTestProvider成功的前提下)
String providerStr = LocationManager.GPS_PROVIDER;
Location mockLocation = new Location(providerStr);
mockLocation.setLatitude(22); // 維度(度)
mockLocation.setLongitude(113); // 經度(度)
mockLocation.setAltitude(30); // 高程(米)
mockLocation.setBearing(180); // 方向(度)
mockLocation.setSpeed(10); //速度(米/秒)
mockLocation.setAccuracy(0.1f); // 精度(米)
mockLocation.setTime(new Date().getTime()); // 本地時間
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
locationManager.setTestProviderLocation(providerStr, mockLocation);
} catch (Exception e) {
// 防止使用者在軟體執行過程中關閉模擬位置或選擇其他應用
stopMockLocation();
}
}
}
- 通過程式碼獲取位置資訊:
//位置監聽
private LocationListener locationListener=new LocationListener() {
/**
* 位置資訊變化時觸發
*/
public void onLocationChanged(Location location) {
double lat = location.getLatitude();
double lot = location.getLongitude();
String str= "Latitude"+lat+"\r\nLongitude:"+lot;
textView.setText(str);
Log.i(TAG, "時間:"+location.getTime());
Log.i(TAG, "經度:"+location.getLongitude());
Log.i(TAG, "緯度:"+location.getLatitude());
Log.i(TAG, "海拔:"+location.getAltitude());
}
/**
* GPS狀態變化時觸發
*/
public void onStatusChanged(String provider, int status, Bundle extras) {
switch (status) {
//GPS狀態為可見時
case LocationProvider.AVAILABLE:
Log.i(TAG, "當前GPS狀態為可見狀態");
break;
//GPS狀態為服務區外時
case LocationProvider.OUT_OF_SERVICE:
Log.i(TAG, "當前GPS狀態為服務區外狀態");
break;
//GPS狀態為暫停服務時
case LocationProvider.TEMPORARILY_UNAVAILABLE:
Log.i(TAG, "當前GPS狀態為暫停服務狀態");
break;
}
}
/**
* GPS開啟時觸發
*/
public void onProviderEnabled(String provider) {
}
/**
* GPS禁用時觸發
*/
public void onProviderDisabled(String provider) {
}
};
- 檢視其它App的定位資訊是否收到了影響
我們開啟我們的其它應用發現其定位資訊並未受到影響。也就是說android M中的選擇模擬位置資訊應用與Android M以下的手機中的允許模擬位置區別。
允許模擬位置與選擇模擬位置資訊應用的區別:
允許模擬位置,可以通過模擬位置影響其他應用的定位資訊
選擇模擬位置資訊應用只能影響當前應用的定位資訊
允許模擬位置與選擇模擬位置資訊應用最終在系統中儲存在兩個資料庫表中,且相互不影響
而我們的手機中判斷的是允許模擬位置開關,在android M中若判斷打開了這個開關,但是系統已經關閉這個開關的設定操作,所以我們只需要遮蔽這個值即可。
最後的解決方案:
/**
* 判斷是否打開了允許虛擬位置,如果打開了 則彈窗讓他去關閉
*/
public static boolean isAllowMockLocation(final Activity context) {
boolean isOpen = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0;
/**
* 該判斷API是androidM以下的API,由於Android M中已經沒有了關閉允許模擬位置的入口,所以這裡一旦檢測到開啟了模擬位置,並且是android M以上,則
* 預設設定為未有開啟模擬位置
*/
if (isOpen && Build.VERSION.SDK_INT > 22) {
isOpen = false;
}
if (isOpen) {
Config.showTiplDialog(context, null, "定位失敗,需要關閉【允許模擬位置】功能後才能使用友友用車檢視附近的車輛。", "去設定", new View.OnClickListener() {
@Override
public void onClick(View view) {
context.startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS));
}
});
}
return isOpen;
}
也就是說,當我們判斷出當前裝置開啟允許模擬位置時,在判斷一下手機系統的版本,若為Android M以及以上,就遮蔽不管。可能部分同學會問那麼android M上的選擇模擬位置資訊應用有影響麼?答案是否定的,由於我們的App沒有新增允許模擬位置的許可權,所以其根本不會出現在選擇模擬位置應用列表,進而不會執行模擬位置的操作。
所以最終的解決方案就是,檢測裝置是否開啟了模擬位置選項,若開啟了,則判斷當前裝置是否為Android M即以上,若是,則遮蔽不管,否則阻塞使用者操作,引導使用者關閉模擬位置選項。