Androidで位置情報を取得する

Androidで位置情報を取得する方法の勉強を兼ねてKotlinでアプリを作成しました。

GooglePlayServices API群のFusedLocationProviderClientを使い位置情報を取得します。
遅延ループで5秒ごとに画面に現在の時刻と緯度経度を表示しています。

class MainActivity : AppCompatActivity() {

    private val UPDATE_GPS_FREQ_SEC = Util.secToMilli(1) //1秒ごとに位置情報を取得
    private val UPDATE_GPS_FREQ_DIS: Long = 0 //動かなくても位置情報を取得

    // 要求パーミッション
    private val LOCATION_PERMISSIONS =
        arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)

    // パーミッション要求識別コード
    private val REQUEST_CODE_LOCATION_PERMISSION = 1

    // 設定情報の確認ダイアログの識別コード
    private val REQUEST_CODE_CHECK_SETTINGS = 2


    // 設定用クラアント
    private lateinit var mSettingsClient:SettingsClient

    // 位置情報取得
    private lateinit var mFusedLocationProviderClient:FusedLocationProviderClient

    // 位置情報取得リクエスト用
    private lateinit var mLocationRequest:LocationRequest

    // 遅延ループ用
    private lateinit var mHandler: Handler
    private lateinit var mUpdateLocation:Runnable

    // 画面表示用
    private lateinit var mLogger:Logger

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mSettingsClient = SettingsClient(this)

        mLogger = Logger(this)

        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

        mLocationRequest = LocationRequest()

        setContentView(mLogger.getOutPutView())

        mHandler = Handler()

        // 5秒ごとに最後に取得した位置情報を表示するループ
        mUpdateLocation = Runnable {
            saveLastKnownLocation()
            mHandler.postDelayed(mUpdateLocation, Util.secToMilli(5))
        }

        // 権限を確認、必要なら取得し、位置情報を取得開始
        permissionCheckStart()
    }

    override fun onPause() {
        super.onPause()

        // 遅延ループ停止
        mHandler.removeCallbacks(mUpdateLocation)
    }

位置情報を取得するために必要なパーミッションの確認を行い、
未許可ならダイアログを表示し許可してもらう。
その後、リクエストに対応する設定を確認し必要な設定がなされていなければ、
ダイアログを表示し設定変更を許可してもらいます。

    /**
     * 現在位置取得の権限のチェックを行い既に許可されている場合は位置情報取得開始。許可されていない場合はユーザーに許可を求めるダイアログを表示。
     */
    private fun permissionCheckStart() {
        val permissionRequests = LOCATION_PERMISSIONS
        val permissionCheck =
            ContextCompat.checkSelfPermission(
            this,
            LOCATION_PERMISSIONS[0]
        ) != PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(
            this,
            LOCATION_PERMISSIONS[1]
        ) != PackageManager.PERMISSION_GRANTED
        if (permissionCheck) {
            // 権限要求ダイアログを表示
            ActivityCompat.requestPermissions(this, permissionRequests, REQUEST_CODE_LOCATION_PERMISSION)

        } else {
            // すでに許可済み
            checkLocationSettings()
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        if (requestCode == REQUEST_CODE_LOCATION_PERMISSION) {
            if (
                grantResults.size == 2 &&
                grantResults[0] == PackageManager.PERMISSION_GRANTED ||
                grantResults[1] == PackageManager.PERMISSION_GRANTED
            ) {
                // リクエストの結果:成功
                // 必要な設定の確認を行う
                checkLocationSettings()
            } else {
                // リクエストの結果:失敗
                if (ActivityCompat.shouldShowRequestPermissionRationale(
                        this,
                        LOCATION_PERMISSIONS[0]
                    ) || ActivityCompat.shouldShowRequestPermissionRationale(this, LOCATION_PERMISSIONS[1])
                ) {
                    // 再度権限要求
                    permissionCheckStart()
                } else {
                    // 利用者が再表示しないを選んだ場合
                    finish()
                }
            }
        }
    }

    /**
     * 必要な設定の確認を行う
     */
    private fun checkLocationSettings() {
        val request = LocationSettingsRequest.Builder()
            .addLocationRequest(buildLocationRequest())
            .setAlwaysShow(true)
            .build()
        mSettingsClient.checkLocationSettings(request)
            .addOnCompleteListener(OnCompleteListener<LocationSettingsResponse> { task ->
                try {
                    // ここで例外が発生しなければ成功
                    val response = task.getResult(ApiException::class.java)

                    // 位置情報取得の開始
                    startUpdatingLocation()

                } catch (apiException: ApiException) {
                    // 例外が発生したので失敗
                    when (apiException.statusCode) {
                        LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
                            // 要求する設定が足りていない
                            try {
                                // 設定要求ダイアログを表示
                                val resolvable = apiException as ResolvableApiException
                                resolvable.startResolutionForResult(
                                    this@MainActivity,
                                    REQUEST_CODE_CHECK_SETTINGS
                                )
                            } catch (e: Exception) {
                                // ダイアログが表示できなかったエラー
                                finish()
                            }
                        }

                        LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
                            // 要求する設定が足りていないが、設定の変更を行わない
                            finish()
                        }
                    }
                }
            })
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(requestCode == REQUEST_CODE_CHECK_SETTINGS)
        {
            // checkLocationSettings()で設定変更を促すダイアログでOKを選んだ場合ここに来る
            // 位置情報取得の開始
            startUpdatingLocation()
        }
    }

    /**
     * 位置情報用パーミッションチェック関数
     * @param context
     * @return チェックOK:真 NG:偽
     */
    private fun checkPermission(context: Context): Boolean {
        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED ||
            ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.ACCESS_COARSE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            return true
        }
        return false
    }

