Androidアプリにて、Google Maps Platformを動かすようにするまでで少し詰まったので、まとめておきます。
はじめに
- マップ
- ルート
- プレイス
サービスがあって、それぞれ
- Directions API
- Distance Matrix API
- Elevation API
- Geocoding API
- Maps Static API
- Places API
- Roads API
- Time Zone API
のAPIの種類があります。このAPIはWebのURLを通して叩くと、JSONファイルとして取得できます。例えば公式を参考に、緯度・経度が35.684064, 139.774517の場所を知りたければ、API_KEY
が登録されているキーだとして、WebブラウザのURLに
https://maps.googleapis.com/maps/api/geocode/json?latlng=35.684064,139.774517&key=YOUR_API_KEY
とアクセスすると
{
"plus_code" : {
"compound_code" : "MQMF+JR 日本、東京都東京",
"global_code" : "8Q7XMQMF+JR"
},
"results" : [
{
"address_components" : [
{
"long_name" : "日本橋",
"short_name" : "日本橋",
"types" : [ "establishment", "point_of_interest" ]
},
{
"long_name" : "1 国道1号",
"short_name" : "1 国道1号",
"types" : [ "premise" ]
},
{
"long_name" : "8",
"short_name" : "8",
"types" : [ "political", "sublocality", "sublocality_level_4" ]
},
{
"long_name" : "1丁目",
"short_name" : "1丁目",
"types" : [ "political", "sublocality", "sublocality_level_3" ]
},
{
"long_name" : "日本橋室町",
"short_name" : "日本橋室町",
"types" : [ "political", "sublocality", "sublocality_level_2" ]
},
{
"long_name" : "中央区",
"short_name" : "中央区",
"types" : [ "locality", "political" ]
},
{
"long_name" : "東京都",
"short_name" : "東京都",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "日本",
"short_name" : "JP",
"types" : [ "country", "political" ]
},
{
"long_name" : "103-0022",
"short_name" : "103-0022",
"types" : [ "postal_code" ]
}
],
...
と、先程の緯度経度に対応する日本橋の住所や情報が出力されます。
このAPI、このようにWebページから叩けるのですが、NativeなJava, Python, Go, Node.js用にクライアントライブラリが公式で用意されていますGoogle Maps (GitHub)。
今回は、その中でのJava用のツールを用いて、Androidアプリ内で使用出来るまでの設定を行います。
前提
- 既にGoogle Cloud PlatformにてGoogle MapsのAPI Keyは取得しています
- Android studio 3.5.3
- Android Gradle Plugin 3.5.3
- Gradle Version 5.4.1
実装
Activityを持ったProjectを作る
クライアントライブラリをインポートする
Google Maps Platform Javaクライアントライブラリを, アプリの方の (プロジェクトの方ではありません) build.gladle
に以下をスコープの外に追記します。
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.maps:google-maps-services:0.10.0'
implementation 'org.slf4j:slf4j-simple:1.7.26'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
implementation 'com.squareup.okhttp3:okhttp:3.4.1'
}
google-maps-servicesのバージョンは最新版が0.10.1ですが、ここを0.10.1にすると動きませんでしたので、バージョンを落として0.10.0にしています。また、JSONを取得するのにgson
, HTTP通信を行うのに必要なモジュール類も入れています。
書き終わりましたら、右上にあるSync Now
をクリックします。
Maps APIを準備する
strings.xml
に書いても良いかもしれませんが、今回はapp/res/values/google_maps_api.xml
ファイルを作って、そちらに書きます。
<resources>
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">API_KEY</string>
</resources>
MainActivityのレイアウトを作る
出力形式をJSONにするので、ScrollView
でTextView
をくくります。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="TextView"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="179dp" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivityを書く
Activityの中身を書いていきます。今回は勉強中のKotlinで書いてみました。
package com.example.googlemaps_test_kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.gson.GsonBuilder
import com.google.maps.GeoApiContext
import com.google.maps.GeocodingApi
import com.google.maps.model.LatLng
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val context = GeoApiContext.Builder()
.apiKey(getString(R.string.google_maps_key)) //API KEYを取得
.build()
val latLng = LatLng(35.684064, 139.774517)
val results = GeocodingApi.reverseGeocode(context, latLng).language("ja").awaitIgnoreError()
val gson = GsonBuilder().setPrettyPrinting().create()
textView.apply {
text = gson.toJson(results[0].addressComponents)
}
}
}
(例外処理などはとりあえずなしで書いてしまいました)
Kotlinでも何一つ迷わずにJavaのライブラリを利用出来ました。
インターネット接続の権限をマニフェストに追加する
これを忘れていてずっと悩んでいた…
HTTPでAPIを叩くので、必要な権限です。
<uses-permission android:name="android.permission.INTERNET" />
を追加
結果
URLからAPIを叩いたのと同じ結果になりました!
あとは、緯度経度をGPSで取得したり、他のAPIを使ったり、その結果をGSONで煮たり焼いたりすれば、色々と楽しいことができそうです!
2019/12/18 追記
Java clientからAPIを直接叩くのは危険だと分かりました。
API Keyには何らかの制限をかけて無断で使われることを保護します。
今回、このテストを行ったときはIPアドレスでフィルタリングしてAPI Keyを保護していましたが、途中からAndroidアプリのパッケージ名とFingerprintで保護する形式に変えたところ、Java client経由でAPIがたたけなくなってしまいました。
Roads APIのサンプルに
The code supporting this sample has been provided as a single Android app for illustrative purposes. In practice you should not distribute your server-side API keys in an Android app as your key cannot be secured against unauthorised access from a third party. Instead, to secure your keys you should deploy the API-facing code as a server-side proxy and have your Android app send requests via the proxy, ensuring requests are authorized.
とあります。
アプリでAPIを使用するときはプロキシサーバーを立てて、そこ経由でリクエストするなどの対策が必要です。
参考
google maps platform java client library(READMEにサンプルコードが載っています)
https://github.com/googlemaps/google-maps-services-java
Gradleについて(TIPS # 2とか)
http://gihyo.jp/dev/serial/01/android_studio/0006?page=1
Android studio 3からcompile
-> implementation
に変わった
http://tech.hikware.com/article/20180330a.html
Buildのエラー対処
https://stackoverflow.com/questions/39545629/noclassdeffounderror-failed-resolution-of-lokhttp3-internal-platform
https://stackoverflow.com/questions/17360924/securityexception-permission-denied-missing-internet-permission