Android 系統(tǒng)提供了地理位置服務(wù)相關(guān)的API物蝙,方便了開發(fā)者去獲得當(dāng)前地理位置。用戶可以通過GPS接收器接受地理位置敢艰。也可以通過network(wifi或者基站)來獲得當(dāng)前的定位诬乞。
知識(shí)點(diǎn)
一、梳理
1钠导、誰提供的定位信息震嫉?
安卓的位置信息由“位置提供者”提供,在安卓中有三種位置提供者即:gps牡属、network票堵、passive。前兩者我們或許還可以理解但是這個(gè)passive是個(gè)啥東西逮栅?客官別急悴势,窗宇,,特纤,這就開始分析军俊!
1、gps:gps提供者就是使用手機(jī)內(nèi)置的gps硬件接收器接受精確的位置信息捧存。
2粪躬、network:就是使用網(wǎng)絡(luò)作為位置提供者,這里網(wǎng)絡(luò)可以為wifi昔穴、基站镰官。因?yàn)閣ifi、基站也是可以提供粗略的定位的吗货。
3朋魔、psssive:這個(gè)提供者有點(diǎn)特別。從這個(gè)單詞來看是“被動(dòng)的”意思卿操。其實(shí)這個(gè)就是個(gè)被動(dòng)接收者警检。怎么理解呢?你可以這樣理解:當(dāng)其他的app害淤、或者服務(wù)(service)請(qǐng)求到位置信息時(shí)扇雕,這個(gè)接收者可以接受到他們請(qǐng)求到的位置信息】悖看到這里我們就明白了镶奉。也可以知道使用這個(gè)提供者時(shí)獲取到的位置并不一定是正確的。
2崭放、安卓Location框架的架構(gòu)
如下圖(圖片來自網(wǎng)絡(luò)哨苛,如有侵權(quán)聯(lián)系作者刪除,,,,)開發(fā)者主要使用LocationManager來獲得位置信息。然而位置信息的獲得是個(gè)跨進(jìn)程通信的過程币砂。真正的位置信息的獲取建峭、位置提供者的查詢、位置的實(shí)時(shí)監(jiān)聽等都是由系統(tǒng)的LocationManagerService來負(fù)責(zé)接手的决摧。
1壹士、如果用戶是使用了gps定位方式太援,那么LocationManagerService就委托給GpsLocationProvider去獲得精確位置信息挠进。
2矾麻、如果用戶使用的network 定位方式,LocationManagerService就委托給第三方實(shí)現(xiàn)去獲得粗略的位置信息波岛。
ps:google的api中并未實(shí)現(xiàn)netWorkProvider的具體邏輯茅坛,這個(gè)功能交給了第三方服務(wù)去完成。國內(nèi)手機(jī)一般都集成了百度地圖则拷、或者高德地圖贡蓖。這兩個(gè)第三方的app中就有一個(gè)service祟剔,就是netWorkProvider的具體實(shí)現(xiàn)。這里又涉及到了 安卓系統(tǒng)位置服務(wù)于第三方app的位置服務(wù)跨進(jìn)程通信摩梧。
二物延、常用類
1、LocationManager
位置管理者仅父,應(yīng)用層(開發(fā)者)主要使用這個(gè)類和系統(tǒng)的位置服務(wù)進(jìn)行通信叛薯。
(1)獲取方式
LocationManager locationManager =
(LocationManager) getSystemService(LOCATION_SERVICE);
(2)常用方法或字段
// 常用的靜態(tài)常量
GPS_PROVIDER // gps 提供者
NETWORK_PROVIDER // network提供者
PASSIVE_PROVIDER // passive提供者(需要)
// 返回手機(jī)支持的所有提供者(上述三種:gps、network笙纤、passive)
public List<String> getAllProviders()
//根據(jù)篩選條件返回最符合條件的位置提供者耗溜,一般篩選最符合條件能用的提供者。
public String getBestProvider(Criteria criteria, boolean enabledOnly)
// 一般參數(shù)為true省容,返回能用的提供者集合
List<String> getProviders(boolean enabledOnly)
// 獲得最近一次使用過的Location信息對(duì)象抖拴,最近沒有使用過時(shí)返回null。一般此方法不用腥椒。
public Location getLastKnownLocation(String provider);
// 注冊(cè)阿宅,需要位置的實(shí)時(shí)更新
//provider:位置提供者,表名位置數(shù)據(jù)由那種提供者提供笼蛛。
//minTime:最小時(shí)間間隔,位置刷新期間的洒放。多久刷新一次。
//minDistance:最小的位置滨砍,位置刷新期間往湿。超過這個(gè)位置也觸發(fā)刷新。
// LocationListener :位置監(jiān)聽
void requestLocationUpdates(String provider, long minTime, float minDistance,
LocationListener listener)
// 解注冊(cè)
public void removeUpdates(LocationListener listener)
2惋戏、Location:
位置類领追,內(nèi)部封裝了經(jīng)緯度、海拔之類的位置信息响逢。
location.getLongitude();//經(jīng)度
location.getLatitude();// 緯度
location.getProvider();//位置提供者
3绒窑、Geocoder:將地址轉(zhuǎn)換為相應(yīng)的地理坐標(biāo)。這一過程稱為地理編碼龄句』芈郏或者散罕,您可以將地理位置轉(zhuǎn)換為地址分歇。地址查詢功能也稱為逆向地理編碼。
maxResults:返回地址的數(shù)目取1~5之間
因?yàn)橥唤?jīng)緯度可能對(duì)應(yīng)多個(gè)地址
public List<Address> getFromLocation(double latitude, double longitude, int maxResults)
1欧漱、可見提供了三個(gè)方法职抡,常用的為第一個(gè)。根據(jù)經(jīng)緯度獲取Address集合误甚。
2缚甩、Geocoder 請(qǐng)求的是一個(gè)后臺(tái)服務(wù)谱净,但是該服務(wù)不包括在標(biāo)準(zhǔn)android framework中。因此如果當(dāng)前設(shè)備不包含location services擅威,則Geocoder返回的地址或者經(jīng)緯度為空壕探。當(dāng)然你可以使用 Geocoder#isPresent()方法來判斷當(dāng)前設(shè)備是否包含地理位置服務(wù)。由于國內(nèi)使用不了Google Services服務(wù)郊丛,因此一般的手機(jī)廠商都會(huì)在自己的手機(jī)內(nèi)內(nèi)置百度地圖服務(wù)李请,或者高德地圖服務(wù)來替代Google Services服務(wù)。
4厉熟、Address:詳細(xì)的地理位置
一般通過如下api我們即可獲得 XXX市XXX區(qū)XXX街區(qū)XXX小區(qū)信息导盅。
三、權(quán)限&api請(qǐng)求流程
1揍瑟、動(dòng)態(tài)權(quán)限
<!-- GPS定位權(quán)限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- wifi/基站定位權(quán)限-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
安卓6.0開始白翻,系統(tǒng)對(duì)危險(xiǎn)權(quán)限添加了動(dòng)態(tài)權(quán)限申請(qǐng)的功能。上述的位置權(quán)限在6.0或者以上的手機(jī)中需要?jiǎng)討B(tài)申請(qǐng)下绢片。
參考文章:安卓動(dòng)態(tài)權(quán)限
2滤馍、大致使用流程圖解
四 、栗子及其Criteria類的補(bǔ)充
1底循、Criteria
String bestProvider = locationManager.getBestProvider(new Criteria(), true);
細(xì)心的你可能會(huì)留意到纪蜒,上述api簡介中,獲得provider時(shí)我們還會(huì)用到Criteria類此叠。其實(shí)這個(gè)類就是篩選合適的provider的纯续。常見的篩選條件如下:
public void setAccuracy(int accuracy):位置解析精確度,參數(shù)Criteria.ACCURACY_FINE:表示高精確度灭袁。Criteria.ACCURACY_COARSE:表示模糊精確度猬错。
public void setAltitudeRequired(boolean altitudeRequired ):是否要求海拔信息。
public void setBearingRequired(boolean bearingRequired):是否要求方向信息茸歧。
public void setCostAllowed(boolean costAllowed):是否允許收費(fèi)倦炒。
public void setSpeedRequired(boolean speedRequired):是否要求速度信息。
public void setBearingRequired(boolean bearingRequired):是否要求方向信息软瞎。
public void setPowerRequirement(int level):設(shè)置電池消耗要求逢唤,參數(shù) Criteria. NO_REQUIREMENT, Criteria. POWER_LOW, Criteria. POWER_MEDIUM, Criteria. POWER_HIGH。分別表示 :無涤浇、低鳖藕、中、高只锭,著恩。
public void setBearingAccuracy(int accuracy):設(shè)置方向的精準(zhǔn)度。參數(shù) Criteria.NO_REQUIREMENT, Criteria.ACCURACY_LOW, Criteria.ACCURACY_HIGH。分別表示:無喉誊,低邀摆,高。
public void setSpeedAccuracy(int accuracy):設(shè)置速度的精準(zhǔn)度伍茄。參數(shù)同上栋盹。
public void setHorizontalAccuracy(int accuracy):設(shè)置水平方向的精準(zhǔn)度。參數(shù)同上敷矫。
public void setVerticalAccuracy(int accuracy):設(shè)置垂直方向的精準(zhǔn)度贞盯。參數(shù)同上。
Criteria類該怎么使用沪饺?代碼示例如下:
criteria.setAccuracy(Criteria.ACCURACY_FINE);//設(shè)置定位精準(zhǔn)度
criteria.setAltitudeRequired(false);//是否要求海拔
criteria.setBearingRequired(true);//是否要求方向
criteria.setCostAllowed(true);//是否要求收費(fèi)
criteria.setSpeedRequired(true);//是否要求速度
criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);//設(shè)置電池耗電要求
criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);//設(shè)置方向精確度
criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);//設(shè)置速度精確度
criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);//設(shè)置水平方向精確度
criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);//設(shè)置垂直方向精確度
//返回滿足條件的躏敢,當(dāng)前設(shè)備可用的location provider,當(dāng)?shù)诙€(gè)參數(shù)為false時(shí)整葡,返回當(dāng)前設(shè)備所有provider中最符合條件的那個(gè)provider(但是不一定可用)件余。
String mProvider = mLocationManager.getBestProvider(criteria,true);
2、栗子
public class MainActivity extends AppCompatActivity {
private TextView tvLocation;
private TextView tvAddress;
private Geocoder geocoder;
private List<Address> addressList;
private StringBuilder sb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUi();
initData();
}
private void initData() {
// 獲取經(jīng)緯度坐標(biāo)
// 1 獲取位置管理者對(duì)象
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// 2 通過lm獲得經(jīng)緯調(diào)度坐標(biāo)
// (參數(shù): provider(定位方式 提供者 通過 LocationManager靜態(tài)調(diào)用)遭居,
// minTime(獲取經(jīng)緯度間隔的最小時(shí)間 時(shí)時(shí)刻刻獲得傳參數(shù)0)啼器,
// minDistance(移動(dòng)的最小間距 時(shí)時(shí)刻刻傳0),LocationListener(監(jiān)聽))
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
// 獲取經(jīng)緯度主要方法
double latitude = location.getLatitude();
double longitude = location.getLongitude();
tvLocation.setText("latitude"+latitude+" "+"longitude"+longitude);
sb = new StringBuilder();
geocoder = new Geocoder(MainActivity.this);
addressList = new ArrayList<Address>();
try {
// 返回集合對(duì)象泛型address
addressList= geocoder.getFromLocation(latitude,longitude,1);
if (addressList.size() > 0) {
Address address = addressList.get(0);
for (int i = 0; i < address.getMaxAddressLineIndex(); i++) {
sb.append(address.getAddressLine(i)).append("\n");
}
sb.append(address.getFeatureName());//周邊地址
}
tvAddress.setText("當(dāng)前位置"+sb.toString());
;
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
//狀態(tài)發(fā)生改變監(jiān)聽
}
@Override
public void onProviderEnabled(String s) {
// ProviderEnabled
}
@Override
public void onProviderDisabled(String s) {
// ProviderDisabled
}
});
}
private void initUi() {
tvLocation = (TextView) findViewById(R.id.tv_location);
tvAddress = (TextView) findViewById(R.id.tv_address);
}
}
貼圖如下
五俱萍、擴(kuò)展及其采坑
1端壳、擴(kuò)展
安卓手機(jī)從屏幕上端下滑,拉出功能欄枪蘑,點(diǎn)擊GPS圖標(biāo)開啟GPS损谦,這時(shí)你的手機(jī)設(shè)置->位置信息模式 會(huì)選中“高精確度”定位方式,當(dāng)你點(diǎn)擊GPS圖標(biāo)關(guān)閉時(shí)岳颇,會(huì)使用默認(rèn)的“低耗電量”(如下圖)
2照捡、采坑network 提供者下onLocationChange不回調(diào),拿不到Location對(duì)象话侧。
1栗精、場景:
安卓TV開發(fā)的同學(xué)或許會(huì)碰到這種情況,TV真機(jī)上onLocationChange沒有回調(diào)瞻鹏。拿不到Location對(duì)象悲立。
2、解釋:
其實(shí)TV不支持gps硬件功能新博,如果想采用定位那只有使用network提供者了薪夕,但是通過上述的架構(gòu)圖我們也可以知道。提供者為network時(shí)叭披,位置的獲取需要第三方服務(wù)寥殖。由于TV上一般都沒有地圖類app所以第三方就是空實(shí)現(xiàn)了玩讳。location對(duì)象就拿不到了涩蜘。
3嚼贡、驗(yàn)證依據(jù):
- 安卓系統(tǒng)開機(jī)時(shí)可以打印系統(tǒng)log(使用adb logcat 命令)
- LocationManagerService的初始化在安卓系統(tǒng)類的SystemServer的startOtherService方法中完成。這個(gè)操作在安卓系統(tǒng)啟動(dòng)時(shí)就進(jìn)行同诫。
4粤策、進(jìn)行驗(yàn)證:重啟TV打印系統(tǒng)log(如下圖找到你指定輸出的log.txt文件),結(jié)果搜索LocationManagerService關(guān)鍵字發(fā)現(xiàn)“no network location provider found”误窖,默認(rèn)使用的gms(google map service)也沒找到叮盘。
5、一種解決方案:在tv上集成三方sdk比如高德地圖霹俺。
參考:
1柔吼、Android網(wǎng)絡(luò)定位源碼分析
2、https://developer.android.google.cn/reference/android/location/LocationManager?hl=en