Androidアプリ開発 Google Play services Location Serviceで位置情報を取得する

2016年07月20日(編集2016年07月26日)
このエントリーをはてなブックマークに追加

Androidアプリでも、IngressやPokémon GOのような位置情報を使うアプリが充実してきました。
この記事は、Google Play servicesを使って位置情報を取得する方法を記載した記事です。

環境はAndroid 6.0 (API level 23) です。

環境

  • OS X Yosemite
  • Oracle jdk version 1.8.0_72
  • Android Studio 2.1.2
  • android sdk 24

難易度

中級者向け

サンプルコード

Android-Google-Maps-Demo

Google Play services

Andoridで位置情報を扱う場合、Google Play servicesを利用するのが一般的です。
Google Play servicesには、多くのgoogle APIが用意されています。位置情報の取得は、

com.google.android.gms:play-services:9.2.0

を利用します。

ライブラリインストール

ライブラリをインストールします。

{project_folder}/build.gradle
     
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.0.0'
    compile 'com.android.support:design:24.0.0'
    +compile 'com.google.android.gms:play-services-location:9.2.0'
}
        

プロジェクトをRebuildして、エラーがなければ、OKです。

基本知識

位置情報の取得方法には、以下の2通りがあります。

  • ACCESS_COARSE_LOCATION
  • ACCESS_FINE_LOCATION

実装の前に、上記についての違いを説明します。

ACCESS_COARSE_LOCATION

電波塔WIFIのようなネットワークの場所の供給源から、適切な位置にアクセスすることを許可します。
ネットワークを利用するので、インターネットにアクセスする必要があります。なので、android.permission.INTERNETのパーミッションを設定しないといけません。

{project_folder}/app/AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
        

ACCESS_FINE_LOCATION

GPS, 電波塔, WIFIのようなットワークの場所の供給源から、正確な位置にアクセスすることを許可します。
android.permission.ACCESS_COARSE_LOCATIONで利用できる電波塔, WIFIの他に、GPSを使って正確な位置情報を取得できます。

{project_folder}/main/AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        

実装手順

位置情報の取得に最低限必要な実装は、以下のようになります。

  • GPSの設定を取得
  • GoogleApiに接続
  • Permissions at Run Time
  • 位置情報取得

実装

上記の実装をします。

{project_folder}/ui/LocationFragment.java
import android.Manifest;
import android.content.Context;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResult;
import com.google.android.gms.location.LocationSettingsStatusCodes;
import com.java_lang_programming.android_pokemon_go_study.R;


