Android 可以使用二種方式進行定位, 一為GPS, 另一個是網路定位. GPS定位較為精準, 但若在室內無GPS訊號時, 可使用網路定位, 較不準確.
權限
使用GPS定位需於AndroidManifest.xml開啟如下權限
請注意, 若是Adroid 6.0以上,還需使用 ActivityCompat.requestPermissions() 像使用者要求權限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
開啟定位裝置
當Android系統的定位沒有開啟時, 執行定位的App並不會出錯, 只是抓不到訊號而以. 所以在執行App時就需先檢查系統的定位功能是否有打開.
檢查的功能安插在onResume()之中. onResume有一個特性, 就是當暫時離開本App時, 比如彈出對話視窗, 或是轉換到系統設定視窗時, 會先執行onPause(), 待回到本App時, onResume()會再被執行一次. 因此可以利用此特性, 當回到本App時,再度檢查一次定位功能是否有打開. 相關程式碼如下
protected void onResume() { LocationManager lm=(LocationManager)getSystemService(Context.LOCATION_SERVICE); if(!(lm.isProviderEnabled(LocationManager.GPS_PROVIDER) || lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER))){ AlertDialog.Builder dialog=new AlertDialog.Builder(this); dialog.setTitle("開啟GPS裝置") .setMessage("GPS裝置未開啟, 是否進行設定") .setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }).show(); } super.onResume(); }
LocationManager
啟動定位功能相當的簡單, 只要透過Appactivity的getSystemService(Context.LOCATiON_SERVICE)就可取得LocationManager物件, 然後設定回調方法即可. 如下片段程式碼, 另使用了Criteria設定精準度, Geocoder 則是將經緯度轉換成Address.
requestLocationUpdates是設定LocationListener的回調物件, 第一參數設定精準度, 第二參數是設定多久執行一次, 單位為毫秒, 第三參數是設定地點變更的最小距離,第四參數為LocationListener物件
registerGnssStatusCallback則是設定GPS產生變化時要回調的物件
上述二個回調, 都是以UI主執行緒執行, 所以可以在這二個裏面更改UI的元件內容
LocationManager lm=(LocationManager)context.getSystemService(Context.LOCATION_SERVICE); Criteria criteria=new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); String bestProvider = lm.getBestProvider(criteria, true);//選擇精準度最高的提供者 Geocoder geocoder = new Geocoder(context, Locale.getDefault()); lm.requestLocationUpdates(bestProvider, 1000, 1, locationListener); lm.registerGnssStatusCallback(gpsCallback);
LocationListener
LocationListener為一介面, 實作此物件需實作如下四個方法. 當經緯度產生變化後, 會執行onLocationChanged方法, 借由Location物件傳出經緯值
@Override public void onLocationChanged(Location location) { try { double lat=location.getLatitude(); double lng=location.getLongitude(); List<Address> listAddr = geocoder.getFromLocation(lat, lng, 1); Log.d("Thomas", lat+":"+lng+":"+listAddr.get(0).getAddressLine(0)); } catch (IOException e) { e.printStackTrace(); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { switch(status){ case LocationProvider.AVAILABLE: Log.d("Thomas", "GPS裝置可用"); break; case LocationProvider.OUT_OF_SERVICE: Log.d("Thomas", "無GPS裝置"); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: Log.d("Thomas", "GPS裝置暫時使用"); break; } } @Override public void onProviderEnabled(String provider) { Log.d("Thomas","GPS裝置開啟"); } @Override public void onProviderDisabled(String provider) { Log.d("Thomas","GPS裝置關閉"); }
GPS狀態
GPS抓到的衛星數量及每個衛星的訊號強度, 早期可以使用GpsStatus.Listener取得, 但現在這個介面已被遺棄了, 請改換 GnssStatus.Callback
GnssStatus.Callback也是一個介面, 需實作如下的方法
GnssStatus.Callback gpsCallback=new GnssStatus.Callback() { @Override public void onStarted() { super.onStarted(); } @Override public void onStopped() { super.onStopped(); } @Override public void onFirstFix(int ttffMillis) { super.onFirstFix(ttffMillis); } @Override public void onSatelliteStatusChanged(GnssStatus status) { super.onSatelliteStatusChanged(status); int count = status.getSatelliteCount(); for (int i=0;i<count;i++){ float zaimuth = status.getAzimuthDegrees(i);//方位角 float elevation = status.getElevationDegrees(i); float snr = status.getCn0DbHz(i);//信噪比 boolean almanac = status.hasAlmanacData(i);//年曆 boolean ephemeris = status.hasEphemerisData(i);//星曆 String f = String.format("%f:%f:%f:%s:%s", zaimuth, elevation, snr, almanac, ephemeris); Log.d("Thomas", f); } } };
Address
將經緯度傳入Geocoder.getFormLocation方法即可取得Address物件. Address的相關方法如下
listAddress.get(0).getAddressLine(0);//取得完整住址 lstAddress.get(0).getCountryName();//台灣省 lstAddress.get(0).getAdminArea();//台北市 lstAddress.get(0).getLocality();//中正區 lstAddress.get(0).getThoroughfare();//中正路(包含路巷弄) lstAddress.get(0).getFeatureName();//50 lstAddress.get(0).getPostalCode();//100(郵遞區號)
完整代碼
以下代碼, 可在MainActivity中顯示經緯度及地址
package com.asuscomm.mahaljsp.gpstest; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.Manifest; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.location.Address; import android.location.Criteria; import android.location.Geocoder; import android.location.GnssStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.util.Log; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.util.List; import java.util.Locale; public class MainActivity extends AppCompatActivity { private static final int REQUEST_LOCATION_PERMISSION = 100; TextView txt; LocationManager lm; String bestProvider; Geocoder geocoder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); txt = (TextView) findViewById(R.id.txt); processPermission(); } private void processPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { int hasPermission = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION); if (hasPermission != PackageManager.PERMISSION_GRANTED) { requestPermissions( new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_LOCATION_PERMISSION); } else { init(); } } else { init(); } } public void onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_LOCATION_PERMISSION) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { init(); } else { Toast.makeText(this, "無法取得GPS權限, 無法執行本程式", Toast.LENGTH_LONG).show(); finish(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } @SuppressLint("MissingPermission") private void init(){ lm=(LocationManager)getSystemService(Context.LOCATION_SERVICE); requestGps(); Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); bestProvider = lm.getBestProvider(criteria, true);//選擇精準度最高的提供者 geocoder = new Geocoder(this, Locale.getDefault()); lm.requestLocationUpdates(bestProvider, 1000, 1, locationListener); lm.registerGnssStatusCallback(gpsCallback); } @Override protected void onResume() { super.onResume(); if(lm!=null)requestGps(); } @Override protected void onDestroy() { lm.unregisterGnssStatusCallback(gpsCallback); lm.removeUpdates(locationListener); super.onDestroy(); } private void requestGps(){ if(!(lm.isProviderEnabled(LocationManager.GPS_PROVIDER) || lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER))){ AlertDialog.Builder dialog=new AlertDialog.Builder(this); dialog.setTitle("開啟GPS裝置") .setMessage("GPS裝置未開啟, 是否進行設定") .setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }) .show(); } } LocationListener locationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { try { double lat = location.getLatitude(); double lng = location.getLongitude(); List<Address> listAddr = geocoder.getFromLocation(lat, lng, 1); txt.setText(String.format("緯度%f: 經度%f\n地址 : %s", lat, lng, listAddr.get(0).getAddressLine(0))); } catch (IOException e) { e.printStackTrace(); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } }; GnssStatus.Callback gpsCallback = new GnssStatus.Callback() { @Override public void onStarted() { super.onStarted(); } @Override public void onStopped() { super.onStopped(); } @Override public void onFirstFix(int ttffMillis) { super.onFirstFix(ttffMillis); } @Override public void onSatelliteStatusChanged(GnssStatus status) { super.onSatelliteStatusChanged(status); int count = status.getSatelliteCount(); for (int i = 0; i < count; i++) { float zaimuth = status.getAzimuthDegrees(i);//方位角 float elevation = status.getElevationDegrees(i);//海拔 float snr = status.getCn0DbHz(i);//信噪比 boolean almanac = status.hasAlmanacData(i);//年曆 boolean ephemeris = status.hasEphemerisData(i);//星曆 String f = String.format("方位角:%f, 海拔:%f, 信噪比:%f, 年曆:%s, 星曆:%s", zaimuth, elevation, snr, almanac, ephemeris); Log.d("Thomas", f); } } }; }