87
83

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

モバイルDevOpsAdvent Calendar 2015

Day 6

アプリ上でサーバの「メンテナンス中」をリアルタイムに伝える方法

Last updated at Posted at 2015-12-06

TL;DR

アプリは何も悪くないのにサーバ障害のせいでレビューががた落ち、というのは30分ぐらいの開発で防げるので実装しておこう

アプリは悪くないのに評価が下がって辛いとき

どんなに完璧でバグがなく実装されたアプリでも、通信相手となるサーバ側で問題が起きてしまうとアプリは使えなくなってしまいます。

このとき、ずっと 読み込み中 のままだったり エラーが発生しました と表示され続けるだけになっていると、ユーザーにとっては アプリそのものが 壊れているのと変わりません。「せっかくインストールしたのに、壊れてて使えない」わけなので、必然的にレビュー欄が荒れてしまいます。

一方、障害の発生時でも起動したアプリ中で ごめん、いまなんとかしてるよ! ということが正しく伝えられれば、ユーザーもいきなり怒ったりはしません。

この記事では、

  • サービス提供側からのメッセージを
  • リアルタイムにユーザーに提示する
  • できる限り簡単な実装

について紹介します。

今回は Google が 2014 年に買収した Firebase というリアルタイムバックエンドサービスを使います。以下ではAndoroidで説明してますが、iOSでもCocoaPodsでサクッと導入して同じように作って共通化することができます。

どんな感じ

「やばい、サーバ止まってる……」

そんな切羽詰まった状況下でも、Web上でステータスを更新することで、ユーザー側に状況をリアルタイムに伝えることができます。

out2.gif

  • 稼働状態 (active) が false になると、アプリに指定したメッセージの Snackbar が表示され、問題が発生していることをアプリのユーザーに伝えます。
  • 回復状況に応じて書き換えていくと、リアルタイムに反映されます

この実装本体は 諸々含めて 61行 120 行 程度で書かれており、 Activity へこんな感じのコールバックイベント実装を追加する だけで実装できます。これだけでメンテ中にアプリ起動されたときや、通信が切れたり復活したりした場合も自動的に再接続が行わるなど、様々な状況下で正常に動作するようになっています。(Firebaseよくできてる)

リポジトリはこちら: https://github.com/tnj/FirebaseMaintenanceMode

作り方

まず Android Studio でいつもの Blank Activity プロジェクトを作ります。

Android Studio でプロジェクトに Firebase を追加する

Command + ; (Windows だと Ctrl + Alt + Shift + S ) で Project Structure を開き、 Cloud を開くと Firebase のチェックボックスがあるのでこれにチェックを入れて OK を押します。

Screen Shot 2015-12-06 at 7.28.16 PM.png

以下の行が追加されます。

app/build.gradle
compile 'com.firebase:firebase-client-android:2.3.1'
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />

あと、ライブラリのファイルが重複していてビルドがこけるので、手動で以下の設定を app/build.gradle へ追加します。

app/build.gradle
android {
    // ...
    // 諸々の設定のあと
    // ...

    packagingOptions {
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE-FIREBASE.txt'
        exclude 'META-INF/NOTICE'
    }
}

Firebase のアカウントを作る

次に Firebase のサイト の右上の Sign Up からアカウントを作成します。 Google のアカウントを選択するだけです。

Screen Shot 2015-12-06 at 7.39.22 PM.png

サインアップできると My First App という最初のアプリが既に作成されています。

データを作る

My First AppManage App を開くとデータの編集画面になります。

Screen Shot 2015-12-06 at 10.47.43 PM.png

Firebase は基本的に JSON ライクな key-value のツリー型でデータを保存します。

今回は、 status という名前で、「サービスが生きているかどうか(active)」「死んでいるときに表示するメッセージ(message)」の2つをキーにデータを作ってみます。

親要素 (名前は自動生成) にカーソルを合わせると + マークが現れるのでこれをクリックします。

Screen Shot 2015-12-06 at 10.48.14 PM.png

namestatus と入力して、即値でなくて子要素を追加するため value は空のまま、その右の + を押します。

Screen Shot 2015-12-06 at 10.48.29 PM.png

子要素の1つ目は name: active, value: true と入力して Add を押します。

Screen Shot 2015-12-06 at 10.58.28 PM.png

一旦ここまでのものがコミットされます。

Screen Shot 2015-12-06 at 11.00.35 PM.png

status に要素を追加するために、マウスカーソルを合わせると右に出てくる + を押します。

Screen Shot 2015-12-06 at 11.02.32 PM.png

name: message, value: "" と入力して Add を押します。

Screen Shot 2015-12-06 at 11.03.32 PM.png

最終的にはこんな形になっているはずです。

Screen Shot 2015-12-06 at 11.04.15 PM.png

JSON で表現するとこんなシンプルな感じのデータです。

{
  "status": {
    "active": true,
    "message": ""
  }
}

これでデータの準備ができました。

セキュリティ&ルールの設定

Firebase では読み書き権限やバリデーションのルールを Security & Rules から JSON 形式で設定します。

Screen Shot 2015-12-06 at 8.20.49 PM.png

今回の status は読み取りは誰でもできてよく、書き込みは認証された人しか書けてはいけないので、以下のように設定します。

{
  "rules": {
    "status": {
      ".read": true,
      ".write": "auth != null"
    }
  }
}

詳しくはセキュリティのドキュメントをご参照ください。

アプリ側の実装

まず、 Firebase を Application#onCreate() で初期化します。

Firebase.setAndroidContext(this);

次に、先ほど定義した status を受け取る POJO なオブジェクトを用意します。オプションですが、 @IgnoreJsonProperties アノテーションを付けておくと後々キーを追加したときにエラーにならずに済みます。

@JsonIgnoreProperties(ignoreUnknown = true)
public class Status {
    private boolean active;
    private String message;

    public Status() {
    }

    public boolean isActive() {
        return active;
    }

    public String getMessage() {
        return message;
    }
}

次に、実際に受け取る部分を書きます。 Firebase ではデータの受け取りは常に非同期でコールバック経由で行われます。

Firebase statusRef = new Firebase(FIREBASE_ROOT);
statusRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        Status status = dataSnapshot.getValue(Status.class);
        // ここで通知のブロードキャストを投げる
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {
        // エラー時の処理、今回は特に何もしない
    }
});

FIREBASE_ROOT には、 Firebase のデータ URL を書きます。これは先のデータ編集画面で、 status をクリックして開いた (status がルートになった) 状態のブラウザのアドレスバーに表示される URL そのものです。今回の場合は以下のアドレスになります。

https://fiery-inferno-8489.firebaseio.com/status

今回のアプリのメンテナンス情報は、アプリケーション全体で反映されるべき情報なので、この処理も Application#onCreate() のタイミングで呼ばれるようにします。 Activity などではブロードキャストイベントを受信して画面に反映するようにします。サンプルではブロードキャストにはイベント通知に実装が手軽な otto を使っていますが、 LocalBroadcastManager でも何でも問題ありません。

あとは受け取る側の Activity 側の実装を書きます。

@Override
protected void onResume() {
    super.onResume();
    // イベントリスナを登録
    MaintenanceMode.bus().register(this);
}

@Override
protected void onPause() {
    super.onPause();
    // イベントリスナの登録解除
    MaintenanceMode.bus().unregister(this);
}

@Produce
public MaintenanceMode.Status getCurrentMaintenanceMode() {
    // 起動直後に呼び出される処理。現在知りうる最新のデータを返す。
    return MaintenanceMode.current();
}

@Subscribe
public void maintenanceModeChanged(MaintenanceMode.Status status) {
    // statusに変更があった場合に呼び出されるのでSnackbarを表示/非表示にする
    if (!status.isActive()) {
        bar = Snackbar.make(findViewById(R.id.coordinatorLayout), status.getMessage(), Snackbar.LENGTH_INDEFINITE);
        bar.show();
    } else if (bar != null && bar.isShown()) {
        bar.dismiss();
    }
}

できあがった最終的な実装はこちら

まとめ

ちょっとした仕組みですが、いざというときにあるととても役立ちます。機能追加の例としては、

  • URL を付けられるようにしてお知らせページへ誘導する
  • アプリケーションのバージョンやOSのバージョンにあわせて出し分けられる
  • FirebaseのREST APIを叩いてSlackから更新する仕組みを作る

などなどが考えられます。

ちなみに Firebase は同時 100 接続までは無料ですが、それを超える接続数を捌くためには月額 $25 からの利用料が掛かります。ただそれで接続数が無制限になるので、お仕事としてアプリを運用しているならサーバ代と考えれば全然元は取れると思います。もちろん普通にバックエンドとして使っても便利です。

追記

実機で試せるようにしてみました: Download to device

いま上のgifのように10秒に1回メンテになったり問題解消したりするbotを動かしてます。実際に手元で起動直後の動作を見たり、マナーモードにして実際に通信切ってみたりしたときの動作が試せます。

87
83
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
87
83

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?