GPS
1.1 概説
Androidは現在位置に関する情報を提供するサービスを備えています。これはシステムサービスの一つで、ロケーションサービス(Location Service:位置サービス)と呼ばれています。ロケーションサービスのクラスがLocationManagerクラスです(android.locationパッケージ)。
図1.1 TelephonyManagerを使うためのクラスの構成
ロケーションサービスの主な機能は次の三つです。
(1)ロケーションプロバイダの追加・削除
位置情報を提供する主な情報源は、GPSですが、GPSに限定されるわけではありません。位置の情報源はロケーションプロバイダ(Location Provider:位置情報提供者)と呼ばれます。LocationManagerは利用できるロケーションプロバイダの中から、実際に使うものを追加・削除する機能を持っています。
(2)現在位置の通知
LocationManagerは、位置情報をイベント駆動の形で知らせます。指定したリスナ(のメソッド)を呼び出して知らせます。
(3)近接警報
あらかじめ設定した地点に近づくとイベントで知らせます。知らせる相手はIntentを使って決定します。現在位置の通知はリスナを使います。この点が現在位置の通知と異なる点です。
AVDで確認できる機能は限定されますので、(2)について、例を作って機能を確認することにします。(2)に関するクラスを説明します。
1.2 LocationManagerクラス
LocationManagerはシステムサービスの一つです。したがって、そのインスタンスはgetSystemService()メソッドで取得します。LocationManagerを特定する引数の値はContext.LOCATION_SERVICEです。次はLocationManagerのインスタンスを取得する具体的なコードの例です。
LocationManager locationManager; ... locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
|
LocationManagerクラスは、概説で説明した機能を果たすためのメソッドが揃っています。本資料では、requestLocationUpdates()メソッドだけ説明します。
1.2.1 requestLocationUpdates()メソッド
requestLocationUpdates(
String provider, long minTime, float minDistance, LocationListener listener)
引数
# |
引数 |
説明 |
1 |
provider |
リスナを登録するロケーションプロバイダ |
2 |
minTime |
位置の変更を通知する最小の時間の間隔。電量消費を抑えるための一応の目安です。これより短いこともあれば、長いこともあります。単位はミリ秒です。 |
3 |
minDistance |
位置の変更を通知する最小の距離。単位はメートルです。 |
4 |
listener |
LocationListenerオブジェクト。位置が変わると、onLocationChanged(Location)メソッドが呼ばれます。 |
指定したプロバイダによって、定期的に通知してもらうためにActivity(に結合したリスナ)を登録します。引数で指定したLocationListenerに現在位置又は状態の更新情報が定期的に渡されます。
最新の位置を受信するまでにしばらく時間がかかるかもしれません。位置情報がすぐに必要なら、アプリはgetLastKnownLocation(String)メソッドを使用できます。
ユーザが指定されたロケーションプロバイダを無効にすると、位置の更新は停止します。そして、onProviderDisabled(String)メソッドが呼ばれます。該ユーザが当ロケーションプロバイダを再び有効にすると、onProviderEnabled(String)メソッドが呼ばれます。そして、位置の更新を再開します。
通知の頻度は、引数minTimeとminDistanceで制御できます。minTimeが0より大きければ、その時間だけLocationManagerは停止する可能性があり、電力消費を抑えます。minDistanceが0より大きければ、デバイス(ケイタイ)がその距離だけ動いたときだけ、位置が配信されます。できるだけ頻繁に位置情報を得るには、両方のパラメタを0に設定して下さい。
バックグラウンドサービス側はminTimeを十分高く設定すべきです。GPSもしくは無線通信を始終つながないことによって、過度の電力消費をしなくてすみます。特に60000ms(1分)未満の値はできるだけさけて下さい。
このメソッドを呼ぶスレッドはActivityの主スレッドのようなスレッドであるLooperスレッドでなければんりません。
1.2.2 addProximityAlert()メソッド
public void addProximityAlert (double latitude, double longitude, float radius, long expiration, PendingIntent intent)
引数
# |
引数 |
説明 |
1 |
latitude |
警告する領域の中心の緯度 |
2 |
longitude |
警告する領域の中心の経度 |
3 |
radius |
警告する領域の中心からの半径 単位メートル |
4 |
expiration |
警告の時間。単位ミリ秒。-1の場合は期限なし。 |
5 |
intent |
警告領域に入ったとき又はそこを離れたときに使うIntentを含むペンディングインテント |
引数で与えた位置(緯度、経度)と半径で定まる領域に近づくと発する警報(近接警報)を設定します。デバイスが設定した領域に入ったか又はそこを離れたかを検出すると指定したPendingIntentを使って、発火(*1)すべきIntentを作成します。
発火したIntentに、キーがKEY_PROXIMITY_ENTERINGで値がboolean型のキー・値対のデータ(extra *2)が追加されます。この値がtrueならデバイスは領域に入りつつあり、falseなら領域から遠ざかりつつあることを意味します。
注*1 このメソッドの説明の中ではIntentをイベントであるかのような言葉づかいがされています。該当Intentが通過するIntentFilterを探し(query)、パスしたIntentFilterを持つコンポーネントを起動する(又はバインドする/配信する)一連の処理を「発火」で済ませています。
*2 Intentで副のデータを伝搬するのに使う引数をextra(s)と呼んでいます。
位置の推定値が近似値であるため、その領域をかすっただけではIntentが発火しないこともあります。逆に、領域にかなり接近しながら実際には入らなくてもIntentが発火することもあります。
期限の引数(expiration)によって与えられた時間だけ経過すると、ロケーションマネージャは、この近接警報を削除して、以降監視しません。expirationの値が-1の場合は、期限は無限大を指定したことになります。画面が使われない状態になると、近接警報のチェックは4分ごとに行われます。デバイスが休むことなく動くことがないようにすることで、電池の寿命を保ちます。
内部的には、このメソッドはNETWORK_PROVIDERとGPS_PROVIDERの両方を使います。
1.3 LocationListenerインタフェース
# |
メソッド |
説明 |
1 |
onLocationChanged( Location location) |
位置が変わったときに呼ばれます。 |
2 |
onProviderDisabled(String provider) |
ユーザが該当プロバイダを無効にすると呼ばれます。 |
3 |
onProviderEnabled(String provider) |
ユーザが該当プロバイダを有効にすると呼ばれます。 |
4 |
onStatusChanged(String provider , int status, Bundle extras) |
プロバイダの状態が変わると呼ばれます。 |
戻り値はいづれのメソッドも持っていません。
引数
# |
引数 |
説明 |
1 |
location |
位置情報。Locationクラス参照 |
2 |
provider |
ロケーションプロバイダの名前 |
3 |
status |
状態を意味する整数。定数名で定義されている。 OUT_OF_SERVICE:動いていない。まもなく動く可能性小。 TEMPORARILY_UNAVAILABLE:動いていない。まもなく動く可能性大。 AVAILABLE:動いている。 |
4 |
extras |
プロバイダが提供する情報。必須ではない。 キーの例:satellites - 位置情報を修正するのに使う衛星の個数 |
1.4Locationクラス(android.locationパッケージ)
特定の時刻に読み取った位置の情報を保持するクラスです。位置は緯度と経度、時刻(UTC)から成ります。必須ではありませんが、高度、速度、および方位角の情報を含む場合もあります。
特定のプロバイダ又はプロバイダ(複数も可)のクラスに固有の情報は、getExtrasを使用してアプリに伝えられます。ただし、各プロバイダはそれらの入り口を提供するだけで、中身には関与しません。
Locationクラスのうち、必須の情報を取得する次の三つのgetterを紹介します。
# |
getter |
戻り値 |
1 |
getLatitude() |
double型、 (このFixの)緯度(*1). |
2 |
getLongitude() |
double型、 (このFixの)経度 |
3 |
getTime() |
long型、(このFixの)時刻(new Date(戻り値)で該当するDateオブジェクトを生成できる) |
注*1 Fixとは In position fixing navigation, a position fix or simply a fix is a position derived from measuring external reference points。(出典 http://en.wikipedia.org/wiki/Fix_(position))
位置航法でposition fix又は単にfixというのは外部の基準点の測量から(計算して)導いた位置のことです。
2. 現在位置の取得例
AVDとDDMSを使ってGPSの情報を模擬的に取得するプログラムを作ります。
2.1GPSのlistenerの設定と削除
public class MyActivity extends Activity { private LocationManager mgr; // listener等で使うのでフィールド変数にする。 private LocationListener locationListner; // onDestroyで必要なのでフィールド変数にする。
@Override public void onCreate(Bundle savedInstanceState) { ...
locationListner = new MyLocationListener(); ① mgr=(LocationManager)getSystemService(Context.LOCATION_SERVICE); ② mgr.requestLocationUpdates(LocationManager.GPS_PROVIDER ③ ,10000, 10000.0f,locationListner);// listenerの設定。引数についてマニュアル参照 ... }// end of onCreate ... public void onDestroy() { super.onDestroy(); mgr.removeUpdates(locationListner);// listenerの削除 ④ }// end of onDestroy |
①リスナを生成します。リスナのクラスは後で作ります。
②ローケーションサービスはシステムサービスですから、システムサービスのオブジェクトを取得するための汎用的なメソッドContext.getSystemService()を使います。引数はContext.LOCATION_SERVICEです。
③ロケーションサービスにリスナを設定します。requestLocationUpdates()メソッドの仕様は次の通りです。
④ロケーションリスナを削除します。
注 実際のアプリで、onCreate()メソッドでリスナを登録し、ondestroy()メソッドでリスナを削除するのはよくありません。例題ではその方が見通しやすいので、この方法を採用しています。
2.2listenerの実装
listenerはinterface "LocationListener"( android.location.LocationListener)を実装しなければなりません。
public class MyLocationListener implements LocationListener { @Override public void onLocationChanged(Location location) { updateList(location); } public void onProviderDisabled(String provider){ Log.d(tag,"onProviderDisabled is called. provider=" + provider); } public void onProviderEnabled(String provider){ Log.d(tag,"onProviderEnabled is called. provider=" + provider); } public void onStatusChanged(String provider, int status, Bundle extras){ Log.d(tag,"onStatusChanged is called. status=" + status + " provider=" + provider); } } |
必ずしも内部クラスである必要はありません。これは(名前付き)内部クラスを使った例です。
実質的な処理があるのはonLocationChangedだけです。他のメソッドも必要に応じて処理を記入します。interfaceの実装なので、処理はなくてもメソッドの宣言は必要です。
onLocationChanged()メソッドは、先に実装したupdateList()メソッドを呼んでいます。引数はLocationクラスです。ここに緯度と経度が含まれています。
listenerから、実質的な処理を委託されるlistenerのhelpメソッドupdateListを実装します。ここではMyActivityの中に記述する例です。この例題では、位置と時間をログに出力しているだけです。
public void updateList(Location location){ Log.d(tag,"updateList lat=" + location.getLatitude() + " lng=" + location.getLongitude() + " time=" + new Date(location.getTime()) ); } |
これはGPSから送付された位置と位置のデータをプリントしているだけです。
2.3 パーミッション
LocationManagerを使うには次のパーミッションが必要です。AndroidManifest.xmlに設定して下さい。applicationタグと同じレベルです(applicationタグの中ではありません)。
... <application ... ... </application> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission> ... |
2.4.GPSのエミュレーション
例題を動かすにはGPSのエミュレータを動かす必要があります。その動かし方を説明します。GPSのエミュレータが動くためには、LocationManagerオブジェクトを取得する必要がありますので、例題を実行して、次の手順に従ってください。
該当プロジェクトを実行したら、DDMSパースペクティブに切り替えます。(標準の配置では)左の中ほどに、「Emulator Control」タブがあります。このやや下の方に(多分スクロールが必要)、「Location Cntrols」があり、そのすぐ下に「Manual」タブがあります。そこにLongitude(経度)とLatitude(緯度)のフィールドがあります。この値が現在位置となります。(Manual以外の指定法もありますが、この例はManualだけ使います)。値を設定して「send」をクリックすると、設定した値を現在地として送信されます。なお、LocationManager(第1章参照)をインスタンシェートしないと、Longitude(経度)とLatitude(緯度)のフィールドはgray outされて利用できないようになっています。
Longitude(経度)とLatitude(緯度)の例は付録に記載しました。これはgoogleのホームページから地図を表示して取得できます。
図 2.1DDMSのLocationControls
3.近接警報の例
3.1 近接警報のIntentの設定
概説で述べた通り、近接警報は、Intentのしくみで通知します。
そのために、Intentを設定します。実際には、Intentを生成した時点と、それが実際に使われる時点が離れていますので、このIntentはPendingIntentに包んで使います。
具体的なコードを示します。(一部略されています)
public class HelloGPS_0_2_alert extends Activity {
private LocationManager mgr;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.d(tag,"onCreate starts.");
// 自由の女神 location=(40.690466,-74.045019) double latitude = 40.690466; ① double longitude = -74.045019; float radius = 10000F;// m ang = 10km*360度/40000km =0.09度に相当 long expiration = -1L; ② PendingIntent pendingIntent = createPendingIntent(); ③
mgr=(LocationManager)getSystemService(Context.LOCATION_SERVICE); ④ mgr.addProximityAlert (latitude, longitude, radius, expiration, pendingIntent); ⑤ }
public void onDestroy() { super.onDestroy(); Log.d(tag,"onDestroy starts."); }
private PendingIntent createPendingIntent(){ ⑥ Intent intent = new Intent(this, ProximityAlertActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pendingIntent = PendingIntent.getActivity( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); return pendingIntent; } } |
①近接警報の対象になる位置を設定します。自由の女神を中心にして半径10km以内という設定です。半径10kmは角度で約0.1度に相当します。DDMSで値を手で入力する場合はこの程度が便利だろうという観点で選定した値です。
②監視を継続する時間を無制限に設定します。テストする場合は、同じ監視状態が続いた方が分かりやすいのでこの値にしています。
③PendingIntentを作成します。見やすくするため、その処理は一つのメソッドに収めました。これが⑥です。
④ロケーションサービスのオブジェクトを取得します。
⑤近接警報を出すための設定をするメソッドを呼びます。
⑥PendhingIntentの作成です。Notificationの説明を参考にして下さい。実際に近接すると、ここで生成したIntentをパスするIntentFilterをもつActivityを起動します。
3.2 近接警報を処理するActivity
実際に近接警報を処理するActivityのコードは次の通りです。
public class ProximityAlertActivity extends Activity { private String tag = this.getClass().getSimpleName(); private TextView tv;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.alert); Log.d(tag,"onCreate starts.");
tv = (TextView)findViewById(R.id.tv1);
showStatus(); ① }
@Override public void onDestroy() { super.onDestroy(); Log.d(tag,"onDestroy starts."); showStatus(); ① }
private void showStatus(){ ② Intent intent = getIntent(); boolean flag = intent.getBooleanExtra(LocationManager.KEY_PROXIMITY_ENTERING,false); tv.setText(Boolean.toString(flag)); }
} |
①近接警報を出すメソッドを呼び出します。
②近接情報は、仕様により、Intentのextrasの中に、キーがLocationManager.KEY_PROXIMITY_ENTERINGの値として設定されていますので、取り出します。
3.演習
習得するための例題として、付録にあげたLongitude(経度)とLatitude(緯度) の地点から現在位置(可変)までの距離を表示するプログラムを作成して下さい。
付録
付録 A 緯度・経度サンプル
DDMS 初期値 location=(37.422006,-122.084095)
東京都庁 location=(35.689498,139.691766)
ホワイトハウスlocation=(38.897822,-77.036583)
自由の女神location=(40.690466,-74.045019)
付録 B 現在位置の取得例のソースコード
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.gps" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".HelloGPS_0_1" 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> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission> </manifest> |
main.xml
EclipseのAndroidプロジェクトを作成したときに自動生成されるmain.xmlと同じ。
主Activity
public class HelloGPS_0_1 extends Activity { private String tag = this.getClass().getSimpleName(); private LocationManager mgr; private LocationListener locationListner;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
locationListner = new MyLocationListener(); mgr=(LocationManager)getSystemService(Context.LOCATION_SERVICE); mgr.requestLocationUpdates( LocationManager.GPS_PROVIDER,10000,10000.0f,locationListner); }
public void onDestroy() { super.onDestroy(); mgr.removeUpdates(locationListner); }
public class MyLocationListener implements LocationListener { @Override public void onLocationChanged(Location location) { updateList(location); } public void onProviderDisabled(String provider){ Log.d(tag,"onProviderDisabled is called. provider=" + provider); } public void onProviderEnabled(String provider){ Log.d(tag,"onProviderEnabled is called. provider=" + provider); } public void onStatusChanged(String provider, int status, Bundle extras){ Log.d(tag,"onStatusChanged is called. status=" + status + " provider=" + provider); } } public void updateList(Location location){ Log.d(tag,"updateList lat=" + location.getLatitude() + " lng=" + location.getLongitude() + " time=" + new Date(location.getTime()) ); } } |
付録 C 近接警報の例のソースコード
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.gps" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".HelloGPS_0_2_alert" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".ProximityAlertActivity" android:label="ProximityAlertActivity"> <intent-filter> <action android:name="jp.co.ichi.action.ALERT" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission> </manifest> |
main.xml
EclipseのAndroidプロジェクトを作成したときに自動生成されるmain.xmlと同じ。
alert.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/tv1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="alert" /> </LinearLayout> |
主Activity
public class HelloGPS_0_2_alert extends Activity { private String tag = this.getClass().getSimpleName(); private LocationManager mgr;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.d(tag,"onCreate starts.");
// 自由の女神 location=(40.690466,-74.045019) double latitude = 40.690466; double longitude = -74.045019; float radius = 10000F;// m ang = 10km*360度/40000km =0.09度に相当 long expiration = -1L; PendingIntent pendingIntent = createPendingIntent();
mgr=(LocationManager)getSystemService(Context.LOCATION_SERVICE); mgr.addProximityAlert (latitude, longitude, radius, expiration, pendingIntent); }
public void onDestroy() { super.onDestroy(); Log.d(tag,"onDestroy starts."); }
private PendingIntent createPendingIntent(){ Intent intent = new Intent(this, ProximityAlertActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pendingIntent = PendingIntent.getActivity( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); return pendingIntent; } } |
近接警報用Activity
public class ProximityAlertActivity extends Activity { private String tag = this.getClass().getSimpleName(); private TextView tv;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.alert); Log.d(tag,"onCreate starts.");
tv = (TextView)findViewById(R.id.tv1);
showStatus(); }
@Override public void onDestroy() { super.onDestroy(); Log.d(tag,"onDestroy starts."); showStatus(); }
private void showStatus(){ Intent intent = getIntent(); boolean flag = intent.getBooleanExtra(LocationManager.KEY_PROXIMITY_ENTERING,false); tv.setText(Boolean.toString(flag)); }
} |
付録 D 参考資料
http://blogoscoped.com/archive/2007-11-19-n27.html 記事が旧いので一部役立ちません。
GPSからのイベントがBroadcastになっています。現バージョンはlistenerです(Intentを使う/使わないが異なる)。pendingIntentを使う方法は今でもあります。
http://code.google.com/p/wherearemyfriends/source/browse/trunk/src
/com/radioactiveyak/wamf/WhereAreMyFriends.java?r=5
上記の資料を少し変更したものと推測します。経度・緯度から場所を引き出し、現在位置として経度・緯度及び場所(の名前)を表示します。経度・緯度から場所を引き出すのはgoogleのmapライブラリを使っています(ライセンスが要るので登録が必要と思います)
さらに、友達の住所を元に現在位置と友達の住所との間の距離の一覧を表示します。友達の住所はFriendLocationLookupというクラスを使っています。このクラスについては不明です(web上に紹介されているかもしれません)
地図に書き込むバージョンもあるようですので研究して下さい。
以上