Google I/O 2013 で発表された Fused Location Provider を使ってみる

Activity Recognition に続いて使ってみました。

Fused Location Provider とは?

GPS と WiFi とセンサー(加速度など) を組み合わせて、その状況に応じた最適な方法で位置を取得出来ます。今までよりも低消費電力で、精度のよい位置情報を。

実際どんな感じかは、Google I/O 2013 のセッション動画にある Fused Location Provider のデモ を見てください。

次から使い方です。

1. SDK の Google Play Services を更新する

image1

Activity Recognition と同じく Google Play services として提供されているので、SDK Manager でライブラリを更新します。

2. Eclipse でプロジェクトを作る

Android Studio は使ってません(まだよくわからないので) Eclipse で、いつもどおりに Android のプロジェクトを作ります。 Fragment も使いませんよ、古きよき、BlankActivity なプロジェクトです。 名前はここでは FusedLocationProviderSample とします。

3. プロジェクトに google-play-services_lib を追加する

Google Play services を使うために、SDK のフォルダにある google-play-services_lib が必要です。

Ecplise の Import で {your sdk location}/extras/google/google_play_services/libproject/google-play-services_lib を選択します。自分の Workspace に Copy しておいた方が無難でしょう。

コピーしたら、FusedLocationProviderSample で、 google-play-services_lib をライブラリ参照します。

image2

次から FusedLocationProviderSample の実装です。

4. AndroidManifest.xml の編集

Fused Location Provider を使うのには今まで通り、ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION を設定します。

LocationClient は、指定した PERMISSION に応じてよしなに動いてくれるそうです。

//AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.fusedlocationprovidersample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.fusedlocationprovidersample.MainActivity"
            android:screenOrientation="portrait"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

5. Fused Location Provider を使う

ActivityRecognition は IntentService を使う必要があったために少々長いコードになりましたが、こちらは今までの LocationProvider が(も)使えるので、画面一つだけのシンプルなコードです。

FusedLocationProvider を使うには、LocationClient クラスを使います。

大雑把な流れは:

  1. インスタンスを生成する

connect を呼ぶ -> ConnectionCallbacks.onConnected がコールバックされる 2. LocationRequest を指定して、requestLocationUpdates を呼ぶ。 -> LocationListener.onLocationChanged が呼ばれる

です。connect を呼んで onConnected を待つのと、位置取得条件が LocationRequest クラスになった以外は LocationManager と同じです。

では、全コードです。

//MainActivity.java
package com.example.fusedlocationprovidersample;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import android.location.Location;
import android.os.Bundle;
import android.text.format.DateFormat;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.app.Activity;

public class MainActivity extends Activity {
	
	// FusedLocationProvider 用の Client
	private LocationClient _locationClient;
	private TextView _textResult;
	
	// 以前と変わらない LocationListener
    private final LocationListener _locationListener = new LocationListener() {
		
		@Override
		public void onLocationChanged(final Location location) {
			MainActivity.this.runOnUiThread(new Runnable() {
				@Override
				public void run() {
					String text = _textResult.getText().toString();
		            text = DateFormat.format("hh:mm:ss.sss", location.getTime()) + " - " 
		                    + location.getLatitude() + "/" +
		                    + location.getLongitude() + "/" +
		                    + location.getAccuracy() + 
		                    "\n" + text;

		            _textResult.setText(text);
				}
			});
		}
	};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		_textResult = (TextView)findViewById(R.id.text_result);
		
		final Button buttonLocate = (Button)findViewById(R.id.button_locate);
		buttonLocate.setOnClickListener(new OnClickListener() {
			private boolean _isStarted = false;
			
			@Override
			public void onClick(View v) {
				if (!_isStarted) {
					startLocate();
					buttonLocate.setText("Stop");
				} else {
					stopLocate();
					buttonLocate.setText("Start");
				}
				
				_isStarted = !_isStarted;
			}
		});
	}
	
	@Override
	protected void onDestroy() {
		stopLocate();
		super.onDestroy();
	}
	
	private void startLocate() {
		_locationClient = new LocationClient(this, new ConnectionCallbacks() {

			@Override
            public void onConnected(Bundle bundle) {
				// 2. 位置の取得開始!
				LocationRequest request = LocationRequest.create()
				.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
				.setInterval(5000); // 5秒おき
            	_locationClient.requestLocationUpdates(request, _locationListener);
            }

            @Override
            public void onDisconnected() {
            	_locationClient = null;
            }

        }, new OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(ConnectionResult result) {
            }
        });
		
		// 1. 位置取得サービスに接続!
		_locationClient.connect();
	}

	private void stopLocate() {
		if (_locationClient == null || !_locationClient.isConnected()) {
			return;
		}
		
		_locationClient.removeLocationUpdates(_locationListener);
		_locationClient.disconnect();
		// ConnectionCallbacks.onDisconnected が呼ばれるまで待った方がいい気がする
	}
}

6. 動くのか!?

HTC J(not蝶) で動かしてみました。

image3

室内での結果ですが、最初 27m の精度だったのが、放っておくとどんどん精度が上がって行きました。が、地図に重ねてみないと実際合ってるのかよくわかりませんね。

そのうち、地図に載せて検証してみたいです。

Permission と Priority と精度の話

Permission と Priority の組み合わせで、位置の精度がどう変わるか、少し調べました。

FINE_LOCATION+COARSE_LOCATION with PRIORITY_HIGH_ACCURACY

GPS と WiFi と センサーフル活用。GPS が捕捉できなくても数十mの位置精度が概ね出るようです。

COARSE_LOCATION with PRIORITY_HIGH_ACCURACY

使えない。FINE_LOCATION が必要ってエラーになりました。

COARSE_LOCATION with PRIORITY_BALANCED_POWER_ACCURACY

位置の精度が数km程度になりました。WiFi測位(従来の NETWORK_PROVIDER)よりも悪いです。うーんこれは期待はずれだなあ。

結局、例えば屋内測位でしか使わないからGPS要らねって FINE_LOCATION を外すと、かえって精度が落ちるという事になります。(GPS使いませんPERMISSIONが欲しいな。。。)

おまけ

によると、Google Maps Android API v2 の [setMyLocationEnabled(true)](https://developers.google.com/maps/documentation/android/reference/com/google/android/gms/maps/GoogleMap#setMyLocationEnabled(boolean) でも FusedLocationProvider が使われるようになったとのことです。

まとめ

公式のコンプリートな Getting Started は

にありますので、こちらを読まれた方が確実です。

ここで作ったサンプルは、

に置いておきます。