Android如何實(shí)現(xiàn)模擬定位
在導(dǎo)航測(cè)試場(chǎng)景中經(jīng)常需要定位模擬和路線回放,記錄下通過(guò)LocationManager.setTestProviderLocation()方法實(shí)現(xiàn)模擬地位,如果要測(cè)試的應(yīng)用不支持TestProviderLocation模擬位置輸入,可以考慮從HAL層入手,hook系統(tǒng)默認(rèn)的GPS實(shí)現(xiàn)。
一、Android模擬權(quán)限開(kāi)啟配置
在Android6.0以下的版本中,需要在設(shè)置中勾選模擬定位的開(kāi)關(guān),在6.0以上就改成了選擇模擬定位的應(yīng)用,對(duì)應(yīng)的開(kāi)啟配置方式也不一樣,相同的是在AndroidManifest.xml都需要配置以下兩個(gè)權(quán)限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
1)Android 6.0以下開(kāi)啟模擬定位開(kāi)關(guān)
Settings.Secure.putInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 1);
通過(guò)這種方式去開(kāi)啟模擬定位需要在AndroidManifest.xml中配置以下系統(tǒng)權(quán)限,應(yīng)用還需要經(jīng)過(guò)系統(tǒng)簽名,對(duì)于非系統(tǒng)應(yīng)用不能通過(guò)這種實(shí)現(xiàn)方式。
android:sharedUserId="android.uid.system" <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
涉及到這種操作可以通過(guò)adb shell命令的方式配置來(lái)繞過(guò)系統(tǒng)權(quán)限配置:
adb shell settings put secure mock_location 1
2)Android 6.0以上代碼配置選擇模擬定位的應(yīng)用
在6.0以上的Android版本就需要設(shè)置指定包名的mock_location權(quán)限為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 */ }
同時(shí)在AndroidManifest.xml中還需要配置如下權(quán)限,可惜這種方式也是需要經(jīng)過(guò)系統(tǒng)簽名和源碼一起編譯,只有系統(tǒng)層級(jí)的應(yīng)用才可以使用。
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
為了在設(shè)置界面模擬定位選項(xiàng)中顯示模擬定位的應(yīng)用,需要配置ACCESS_MOCK_LOCATION權(quán)限。
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
以上配置也可以通過(guò)adb shell命令去實(shí)現(xiàn),<package> 參數(shù)用自己應(yīng)用的包名替換。
adb shell appops set <package> android:mock_location allow
關(guān)閉模擬定位權(quán)限,用如下命令:
adb shell appops set <package> android:mock_location deny
要查詢有哪些應(yīng)用開(kāi)啟了模擬定位權(quán)限,用如下命令:
adb shell appops query-op android:mock_location allow
執(zhí)行后會(huì)輸出應(yīng)用的包名列表參數(shù)。
二、Android模擬定位實(shí)現(xiàn)
1)模擬定位開(kāi)關(guān)檢查
首先是代碼中先判斷模擬定位權(quán)限是否開(kāi)啟,6.0以上的只能通過(guò)添加定位監(jiān)聽(tīng)是否有異常來(lái)判斷。
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)聽(tīng)的源碼實(shí)現(xiàn),以Android 7.0的源碼實(shí)現(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方法的應(yīng)用沒(méi)有模擬定位的權(quán)限,就會(huì)報(bào)SecurityException異常,另外requestLocationUpdates需要在主線程中調(diào)用,如果在子線程中調(diào)用,還需要傳一個(gè)looper參數(shù),不然在實(shí)例化ListenerTransport的時(shí)候會(huì)報(bào)錯(cuò)。
看下ListenerTransport的構(gòu)造函數(shù),如果在子線程中添加監(jiān)聽(tīng),又沒(méi)有傳Loop,初始化mListenerHandler的時(shí)候就會(huì)報(bào)異常。
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); } }; } }
接著添加對(duì)應(yīng)的Provider,設(shè)置開(kāi)關(guān)狀態(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)緯度、車速、定位準(zhǔn)確性、方位、海拔參數(shù)根據(jù)實(shí)際需求設(shè)置。
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);
來(lái)看下為什么需要設(shè)置elapsedRealtimeNanos和time參數(shù)。在SDK版本17以下,Location(Android 4.1.1)是沒(méi)有setElapsedRealtimeNanos這個(gè)方法的,在SDK版本17開(kāi)始,Location(Android 4.2)加了這個(gè)方法,在調(diào)用setTestProviderLocation設(shè)置定位信息的時(shí)候,Android SDK版本17以上會(huì)做定位參數(shù)是否完整的校驗(yàn),17以下的版本自動(dòng)做補(bǔ)足,17開(kāi)始的版本直接拋異常。
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的判斷。
@SystemApi public 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(); } @SystemApi public boolean isComplete() { if (mProvider == null) return false; if (!hasAccuracy()) return false; if (mTime == 0) return false; if (mElapsedRealtimeNanos == 0) return false; return true; }
以上是就是單個(gè)點(diǎn)的模擬定位實(shí)現(xiàn),如果要實(shí)現(xiàn)路線回放模擬,只要在后臺(tái)請(qǐng)求到路線定位點(diǎn)數(shù)組數(shù)據(jù)之后,每隔1秒刷新調(diào)用setTestProviderLocation接口就可以了。
在模擬定位操作完畢之后,需要移除模擬定位對(duì)象,避免定位信息還是使用模擬定位接口的參數(shù),如果沒(méi)有移除下一次使用的時(shí)候又調(diào)用添加同名的Provider也會(huì)拋異常。
locationManager.removeTestProvider(LocationManager.GPS_PROVIDER);
以上就是Android如何實(shí)現(xiàn)模擬定位的詳細(xì)內(nèi)容,更多關(guān)于Android 模擬定位的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開(kāi)發(fā)之ClipboardManager剪貼板功能示例
這篇文章主要介紹了Android開(kāi)發(fā)之ClipboardManager剪貼板功能,結(jié)合簡(jiǎn)單實(shí)例形式分析了Android使用ClipboardManager實(shí)現(xiàn)剪貼板功能的相關(guān)操作技巧,需要的朋友可以參考下2017-03-03Android自定義TitleView標(biāo)題開(kāi)發(fā)實(shí)例
這篇文章主要介紹了Android自定義TitleView標(biāo)題開(kāi)發(fā)實(shí)例的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Android Handler的postDelayed()關(guān)閉的方法及遇到問(wèn)題
這篇文章主要介紹了Android Handler的postDelayed()關(guān)閉的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04Android Handler主線程和一般線程通信的應(yīng)用分析
本篇文章小編為大家介紹,Android Handler主線程和一般線程通信的應(yīng)用分析。需要的朋友參考下2013-04-04Android中activity跳轉(zhuǎn)按鈕事件的四種寫(xiě)法
這篇文章主要介紹了Android中activity跳轉(zhuǎn)按鈕事件的四種寫(xiě)法,下文中包括四個(gè)activity的內(nèi)容詳解,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10分析CmProcess跨進(jìn)程通信的實(shí)現(xiàn)
CmProcess是Android一個(gè)跨進(jìn)程通信框架,無(wú)需進(jìn)行bindService()操作,不用定義Service,也不需要定義aidl。 支持IPC級(jí)的 Callback,并且支持跨進(jìn)程的事件總線,可同步獲取服務(wù),采用面向接口方式進(jìn)行服務(wù)注冊(cè)與調(diào)用,服務(wù)調(diào)用方和使用者完全解耦2021-06-06