public class LocationFragment extends Fragment implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener,
        ResultCallback<LocationSettingsResult> {

    /**
     * Constant used in the location settings dialog.
     */
    protected static final int REQUEST_CHECK_SETTINGS = 0x1;

    protected static final String TAG = "LocationFragment";

    private OnFragmentInteractionListener mListener;

    /**
     * Provides the entry point to Google Play services.
     */
    private GoogleApiClient mGoogleApiClient;

    /**
     * Stores parameters for requests to the FusedLocationProviderApi.
     */
    private LocationRequest mLocationRequest;

    private Location mCurrentLocation;

    /**
     * Stores the types of location services the client is interested in using. Used for checking
     * settings to determine if the device has optimal location settings.
     */
    protected LocationSettingsRequest mLocationSettingsRequest;

    private TextView mLatitudeValue;
    private TextView mLongitudeValue;

    public static LocationFragment newInstance() {
        LocationFragment fragment = new LocationFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        buildGoogleApiClient();
        createLocationRequest();
        buildLocationSettingsRequest();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_location, container, false);

        mLatitudeValue = (TextView) view.findViewById(R.id.latitude_value);
        mLongitudeValue = (TextView) view.findViewById(R.id.longitude_value);

        return view;
    }

    @Override
    public void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override
    public void onResume() {
        super.onResume();
        if (isLocationEnabled(getActivity().getApplicationContext())) {
            if (mGoogleApiClient.isConnected()) {
                startLocationUpdates();
            }
        }
    }

    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    private void setLocation() {
        if (mCurrentLocation != null) {
            mLatitudeValue.setText(String.valueOf(mCurrentLocation.getLatitude()));
            mLongitudeValue.setText(String.valueOf(mCurrentLocation.getLongitude()));
        }
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        // If the initial location was never previously requested, we use
        // FusedLocationApi.getLastLocation() to get it. If it was previously requested, we store
        // its value in the Bundle and check for it in onCreate(). We
        // do not request it again unless the user specifically requests location updates by pressing
        // the Start Updates button.
        //
        // Because we cache the value of the initial location in the Bundle, it means that if the
        // user launches the activity,
        // moves to a new location, and then changes the device orientation, the original location
        // is displayed as the activity is re-created.
        if (mCurrentLocation == null) {
            if (ActivityCompat.checkSelfPermission(
                    getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                updateLocationUI();
            }
        }
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.d(TAG, "onConnectionSuspended i" + i);
    }

    @Override
    public void onLocationChanged(Location location) {
        Log.d(TAG, "onLocationChanged ");

    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.d(TAG, "onConnectionFailed ");
    }

    @Override
    public void onResult(@NonNull LocationSettingsResult locationSettingsResult) {
        final Status status = locationSettingsResult.getStatus();
        switch (status.getStatusCode()) {
            case LocationSettingsStatusCodes.SUCCESS:
                Log.i(TAG, "All location settings are satisfied.");
                startLocationUpdates();
                break;
            case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                Log.i(TAG, "Location settings are not satisfied. Show the user a dialog to" +
                        "upgrade location settings ");

                try {
                    // Show the dialog by calling startResolutionForResult(), and check the result
                    // in onActivityResult().
                    status.startResolutionForResult(getActivity(), REQUEST_CHECK_SETTINGS);
                } catch (IntentSender.SendIntentException e) {
                    Log.i(TAG, "PendingIntent unable to execute request.");
                }
                break;
            case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                Log.i(TAG, "Location settings are inadequate, and cannot be fixed here. Dialog " +
                        "not created.");
                break;
        }
    }

    public interface OnFragmentInteractionListener {
        void onFragmentInteraction(Uri uri);
    }

    /**
     * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the
     * LocationServices API.
     */
    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    }

    /**
     * Sets up the location request. Android has two location request settings:
     * {@code ACCESS_COARSE_LOCATION} and {@code ACCESS_FINE_LOCATION}. These settings control
     * the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in
     * the AndroidManifest.xml.
     * <p/>
     * When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update
     * interval (5 seconds), the Fused Location Provider API returns location updates that are
     * accurate to within a few feet.
     * <p/>
     * These settings are appropriate for mapping applications that show real-time location
     * updates.
     */
    private void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(5000);
        mLocationRequest.setFastestInterval(5000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }

    /**
     * Uses a {@link com.google.android.gms.location.LocationSettingsRequest.Builder} to build
     * a {@link com.google.android.gms.location.LocationSettingsRequest} that is used for checking
     * if a device has the needed location settings.
     */
    protected void buildLocationSettingsRequest() {
        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
        builder.addLocationRequest(mLocationRequest);
        mLocationSettingsRequest = builder.build();
    }

    public static boolean isLocationEnabled(Context context) {
        int locationMode = 0;
        String locationProviders;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
            try {
                locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
            } catch (Settings.SettingNotFoundException e) {
                e.printStackTrace();
            }

            return locationMode != Settings.Secure.LOCATION_MODE_OFF;

        } else{
            locationProviders = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
            return !TextUtils.isEmpty(locationProviders);
        }
    }

    /**
     * Requests location updates from the FusedLocationApi.
     * http://qiita.com/daisy1754/items/aa9ad75d1a84b745469b
     */
    protected void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(
                getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) {
            } else {
            }
            return;
        } else {
        }

        LocationServices.FusedLocationApi.requestLocationUpdates(
                mGoogleApiClient,
                mLocationRequest,
                this
        ).setResultCallback(new ResultCallback<Status>() {
            @Override
            public void onResult(Status status) {
                if (status.isSuccess()) {
                    Log.d(TAG, "App Indexing API: Recorded recipe view end successfully."+ status.toString());
                } else {
                    Log.d(TAG, "App Indexing API: There was an error recording the recipe view."
                            + status.toString());
                }
            }
        });

    }

    /**
     * Sets the value of the UI fields for the location latitude, longitude and last update time.
     */
    private void updateLocationUI() {
        if (mCurrentLocation != null) {
            setLocation();
        } else {
            startLocationUpdates();
        }
    }
}
        

