読者です 読者をやめる 読者になる 読者になる

Android Studioに追加されたGoogle App Engineテンプレートを試そう 導入編

モバイルファースト室の@sys1yagiです。

Android Studio使ってますか?

Google I/O 2014の直前に3つのGoogle App EngineテンプレートがAndroid Studioに追加されました。追加されたテンプレートのうちのApp Engine Java Endpoints Moduleを用いたGoogle Cloud Endpointsの作成と利用について解説します。

MBaaSとCloud Endpoints

Google Cloud EndpointsはGoogleが提供するGoogle Cloud Platformの機能の一つで、Google App Engineを使ってAPIのエンドポイントを定義する仕組みを提供します。

一般的なMBaaSではMBaaSが用意したクライアントライブラリを使ってバックエンドにアクセスします。アプリケーション開発者はスキーマレスなデータストアに対してアプリケーションのモデルを定義したり、クライアントライブラリをラップする構造を構築するといった「MBaaSの提供する機能とアプリケーションを接続する部分の設計」に集中する事になります。

f:id:sys1yagi:20140911152655p:plain

一方Cloud Endpointsはより抽象的なレイヤを設計する仕組みを提供します。一言で表すと「MBaaSを構築する為のPaaS」という言い方になるでしょうか。Javaで宣言的にエンドポイントを記述すると、アプリケーション用のRESTfulなJSON APIのセットが出来上がります。エンドポイントの定義を元に生成されるクライアントライブラリにはエンドポイントへのアクセス用メソッドと、エンドポイントでやりとりするモデルが含まれており、アプリケーションとバックエンドが接続する部分をストレスなく開発できます。

f:id:sys1yagi:20140911152704p:plain

ただし、Cloud Endpointsそのものはデータストアや認証、Push等の機能は備えていません。Google App EngineやGoogle Cloud Messaging等の機能を用いて自ら構築する事になります。Cloud Endpointsを利用してMBaaSの機能を実装している例としてMobile Backend Starterがあり、ソースコードが公開されているので参考にしたり、カスタマイズして利用するといった事もできます。

Android StudioとGoogle App Engineテンプレートでできる事

Android StudioでのGoogle App EngineおよびCloud Endpointsのサポートは初期からありました(Adding a Backend to Your App In Android Studio)。しかしセットアップが煩雑で、元々Google App EngineやCloud Endpointsを使っているケースでなければ利用するモチベーションが上がらないような状況でした。しかし各種テンプレートが追加された事によって、Google App EngineやCloud Endpointsの利用のハードルが下がり、カジュアルに試せる環境が整いました。

Android Studioとテンプレートの利用によって以下の事が出来ます。

  • テンプレートを使ってGoogle App Engine用のモジュールをプロジェクトに追加する
  • Google App Engine, Cloud Endpoints等の機能実装
  • ローカルでAPIサーバを起動し、動作テストする
  • Google App Engineにデプロイする

早速試してみましょう。

セットアップ

※本エントリはAndroid Studio 0.8.9を使用して書いています

Cloud Endpointsのテンプレートは、Androidプロジェクトに対してモジュールを追加する形でセットアップします。ですので予めAndroidアプリケーションのプロジェクトを作成しておく必要があります。本エントリでは「HelloCloudEndpoints/app」というAndroidアプリケーションのモジュールを作成したものとして進めます。

Androidアプリケーションのプロジェクトを開いた状態で[File]-[New Module]を選択します。

f:id:sys1yagi:20140911152727p:plain

"App Engine Java Endpoints Module"を選択し、モジュール名を入力します。

f:id:sys1yagi:20140911152727p:plain

するとプロジェクト内にモジュールが生成されます。生成される内容はGoogle App Engineのプロジェクトで、最低限のモデルとエンドポイントの実装が含まれています。

f:id:sys1yagi:20140911152816p:plain

この時アプリケーション側のbuild.gradleやAndroidManifest.xmlが以下の様に更新されます。

  • settings.gradleに':api'が追加される(入力したモジュール名によって異なります)
include ':app', ':api'
  • build.gradleのdependenciesにCloud Endpointsのクライアントライブラリが追加される
compile project(path: ':api', configuration: 'android-endpoints')
  • AndroidManifest.xmlにINTERNETパーミッションが追加される
<uses-permission android:name="android.permission.INTERNET" />

これにより特にアプリケーション側で設定を追加しなくてもCloud Endpointsのクライアントライブラリを利用出来るようになります。

バックエンドを動かす

まずは生成されたバックエンドを動かしてみましょう。最終的にはGoogle Developers Consoleでバックエンドのアプリケーションを登録してデプロイする形になりますが、開発中はローカルでサーバを立ち上げて動作確認ができます。

ツールバーのRun Configurationに、Cloud Endpointsの構成が追加されているのでワンタッチで起動できます。

f:id:sys1yagi:20140911152827p:plain

実行するとエンドポイントがコンパイルされ、localhost:8080でサーバが立ち上がります。開いてみると以下の様なデモ画面が現れます。この画面ではCloud Endpointsで生成したjavascriptのクライアントを使って、テンプレート生成時に定義されたエンドポイントへのアクセスを試せます。

f:id:sys1yagi:20140911152834p:plain

フォームに値を入力して送信するとエンドポイントから値が返り、画面に表示されます。

f:id:sys1yagi:20140911152844p:plain

実装を読む

では実装がどのようになっているか見てみましょう。予め生成されるクラスはMyEndpointMyBeanの2つです。

