Android如何實現(xiàn)模擬定位
在導航測試場景中經(jīng)常需要定位模擬和路線回放,記錄下通過LocationManager.setTestProviderLocation()方法實現(xiàn)模擬地位,如果要測試的應用不支持TestProviderLocation模擬位置輸入,可以考慮從HAL層入手,hook系統(tǒng)默認的GPS實現(xiàn)。
一、Android模擬權限開啟配置在Android6.0以下的版本中,需要在設置中勾選模擬定位的開關,在6.0以上就改成了選擇模擬定位的應用,對應的開啟配置方式也不一樣,相同的是在AndroidManifest.xml都需要配置以下兩個權限:
<uses-permission android:name='android.permission.ACCESS_FINE_LOCATION' /><uses-permission android:name='android.permission.ACCESS_COARSE_LOCATION' />1)Android 6.0以下開啟模擬定位開關
Settings.Secure.putInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 1);
通過這種方式去開啟模擬定位需要在AndroidManifest.xml中配置以下系統(tǒng)權限,應用還需要經(jīng)過系統(tǒng)簽名,對于非系統(tǒng)應用不能通過這種實現(xiàn)方式。
android:sharedUserId='android.uid.system'<uses-permission android:name='android.permission.WRITE_SECURE_SETTINGS' />
涉及到這種操作可以通過adb shell命令的方式配置來繞過系統(tǒng)權限配置:
adb shell settings put secure mock_location 12)Android 6.0以上代碼配置選擇模擬定位的應用
在6.0以上的Android版本就需要設置指定包名的mock_location權限為allow。
try { String mockLocationPkgName = getPackageName(); PackageManager mPackageManager = getPackageManager(); final ApplicationInfo ai = mPackageManager.getApplicationInfo( mockLocationPkgName, PackageManager.MATCH_DISABLED_COMPONENTS); AppOpsManager mAppsOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); mAppsOpsManager.setMode(AppOpsManager.OPSTR_MOCK_LOCATION, ai.uid, mockLocationPkgName, AppOpsManager.MODE_ALLOWED);} catch (PackageManager.NameNotFoundException e) { /* ignore */}
同時在AndroidManifest.xml中還需要配置如下權限,可惜這種方式也是需要經(jīng)過系統(tǒng)簽名和源碼一起編譯,只有系統(tǒng)層級的應用才可以使用。
<uses-permission android:name='android.permission.MANAGE_APP_OPS_MODES' />
為了在設置界面模擬定位選項中顯示模擬定位的應用,需要配置ACCESS_MOCK_LOCATION權限。
<uses-permission android:name='android.permission.ACCESS_MOCK_LOCATION'/>
以上配置也可以通過adb shell命令去實現(xiàn),<package> 參數(shù)用自己應用的包名替換。
adb shell appops set <package> android:mock_location allow
關閉模擬定位權限,用如下命令:
adb shell appops set <package> android:mock_location deny
要查詢有哪些應用開啟了模擬定位權限,用如下命令:
adb shell appops query-op android:mock_location allow
執(zhí)行后會輸出應用的包名列表參數(shù)。
二、Android模擬定位實現(xiàn)1)模擬定位開關檢查首先是代碼中先判斷模擬定位權限是否開啟,6.0以上的只能通過添加定位監(jiān)聽是否有異常來判斷。
boolean mockPermission = false;if (Build.VERSION.SDK_INT <= 22) {//6.0以下 mockPermission = Settings.Secure.getInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) == 1;} else { try {LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { mockPermission = false;return;}locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, new LocationListener() { @Override public void onLocationChanged(Location location) { } @Override public void onStatusChanged(String s, int i, Bundle bundle) { } @Override public void onProviderEnabled(String s) { } @Override public void onProviderDisabled(String s) { }});mockPermission = true; } catch (SecurityException e) {mockPermission = false; }}
可以看下添加定位監(jiān)聽的源碼實現(xiàn),以Android 7.0的源碼實現(xiàn)做參考,LocationManager調(diào)用接口之后,最終是調(diào)用到LocationManagerService,在requestLocationUpdates 方法或 getLastLocation 方法中都有checkPackageName方法的調(diào)用
private void checkPackageName(String packageName) { if (packageName == null) {throw new SecurityException('invalid package name: ' + packageName); } int uid = Binder.getCallingUid(); String[] packages = mPackageManager.getPackagesForUid(uid); if (packages == null) {throw new SecurityException('invalid UID ' + uid); } for (String pkg : packages) {if (packageName.equals(pkg)) return; } throw new SecurityException('invalid package name: ' + packageName);}
如果調(diào)用requestLocationUpdates方法的應用沒有模擬定位的權限,就會報SecurityException異常,另外requestLocationUpdates需要在主線程中調(diào)用,如果在子線程中調(diào)用,還需要傳一個looper參數(shù),不然在實例化ListenerTransport的時候會報錯。
看下ListenerTransport的構(gòu)造函數(shù),如果在子線程中添加監(jiān)聽,又沒有傳Loop,初始化mListenerHandler的時候就會報異常。
ListenerTransport(LocationListener listener, Looper looper) { mListener = listener; if (looper == null) {mListenerHandler = new Handler() { @Override public void handleMessage(Message msg) {_handleMessage(msg); }}; } else {mListenerHandler = new Handler(looper) { @Override public void handleMessage(Message msg) {_handleMessage(msg); }}; }}
接著添加對應的Provider,設置開關狀態(tài)為true,配置狀態(tài)為AVAILABLE。
LocationProvider provider = locationManager.getProvider(LocationManager.GPS_PROVIDER);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 { locationManager.addTestProvider(LocationManager.GPS_PROVIDER, true, true, false, false, true, true,true, Criteria.POWER_LOW, Criteria.ACCURACY_FINE);}locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);locationManager.setTestProviderStatus(LocationManager.GPS_PROVIDER, LocationProvider.AVAILABLE, null, System.currentTimeMillis());2)setTestProviderLocation調(diào)用
調(diào)用代碼示例如下,經(jīng)緯度、車速、定位準確性、方位、海拔參數(shù)根據(jù)實際需求設置。
Location loc = new Location(LocationManager.GPS_PROVIDER);loc.setLongitude(24.522301);loc.setLatitude(118.115756);loc.setSpeed(60);loc.setAccuracy(Criteria.ACCURACY_HIGH);loc.setBearing(0);loc.setAltitude(0);loc.setTime(System.currentTimeMillis());if (Build.VERSION.SDK_INT >= 17) { loc.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());}locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER,loc);
來看下為什么需要設置elapsedRealtimeNanos和time參數(shù)。在SDK版本17以下,Location(Android 4.1.1)是沒有setElapsedRealtimeNanos這個方法的,在SDK版本17開始,Location(Android 4.2)加了這個方法,在調(diào)用setTestProviderLocation設置定位信息的時候,Android SDK版本17以上會做定位參數(shù)是否完整的校驗,17以下的版本自動做補足,17開始的版本直接拋異常。
public void setTestProviderLocation(String provider, Location loc) { if (!loc.isComplete()) {IllegalArgumentException e = new IllegalArgumentException( 'Incomplete location object, missing timestamp or accuracy? ' + loc);if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) { // just log on old platform (for backwards compatibility) Log.w(TAG, e); loc.makeComplete();} else { // really throw it! throw e;} } try {mService.setTestProviderLocation(provider, loc, mContext.getOpPackageName()); } catch (RemoteException e) {throw e.rethrowFromSystemServer(); }}
接著看下Location類中makeComplete和isComplete做的邏輯處理,isComplete里面有provider,Accuracy,mTime和mElapsedRealtimeNanos的判斷。
@SystemApipublic void makeComplete() { if (mProvider == null) mProvider = '?'; if (!hasAccuracy()) {mFieldsMask |= HAS_ACCURACY_MASK;mAccuracy = 100.0f; } if (mTime == 0) mTime = System.currentTimeMillis(); if (mElapsedRealtimeNanos == 0) mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();}@SystemApipublic boolean isComplete() { if (mProvider == null) return false; if (!hasAccuracy()) return false; if (mTime == 0) return false; if (mElapsedRealtimeNanos == 0) return false; return true;}
以上是就是單個點的模擬定位實現(xiàn),如果要實現(xiàn)路線回放模擬,只要在后臺請求到路線定位點數(shù)組數(shù)據(jù)之后,每隔1秒刷新調(diào)用setTestProviderLocation接口就可以了。
在模擬定位操作完畢之后,需要移除模擬定位對象,避免定位信息還是使用模擬定位接口的參數(shù),如果沒有移除下一次使用的時候又調(diào)用添加同名的Provider也會拋異常。
locationManager.removeTestProvider(LocationManager.GPS_PROVIDER);
以上就是Android如何實現(xiàn)模擬定位的詳細內(nèi)容,更多關于Android 模擬定位的資料請關注好吧啦網(wǎng)其它相關文章!
相關文章:
1. css進階學習 選擇符2. CSS Hack大全-教你如何區(qū)分出IE6-IE10、FireFox、Chrome、Opera3. css代碼優(yōu)化的12個技巧4. 低版本IE正常運行HTML5+CSS3網(wǎng)站的3種解決方案5. HTML DOM setInterval和clearInterval方法案例詳解6. 詳解瀏覽器的緩存機制7. HTML <!DOCTYPE> 標簽8. CSS3實例分享之多重背景的實現(xiàn)(Multiple backgrounds)9. 使用css實現(xiàn)全兼容tooltip提示框10. 告別AJAX實現(xiàn)無刷新提交表單