パーミッションの確認と、リクエストに対応する設定の確認が終わったら
5秒ごとに最後に取得した位置情報を画面に表示するループを開始し、
FusedLocationProviderClientのrequestLocationUpdatesメソッドで位置情報の取得を開始します。
位置情報の更新時のコールバックでは、文字列を画面に表示させています。

    /**
     * 位置情報取得の開始
     */
    private fun startUpdatingLocation() {

        // リクエスト組み立て
        mLocationRequest = buildLocationRequest()

        // 位置情報用パーミッションチェック
        if (checkPermission(this)) {
            // 位置情報の取得リクエストを送る
            mFusedLocationProviderClient.requestLocationUpdates(mLocationRequest, mLocationCallback, null)
            // 遅延ループ開始
            mHandler.post(mUpdateLocation)
        }
    }

    // 位置情報更新コールバック
    private val mLocationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult?) {
            super.onLocationResult(locationResult)
            mLogger.d("Change_Location")
        }
    }

    /**
     * リクエスト組み立て用関数
     * @return 組み立てたリクエスト
     */
    private fun buildLocationRequest(): LocationRequest {
        val req = LocationRequest();
        req.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        req.interval = UPDATE_GPS_FREQ_SEC * 2
        req.fastestInterval = UPDATE_GPS_FREQ_SEC
        req.smallestDisplacement = UPDATE_GPS_FREQ_DIS * 1.0f
        return req
    }

    /**
     * 最後に取得した位置情報を表示
     */
    private fun saveLastKnownLocation() {
        if (checkPermission(this)) {
            mFusedLocationProviderClient.lastLocation.addOnSuccessListener { location ->
                if (Util.notNull(location)) {
                    val timeStump = Calendar.getInstance().timeInMillis
                    val lat = location.latitude
                    val lon = location.longitude

                    output(timeStump, lat, lon)
                }
            }
        }
    }

outputは画面表示用のメソッドです。

    /**
     * ログを表示
     * @param timeStump
     * @param lat
     * @param lon
     */
    private fun output(timeStump: Long, lat: Double, lon: Double) {
        val formatter = SimpleDateFormat("HH:mm:ss")
        formatter.timeZone = Calendar.getInstance().timeZone
        val timeFormatted = formatter.format(timeStump)
        val address = Util.getAddress(this, lat, lon)
        val out = String.format("%s 緯度:%f 経度:%f", timeFormatted, lat, lon)
        if (Util.notNull(mLogger)) {
            mLogger.d(out)
            mLogger.d(address)
        }
    }
}

最新の情報に更新された位置情報の受け取りは
コールバックの非同期処理で行うので
一定時間間隔で表示するのには向いていませんでした。
なので、画面表示時の最新のデータではありませんが
最後に更新された位置情報を表示するようにしました。
位置情報は周辺環境にも左右されますがそれなりに誤差のあるデータなので
数秒程度前のデータであれば問題ないかと思います。

水曜担当:Tanaka



アプリ関連ニュース

お問い合わせはこちら

お問い合わせ・ご相談はお電話、またはお問い合わせフォームよりお受け付けいたしております。

tel. 06-6454-8833(平日 10:00~17:00)

お問い合わせフォーム