MyEndpointはエンドポイントを定義するクラスです。エンドポイントはアノテーションを使って宣言します(アノテーションの詳細は→Endpoint Annotations)。クラス宣言の上部で@ApiアノテーションによってAPI名やバージョン、namespaceを宣言しています。このクラスの中に@ApiMethodアノテーションを使ってエンドポイントの宣言を追加していきます。@ApiMethodのname属性にエンドポイント名を定義し、メソッドの引数、戻り値によってそのエンドポイントのパラメータとレスポンスを定義します。

MyEndpoint.java

package com.sys1yagi.hellocloudendpoints.api;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;

import javax.inject.Named;

/** An endpoint class we are exposing */
@Api(name = "myApi", version = "v1", namespace = @ApiNamespace(ownerDomain = "api.hellocloudendpoints.sys1yagi.com", ownerName = "api.hellocloudendpoints.sys1yagi.com", packagePath=""))
public class MyEndpoint {

    /** A simple endpoint method that takes a name and says Hi back */
    @ApiMethod(name = "sayHi")
    public MyBean sayHi(@Named("name") String name) {
        MyBean response = new MyBean();
        response.setData("Hi, " + name);

        return response;
    }

}

MyBeanクラスはひとつのStringを持つシンプルなモデルです。MyEndpointのsayHiメソッドの戻り値に使われています。

MyBean.java

package com.sys1yagi.hellocloudendpoints.api;

/** The object model for the data we are sending through endpoints */
public class MyBean {

    private String myData;

    public String getData() {
        return myData;
    }

    public void setData(String data) {
        myData = data;
    }
}

ここでエンドポイントsayHiの実際のレスポンスを見てみましょう。以下のリクエストを行うと、

http://localhost:8080/_ah/api/myApi/v1/sayHi/Cookpad

以下の様なレスポンスが返ります。MyBeanクラスが自動的にJSONに展開されている事がわかります。

{
  "data" : "Hi, Cookpad"
}

詳細は省きますが、Cloud Endpoints用のコードをコンパイルする際、エンドポイントに関わるクラスを解析しGoogle APIs Discovery Serviceに従ったdiscoveryファイルを生成します。discoveryファイルにはエンドポイントの仕様やレスポンスの定義がされており、この定義に従ってサーバを構成します。またこのdiscoveryファイルを使ってクライアントライブラリの生成なども行っています。エンドポイントが返すJSONもこのdiscoveryファイルによって生成します。

アプリケーションでAPIを利用する

次にCloud Endpointsが生成したクライアントライブラリを使ってAPIにアクセスしてみましょう。アプリケーション側で用意するものは特にありません。最初にテンプレートを追加した時点でdependenciesにクライアントライブラリの定義が追加されているからです。

クライアントライブラリのパッケージ名はエンドポイントの@Apiアノテーションに指定したnameとnamespaceに従います。ですので今回は「com.sys1yagi.hellocloudendpoints.api.myApi.MyApi」がエンドポイント用のクライアントとなります。

初期化

クライアントはBuilderを使って作成します。BuilderのコンストラクタにはHttp通信をハンドリングするHttpTransport、JSONをパースするJsonFactory, Httpリクエストを初期化するHttpRequestInitializerを渡します。特殊な事をするのでなければクライアントライブラリに含まれているデフォルトのクラス群を利用すれば問題ありません。

private MyApi getApiClient() {
  return new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
      new AndroidJsonFactory(), null)
      //for genymotion.
      //if you use android emulator, you should replace to "10.0.2.2".
      .setRootUrl("http://10.0.3.2:8080/_ah/api/")
      .build();
}

Builderクラスでは各種クライアントの設定ができます。上記の例では、ローカルサーバに対してエミュレータ(Genymotion)で接続するためにsetRootUrl(String)でURLを指定しています。

実行

MyApiクラスには各種エンドポイントへアクセスする為のメソッドが実装されています。以下の様にエンドポイント名と一致するメソッドに引数を渡し、execute()を実行するとエンドポイント側で定義した戻り値と同じモデルを受け取れます(実際は異なるクラスです)。

MyBean response = myApi.sayHi("Cookpad").execute();

execute()はブロッキングされるので、実際に利用する際はAsyncTaskなどを使う事になります。

class SayHiAsyncTask extends AsyncTask<String, Void, String> {

  @Override
  protected String doInBackground(String... params) {
    try {
      MyBean response = getApiClient().sayHi(params[0]).execute();
      return response.getData();
    } catch (IOException e) {
      return e.getMessage();
    }
  }
}

実行するスレッドを意識しなければなりませんが、通信にまつわる部分を意識しなくてもAPIを簡単に利用できます。

new SayHiAsyncTask() {
  @Override
  protected void onPostExecute(String s) {
    textView.setText(s);
  }
}.execute("Cookpad");

f:id:sys1yagi:20140911152904p:plain

次回

如何でしょうか。Cloud Endpointsのテンプレートを使う事で簡単にAndroidプロジェクトにバックエンドの実装を追加できる事がわかりました。バックエンドでモデルを定義すると、通信処理やJSONのフォーマット等について意識する事なくクライアント側でデータを受け取れるようになります。バックエンドが決まっていなかったり、インタフェースが決まっていない状態でのプロトタイピングに使えそうです。もちろんバックエンドはGoogle App Engineですので、しっかり作りこんで本番で運用する事もできます。

今回作成したプロジェクトのソースはコチラで公開しています->HelloCloudEndpoints

次回はTodoアプリの作成を通して、Objectifyを使ったモデルの定義とデータストア操作、APIの作成、デプロイまでを解説したいと思います。

参考

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/