■ 実装の解説

1. interfaceの実装

位置情報の取得に必要なインターフェースをFragmentにimplementsします

public class LocationFragment extends Fragment implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener,
        ResultCallback<LocationSettingsResult> {
        

オーバーライドが必要なメソッドも追加します。

2. GPSの設定状態を取得

端末のGPSの設定を取得します。

public static boolean isLocationEnabled(Context context) {
    int locationMode = 0;
    String locationProviders;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
        try {
            locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
        } catch (Settings.SettingNotFoundException e) {
            e.printStackTrace();
        }

        return locationMode != Settings.Secure.LOCATION_MODE_OFF;

    } else{
        locationProviders = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
        return !TextUtils.isEmpty(locationProviders);
    }
}
        

位置情報設定がOFFの場合にfalseを返します。

位置情報OFF
isLocationEnabled : false
        

位置情報設定がONの場合にtrueを返します。

位置情報ON
isLocationEnabled : true
        

3. GoogleApiClient.onConnected

インターフェースConnectionCallbacksのonConnectedは、GoogleApiClientから位置情報を取得できます。

@Override
public void onConnected(@Nullable Bundle bundle) {
    // If the initial location was never previously requested, we use
    // FusedLocationApi.getLastLocation() to get it. If it was previously requested, we store
    // its value in the Bundle and check for it in onCreate(). We
    // do not request it again unless the user specifically requests location updates by pressing
    // the Start Updates button.
    //
    // Because we cache the value of the initial location in the Bundle, it means that if the
    // user launches the activity,
    // moves to a new location, and then changes the device orientation, the original location
    // is displayed as the activity is re-created.
    if (mCurrentLocation == null) {
        if (ActivityCompat.checkSelfPermission(
                getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            updateLocationUI();
        }
    }
}
        

onConnectedはGoogleApiClient.ConnectionCallbacksを利用することで呼び出せます。

/**
 * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the
 * LocationServices API.
 */
protected synchronized void buildGoogleApiClient() {
    mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(LocationServices.API)
            .build();
}
        

4. ActivityCompat.checkSelfPermission

Permissions at Run Timeの処理を実装します。

if (ActivityCompat.checkSelfPermission(
        getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
    mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
    updateLocationUI();
}
        

LocationServices.FusedLocationApiを利用する場合、ContextCompat.checkSelfPermission()メソッドを呼び出します。このメソッドは、サービスのPermissionの状態を取得できます。

5. 位置情報取得

LocationServices.FusedLocationApi.getLastLocationで位置情報を取得します。

mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        

getLastLocationメソッドは、最新の現在利用可能な位置情報を返します。
このメソッドは、位置情報を取得する最も単純な方法です。精緻な位置情報を取得する必要のないほとんどのアプリケーションに適しています。

ビルドして実行

実装を終えたら、ビルドしてアプリを実行します。

Sample App

位置情報が確認できました。

まとめ

play-services-locationを使うことで、位置情報を取得することができます。
もっと細かな設定もできるので、アプリに応じて変更してみましょう。

関連記事

タグ検索で調べてみよう

Android6.0 位置情報