※この記事はイトナブ開発合宿in滝沢のイベントによって書かれました。
イトナブ開発合宿in滝沢を知らない方はまず
こちらをご覧ください。
また他の参加者の方が書いた記事も掲載させて頂いております。
こんにちは、イトナブ石巻のフィッシュこと津田恭平です。
今回はAndroidのLocationAPIについて触れていきたいと思います。
Locations API
GooglePlayサービスが提供するLocation APIを使用すれば、簡単に位置情報の機能をアプリケーションに組み込むことができます。そしてGooglePlayサービスが提供するLocationAPIはなるべく端末に負担をかけずに省電力な実現を目指しています。
1.GooglePlayServiceのセットアップ
※Location APIの機能を使用するには、GooglePlayServiceのライブラリが必要となります。まずは下記のリンクを参考にインストールしてください。
2.チュートリアルをやってみる。
3.慣れてきたら詳細リファレンスを参照しカスタマイズを行う
GooglePlayサービスが提供する3つのLocationAPI
LocationAPIは大きく分けて3つのAPIを提供しています。
Fused location provider
緯度経度を取得することができます。また従来の緯度経度取得に比べ省電力になる他、端末は携帯基地局から情報をすればいいのか、Wi-fiなのか、GPSなのか、うまく切り替えをしてくれます。
使用例:
緯度経度と時間を常に取得すれば、(時間・距離・速さ)を取得出来るのでランニングアプリなどを作ることができます。
Geofencing APIs
あらかじめ任意の緯度経度と半径を指定し、目には見えませんが地図にサークル(円)を作成します。そして端末がその円を出たり入ったりするとGeofencing APIが起動しIntentを発行することができます。
使用例:
指定した場所(会社、自宅、お店)などに行くとそれに関連したアプリケーションを起動させることができます。
Activity recognition
ユーザーの行動(歩き、自転車、車など)を検知することができます。
使用例:さきほど紹介したFusedLocationAPIと組み合わせ、ランニングアプリを作成したとします。しかしユーザーが急に自転車などに乗った場合、ランニングなのか自転車なのか分からなくなります。Activity recognitionを使用すればユーザーの動きを検知できるので、A地点→B地点までランニング、B地点→C地点は自転車というような備忘録を作成することができます。
今回はFused location providerとActivity recognitionを実装してみる。
3つLocationAPIを紹介させて頂きました。
今回はこのAPIの3つうち、Fused location providerとActivity recognitionの2つを実装していきます。Geofencing APIsはイトナブ合宿の発表では不向きなので、今回は割愛させていただきます。
イトナブ開発合宿in滝沢を知らない方はまず
こちらをご覧ください。
Fused location providerで使用し緯度経度を取得するサンプル
Manifestに下記の許可をいれてください。
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
MainActivity.java
import android.graphics.Typeface;
import android.location.Location;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import java.text.DateFormat;
import java.util.Date;
public class MainActivity extends ActionBarActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
protected static final String TAG = "location-updates-sample";
public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;
public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2;
protected final static String REQUESTING_LOCATION_UPDATES_KEY = "requesting-location-updates-key";
protected final static String LOCATION_KEY = "location-key";
protected final static String LAST_UPDATED_TIME_STRING_KEY = "last-updated-time-string-key";
protected GoogleApiClient mGoogleApiClient;
protected LocationRequest mLocationRequest;
protected Location mCurrentLocation;
protected Button mStartUpdatesButton;
protected Button mStopUpdatesButton;
protected TextView mLastUpdateTimeTextView;
protected TextView mLatitudeTextView;
protected TextView mLongitudeTextView;
protected String mLastUpdateTime;
protected Boolean mRequestingLocationUpdates;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStartUpdatesButton = (Button)findViewById(R.id.start_button);
mStartUpdatesButton.setTypeface(Typeface.DEFAULT_BOLD);
mStopUpdatesButton = (Button)findViewById(R.id.stop_button);
mStopUpdatesButton.setTypeface(Typeface.DEFAULT_BOLD);
mLastUpdateTimeTextView = (TextView) findViewById(R.id.latitude);
mLatitudeTextView = (TextView) findViewById(R.id.longitude);
mLongitudeTextView = (TextView) findViewById(R.id.time);
mRequestingLocationUpdates = false;
mLastUpdateTime = "";
updateValuesFromBundle(savedInstanceState);
buildGoogleApiClient();
}
protected synchronized void buildGoogleApiClient() {
Log.i(TAG, "Building GoogleApiClient");
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
createLocationRequest();
}
protected void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
public void startUpdatesButtonHandler(View view) {
if (!mRequestingLocationUpdates) {
mRequestingLocationUpdates = true;
setButtonsEnabledState();
startLocationUpdates();
}
}
public void stopUpdatesButtonHandler(View view) {
if (mRequestingLocationUpdates) {
mRequestingLocationUpdates = false;
setButtonsEnabledState();
stopLocationUpdates();
}
}
private void setButtonsEnabledState() {
if (mRequestingLocationUpdates) {
mStartUpdatesButton.setEnabled(false);
mStopUpdatesButton.setEnabled(true);
} else {
mStartUpdatesButton.setEnabled(true);
mStopUpdatesButton.setEnabled(false);
}
}
protected void startLocationUpdates() {
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, this);
}
@Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected to GoogleApiClient");
if (mCurrentLocation == null) {
mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
updateUI();
}
if (mRequestingLocationUpdates) {
startLocationUpdates();
}
}
@Override
public void onConnectionSuspended(int i) {
Log.i(TAG, "Connection suspended");
mGoogleApiClient.connect();
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + connectionResult.getErrorCode());
}
@Override
public void onLocationChanged(Location location) {
mCurrentLocation = location;
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
updateUI();
Toast.makeText(this, "update",
Toast.LENGTH_SHORT).show();
}
private void updateUI() {
mLatitudeTextView.setText(String.valueOf(mCurrentLocation.getLatitude()));
mLongitudeTextView.setText(String.valueOf(mCurrentLocation.getLongitude()));
mLastUpdateTimeTextView.setText(mLastUpdateTime);
}
@Override
protected void onPause() {
super.onPause();
stopLocationUpdates();
}
protected void stopLocationUpdates() {
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient, this);
}
@Override
public void onResume() {
super.onResume();
if (mGoogleApiClient.isConnected() && !mRequestingLocationUpdates) {
startLocationUpdates();
}
}
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY,
mRequestingLocationUpdates);
savedInstanceState.putParcelable(LOCATION_KEY, mCurrentLocation);
savedInstanceState.putString(LAST_UPDATED_TIME_STRING_KEY, mLastUpdateTime);
super.onSaveInstanceState(savedInstanceState);
}
private void updateValuesFromBundle(Bundle savedInstanceState) {
Log.i(TAG, "Updating values from bundle");
if (savedInstanceState != null) {
if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) {
mRequestingLocationUpdates = savedInstanceState.getBoolean(
REQUESTING_LOCATION_UPDATES_KEY);
setButtonsEnabledState();
}
if (savedInstanceState.keySet().contains(LOCATION_KEY)) {
mCurrentLocation = savedInstanceState.getParcelable(LOCATION_KEY);
}
if (savedInstanceState.keySet().contains(LAST_UPDATED_TIME_STRING_KEY)) {
mLastUpdateTime = savedInstanceState.getString(
LAST_UPDATED_TIME_STRING_KEY);
}
updateUI();
}
}
@Override
protected void onStart() {
super.onStart();
mGoogleApiClient.connect();
}
@Override
protected void onStop() {
super.onStop();
if (mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:background="#FFC107"
tools:context=".MainActivity">
<TextView
android:textSize="30dp"
android:textColor="#ffffff"
android:gravity="center"
android:text="FusedLocationProvider"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp" />
<TextView
android:textSize="30dp"
android:textColor="#ffffff"
android:id="@+id/latitude"
android:gravity="center"
android:text="latitude"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp" />
<TextView
android:textSize="30dp"
android:textColor="#ffffff"
android:id="@+id/longitude"
android:gravity="center"
android:text="longitude"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp" />
<TextView
android:textSize="30dp"
android:textColor="#ffffff"
android:id="@+id/time"
android:gravity="center"
android:text="longitude"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:orientation="horizontal"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp">
<Button
android:text="更新"
android:onClick="startUpdatesButtonHandler"
android:id="@+id/start_button"
android:layout_marginTop="40dp"
android:layout_marginRight="8dp"
android:layout_marginLeft="8dp"
android:gravity="center"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<Button
android:text="停止"
android:onClick="stopUpdatesButtonHandler"
android:id="@+id/stop_button"
android:layout_marginTop="40dp"
android:layout_marginRight="8dp"
android:layout_marginLeft="8dp"
android:gravity="center"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
Activity recognitionでユーザーの行動を取得
コミットさせて頂きました。
画像を切替えているソースのみこちらへ掲載させて頂きます。
Constants.java
public static Bitmap getActivityImage(Context context, int detectedActivityType) {
Resources r = context.getResources();
switch (detectedActivityType) {
case DetectedActivity.IN_VEHICLE:
return BitmapFactory.decodeResource(r, R.drawable.car);
case DetectedActivity.ON_BICYCLE:
return BitmapFactory.decodeResource(r, R.drawable.bike);
case DetectedActivity.ON_FOOT:
return BitmapFactory.decodeResource(r, R.drawable.foot);
case DetectedActivity.RUNNING:
return BitmapFactory.decodeResource(r, R.drawable.running);
case DetectedActivity.STILL:
return BitmapFactory.decodeResource(r, R.drawable.still);
case DetectedActivity.TILTING:
return BitmapFactory.decodeResource(r, R.drawable.balance);
case DetectedActivity.UNKNOWN:
return BitmapFactory.decodeResource(r, R.drawable.question);
case DetectedActivity.WALKING:
return BitmapFactory.decodeResource(r, R.drawable.walk);
default:
return BitmapFactory.decodeResource(r, R.drawable.ic_launcher);
}
}
画像の方は任意の画像をWebなどへ検索し、drawable(mipmap)へ入れて下さい。 画像を検索する場合はライセンスなどに注意してください。
まとめ・イトナブ開発合宿の感想
今回2つのLocationAPIに触れました。GPSが多くのアプリケーションで採用されていることから、モバイルアプリでは、Androidに限らずGPSの機能が必要不可欠です。またスマートフォンのセンサーやGPSは日々進化しています。それに合わせLocationAPIなどの便利なAPIが、どんどん出てくると予想しています。それに合わせ毎回リファレンスを読むことになりますが、じっくり読まないで次へ次へと進んでしまいがちです。今回のイトナブ開発合宿ではゆっくりと腰を据えて、じっくりリファレンスを読みながら開発することが出来たのでとても満足しています。
イトナブ開発合宿が終わっても、リファレンスをはじめ新しい情報をキャッチした時に備え、日々リファレンスを読む練習を続けていきたいと感じました。
イトナブ開発合宿in滝沢を知らない方はまず
こちらをご覧ください。