GPS 與網路定位

      在〈GPS 與網路定位〉中尚無留言

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);
            }
        }
    };
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *