Posted:
Posted by 荒木 佑一 Developer Relations Team

「本記事は Google I/O 2014 アプリのテクニカル リード Bruno Oliveira が 8 月 27 日に Android Developers blog に投稿した「Conference Data Sync and GCM in the Google I/O App」という記事を元に、翻訳・作成しています。詳しくは元記事をご覧ください。 - 荒木」

クラウドとのデータ同期は多くのアプリで重要な部分です。Google I/O アプリも例外ではありません。これにあたって標準の Android 機能を存分に活用しました。SyncAdapter です。SyncAdapter を使うことにより、例えば定期的にアラームを仕掛けたりするような原始的なやり方に比べてたくさんのメリットがあります。システムがバッテリー効率を勘案して自動的に SyncAdapter のスケジューリングを行ってくれるからです。

データはローカルの SQLite データベースに格納してあります。ただし、アプリのあらゆる部分が直接データベースにアクセスするようなことはせず、Android 標準のメカニズムに従ってデータへのアクセスを管理しています。皆さんお分かりの通り、ContentProvider を利用しています。SQLite データベースに直接アクセスするのは ContentProvider だけです。アプリの他の部分は ContentProvider を通してのみデータにアクセスするようにします。これにより、データベース内でのデータ表現とアプリ中での表示を切り離して、柔軟に構成することができます。

I/O アプリは大きく分けて 2 種類のデータを扱います。カンファレンス データ(セッション、講演者、部屋など)と、ユーザー データ(ユーザー個人のスケジュール)です。カンファレンス データが Google Cloud Storage に格納された JSON ファイルからデバイスへの一方向にのみ同期されるのに対して、ユーザー データはユーザーの Google Drive AppData フォルダーとの間で双方向に同期されます。



カンファレンス データの効率的なダウンロード

Google I/O ともなると、カンファレンス データはかなり巨大です。セッション、部屋、講演者、地図の位置情報、ソーシャル ハッシュタグ、ビデオ ライブラリの項目など、すべての情報が入っています。すべてのデータを繰り返しダウンロードするのはバッテリー使用料や通信容量の観点から見て無駄です。そこで我々は、どうすればダウンロードしたり処理したりするデータの量を最小化できるか検討しました。

検討の結果、データをいくつかの JSON ファイルに分割し、それらをメインの JSON ファイル(「マニフェスト ファイル」)から参照するようにしました。マニフェスト ファイルの URL が唯一アプリの中に埋め込まれている URL です(Config.javaMANIFEST_URL 定数)。JSON ファイルを置いておくための場所として、今回の I/O アプリでは Google Cloud Storage を利用しましたが、HTTP でアクセスできる同様のホスティング サービスであれば同じように利用できます。

同期プロセスの最初の部分でマニフェスト ファイルを見に行きます。前回ダウンロードしたときから変更があるかチェックし、ある場合のみ処理します。このロジックを実装しているのは RemoteConferenceDataFetcher クラスの fetchConfenceDataIfNewer メソッドです。

public class RemoteConferenceDataFetcher {
    // (...)
    public String[] fetchConferenceDataIfNewer(String refTimestamp) throws IOException {
        BasicHttpClient httpClient = new BasicHttpClient();
        httpClient.setRequestLogger(mQuietLogger);
        // (...)

        // データが refTimestamp より新しいときのみダウンロード
        if (!TextUtils.isEmpty(refTimestamp) && TimeUtils
            .isValidFormatForIfModifiedSinceHeader(refTimestamp)) {
                httpClient.addHeader("If-Modified-Since", refTimestamp);
            }
        }

        HttpResponse response = httpClient.get(mManifestUrl, null);
        int status = response.getStatus();
        if (status == HttpURLConnection.HTTP_OK) {
            // データに変更があったので処理する
        } else if (status == HttpURLConnection.HTTP_NOT_MODIFIED) {
            // サーバーのデータに変更はないので何もしない
            return null;
        } else {
            // (エラー処理)
        }
    }
    // (...)
}

HTTP リクエストに If-Modified-Since ヘッダーがついていることに注意してください。もしマニフェストに前回から変更がない場合、レスポンス コードとして HTTP_OK ではなく HTTP_NOT_MODIFIED が返ってくるので、後はダウンロードもパースも必要ありません。つまり、マニフェスト ファイルに変更がない限り同期プロセスは実に経済的です。HTTP リクエストが 1 回で、レスポンスもとても短いものです。

マニフェスト ファイルのフォーマットは単純明快です。カンファレンス データを含んだ JSON ファイルへの参照が並んでいます。

{
  "format": "iosched-json-v1",
  "data_files": [
    "past_io_videolibrary_v5.json",
    "experts_v11.json",
    "hashtags_v8.json",
    "blocks_v10.json",
    "map_v11.json",
    "keynote_v10.json",
    "partners_v2.json",
    "session_data_v2.681.json"
  ]
}

同期プロセスは次にデータ ファイルを順に処理していきます。この部分も経済的に実装してあります。すでにデータ ファイルのキャッシュがある場合は処理を省略してローカルのキャッシュを使います。この部分は processManifest メソッドで行っています。

JSON ファイルをそれぞれパースして情報をメモリに乗せ、最後にまとめて ContentProvider に書き込みます。

ContentProvideOperation の効率的な実行

カンファレンス データの同期は効率的でなければなりませんが、それはダウンロードするデータの容量だけでなく、データベースに対して実行する処理の量についても同様です。出来る限り経済的に行うために、この部分も最適化されています。データベース全体を新しいデータで上書きするのではなく、既存のローカル データを活かして変更のあるデータだけ更新するようにするのです。

これは 3 階層目の重要な最適化です。これを実現するためには、アプリがメモリ上の情報と ContentProvider 内の情報を比較して、更新の必要があるかどうか判別できなければなりません。メモリーとデータベースのフィールドを 1 つずつ見ていってもいいのですが、手間ですし、すべてのフィールドを見ていくのでパフォーマンスが劣ります。その代わりに、今回は各エンティティーに「インポート ハッシュコード」というフィールドを付け加えることにしました。インポート ハッシュコードはデータから生成される弱いハッシュです。例えば、講演者のデータに対するインポート ハッシュは以下のように求められます。

public class Speaker {
    public String id;
    public String publicPlusId;
    public String bio;
    public String name;
    public String company;
    public String plusoneUrl;
    public String thumbnailUrl;

    public String getImportHashcode() {
        StringBuilder sb = new StringBuilder();
        sb.append("id").append(id == null ? "" : id)
                .append("publicPlusId")
                .append(publicPlusId == null ? "" : publicPlusId)
                .append("bio")
                .append(bio == null ? "" : bio)
                .append("name")
                .append(name == null ? "" : name)
                .append("company")
                .append(company== null ? "" : company)
                .append("plusoneUrl")
                .append(plusoneUrl == null ? "" : plusoneUrl)
                .append("thumbnailUrl")
                .append(thumbnailUrl == null ? "" : thumbnailUrl);
        String result = sb.toString();
        return String.format(Locale.US, "%08x%08x", 
            result.hashCode(), result.length());
    }
}

データベースでエンティティーが更新されるたびにそのインポート ハッシュがデータベースのカラムに保存されます。後から更新されている可能性のあるバージョンがあるときは、そのインポート ハッシュを計算してデータベースに保存されているバージョンのインポート ハッシュと比較するだけでいいのです。値が異なる場合は、データベースのエンティティーを更新する ContentProviderOperation を実行します。値が同一の場合、そのエンティティーの更新は必要ないので飛ばします。この部分の処理は SpeakersHandler クラスの makeContentProviderOperations メソッドなどで見ることができます。

public class SpeakersHandler extends JSONHandler {
    private HashMap mSpeakers = new HashMap();
    // (...)
    @Override
    public void makeContentProviderOperations(ArrayList list) {
        // (...)
        int updatedSpeakers = 0;
        for (Speaker speaker : mSpeakers.values()) {
            String hashCode = speaker.getImportHashcode();
            speakersToKeep.add(speaker.id);

            if (!isIncrementalUpdate || !speakerHashcodes.containsKey(speaker.id) ||
                    !speakerHashcodes.get(speaker.id).equals(hashCode)) {
         // スピーカーが更新されているので、ContentProviderOperation を発行する
                ++updatedSpeakers;
                boolean isNew = !isIncrementalUpdate || 
                 !speakerHashcodes.containsKey(speaker.id);
                buildSpeaker(isNew, speaker, list);
            }
        }

 // 古いスピーカーを削除
        int deletedSpeakers = 0;
        if (isIncrementalUpdate) {
            for (String speakerId : speakerHashcodes.keySet()) {
                if (!speakersToKeep.contains(speakerId)) {
                    buildDeleteOperation(speakerId, list);
                    ++deletedSpeakers;
                }
            }
        }
    }

buildSpeaker()buildDeleteOperation() というメソッド(ここでは中身を省略しています)はただ単に ContentProviderOperation を組み立てるだけです。ContentProvider にスピーカーの情報を挿入、更新、削除する ContentProviderOperation です。ここで注目していただきたいのは、ContentProviderOperation でスピーカー情報を更新するのはインポート ハッシュコードが変わったときだけだということです。また、以前は存在していたのに今はデータ上で参照されていないスピーカー情報はもう必要ないので、削除を行っています。

同期の信頼性を高める

I/O アプリの SyncAdapter はカンファレンス データの同期、ユーザーのスケジュールの同期、ユーザーからのフィードバックの同期など、いくつかのタスクを担っています。ネットワークの状態によっては、そういったタスクが途中で失敗することもあり得ます。それでも、あるタスクが失敗したからといって他のタスクを巻き添えにするのは好ましくありません。そのため、同期のプロセスはそれぞれ独立したタスクで構成されています。つまり、それぞれが try/catch ブロックで保護されています。これは SyncHelper クラスの performSync() メソッドでご覧いただけます。

// リモート同期を構成するタスク。
// 1 つづつ実行される(どれかが失敗しても他に影響を与えない)。
final int OP_REMOTE_SYNC = 0;
final int OP_USER_SCHEDULE_SYNC = 1;
final int OP_USER_FEEDBACK_SYNC = 2;

int[] opsToPerform = userDataOnly ?
        new int[] { OP_USER_SCHEDULE_SYNC } :
        new int[] { OP_REMOTE_SYNC, OP_USER_SCHEDULE_SYNC, OP_USER_FEEDBACK_SYNC};

for (int op : opsToPerform) {
    try {
        switch (op) {
            case OP_REMOTE_SYNC:
                dataChanged |= doRemoteSync();
                break;
            case OP_USER_SCHEDULE_SYNC:
                dataChanged |= doUserScheduleSync(account.name);
                break;
            case OP_USER_FEEDBACK_SYNC:
                doUserFeedbackSync();
                break;
        }
    } catch (AuthException ex) {
        // (... 認証エラーの後始末 ...)
    } catch (Throwable throwable) {
        // (... その他のエラーの後始末 ...)

        // 例外の発生をシステムに知らせる
        if (syncResult != null && syncResult.stats != null) {
            ++syncResult.stats.numIoExceptions;
        }
    }
}

同期プロセスの一部が失敗したときは syncResult.stats.numIoExceptions をインクリメントしてシステムに知らせます。こうしておけば、システムがあとで指数バックオフを利用しつつ同期を再試行します。

同期をいつ行うか。GCM の世界にようこそ。

カンファレンス データの更新を迅速に受け取ることができるかどうかは、ユーザーにとってとても重要です。特に Google I/O の最中(と、その前数日)はそうです。これを実現する方法として安易に思いつくのはアプリがサーバーをポーリングすることですが、当然のごとくそれでは通信帯域やバッテリーを多量に消費してしまいます。

より洗練された解決法として、我々は GCM (Google Cloud Messaging) を利用することにしました。サーバー側に新しいデータがあるときは、サーバーからすべての登録済みデバイスに GCM メッセージを送るのです。GCM メッセージを受け取ったデバイスは同期を開始して新しいカンファレンス データをダウンロードします。GCM メッセージを処理しているのは GCMIntentService クラスです。

public class GCMIntentService extends GCMBaseIntentService {

    private static final String TAG = makeLogTag("GCM");

    private static final Map MESSAGE_RECEIVERS;
    static {
        // 既知のメッセージと GCM メッセージの受信部
        Map  receivers = new HashMap();
        receivers.put("test", new TestCommand());
        receivers.put("announcement", new AnnouncementCommand());
        receivers.put("sync_schedule", new SyncCommand());
        receivers.put("sync_user", new SyncUserCommand());
        receivers.put("notification", new NotificationCommand());
        MESSAGE_RECEIVERS = Collections.unmodifiableMap(receivers);
    }

    // (...)

    @Override
    protected void onMessage(Context context, Intent intent) {
        String action = intent.getStringExtra("action");
        String extraData = intent.getStringExtra("extraData");
        LOGD(TAG, "Got GCM message, action=" + action + ", extraData=" + extraData);

        if (action == null) {
            LOGE(TAG, "Message received without command action");
            return;
        }

        action = action.toLowerCase();
        GCMCommand command = MESSAGE_RECEIVERS.get(action);
        if (command == null) {
            LOGE(TAG, "Unknown command received: " + action);
        } else {
            command.execute(this, action, extraData);
        }

    }
    // (...)
}

ここでは onMessage() メソッドが GCM メッセージの "action" フィールドに従ってメッセージを適切なハンドラーに引き渡しています。action フィールドが "sync_schedule" なら、メッセージは SyncCommand クラスのインスタンスに引き渡され、同期が開始します。ちなみに、SyncCommand クラスに対する GCM メッセージには jitter というパラメーターを指定することができます。同期は即座に行われるのではなく、jitter で指定された猶予期間までのランダムなタイミングで実行されます。これにより、全てのクライアントが一斉にリクエストを投げてサーバー側が大量のリクエストにさらされることを防いでいます。

ユーザー データの同期

I/O アプリではユーザーが興味のあるセッションを選択して自分個人のスケジュールを作ることができるようになっています。これは「私のスケジュール」画面で見ることができます。

このデータはユーザーが利用している複数の Android 端末間で同期され、さらに I/O のウェブサイトとも同期される必要があります。つまり、ユーザーの Google アカウントを使ってクラウド上にデータを保存する必要があります。今回は Google Drive AppData フォルダーを利用することにしました。

ユーザー データは SyncHelper クラスの doUserScheduleSync() メソッドで Google Drive に同期されます。ソース コードを見ていただくとわかりますが、このメソッドでは Google Drive HTTP API を使って Google Drive AppData フォルダーにアクセスし、ユーザーが登録したセッションのデータをクラウド上と端末内とですり合わせた上で、必要に応じてクラウド上のデータにローカルでの変更を反映しています。

つまり、ユーザーが Android 端末であるセッションを登録し、I/O のウェブサイト上で別のセッションを登録した場合、Android 端末と I/O ウェブサイトともに、両方のセッションが登録されている状態に同期されます。

これに加えて、ユーザーが I/O のウェブサイトでセッションを登録したり解除したりした場合にデータがすべての Android 端末に同期される必要があります。逆方向も同様です。このため、ユーザーがスケジュールに変更を行うたび、I/O ウェブサイトから GCM サーバーに通知を送ります。GCM サーバーはユーザーが利用しているすべてのデバイスに GCM メッセージを送信し、同期が開始されます。同じメカニズムが複数の端末の間でも働きます。つまり、あるデバイスでデータが更新されれば、他のすべてのデバイスに GCM メッセージが送信されるのです。

まとめ

データを新鮮に保つことは多くの Android アプリで肝心な機能です。ネットワーク通信やデータベースへの書き込みを最小限にとどめつつデータを最新に保ち、Google Cloud Storage や Google Drive や Google Cloud Messaging を活用して、複数のプラットフォーム、複数の端末間でデータの同期を取るなど、I/O アプリの開発での我々の取り組みについてご紹介しました。

Posted:
Posted by 鈴木拓生 Developer Relations Team

Google Developer Group(GDG)コミュニティの皆さんが今年の 8 月 〜 9 月に Android Wear の勉強会を日本各地で開催します。

今年の Google I/O で新たな SDK も公開され、日本でも実機の発売が開始されたのをきっかけに、GDG のコミュニティが主導となって 実際に Android Wear で 「どんなアプリが作れるのか」、「実際にアプリを作ってみる」を開発者の皆さまと一緒に学ぶ勉強会になっています。

参加を希望される方は、会場毎に詳細と参加お申し込みフォームが用意されていますので、ご確認のうえ登録ください。

イベント概要

■Android Wear Hackathon in Tokyo 2014-8
日程:2014 年 8 月 30 日(土)11:00 - 18:00
場所:Google 東京オフィス、六本木ヒルズ森タワー
定員:60 名
主催:GDG 東京
詳細:http://goo.gl/vo8jVh


■Global Android Dev Camp in 信州
日程:2014 年 9 月 12 日(金)18:00 - 21:30(アイデアソン)、9 月 13 日(土)09:00-18:00 (ハッカソン)
場所:9 月 12 日:塩尻インキュベーションプラザ SIP、9 月 13 日:扉温泉檜の湯(松本市)
定員:15 名
主催:GDG 信州
詳細:世界同時期開催の Global Android Dev Camp を GDG 信州でも行います。
9 月 12 日はアイデアソン、9 月 13 日はハッカソンを予定しています。
申込みhttp://goo.gl/mQNUvF


■ Global Android Dev Camp Kyoto 2014
日程:2014 年 9 月 13 日(土)10:00 - 17:30 ハッカソン
場所:京都リサーチパーク
定員:20 名
主催:GDG 京都
詳細:http://goo.gl/yLARA9
申込み:http://goo.gl/PKXwiD


■DevFest Shikoku - Android Wear Hackathon in Shikoku 2nd
日程:2014 年 9 月 28 日(日)10:00 - 17:00
場所:JR 高松駅前 e -とぴあ・かがわ クラスルーム C
定員:15 名
主催:GDG 四国
共催:日本 Android の会 香川支部
詳細:http://goo.gl/HRHSRF
申込み:http://goo.gl/aXsr80


開発者の皆さまのご参加をお待ちしております。

※四国会場の情報を追記いたしました

Posted:
Posted by 鈴木拓生 Developer Relations Team

Google Developer Experts は Google が認定した API やツールに精通したデベロッパーの方々です。現在、18 名の Google Developer Experts が 8 つ の準公式コミュニティを運営し、Google が提供する API やツールを利用するデベロッパーの皆さまをサポートしています。

【準公式コミュニティと Google Developer Experts の一覧】
http://sites.google.com/site/devreljp/Home/api-expert

それでは、7 月 8 日に開催された 第 67 回 Google Developer Experts ミーティングの内容から、いくつかハイライトをお伝えします。

【全体会より】

すでにお気づきの方もいるかもしれませんが、今回から会の名称を API Expert から Google Developer Experts に変更しています。 Google Developer Experts は日本で始まった API Expert の仕組みを世界に広げた際につけられた名称です。

Google Developer Experts

日本では API Expert という名称が馴染みであったため、この名称を使い続けていましたが、今年の Google I/O を機会に日本でも Google Developer Experts の名称に統一することにしました。

名前は変わりましたが、これからも Google Developer Experts をよろしくお願いします。

【分科会より】
各技術分野で行われている分科会からいくつかトピックを取り上げます。
今回は Google I/O の基調講演やセッションで発表された技術やツールの話題が多くを占めました。これらはすべて YouTube で公開されています。下記の URL からぜひご覧ください。

- Google I/O 2014 セッションビデオ [http://goo.gl/cZDlTs]

次回の Google Developer Experts ミーティングは、 2014 年 8 月下旬に開催を予定しています。

Posted:
Posted by 北村英志 Developer Relations Team

[Web Music Developers JP 代表の河合良哉さんから Web Music ハッカソンについての寄稿を頂きました。- 北村]

9 月 13 日 (土) Google 東京オフィスにて Web Music ハッカソン #3 を開催します


Web Music ハッカソンとは、ブラウザで音声処理・信号処理を行う Web Audio API、外部 MIDI デバイスと連携をする Web MIDI API など、ブラウザの持つ音楽に関わる API を中心に使って、一日でアプリケーションを制作しよう、というイベントです。

Web Audio API、Web MIDI API を使ったアプリケーション、また他の Web API(Web RTC、Web Speech API、WebGL など)や MIDI デイバスを含めた外部ガジェットと連携するアプリケーション等、アイデア次第で可能性は無限大です。

前回のハッカソンでは Google Spreadsheet をシーケンサーにして音楽を鳴らす Chrome Extension が最優秀賞を受賞しました。

 

詳しい当日の様子はこちらのページをご覧ください。

なお、今回のイベントでは、前日の 9 月 12 日にベルリン(ドイツ)で開催される JS Fest Berlin の Web Audio Hackday とコラボレーションを行う予定です。詳しい内容は未定ですが、優秀作品はお披露目できるチャンスがあるかもしれません。

イベント詳細

名称 : Web Music ハッカソン#3
会場 : Google 東京オフィス
日時 : 2014 年 9 月 13 日 (土) 10:00-18:00
会費 : 無料
主催 : Web Music Developers JP、Google
協賛 : AMEI (一般社団法人 音楽電子事業協会)
参加資格: Web Audio API、Web MIDI API に関するアプリケーション開発に関心をお持ちの方 (JavaScript を書いたことのある方)
定員 : 50 名

お申込み方法:こちらのお申込みフォームからご登録ください。(先着順 50 名まで)
参加できる方には 9 月 5 日までに参加証をメールにて送付いたします。

Google+ イベント:本イベントにおける質問、連絡等のコミュニケーションおよび、当日の様子を写真やビデオで残す目的で、Google+ イベント を利用します。ぜひご活用下さい。
https://plus.google.com/events/cqvnr68c6r4b43dikum0kaljme4

(この Google+ Event の参加に「はい」を設定しただけでは、会場に入場できません。必ず上記の申込みフォームからお申込みください。)

注意事項:
  • お申込みフォームに当日ハックしたいアイデアを記入していただきます。ハッカソン当日、チームを組みやすくする目的で、お名前は伏せさせていただき事前にオンラインで共有させていただく可能性がありますのであらかじめご了承ください。
  • 音が出るアプリケーションが多くなると予想されますので、ヘッドフォンまたはイヤホンのご持参をお願い致します。
  • チームで参加される方も、チーム全員のお申し込みが必要となります。
  • 当日使われる PC はご自身で準備をお願いします。
提供予定楽器 :今回も、協賛である AMEI 様のご協力により、クリムゾンテクノロジー、KORG、Roland、Yamaha も各社から楽器、ガジェットを提供する予定です。

[クリムゾンテクノロジー]
[KORG]
[Roland]
[Yamaha]

よくある質問

ハッカソンとは何ですか?

ハッカソンとは、新しい技術を試す 1 つのおもしろい方法です。通常チームで行われ、新しいフレームワーク、API、言語を試すことに焦点を置いたイベントです。各チームでは実験を行ったり、アプリケーションを作ったりします。イベントの最後にはお互いの成果を評価し、表彰されることもあります。

誰が参加できますか?

経験豊富なソフトウェア開発者、ハードウェア開発者から、Web Music のアプリケーション開発に興味のある方を歓迎いたします。Web Audio API、Web MIDI API をハックする絶好の機会です。当日は楽器、ガジェットの提供を予定しておりますので、思う存分効率よくハックをしていただけます。

チームメンバー全員の参加登録が必要ですか?

はい。参加者の人数把握と入館の手続き上、チームで参加される場合もチームメンバー全員の登録をお願いします。

チームに入らないといけませんか?

いいえ、必ずしもチームに入る必要はありません。ただし、これまでのハッカソンをでは仲間が 1 人ないし 2 人はいた方がより多くのことを得られるようです。

チームに入りたいのですが、知っている人がいません。どうすればよいですか?

心配はご無用です。お申込時にアイデア、またはその欠片をご記入いただきます。その情報を元にしてハッカソンのキックオフ時にテーマの発表を行いますので、その場でご興味のあるテーマのチームに参加していただけます。



これまでのハッカソンの様子はこちら(#1#2)からご覧頂けます。

Posted:
Posted by 荒木佑一Developer Relations Team

「本記事は Google I/O 2014 アプリのリードデザイナー Roman Nurik が 8 月 5 日に Android Developers blog に投稿した「Material design in the 2014 Google I/O app」という記事を元に、翻訳・作成しています。詳しくは元記事をご覧ください。 - 荒木」

Google I/O では毎年カンファレンス用のアプリを公開しています。これには 2 つの目的があります。まず、カンファレンスの参加者や自宅からのリモート参加者が自分のスケジュールを管理したり講演を閲覧したりするための手引きとしての役割です。しかし、場合によってはもう 1 つの目的の方が重要かもしれません。Android におけるデザインや開発のベスト プラクティスを示すリファレンス デモとしての役割です。

先週 Google I/O 2014 アプリのソース コードを公開したことをアナウンスしました。カンファレンスの間ご利用いただいた様々な機能やデザインがどのように実現されているかご覧頂くことができます。今回の投稿では、今年のアプリがどのような意図のもとデザインされているかご紹介します。


デザインを重視するにあたって、今年の I/O アプリではマテリアル デザインという新しいアプローチを採用しました。また、Android L Developer Preview の機能も利用することで、合理的で一貫性があり、わかりやすく美しいものに仕上げました。このアプリをデザインしていく上での様々な判断とその成果について見ていきましょう。

表面と影

マテリアル デザインでは、アプリの構造をユーザーに伝えるにあたって表面と影が重要な役割を担います。マテリアルデザインの仕様をご覧いただくと、いつどこで影が表示されるべきか判断するためのレイアウト指針が示されています。例として、スケジュール画面をデザインしていく途中段階をご覧に入れます:

第一段階

第二段階

最終段階



第一段階にはいくつか問題がありました。まず、アクション バーの下に影があることで、「紙」が 2 枚あることを印象付けています。片方はアクション バーの紙で、もう一方はタブとコンテンツの紙です。問題は、下になっている方の紙が複雑すぎることです。コンテンツの紙を表現する「インク」はもっとシンプルであるべきです。この段階ではインクがあまりに多くのことをしすぎていて、視覚的な煩雑さが出てしまっています。タブだけをさらに別の紙として分離することもできたのですが、アクション バーとコンテンツの間にもう 1 つ層が重なるというのは、それはそれで煩わしいでしょう。

第二段階と最終段階では、アプリの機能部とコンテンツ部を明確に分離することで、インクがテキスト、アイコン、強調線などの描画に集中できています。


他にも「表面」の概念が重要な役割を果たしている画面があります。詳細画面です。
最初にリリースした時は、トップのバナーがセッションのテーマ カラーから画像に徐々に変わっていくようになっていました。画像は他のコンテンツの半分のスピードで動き、パララックス効果を生み出しています。しかし、このデザインではマテリアル デザインにおける物理法則が曲げられすぎているように感じられました。紙にテキストが書かれているにも関わらず、その透明度がアニメーションに従って変化してしまうのです。

6 月 25 日のアップデートでは新しいアプローチを導入しました。タイトルのテキストを表示する部分を小さな紙として分離しました。この紙の色と透明度は変化しません。スクロールする前は本文の紙とくっついて一体となっています。スクロールするに従い、この紙(とその上に付いた Floating Action Button)が浮かび上がり、本文の紙が下をスクロールしていきます。
これにより、マテリアル デザインの世界での物理法則に準じた挙動が得られました。ユーザーから見て理路整然とした外観、操作、動作に結びついています(こちらのコードを参照してください: Fragment, レイアウト XML) 。



マテリアル デザインの重要な原則は、インターフェイスが「大胆で視覚的で意図的」であり、印刷品質のデザインで作られた基本的な要素によって視覚的な表現を支えることです。そのような要素を 2 つ見ていきましょう。色とマージンです。

マテリアル デザインにおいて UI 要素のカラー パレットは一般に 1 つの主要色と 1 つの強調色によって成り立ちます。大きな色領域(例えばアクション バー)にはシェード 500 の主要色を使い、小さな色領域(例えばステータス バー)には暗いシェード 700 を使うようにします。

強調色が用いられるのはアプリの中の少しだけです。重要な要素に注目を集めるために用います。控えめな主要色と鮮やかな強調色のコントラストによって、アプリのコンテンツの魅力を活かしたまま、大胆でカラフルな印象を付け加えることができます。

I/O アプリでは状況に応じて 2 つの強調色を使い分けました。ほとんどの場合はピンク 500 ですが、セッションのテーマ カラーに隣接する「スケジュールに追加」ボタンのような場合には控えめに ライト ブルー 500 を用いました(コードを参照: XML 色定義テーマ XML)。


セッションのテーマ カラーについては、セッションごとの主要トピックによって詳細画面を色分けしました。Floating Action Button やセッション画像と一貫性のある明るさや最適なコントラストを考え、ベースとなるマテリアル デザインのカラー パレットにほんの少し変更を加えただけです。


最終的には以下のようなカラー パレットになりました。

セッションのテーマ カラー。FAB と隣り合わせに並べてコントラストがわかりやすくしてあります

彩度を落としたテーマ カラー。パレット上すべてで明るさに一貫性があることがわかります





マージン

「伝統的な印刷デザイン」に欠かせない要素として他に思いつくものといえば、マージン、厳密に言えばキーラインです。縦向きの配置としては 4dp のグリッドは既に馴染み深い(ボタンや単純なリスト項目は 48dp、標準のアクション バーは 56dp など)ですが、キーラインに関するガイドラインはマテリアル デザインで新しく導入されました。特に、タイトルなどのテキスト項目をキーライン2(電話では 72dp、タブレットでは 80dp)とすることで、画面に印刷物のような美しいリズムが生まれ、快適に画面上の情報を読み取ることができるようになりました。ゲシュタルト原理の賜物ですね。

グリッド

もう 1 つ、マテリアル デザインの原則として「1 つの適応性のあるデザイン」があります。

1 つの根本的なデザイン システムが操作と空間を統合します。1 つの根本的なデザインがデバイスごとに違った外観で反映されます。外観は各デバイスのサイズや操作方法に最適な形で調整されます。色、アイコン、構造、空間的関係はそのままです。

さて、I/O アプリの画面の多くはセッションの集合です。集合を表示するために、マテリアル デザインはいくつかのコンテナーを提供しています。カード、リスト、グリッドなどです。当初セッション項目を表示するためにはカードを利用することを考えていたのですが、各要素はほとんど同質であるため、この場合にカードを使うのは適切ではないということになりました。カードを使う場合、影や丸角がどうしても視覚的に乱雑になりがちで、コンテンツごとの視覚的なグルーピングとしてもこの場合対して役にたちません。グリッドの方がいい選択でしょう。画面の大きさに従ってカラムの数を変えることもできますし(コード を 参照)、テキストと画像をひとまとめにしてスペースを節約することもできます。

楽しい細部

アプリの中で L Developer Preview に関して特に手間ひまかけて仕上げた部分を 2 つ紹介しましょう。タッチ フィードバックの波紋と、「スケジュールに追加」の Floating Action Button です。

アプリでは波紋のスタイルとして境界線があるものとないもの両方を使いました。必ず波紋の色をカスタマイズして、背景に関わらず見やすい(それでいて控えめ)ようにしました(コードを参照: 明るい波紋暗い波紋)。

特に我々開発陣が気に入っているのが、セッションが自分のスケジュールに追加されているかどうかを示す Floating Action Button です。


これを実現するため、L プレビューの様々な API メソッドを(互換性のための実装を含め)利用しました。

1)View.setOutline と setClipToOutline で、円形の境界線と動的な影の描画
2)android:stateListAnimator で、押下時に指に向かって浮かび上がるように(影が濃くなる)
3)RippleDrawable で、押下時のインク タッチ フィードバック
4)ViewAnimationUtils.createCircularReveal で、青と白の背景が切り替わる効果
5)AnimatedStateListDrawable で、アイコンの状態(チェック・非チェック)を切り替えるフレーム アニメーションを定義

これらのおかげで使っていて楽しい UI 要素ができました。我々開発陣も気に入っています。開発者の皆様のインスピレーションとなることを願っています。単にコードをコピーして使っていただくのも歓迎です。

今後の展開

コードを使っていただくといえば、L Develoepr Preview と互換性のためのコード パスを含めて、アプリのソース コードはすべて公開されています。以上のようなデザインがどのように実装されているか、ぜひご覧ください。

今回の投稿ではマテリアル デザインを使って、プラットフォームを最大限活用する美しい Android 向けアプリを作る方法をご紹介しました。皆様のお役に立つことを願っています。今後数週間にわたって今年の I/O アプリに関する情報をお伝えしていきます。ユーザーに対して良い体験を提供するための開発手法をさらにご紹介できればと思います。

Posted:
Posted by 荒木佑一 Developer Relations Team


Google I/O アプリの 2014 年版のソースコードが公開されました。I/O が開催される数週間前に Google Playリリースされて以来、何十万人ものユーザーにダウンロードされています。このアプリは I/O 参加者にとって役立つのはもちろん、Android アプリ開発のベスト プラクティスを示す実用的な例として参照していただくことも意図して設計されています。

I/O アプリでは多くの Android アプリで利用する様々な機能も利用しています。Fragment、Loader、Service、BroadcastReceiver、アラーム、通知機能、SQLite データベース、ContentProvider、ActionBar、NavigationDrawer などです。また、Google Drive APIGoogle Cloud Messaging といった、各種 Google 製品やサービスを統合する方法もご覧いただけます。マテリアル デザインAndroid L Preview の API、Android Wear のウェアラブル アプリからセッションのフィードバックを送信する機能なども実装されています。

皆さんがソース コードを簡単に再利用・カスタマイズして他のカンファレンス向けに活用していただけるように、カンファレンスのデータ同期処理は特定のバックエンドに依存しないようになっています。特別なサーバー側の API は必要ありません。今回の I/O アプリは JSON ファイルからカンファレンスのデータ (セッション、スピーカー、部屋など) を読み込むようになっています。ファイルはどんなウェブ サーバーでも開発者の皆さんがやりやすい形でホストしていただけます。フォーマットに関してはこちらにドキュメントがあります。

ユーザー データ (ユーザーが追加したセッションのスケジュール) の保存・同期はこのアプリの肝となる部分です。ソースコードをご覧いただくと、ユーザー データがそれぞれのユーザー自身の Google Drive のアプリケーション データ フォルダーに保存され、複数の端末で同期を取るようになっていることがお分かりいただけるかと思います。また、Google Cloud Messaging を適切なタイミングで利用することで各端末のデータを常に最新に保つ方法などもご参照ください。

App Engine 上で実装された GCM サーバーのコードも再利用可能な形で含まれています。データ同期を開始するメッセージを端末に送信するだけでなく、他のバックエンドからカンファレンスのデータを読み込んで、アプリから取得する JSON ファイルを生成するためのモジュール (Updater) もご覧いただけます。

このソース コードを開発者コミュニティーと共有できることを大変嬉しく思っています。学習ツールとして、再利用可能なコード スニペットとして、また、Android 開発一般についての実例として、存分に活用されることを願っています。これから数週間にわたって IOSched のソース コードに関する詳細な技術情報を投稿していきます。アプリの開発プロセスについてもご紹介する予定です。今後もアプリの更新を続けていきます。いつも通り、開発者の皆様からのプル リクエストも歓迎です。

Posted:
Posted by 荒木佑一 Developer Relations Team

Google は、このたび Google Play 開発者サービス 5.0 を全世界でロールアウトしました。新バージョンはみなさんのアプリを強化するための機能が満載です。本リリースは Android ウェアラブルサービスAPI、Dynamic Security Provider と App Indexing の新機能を導入するとともに、Google Play ゲーム サービス、Google Cast、Google ドライブ、Google ウォレット、Google アナリティクス、そして Google Mobile Ads にアップデートが加えられております。

Android ウェアラブル サービス

Google Play 開発者サービス 5.0 には、Android ウェアラブル デバイス上で動作しているアプリとの通信をより効果的に実行できる API セットが用意されています。この API はデータを自動的に同期し永続的に保存するほか、低遅延のメッセージング インターフェースによって、データの同期やコントロールメッセージのやりとり、アセットの移動が容易に行えます。

Dynamic Security Provider

アプリが Dynamic Security Provider を容易にインストールできる API を提供いたします。Dynamic Security Provider には、プラットフォーム既存のセキュア ネットワーキング API に対する代替物が含まれ、セキュリティ パッチを迅速に配信するために頻繁にアップデートすることができます。現在のバージョンには OpenSSL で最近特定された問題の修正が含まれます。

Google Play ゲーム サービス

Quests は、プレイヤーに時間ベースの目標達成型イベントを提供するための API セットです。ゲームのアップデートを行わずにイベントの報酬を与えることが可能になります。これを行うには、たとえばプレイヤーがステージをクリアしたり、エイリアンを倒したり、レアな黒羊を救出したりするたびにゲーム活動データを Quests サービスに送信します。こうすることで Quests サービスにゲーム内の状況を把握させることができ、開発者はそのゲーム内活動を元に新規 Quests を作成できます。Quests を定期的に実行することで無限にプレイヤー体験を生み出すことができるようになり、高い再エンゲージメントとユーザー維持率を確保できます。
Saved Games は、Saved Games Snapshot API の使用により、ゲームの進捗状況をクラウドに保存して複数のデバイスで同じゲームを楽しめます。ゲームの進捗以外にもカバー画像、詳細説明、プレイ時間も保存できます。Google に進捗が保存されていれば、プレイヤーはゲームを最初からやりなおさずに済み、カバー画像と説明を加えることで前回プレイした状況がわかります。カバー画像と説明の追加はプレイヤーにゲーム進捗状況を伝える新たな機能であり、Google Play ゲーム アプリを通じて再エンゲージメントを向上します。

App Indexing API

App Indexing APIは、ユーザー エンゲージメントを高めるため、ネイティブのモバイル アプリのディープリンクを Google に通知する方法を提供します。App Indexing API を活用することで、Google 検索アプリがサジェスト検索でユーザーにアプリの履歴を提供できるようになり、これによりアプリの内部ページへのアクセスが今まで以上に速く快適になります。Google も、App Indexing API で報告されたディープリンクをアプリ コンテンツのインデックスに使用し、Google の検索結果にディープリンクとして表示します。

Google Cast

Google Cast SDK に、Chromecast の字幕サポートを新たに含むメディア トラックを導入しました。

ドライブ

Google ドライブの API は、クエリ結果のソート、オフラインでのフォルダ作成、デフォルトでファイル ピッカーでのMIME タイプによる選択も可能になりました。

ウォレット

Google のウォレット オブジェクトは物理的オブジェクト(ロイヤルティ カードやクーポン等)をあなたのウォレットから取得してクラウドに保存します。本リリースでは、こういったクーポンを支援するために、「ウォレットに保存」ボタンが追加されました。ユーザーが「ウォレットに保存」をクリックすると、クーポンが保存されて、ユーザーの Google ウォレット アプリに表示されるようになります。地域を絞ったストア内通知が、購入場所でデジタル カードを見せてスキャンするように促し、換金率の向上につながります。加えて、ユーザーもクーポンやロイヤルティ カードを持ち歩く煩わしさから解放されます。
複合取引に対応したことにより、ユーザーは Google ウォレット残高を使って Instant Buy 取引の支払いを行えます。ユーザーのウォレット残高が不足している場合、支払金額はウォレット残高と Google ウォレットに登録されているクレジットカード/デビットカードで分割されます。

アナリティクス

拡張 e コマースに、顧客活動を全面的に可視化し、製品インプレッション、クリック数、製品詳細の閲覧数、ショッピング カートへの製品追加、チェックアウト プロセスの開始、内部プロモーション、決済、返金等を測定する機能が追加されました。これらの情報をもとに、ユーザーが購入プロセスをどのように、どこまで辿っているのか、そしてどこで離脱しているのか、深く理解することができるようになります。拡張 e コマースでは、内部プロモーション、クーポン、アフィリエイト マーケティング プログラム等のマーケティング/マーチャンダイジング活動の効果も分析できます。

Mobile Ads

Google Mobile Ads はアプリから収益を得る優れたツールです。今回のアップデートによって、アプリ内課金広告が強化されました。具体的には、Google Play アプリ内課金サービスを通した消費財購入のためのデフォルトの実装を追加しました。

以上が新しい Google Play 開発者サービスの概要です。Google Play 開発者サービス SDK のアップデート版は、Android SDK マネージャーからダウンロード可能です。API についての詳細は、Google Play 開発者サービス 5.0 の新機能をご確認ください。

+Android Development - Japan にもぜひご参加下さい。