■表紙 法人向け Android アプリケーション 開発実践手引書 ~トラブルの未然防止とお客様満足度向上に向けて~ Ver. 1.0 2014 年 10 月 21 日 法人事業部 ソリューションビジネス部 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. |2 ■注意・ 商標 (注意) • 本書の内容は、2014 年 9 月時点のものです。本書のご利用にあたっては、事前に利用規約をご一読いただき、同意された場合に のみご利用いただくようお願い申し上げます。 • 本書で紹介した製品/サービスなどの名前や内容は変更される可能性があります。あらかじめご了承ください。 • 本書の内容に基づく実施・運用において発生したいかなる損害も、株式会社 NTT ドコモは一切の責任を負いません。 • 本書の全部あるいは一部について、株式会社 NTT ドコモから文書による許諾を得ずに複製することは、法律により固く禁じられてい ます。 (商標等) • Google, Android, Android Studio, Chrome は、米国 Google Inc.の米国およびその他の国における商標または登録商標 です。 • Java, JavaScript は、米国 Oracle Corporation およびその子会社の米国およびその他の国における商標または登録商標です。 • Eclipse は、米国 Eclipse Foundation の米国およびその他の国における商標または登録商標です。 • Microsoft, Windows は、米国 Microsoft Corporation の米国およびその他の国における商標または登録商標です。 • Apple, iPhone, iPad, iOS, Mac, Mac OS X,Safari は、米国 Apple Inc.の米国およびその他の国における商標または登録 商標です。 • iPhone の商標は、アイホン株式会社のライセンスにもとづき使用されています。 • KitKat は、スイス Nestlé S.A(英名 Nestlé Ltd.)のスイスおよび米国およびその他の国における商標または登録商標です。 • 本資料に記載されているその他の社名および製品名は、一般に各社の各国における商標または登録商標です。 • 本資料に掲載されているアイコンおよびスクリーンショット、肖像、その他すべての画像は、各々の著作権者が権利を有しています。 • 本資料では、™ ® © などの表示を省略しています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. |3 ■改版履歴 2014.10.21 Ver.1.0 発行 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. |4 ■目次 第1章 導入 8 1-1. はじめに 9 1-2. 本書の前提条件 10 1-3. あらためて「モバイル開発」とはなにか? 11 1-4. Java と Android の違いを知る 13 1-4-1. Android システムのアーキテクチャ 13 1-4-2. Java VM と Dalvik VM 15 1-4-3. Activity のライフサイクル 17 1-5. 本書が展開するサンプルアプリケーションの構造と意図 第2章 クライアント/サーバモデルを利用したアプリケーション開発 18 23 2-1. 概要 24 2-2. ポーリング型通信処理アプリケーション開発における注意点 25 2-2-1. Android システムとの関連性 25 2-2-2. 適切に実装するためのシーケンスおよびポイント 26 2-3. Push 型通信処理アプリケーション開発における注意点 42 2-3-1. GCM (Google Cloud Messaging) の基本 42 2-3-2. GCM の仕組み 43 2-3-3. GCM 利用上の注意点 44 2-3-4. Android システムとの関連性 45 2-3-5. 適切に実装するためのシーケンスおよびポイント 46 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 67 2-4-1. Service のライフサイクル 67 2-4-2. バッテリ 69 2-4-3. 通信間隔 70 2-4-4. ネットワークの状態 71 2-4-5. リトライ 72 2-4-6. アプリケーションの状態を正しく管理する 73 2-4-7. アプリケーション内で扱うデータ量を少なくする方法 77 2-4-8. 適切なコネクションの管理方法 78 2-4-9. 不安定状態を考慮したネットワークの使用方法 79 2-4-10. 低消費電力を意識した実装方法 80 第3章 端末依存を意識したアプリケーション開発の注意点 83 3-1. 概要 84 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 85 3-2-1. 位置情報取得の基本と実践 - LocationManager 85 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. |5 3-2-2. 適切に実装するためのシーケンスおよびポイント 93 3-3. カメラを利用したアプリケーション開発における注意点 99 3-3-1. Android システムとの関連性 3-3-2. 適切に実装するためのシーケンスおよびポイント 第4章 常時ディスプレイ ON を維持するためのアプリケーション開発 99 109 118 4-1. 概要 119 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 120 4-2-1. Android システムとの関連性 120 4-2-2. 「PowerManager」 は使わない! 121 4-2-3. 適切に実装するためのシーケンスおよびポイント 124 4-2-4. アプリケーション(端末)を再起動させる 128 4-2-5. 自アプリケーションを意図的に、適切なトリガで強制終了して、再実行する 130 4-2-6. 毎日一回、定時に再起動する 133 4-2-7. 端末を人の手で再起動する運用を検討する 134 4-2-8. 予期しない理由によって端末が再起動した際に、自動的にアプリケーションを再実行する 135 第5章 マルチデバイス対応と最適化されたアプリケーション開発 5-1. 概要 136 137 5-1-1. 画面レイアウトを取り扱う背景 137 5-1-2. マルチデバイス対応が求められる背景 138 5-2. マルチデバイスを意識したアプリケーションにおける注意点 139 5-2-1. マルチデバイスのためのレイアウト構成 140 5-2-2. レイアウトファイルの最適化 162 5-3. 最適化されたアプリケーションにおける注意点 220 5-3-1. XML で作成すべき図形と画像で作成すべき図形 221 5-3-2. XML の作成と Draw 9-patch 222 5-3-3. データの共有による容量の最適化 250 5-4. 適切に実装するためのシーケンスおよびポイント 253 5-5. WebView の使用における注意点 260 第6章 WebView を利用したアプリケーション開発の注意点 6-1. WebView の役割と特性 267 269 6-1-1. WebView とは 269 6-1-2. WebView でできること 273 6-1-3. WebView でできないこと 276 6-2. ブラウザと WebView の使い分け-WebView が求められるアプリケーションの条件を見定める 277 6-2-1. WebView が求められる場面 277 6-2-2. ブラウザの使用でこと足りる場面 278 6-3. WebView 実装の基本 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 279 |6 6-4. ハイブリッドアプリケーションの基本 287 6-4-1. ハイブリッドアプリケーションとは 287 6-4-2. ハイブリッドアプリケーション設計について 293 6-5. WebView に適したコンテンツ設計 295 6-5-1. コンテンツ設計の考え方の基本 295 6-5-2. PC 用コンテンツを移植する際の注意点 304 6-6. 望ましくない UI 309 6-7. WebKit と Chromium の違い 313 第7章 Google サービス&ライブラリを利用したアプリケーション開発 316 7-1. はじめに 317 7-2. GCM 319 7-2-1. GCM の概要 320 7-2-2. 従前の手法(C2DM) 321 7-2-3. GCM の基本的な実装方法 328 7-2-4. GCM 実装の基本的な注意点 339 7-3. Location 342 7-3-1. Location API の概要 343 7-3-2. 従前の手法(LocationManager) 351 7-3-3. Location API の基本的な実装方法 356 7-3-4. Location API 実装の基本的な注意点 370 7-4. Maps 373 7-4-1. Maps の概要(Google Map Android API v2) 374 7-4-2. 従前の手法(Google Map Android API v1) 375 7-4-3. Maps(Google Map Android API v2)の基本的な実装方法 378 7-4-4. Maps(Google Map Android API v2)実装の基本的な注意点 386 7-5. Volley 392 7-5-1. 従前の手法 399 7-5-2. Volley の基本的な実装方法 404 7-5-3. Volley 実装の基本的な注意点 412 第8章 Android アプリケーション開発に必須のデバッグツール利用法 8-1. はじめに 421 422 8-1-1. 本章で取り扱うデバッグツールについて 423 8-2. DDMS について 424 8-2-1. DDMS とは 424 8-2-2. DDMS の使用方法 426 8-3. Systrace について 447 8-3-1. Systrace とは 447 8-3-2. Systrace の使用方法 448 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. |7 8-4. Procstats について 452 8-4-1. Procstats とは 452 8-4-2. Procstats の使用方法 453 8-5. StrictMode について 457 8-5-1. StrictMode とは 457 8-5-2. StrictMode の使用方法 458 8-6. 「StrictMode」関連のクラスおよび API 一覧 461 8-7. デバッグツールでの解析事例 464 第9章 補足情報 476 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) 477 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) 493 9-3. 「4-2.常時電源 ON を実装する」(補足:API 情報) 508 9-4. 参考:Google の推奨する「Performance Tips」 509 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 第1章 導入 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. |8 第 1 章 導入 |9 1-1 はじめに 1-1. はじめに Android 端末向けアプリケーションの大多数は、Java 言語で開発されます。 Android ア プ リ ケ ー シ ョ ン は 、 Java Development Kit ( JDK ) と Integrated Developemt Environment(IDE)を使用します。また、必要な Software Development Kit(SDK)を組み込んで 作成した Java アプリケーションが Virtual Machine(VM=仮想マシン)上で動作します。アプリケーション開発 上の“見た目”において、PC 用 Java アプリケーションと Android 用 Java アプリケーションの間には大きな違いは ないと捉えられることがあります。 事実、PC 用 Java アプリケーション開発の経験を活かし Java VM のロジックやノ ウハウに従って開発された Android アプリケーションも、Android 端末の Dalvik VM 上である程度動作します。 しかしながら、PC と Android 端末ではデバイスの持つリソース、ハードウェアアーキテクチャ、さらには VM のアーキ テクチャが大きく異なります。 また、モバイルネットワークならではの特性も、携帯向けアプリケーションの開発では考 慮する必要があります。 これらの違いを理解せず安易な開発を進めた場合、プロダクトの可用性は著しく低下し、 さまざまな問題を引き起こすひきがねとなります。結果として、お客様に不便を強いるアプリケーションとなります。残 念ながら、それらが法人向けソリューションとしても数多く出回っているのが事実です。 Android の健全な発展とお客様満足度向上のためには、プラットフォームのルールを正しく理解したアプリケーシ ョン開発が必要不可欠であると考えております。そのための一助とすることを目的に今般本書を作成いたしました。 本書では、初めに Android プラットフォームのルールに則ったアプリケーション設計や実装手法を、次に、 Android 端末固有の概念が求められる開発事例について、実際のコードを例にしながら解説いたします。 本書が想定する読者は、法人向けの Java システム開発経験者で、これから Android 開発を始めるもしくは簡 単なプログラムを組んだことがある方を対象としております。すでにお持ちの Java での開発の知識・経験を活かした Android アプリケーションの開発を行うにあたり、本書をご活用いただければ幸いです。 2014 年 10 月 21 日 法人事業部 ソリューションビジネス部 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 10 1-2 本書の前提条件 1-2. 本書の前提条件 ※ 本書のサンプルコードの動作確認は、下記に示す環境で行っております。 【公式サイト】 http://developer.android.com/sdk/index.html 内の SYSTEM REQUIREMENTS • Android OS • Android 4.0 (Ice Cream Sandwich, ICS) Android 4.1, 4.2, 4.3 (Jelly Bean, JB) Android 4.4 (KitKat, KK) 標準ソース 4.0.3 (Ice Cream Sandwich, ICS) 4.3.1 (Jelly Bean, JB) 4.4.2 (KitKat, KK) ※ • 標準ソースからの調査は、上記リビジョンを使用します SDK Android SDK, Revision 21.0.1 • NDK Android NDK, Revision 8c (November 2012) • 開発環境 OS Linux Ubuntu 8.04 以降 (64bit 版) Microsoft Windows 7 • 開発言語 Java • (64bit 版) (一部 NDK 対応処理では C, C++) IDE Eclipse 3.7.2 (Indigo) 以上 • JDK JDK 6 • テスト端末 Android 4.0.4(ICS), 4.1.2(JB) ⇒ Galaxy Nexus (SC-04D) Android 4.3(JB) ⇒ Galaxy Note (SC-01F) Android 4.4.2(KK) ⇒ Nexus 4 (参照) 【公式サイト】 http://source.android.com/source/initializing.html http://developer.android.com/index.html http://developer.android.com/sdk/index.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 11 1-3 あらためて「モバイル開発」とはなにか? 1-3. あらためて「モバイル開発」とはなにか? Android OS をはじめとするモバイル端末上で動作するアプリケーションの開発にあたり、モバイル(特に Android システム)特有のいくつかの要素を念頭に置かなければなりません。 それは主に次に示すようなものです。 要素 特徴 同時代の PC と比べて非力である。 端末リソース(CPU/メモリ/ストレージ) 一つのアプリケーションで使用できる heap メモリの上限が決まっている。 (上限は端末により違いがある) 電力源 バッテリーのみで運用されることが多い。 画面全体の遷移 頻繁に発生する。 ユーザ操作 頻繁に発生する。 他プロセスによる割り込み 頻繁に発生する。 通信状態 アプリケーションの挙動に与える影響が大きい。 上位互換性 アプリケーションの上位互換性は、必ずしも保証されない。 対応機種 アプリケーション・OS が同一であっても、端末の違いにより異なる挙動を示すことがある。 常に着信を受けられるようにするため、一つのアプリケーションが端末リソース(特に CPU)を専有 通話機能に対する考慮 しない実装が必要である。 空きメモリが少なくなると自動で Task Kill(=プロセスの終了)されることがある。 OS によるプロセスの強制終了 したがって、起動し続けることを前提としない実装にする必要がある。 以上の要素は、一般的な PC 用ソフトウェア開発においてあまり考慮しない(する必要がない)ことです。しかし、こ れらこそがモバイル、特に Android アプリケーション開発においては、プロジェクトの命運および顧客満足度を大きく 左右するポイントとなります。 Eclipse を使用し Java 言語で業務系システムを開発してきたプログラマが、Android 開発において苦心する多 くのポイントの本質は、「モバイル(Android)アプリケーション開発とはなにか?」 を正確に捉えていないことに由来 していると言えるかもしれません。 それは、「組み込み開発」である、ということです。PC 用業務システム開発とは大きく一線を画するものなのです。 しかし、PC もモバイルも同じコンピュータです。では、何がどう違うのでしょうか? Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 12 1-3 あらためて「モバイル開発」とはなにか? 一般的な PC やサーバは、主に計算や情報処理を目的としています。それに対して、組み込み型コンピュータは、 機器の制御を目的としています。 PC 用のプログラムは、PC 上で開発し、同じ PC 上で実行やデバッグを行います。すなわち、開発環境と実行環 境が同じコンピュータ上です。このような開発は、「セルフ開発」と呼ばれます。 対して、組み込み型コンピュータ(Android OS)は、実行環境(Android 端末)以外の環境(PC)で開 発を行います。このように、「プログラムの開発環境と、実行環境が別のコンピュータ」という開発は、「クロス開発」と 呼ばれます。たとえば、家電製品や自動車に搭載されているコンピュータも、紛れもない組み込み型コンピュータで あり、それらの開発はクロス開発よって行われています。 このような背景を知るなら、「Android 開発は組み込み開発である」ということが理解できます。 つまり、「機器(端末)の制御を常に考慮する必要がある」と意識できるでしょう。 これこそが、Android 端末向けアプリケーション開発において、とても重要な前提なのです。 それでは、Android システムの構造について、見てみることにしましょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 13 1-4 Java と Android の違いを知る 1-4. Java と Android の違いを知る 1-4-1. Android システムのアーキテクチャ 図 1 Android のアーキテクチャ Android システムのアーキテクチャのうち、下側、つまりハードウェア寄りのレイヤから見ることにしましょう。 Linux カーネル Android システムのカーネルは、Linux(ver 2.6 および 3.x ※)が採用されています。 このレイヤは、セキュリティ・メモリ管理・プロセス管理・ネットワークスタック・ドライバモデルなどのコアシステム サービスを提供します。端末提供メーカ側でカスタマイズすることはありますが、基本的にアプリケーション 開発者が直接開発することはありません。 ※ メーカおよび端末によって異なります。 一例として、SC-04D では 3.0.x、Nexus 7 では 3.1.x が採用されています。 ライブラリ 機能ごとにまとめられた汎用的なプログラム群です。このレイヤの機能は、C や C++で作られています。こ のレイヤは、アプリケーション開発を行う上で必須となる機能を提供していますが、アプリケーションフレーム ワーク経由で利用します。そのため、基本的にアプリケーション開発者が直接ライブラリの操作を行う必要 はありません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 14 1-4 Java と Android の違いを知る Android ランタイム Android システム専用の仮想マシンである「Dalvik VM(※1)」および、基本的な API を提供する「コ アライブラリ(※2)」で構成されます。このレイヤは、端末提供メーカ側でカスタマイズすることはありますが、 基本的にアプリケーション開発者が直接開発することはありません。 アプリケーションフレームワーク Java で開発されたアプリケーション用のフレームワークです。このレイヤは、アプリケーション開発者にとって 理解することが必須のものです。なぜならば、フレームワークの機能を理解することが Android 端末向け アプリケーション開発の基礎となるからです。 アプリケーション このレイヤは、電話・連絡先・ブラウザなどの基本的なプリインストールアプリケーションおよび、アプリケーシ ョンベンダが開発する Android アプリケーションが属します。 (※1)(※2)……「Android ランタイム」を構成する仮想マシンおよびコアライブラリの詳細について 1. 仮想マシン(Dalvik Virtual Machine) 低メモリ環境向けに最適化された仮想マシンです。複数の Dalvik 仮想マシンのインスタンスを同時 に動作させることが可能であり、マルチタスクに対応しています。Android アプリケーションと呼ばれる ものは、全てこの Dalvik 仮想マシン上で動作します。 2. コアライブラリ(Core Libraries) 画像描写に利用する Swing パッケージ等を除いた Java SE 5.0 ライブラリの大部分をサポートし ています。コアライブラリを利用することで、Java 開発者は感覚的に違和感を覚えることなく Android アプリケーションの開発が可能となります。 ハードウェア 最後に、ハードウェアについても少し触れなければなりません。Android 端末向けアプリケーション開発に おいて、開発者を最も悩ませるポイントの一つは「機種依存」です。この問題を引き起こすのは、主に以 下に示す要素です。 • CPU の性能 • メモリの容量 • スクリーンの解像度 • カメラ (フロント/バックカメラ各々) の有無および、性能 • GPS センサおよび、加速度センサの特性と性能 これらの点について、アプリケーション開発者は、適正な実装を意識する必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 15 1-4 Java と Android の違いを知る 1-4-2. Java VM と Dalvik VM 初めに、開発にあたって念頭に置きたいことは、Android アプリケーションは Java 仮想マシン(以下、Java VM) 上で動くわけではないということです。Android アプリケーション内の Java コードは、「Dalvik VM」と呼ばれる独自 の仮想マシン上で動作し、Dalvik VM が搭載されたスマートフォンやタブレット、エミュレータなどで動作します。 Java VM と Dalvik VM のアーキテクチャには、「スタックマシン」と「レジスタマシン」という決定的な違いがありま す。 両者の主要な違いについては、以下の表 1. に示します。 特徴 メリット Dalvik VM (レジスタ・ベース) Java VM(スタック・ベース) CPU 内のレジスタに演算処理データを格納する 主メモリ上の記憶領域に演算処理データを格納する • 演算処理にスタックを介在させないため、処理が速い • メモリ上にデータ格納領域を作成しないため、実行 時に必要なメモリの領域が少ない デメリット • 主メモリ上にデータを格納するため、ハードウェアによ る制約が弱く、移植性が高い • レジスタの指定などが不要なため、一般的にバイナリ コードのサイズが小さくなる • CPU のレジスタ構成に依存するため、移植性が低い • 演算処理にスタックが介在するため、処理が遅い • レジスタの指定を要するため、一般的にバイナリコー • メモリ上にデータ格納領域を作成するため、実行時 ドのサイズが大きくなる(※1) に必要なメモリの領域が多い 表 1. 「レジスタ・ベース」と「スタック・ベース」の主要な相違点 (※1)…Android 上で稼働する Dalvik 仮想マシンは、バイナリコード変換時に最適化を行っています。そのため、Java のバイナリコード よりもサイズを小さくすることを実現しています。 Dalvik VM は、実行するバイトコードが JavaVM と異なります。Dalvik VM が実行するのは、DEX(Dalvik Executable)と呼ばれるバイトコードです。DEX は、Java バイトコードから生成されます。つまり、Android アプリ ケーションのビルドは、一度 Java のバイトコードに変換して、さらに DEX に変換するという複数のステップを踏みま す。ビルドから実行までの流れを図示したものが「図 2 Java VM と Dalvik VM における実行手順の違い」です。 Java アプリケーションよりもステップが多いことが分かります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 16 1-4 Java と Android の違いを知る 図 2 Java VM と Dalvik VM における実行手順の違い ビルドの仕組みは複雑に見えますが、実際には ADT やビルドツールの Ant が、変換作業などを自動的に行いま す。そのため、開発者がこの工程を意識する必要はありません。 Android システムでは、一つのアプリケーションを.apk という拡張子が付いた一つのファイルで扱います。APK ファ イルは、署名付き JAR と同様に、ZIP 形式で圧縮された署名付きのファイルです。実際に、apktool などのアプリ ケーションを利用するならば、作成した APK ファイルをデコンパイル(解凍)することも可能です。 このように、Android アプリケーションは Java 言語によって開発されるとはいえ、動作環境や実行ファイルの構造 が PC 用 Java プログラムとは大きく異なります。これを念頭に置くのであれば、アプリケーション開発にあたり、 Android システム固有のルールや作法を知り、正しく実装することの重要性が改めて明確になることでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 17 1-4 Java と Android の違いを知る 1-4-3. Activity のライフサイクル Android 端末でアプリケーションが起動されると、最初の Activity(画面)が開始し前面に表示されます。同じア プリケーションの別 Activity が表示された際には、最初の Activity は背面に隠れますし、別のアプリケーションが起 動することで、異なるアプリケーションの Activity が表示される場合もあります。このように、各々の Activity は表 示されたり隠れたりといったサイクルを繰り返します。 このような、Activity が開始されて破棄されるまでの一連の流れを、「Activity のライフサイクル」と呼びます。 Activity のライフサイクルと、状態が変わる時に呼び出されるメソッドの一覧を以下に示します。 図 3 Activity のライフサイクル Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 18 1-5 本書が展開するサンプルアプリケーションの構造と意図 1-5. 本書が展開するサンプルアプリケーションの構造と意図 本書は、法人向け Java システム開発者の皆さまが、Android アプリケーション開発を進めるにあたっての注意 点を中心に説明します。 そのためのサンプルケースとして、実際に法人の現場で開発される可能性がある架空のプロジェクトを通して、説 明を進めていきます。 サンプルケースは、以下に示すものとします。 営業マン向け 業務管理&営業支援アプリケーション • 概要 社外で営業活動を行う営業マンが、自社専用アプリケーションの導入された Android 端末(phone および tablet)を利用して業務を行います。 専用アプリケーションは、主に次のような機能を持ちます。 a) 自社のグループウェアサーバを通じ、スケジュールを管理する b) 移動履歴を自動的に記録し、日報や車両管理に連携する c) 客先の現場をカメラ撮影し、自社管理サーバに送信して記録する d) 自社のプロモーションビデオを、Android 端末を通じ顧客に見せる 本書では何を扱おうとしているのか、このシステム事例における“Android 的な観点”を簡単に見てみましょう。 1. 自社のグループウェアサーバを通じ、スケジュールを管理 営業マンは、グループウェアに自分の新しいスケジュールが登録されたとき、直ちに手元の Android 端 末で知りたいと思うでしょう。そのためアプリケーションは、グループウェアサーバに登録された情報をユーザ に通知しなければなりません。 また、ネットワーク接続に問題が発生した場合や、バッテリ残量が低下した場合に、ダイアログボックスでユ ーザに必要な情報を知らせる機能も、ユーザビリティ的には必要でしょう。 この「通知」には、次の二種類の選択肢があります。 • ポーリング型通信方式 アプリケーションは、予め決められたタイミングでサーバに問い合わせをかけます。新しいデータが存在 した場合に、アプリケーションが端末側で通知機能を起動する手法です。この手法は、サーバを監 視するプロセスが常駐します。そのため、アプリケーションのライフサイクルや、バックグラウンドに移行し た際のプロセス残存期間に対する考慮が必要です。本書では、AlarmManager を活用した実装 方法をご紹介します。 • Push 型通信方式 サーバに新しい情報が登録された場合、それを監視するサーバ側のシステムが端末に対してトリガを 送信することで、端末に通知する手法です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 19 1-5 本書が展開するサンプルアプリケーションの構造と意図 Android においては、GCM(Google Cloud Messaging for Android)サービスを利用します。 GCM の実現には、専用の実装が求められます。本書では、GCM の具体的な実装方法をご紹介 します。 Push Android端末 Push Register ID GCM Push Push 社内サーバ Google Cloud Messaging 他システム 図 4 Push 型通信イメージ Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 20 1-5 本書が展開するサンプルアプリケーションの構造と意図 2. 移動履歴を自動的に記録し、日報や車両管理に連携 モバイル端末が持つ位置情報取得機能は、「人間の移動と連携する」という、最もモバイル端末に特有 な機能の一つです。Android 端末において、位置情報は次の二種類の方法で取得することができま す。 • GPS プロバイダ GPS 衛星からの電波を受信することで位置を計測し、端末に座標を返却する仕組み。 • ネットワークプロバイダ 3G 回線基地局や Wi-Fi ネットワークを利用することで、各々のアクセスポイントが持つ情報に基づ いて位置情報を計測し、端末に座標を返却する仕組み。 GPS衛星 GPSプロバイダ (屋外のみ・高精度で厳密) (返却速度=遅い、バッテリ消費=多い) ネットワークプロバイダ (屋内外不問・GPSプロバイダほど厳密ではない) (返却速度=速い,バッテリ消費=少ない) 携帯電話基地局 Wi-Fiルータ 図 5 位置情報系システムイメージ いずれの方法にもメリット・デメリットがありますが、重要なのは「どちらが適しているか」を正しく選ぶことです。 また、モバイル端末で位置情報を取得するためには、ユーザの利用シーンに応じた適切な手法を、適切 なタイミングで実行しなければなりません。 本書では、モバイル端末の実運用に即した、位置情報取得の実装方法をご紹介します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 1 章 導入 | 21 1-5 本書が展開するサンプルアプリケーションの構造と意図 3. 客先の現場をカメラ撮影し、自社管理サーバに記録 写真や動画は、モバイル端末で扱うデータの中ではサイズが大きく、動作上の問題を引き起こす要因と なりえます。 その問題とは、主に次に示すようなものです。 • メモリの過剰な使用による、アプリケーションおよび端末動作の不安定化 • 送受信中のエラーを考慮しないことによる、処理の失敗 • 不適切な圧縮率で送受信することによる、データ通信量の増大 加えて、カメラ機能は Android 端末における「機種依存問題」と密接に関連します。 これらはいずれも、アプリケーションの持つ価値を、根本から否定されかねない「ユーザの不満」に直結しま す。 本書では、画像送信を事例としてモバイル端末の運用に適した圧縮と、伝送制御およびカメラ処理の 適切な実装方法をご紹介します。 写真撮影 オリジナル データ 送信用データ BASE64 エンコード 圧縮処理 ギャラリ JSON パース 通信開始 判定 成功? 通信 N リトライ 図 6 カメラ処理イメージ Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. Y 後処理 第 1 章 導入 | 22 1-5 本書が展開するサンプルアプリケーションの構造と意図 4. 自社のプロモーションビデオを、端末を通じて顧客に見せる 常時点灯のテレビ、デジタルサイネージ的なものをイメージしてください。タブレット端末などで動画を再生 し続けるのは、一見簡単に思えます。 しかし、Android 端末は、一定時間操作を受け付けないと電力消費を抑えるように OS がデバイスを制 御する特性を有します。そのため、画面を ON にし続けるために PowerManager で WakeLock を維 持し続けるだけの安易な実装がよく見られますが、これはシステムの動作を不安定にしかねません。 本書では、「画面の常時点灯状態の維持」を、安定して実現する適切な実装方法をご紹介します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. NTT DOCOMO Confidential 第2章 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 クライアント/サーバモデルを利用した アプリケーション開発 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. | 23 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 24 2-1. 概要 2-1. 概要 クライアント/サーバ型モデルは、法人向けアプリケーションにおいてもっとも基本的な構成の一つです。 Android アプリケーション開発において、クライアントとサーバのデータを同期する方法は、以下の二つがあります。 1. サーバとの定期的な通信による方法(ポーリング型通信方式) 2. サーバから通知を受ける方法(Push 型通知方式) いずれも、「サーバへの通信を定期実行」もしくは「サーバからの通信を待機」する動作を必要としています。これら を正しく実装しなければ、自アプリケーションが不安定になるだけでなく、他アプリケーションに影響を与える可能性 もあります。 不安定になる要因として、メモリリークを起こしている、バッテリに対して悪影響を与えているということが考えられま す。定期実行は、この両者に綿密な関係を持ちます。また、自アプリケーションがメモリリークを起こすことによって、 システム全体のメモリが不足すると、Android システムにより他アプリケーションが強制終了されてしまうこともありま す。 法人向けアプリケーションにおいて求められるのは「安定して動作すること」であり、正しいメモリ管理やバッテリへの 悪影響がない実装を心がける必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 25 2-2. ポーリング型通信処理アプリケーション開発における注意点 2-2. ポーリング型通信処理アプリケーション開発における注意点 2-2-1. Android システムとの関連性 ポーリング型通信処理とは、定期的に機器やプログラムに対して問い合わせを行い、条件を満たしたときに処理 を行う方式のことです。サンプルアプリケーションの場合は、無線通信網を利用して、定期的にサーバと通信を行い、 その都度送受信処理を行う「ネットワークを経由する定期実行処理」のことです。 電源が供給され、通信状態が安定している有線ネットワーク環境からサーバと通信を行う場合に比べ、 Android アプリケーションからネットワーク越しにサーバと通信を行う場合には、いくつか注意しなければいけないこと があります。例えば、ネットワーク接続処理や、Service の実行、定期実行のための仕組みなどに対する考慮が必 要です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 26 2-2. ポーリング型通信処理アプリケーション開発における注意点 2-2-2. 適切に実装するためのシーケンスおよびポイント ポーリング型通信処理を行うためには、フォアグラウンド(前面)にいるときはもちろんのこと、電話がかかって来た ときなどに割り込みでバックグラウンドに移動した場合でも、問題なく処理を行う必要があります。これらを実現する ために、Service を使用して定期処理を行います。そのため、Service の起動は、onCreate メソッドで実行して います。 Android システムの Service 起動には、「startService」と「bindService」の 2 つがあります。 それら 2 つの大きな違いを、以下に示します。 ・startService :Service に対して、Activity を関連付ける必要がない場合に使用 ・bindService :Service に対して、Activity を関連付ける必要がある場合に使用 具体的な事例で説明します。 ミュージックプレーヤの場合、音楽を再生する機能は Service が実行します。しかし、再生や停止を行うためには 操作用 Activity(画面)が必要です。再生や停止の操作が行われた場合、Activity から Service に対してそ れらの制御が通知され、Service の動作が変化します。 反対に、再生中の音楽が終了した場合、停止ボタンが再生ボタンへ変化する必要があります。これは、 Service から Activity に対して、通知が行われることにより実現します。このように、Service と Activity が相互 に関連するタイプの機能には、bindService を使用します。 このような bindService 特有のライフサイクルを、以下の「図 7 ミュージックプレーヤを例に見た、Service と Activity のライフサイクル遷移」に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 27 2-2. ポーリング型通信処理アプリケーション開発における注意点 ユーザ操作 ミュージックプレーヤ の起動 Activity Service onCreate() ・Activity の起動 ・画面の作成 など 初期処理 onStart() 画面が起動する bindService() ・Serviceの起動 onCreate() ・Service の起動 ・音楽の読込 など 初期処理 onServiceConnected() ・Service と接続確立 onBind() ボタン操作を行う onClick() 動作制御処理 ・再生ボタン押下 ・再生処理の呼出 ・音楽を再生する レイアウト変更処理 音楽の状態監視 ・ボタンの差替え ・音楽終了 画面の変更 ・停止ボタンが 再生ボタンに変化 ミュージックプレーヤ の終了 onDestroy() ・Activity の終了 onDestroy() ・Service の終了 図 7 ミュージックプレーヤを例に見た、Service と Activity のライフサイクル遷移 次に、位置情報を自動で取得して記録することだけを目的としたアプリケーションを考えてみましょう。アプリケーシ ョンの開始には、Activity が必要です。また、Service が Activity に何かを通知することを求めるのは、計測した 位置情報をユーザに対してフィードバックする必要がある場合のみです。このように、Service と Activity が相互に 関連しないタイプの機能には、startService を使用します。 【公式サイト】 http://developer.android.com/guide/components/services.html サンプルアプリケーションは、バックグラウンドにおいてデータを同期する目的で Service を使用するため、Activity とは綿密な連携は必要ありません。したがって、これから解説するポーリング型処理には、startService を使用し ています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 28 2-2. ポーリング型通信処理アプリケーション開発における注意点 Service を扱う上で、いくつかの重要なポイントがあります。以下の事柄に留意してください。 • Android システムの Service は、「Android で常駐するもの」ではありません。 • Service は、何らかの常駐的な機能を提供するコンポーネントではありません。 • Service は、独立したものではなく、アプリケーションの一部として実行するものとして考えるべきで す。 • Service は、メモリ不足になると Android システムによって Kill されます。そのため、常駐しているも のとして考えてしまうと、アプリケーションが再実行された時に、思わぬ不具合が発生することがありま す。Service も Activity と同様に、Kill されるということを念頭に置いてください。 加えて、Service は明示的に中断しなければ、停止しないという点にも注意してください。例えば、Activity が 何らかの不具合で強制終了した場合、エラーを正しく catch し、明確な中断処理を行わなければ、Activity と連 動していた Service は停止されません。 したがって、Service の中断処理をせずに強制終了などで、アプリケーションを終了した場合、Service は動き続 け、システムリソースおよびバッテリ電力を消費し続けます。 アプリケーションが正常に終了し、Service が不要となるタイミングで Service の中断処理を行うことは必要です。 加えて、不具合が発生した時のエラーハンドリングにも、確実に Service の中断処理を行なう必要があります。 なお、Service の中断処理は、onStop で行います。インターネットの情報などで、Service の中断処理を行う ために onDestroy を用いているプログラムをよく見かけますが、Android では onDestroy が呼ばれずに終了す ることが頻繁にあります。 例えば、タスクキラーと呼ばれるタスクを Kill するためのアプリケーションを使用して、タスクを終了させる場合は、 onDestroy が呼ばれないことがあります。その時には Service は残り続けてしまいます。そのため、他アプリケーショ ンによる影響なども考慮に入れるならば、onDestroy で Service の中断処理を行うことは不適切でしょう。 Service は、バックグラウンドで動作する特性を持ちます。これは、フォアグラウンドで他のアプリケーションが実行さ れているという意味を持ちます。そのため、Service でメモリを使用する処理を多く実装している場合、フォアグラウ ンドのアプリケーションなどに影響を与えます。最悪の場合、電話がかかってきた際に、電話に対する割り込みを妨 害するような事態も起こりえます。通常はその動作が意識されにくいため、Service では効率よくメモリを使用する ように開発を行わなければいけません。 Android アプリケーションから定期的に Service を実行するには、次の二つのパターンが考えられます。 1. Timer を用いて、定期的に処理を実行する 2. AlarmManager を用いて、定期的に処理を実行する Timer と AlarmManager は、定期実行させるタイミングを管理しているのが、自アプリケーションなのか Android システムなのか、という点に違いがあります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 29 2-2. ポーリング型通信処理アプリケーション開発における注意点 Timer の場合、Service は、自アプリケーションが管理しています。ライフサイクルをきちんと考慮に入れているの であれば、Activity で完結する短時間の処理は Timer を用いて定期実行しても特に問題はありません。 しかし、Activity がバックグラウンドに移行したあとも、処理を続ける必要がある場合や、Service から処理を起 動させる必要がある場合には、必ず AlarmManager を用います。 なぜならば、メモリ不足によってバックグラウンドの Activity や Service のプロセスは Kill される場合があり、その 際に Timer で処理していると再実行されなくなる可能性が高いためです。 このように、Android アプリケーション開発では、プロセスが Kill されるということを常に考慮しなければなりません。 これを忘れると、他アプリケーションが起動したことによるメモリ不足の状態で、再度 Activity が構築された場合に、 予期せぬ動作を引き起こすことがあります。 サンプルアプリケーションでは、Service でポーリング型通信を行いますので、バックグラウンドで通信処理を実行す る必要があります。したがって、AlarmManager を用いて定期実行を行います。 AlarmManager を用いる時には、他にも注意点があります。 AlarmManager は、デバイスのシステムタイムを用いて時刻の管理を行なっています。しかし、この「時刻」は、 組み込み系デバイス特有の動作を示すため、PC 系システム開発とは異なる考慮が必要です。 システムタイムは、デバイスの設定である「日付と時刻」の自動設定を有効にすることにより、モバイルネットワーク 経由で自動的に再設定されることがあります。再設定されると、AlarmManager で指定した日時とシステムタイ ムで、相違が発生する場合があるため、システムタイム更新時には AlarmManager で再設定を行う必要があり ます。 具体的な例として、システムタイムが 0:00 の時点で、ある処理が 8:00 になったら実行されるように登録を行っ たと仮定してみましょう。 Windows タスクスケジューラの場合は、登録されたプロセスはシステムタイムを参照して実行されます。何らかの 理由でシステムタイムが変更された場合にも、登録したプロセスは Windows システムタイム上の 8:00 に実行さ れます。 しかし、Android システムで AlarmManager を利用した場合は、Windows タスクスケジューラとは異なる挙 動を示します。Android システムタイムが 0:00 の時点で登録し、8:00 に実行すべき処理は、システムタイム上の 8:00 ではなく、8 時間後 (= 指定時刻 8:00 - 現在時刻 0:00) に実行するものとして扱われます。 したがって、AlarmManager を用いて決められた時刻に処理を実行する場合、時刻を変更した際やタイムゾー ンを変更した際に通知されるブロードキャストを受け取って、再設定しなければいけません。受け取るための設定方 法は、AndroidManifest.xml で intent-filter を受け取る receiver に対して設定します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 30 2-2. ポーリング型通信処理アプリケーション開発における注意点 それぞれのブロードキャストは以下のものが届きます。 • 時刻が変更になった時 android.intent.action.TIME_SET • タイムゾーンが変更になった時 android.intent.action.TIMEZONE_CHANGED Service を起動する際には、これらを IntentFilter に指定した receiver を登録して、時間等が変更になった 場合には必ず Service を再設定します。 このように、Android システムは、ユーザが端末の設定を自由に変更することが可能です。これは、Web アプリケ ーションでは考えられないことですが、Android アプリケーションの開発では考慮に入れなければ、必ずといって良い ほど不具合の原因になります。 AlarmManager を 使 っ て 繰 り 返 し 処 理 を 行 う 場 合 、 通 常 は setRepeating も し く は 、 setInexactRepeating を使用します。 setRepeating と setInexactRepeating の違いとして、setRepeating はおおよそ正確に繰り返すのに対して、 setInexactRepeating は多少厳密性に欠けます。しかし、setInexactRepeating は、バッテリ消費を減らす 役割があります。 例 え ば 、 厳 密 に 15 分 毎 の 実 行 を し な け れ ば な い よ う な 、 正 確 さ を 求 め る ア プ リ ケ ー シ ョ ン の 場 合 、 setRepeating を用います。しかし、サンプルアプリケーション(新規スケジュールの確認が目的)の場合は、数秒 といった多少の誤差があっても問題ありません。そのため、数秒の誤差が許容される場合は setInexactRepeating を使用し、バッテリ消費量を減らすように心がけるといった使いわけをすることが可能で す。 なお、setInexactRepeating の繰り返し時間には、以下の定数を用います。 INTERVAL_FIFTEEN_MINUTES 15 分 INTERVAL_HALF_HOUR 30 分 INTERVAL_HOUR 1 時間 INTERVAL_HALF_DAY 半日 INTERVAL_DAY 1日 これらの値を用いることで、他の処理と同時に行われバッテリ消費を減らす効果があります。 しかし、サンプルアプリケーションでは、ネットワーク状態や電池残量をチェックしてダイアログを出す機能があります。 そのため、これらの方式で処理を繰り返すと、ダイアログを表示し、通信を行うかどうか確認しているにも関わらず、 裏ではすでに次の通信開始時間が決定されてしまいます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 31 2-2. ポーリング型通信処理アプリケーション開発における注意点 そのためサンプルアプリケーションでは、AlarmManager で繰り返し指定をせず、Service で次の通信開始時 間を指定して自 Service を呼び出す方式をとっています。 AlarmManager で定期的に実行する処理には、四つの起動タイプがあります。 1. ELAPSED_REALTIME デバイスを起動してからの経過時間をベースにします。 デバイスがスリープ状態の時には起動せず、次にスリープが解除されるまで起動しません。 2. ELAPSED_REALTIME_WAKEUP デバイスを起動してからの経過時間をベースにします。 デバイスがスリープ状態の時にはデバイスを起動させます。 3. RTC システム日時をベースにします。 デバイスがスリープ状態の時には起動せず、次にスリープが解除されるまで起動しません。 4. RTC_WAKEUP システム日時をベースにします。 デバイスがスリープ状態の時にはデバイスを起動させます。 /** * 次回起動設定 */ private void next() { long now = System.currentTimeMillis(); // アラームをセット PendingIntent pIntent = PendingIntent.getService(this, 0, new Intent(this, this.getClass()), 0); AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); am.set(AlarmManager.RTC, now + INTERVAL, pIntent); } デバイスを起動させることは、バッテリを消費します。したがって、スリープ状態でも動作する必要があるときは 「_WAKEUP」を付与する必要がありますが、バッテリの消費を考えるとそれは好ましい実装ではありません。 通常、Activity でネットワークを使用しデータを取得する場合は、AsyncTask などを用いて別スレッドでネットワ ーク通信をします。なぜならば、描画スレッドでは 5 秒以上応答がないと ANR(Application Not Responding) と呼ばれるエラーが表示されてしまうためです。 また、100 ミリ秒(※)でも描画スレッドが止まってしまうと、ユーザは一瞬固まったという認識を持ってしまい、動 きが悪いという感想をもちます。そのため、ネットワーク通信や、その他時間がかかる処理は必ず別スレッドで行いま す。しかし、サンプルアプリケーションでは、Service でネットワークにアクセスするため、別スレッドにする必要はありま せん。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 32 2-2. ポーリング型通信処理アプリケーション開発における注意点 ※ Google も公式に「一般的に 100~200 ミリ秒が、ユーザが遅いと感じる閾値」との見解を示して います。 【公式サイト】 http://developer.android.com/training/articles/perf-anr.html#Reinforcing ポーリングは、定期的にネットワーク通信を行います。ポーリングで処理するパケットサイズが、小さい場合でも 3〜 5 分程度の短時間でポーリング型処理による通信を行うことは、バッテリ消費量の顕著な増加につながります。 それは、パケットが小さくとも通信を開始するにあたり、無線ネットワークリソースを取得するために、ネットワーク制 御信号の送受信が起きる可能性があるためです。ネットワーク制御信号の送受信が起きる場合には、携帯電話 の各種処理(※)の中でも比較的多くのバッテリを消費します。そのため、ポーリングの通信間隔は適切に行う必 要があります。 【参考サイト】 『Android アプリ作成ガイドライン』 10 ページ http://www.nttdocomo.co.jp/binary/pdf/service/developer/smart_phone/technical_i nfo/etc /Android_app_guide_1_0.pdf ポーリング通信の通信間隔とバッテリの消費量は、トレードオフの関係にあります。つまり、通信間隔が短くなると、 同期が早くなる代わりにバッテリ消費量が増加します。反対に、通信間隔を長くするとバッテリの消費量は減ります が、同期は遅くなります。これらは、作成するアプリケーションの特性により変化するため、そのアプリケーションの特性 で適切に合わせて判断してください。 また、ポーリング処理で有効なデータが得られなかった場合は、それまでと同じ間隔でポーリング処理を行うのでは なく、ポーリング処理の実施回数や、ユーザの最終操作時間からの経過時間に応じて、段階的(初回 1 分、2 回目 10 分、3 回目 100 分など)にポーリング間隔を広げるなどの制御を実装することも有効な手段です。 更新頻度が少ないが同期を早めたい場合などは、次の章で説明する GCM による Push 通信方式を検討するこ とが有用です。 モバイル開発でネットワークを使う場合は、有線ネットワークや無線 LAN ネットワークとは異なり、ネットワークが不 安定になる可能性があるということを考慮に入れる必要があります。 ネットワークが不安定な状態などの理由で通信に失敗した場合は、すぐに通信のリトライ処理を行うことや、通信 が成功するまでリトライ処理を繰り返すといった実装は避けるべきです。ネットワークが不安定な状態でネットワーク 通信を行おうとすることは、無駄にバッテリ消費をしてしまう可能性が高いためです。 このような場合は、バックオフ制御等を行うべきです。 バックオフ制御とは、初回のリトライ処理は 1 分後、次のリトライ処理はその 2 分後、次のリトライ処理はさらに 4 分後にというようにリトライ回数が増えるにしたがって、リトライ処理を行う間隔を徐々に広げていく制御です。 【公式サイト】 http://developer.android.com/google/gcm/adv.html#retry Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 33 2-2. ポーリング型通信処理アプリケーション開発における注意点 リトライ回数制限や経過時間期限を設定し、条件を満たした場合に、以後のリトライ処理を行わないようにする制 御も検討してください。 指数バックオフリトライ方式 定期リトライ方式 通信障害 発生時刻 失敗 失敗 失敗 失敗 失敗 失敗 失敗 失敗 失敗 失敗 失敗 失敗 失敗 失敗 失敗 通信障害 復旧時刻 失敗 失敗 失敗 成功 成功 失敗 : 14 成功 : 1 トライ総数:15 失敗 : 4 成功 : 1 トライ総数:5 時間経過(t) 図 8 バックオフリトライイメージ 前述のとおり、バッテリ消費量の観点から、ネットワーク通信はできる限り行わないようにするべきです。 そのため、通信を行う前に携帯電話の状態を確認し、通信の適否・頻度を適切に制御するようにします。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 34 2-2. ポーリング型通信処理アプリケーション開発における注意点 制御例を以下に挙げます。 • バッテリ状態に応じた制御 • アプリケーション状態に応じた制御 例:ユーザが利用していない(バックグラウンドでの動作中)状態では通信頻度を下げる • 最終利用時刻からの経過時間に応じて通信間隔を広げる(バックオフ制御) • ネットワーク状態に応じた制御 • ネットワーク接続が存在しない状態では通信処理を開始しない 例:Wi-Fi 環境なら通信量を多く、3G/LTE 環境なら通信量を少なくする ※ 「3G/LTE 環境」なら、大量のデータ通信自体を制限するということも有効 バッテリ状態に応じた制御は、次のとおりです。 • 充電中 最大限通信する • 非充電中 通信頻度を下げる • バッテリ残量が少ない時 さらに通信頻度を下げる。または、通信を中止する。など。 【参考サイト:Android アプリ作成ガイドライン(NTT ドコモ 「作ろうスマートフォンコンテンツ」内)】 https://www.nttdocomo.co.jp/service/developer/smart_phone/etc / Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 35 2-2. ポーリング型通信処理アプリケーション開発における注意点 以下のサンプルコードでは、バッテリの状態を以下のようにしてチェックしています。これは、充電中もしくは 15%以 上残量があるかどうかのチェックです。 private boolean checkBattery() { BatteryUtils bu = new BatteryUtils(getApplicationContext()); if (bu.isCharging()) return true; if (bu.getRatio() > 15) return true; return false; } BatteryUtils では、コンストラクタでバッテリ状態を取得するための Intent を発行しています。 public BatteryUtils(Context context) { intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } バッテリが充電中かを調べるには、バッテリの Receiver から BatteryManager.EXTRA_STATUS の値を調べま す。 public boolean isCharging() { int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 0); if (status == BatteryManager.BATTERY_STATUS_CHARGING) { return true; } return false; } バ ッ テ リ の 残 量 は 、 次 の よ う に 、 バ ッ テ リ の Receiver か ら BatteryManager.EXTRA_SCALE お よ び BatteryManager.EXTRA_LEVEL を取得して計算しています。 public int getRatio() { // バッテリの残量の割合(バッテリの容量最大値に対する)を取得 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); // バッテリの最大値を取得 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100); Log.e("XXXXXXXX", "scale:" + scale + " level:" + level); // バッテリの残量の百分率 return scale * 100 / level; } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 36 2-2. ポーリング型通信処理アプリケーション開発における注意点 このサンプルでは、15%以下の場合に更新続行確認を行うという仕様となっています。 そのため、バッテリーチェックが false の場合は更新続行確認ダイアログを出すようにしています。 if (!checkBattery()) { // 背景透明 Activity を起動してダイアログ「バッテリ残量が 15%以下です。更新を停止しますか?」を表示 // ダイアログで NO を選択されたら putExtra で battery_disregard に true を設定し // PollingService を起動 Intent intentDialog = new Intent(getApplicationContext(), TransparentActivity.class); intentDialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intentDialog.putExtra("flag", TransparentActivity.BATTERY); startActivity(intentDialog); return START_REDELIVER_INTENT; } また、ネットワーク接続が出来なかった場合は、ダイアログを表示することでユーザビリティを向上できます。 リトライ確認ダイアログを次のように表示します。 if (!NetworkUtils.isOnline(getApplicationContext())) { // 背景透明 Activity を起動してダイアログ「ネットワークが接続されていません。接続状態を確認してください。いま すぐリトライしますか?」を表示 // ダイアログで YES が選択されたら Service 起動。 Intent intentDialog = new Intent(getApplicationContext(), TransparentActivity.class); intentDialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intentDialog.putExtra("flag", TransparentActivity.NETWORK); startActivity(intentDialog); return START_REDELIVER_INTENT; } NetworkUtils クラスでは、ネットワーク関連のメソッドをまとめています。 NetworkUtils の isOnline メソッドは、ネットワークの状態を調べるメソッドです。isOnline メソッドは、オブジェク トのフィールドにアクセスする必要がないため static にしています。これにより、メソッドの呼び出し速度が向上してい ます。 また、変更されない変数に、final 属性をつけることも呼び出し速度の向上に繋がります。 public static boolean isOnline(Context context) { final ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo info = cm.getActiveNetworkInfo(); if (info != null) { return info.isConnected(); } return false; } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 37 2-2. ポーリング型通信処理アプリケーション開発における注意点 また、NetworkUtils クラスには、Wi-Fi で接続されているかを調べるメソッドも用意されています。こちらも、 isOnline メソッドと同じく、メソッドは static になっており、変数には final をつけます。 public static boolean isOnlineWifi(Context context) { final ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo info = cm.getActiveNetworkInfo(); if (info != null) { return cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected(); } return false; } Android には、HTTP 通信を行うための HTTP クライアント実装が二つあります。HttpURLConnection と Apache HTTP Client です。 • Apache HTTP Client DefaultHttpClient と、DefaultHttpClient サブクラスの AndroidHttpClient は、Web ブラウ ザに適するように拡張された HTTP Client です。そのため、大きくてフレキシブルな API を持ってい ます。 • HttpURLConnection HttpURLConnection は、ほとんどのアプリケーションに適応する、汎用的で軽量な HTTP Client です。Android バージョン 2.2 以前では、HttpURLConnection にいくつかのバグがあり ました。Android バージョン 2.3 以降では、自動的にリクエストにヘッダを追加し、対応するレスポ ンスを処理します。 Accept-Encoding: gzip もしも、この設定に問題があるのであれば無効にする方法もあります。 この 2 つの HTTP クライアント実装は、Android バージョン 2.2 以前では Apache HTTP client を使用する と、動作が安定し望ましいと言えるでしょう。Android バージョン 2.3 以降では、逆に HttpURLConnection を 利用するべきです。なぜならば、API がシンプルでサイズも小さいので Android に適しています。Transparent compression とレスポンスキャッシュはネットワークの利用を減らし、速度やバッテリの持続を改善します。 【公式サイト】 http://android-developers.blogspot.jp/2011/09/androids-http-clients.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 38 2-2. ポーリング型通信処理アプリケーション開発における注意点 DefaultHttpClient を用いて HTTP 通信を行う際は、ResponseHandler を用いて受信データを受け取るよ うにすることを推奨します。その理由は、ResponseHandler を使用することで、HttpResponse の内部リソース を自動で解放するため、コーディングミスの低減につながり、コード自体も簡潔になるためです。 また、コネクションの接続後は、接続を確実に切断しなければなりません。切断を行わない場合、接続が残ったま まになり、メモリリークの原因となります。 DefaultHttpClient client = new DefaultHttpClient(); try { HttpGetTaskResponseHandler response_handler = new HttpGetTaskResponseHandler(); client.execute(request, response_handler); } catch (ClientProtocolException ex) { response_error_message = ex.getLocalizedMessage(); } catch (IOException ex) { response_error_message = ex.getLocalizedMessage(); } finally { client.getConnectionManager().shutdown(); } データ通信量を減らすことはとても重要なことであり、そのための工夫としてレスポンスデータを gzip 形式で受け取 るようにするべきです。 前述のとおり、HttpURLConnection を使用する場合は 、デフォ ルトで gzip 形式とな っていますが、 DefaultHttpClient を使用する場合は、request データに設定します。 request.setHeader("Accept-Encoding", "gzip"); request データで gzip としているため、response データでも当然 gzip かどうかの判断が必要になります。 サンプルアプリケーションでは次のように判断しています。 private boolean isGZipHttpResponse(HttpResponse response) { Header header = response.getEntity().getContentEncoding(); if (header == null) { return false; } String value = header.getValue(); return (!TextUtils.isEmpty(value) && value.contains("gzip")); } レスポンスデータが gzip 対応か非対応かで、後の処理は当然変わってきます。 gzip 対応ではない場合は、org.apache.http.util.EntityUtils を使用すると、レスポンスメッセージは簡単に 取得出来ます。gzip 対応の場合は、自分で取得する必要があります。その際には、必ず InputStream や BufferedReader の close を確実に行なってください。 これも、close を行わないとメモリリークの原因となるためです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 39 2-2. ポーリング型通信処理アプリケーション開発における注意点 // レスポンスデータが gzip 対応かを判定する if (isGZipHttpResponse(response) == true) { // GZIP コンテンツの場合 InputStream stream = new GZIPInputStream(response.getEntity().getContent()); BufferedReader reader = new BufferedReader(new InputStreamReader(stream, RESPONSE_ENCODING)); try { StringBuilder buf = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { buf.append(line); } response_message = buf.toString(); } catch (IOException ex) { } finally { stream.close(); reader.close(); } } else { // レスポンスエンコードに従って、コンテンツを代入 response_message = EntityUtils.toString(response.getEntity() RESPONSE_ENCODING); 定期更新チェックは Service で行うため、アプリケーションがフォアグラウンドにあるとは限りません。アプリケーション がフォアグラウンドにない場合、当然のこととしてフォアグラウンドには別のアプリケーションが立ち上がっています。この 時、更新通知を Toast で表示すると、とても煩わしく感じます。 逆に、アプリケーションがフォアグラウンドにあるにも関わらず、Notification で更新通知が来ることはユーザビリティ を低下させることになります。そのため、アプリケーションがフォアグラウンドにある場合は Toast で表示し、それ以外の 場合は Notification で表示しています。 その際も、アプリケーションがフォアグラウンドにあるかを確認するために、Activity のフィールドの値に直接アクセス しています。Android は、メソッドアクセスよりもフィールド値に直接アクセスする方が、処理は早くなります。 if (PollingActivity.isForeground) { // toast 表示 } else { // notification 表示 } startService で Service を起動した場合、Service から Activity に通知を行うには Broadcast で送信し ます。Activity で Broadcast を受信するために、registerReceiver で BroadcastReceiver を登録します。 ただし、動的に BroadcastReceiver を登録する場合は、解除を忘れてはいけません。解除を忘れることは、メ モリリークの原因となります。 通常、その Activity がフォアグラウンドにいる場合にのみ、Service からの通知を Activity で受信します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 40 2-2. ポーリング型通信処理アプリケーション開発における注意点 上記動作の実装方法は、onResume メソッドで BroadcastReceiver を登録し、onPause メソッドで解除す ることで、アプリケーションがフォアグラウンドにいる場合のみ Service からの通知を受信することができます。 protected void onResume() { super.onResume(); registerReceiver(updateReceiver, updateIntentFilter); } protected void onPause() { super.onPause(); if (this.updateReceiver != null) unregisterReceiver(updateReceiver); } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 41 2-2. ポーリング型通信処理アプリケーション開発における注意点 PollingActivity は、他にもメモリ消費を抑えるためのポイントがあります。それは、ボタンの制御です。 PollingActivity には、複数のボタンがあります。インターネット上のサンプルプログラムでは、ボタンオブジェクトのイ ベント処理に、匿名クラスを宣言する方法がよく見られます。ボタンイベント処理が少なく、リスナが多く発生しない 場合は、匿名クラスで宣言しても問題はありません。しかし、匿名クラスのイベントリスナを、多数インスタンス化する よりは、一つのインスタンスクラスでイベントリスナを使用する方がメモリの消費が少なく済みます。そのため、サンプル アプリケーションでは、ボタン押下時の処理を次のようにインナークラスで一つにしています。 // 戻るボタンのイベント処理 Button buttonBack = null; buttonBack = (Button) findViewById(R.id.button_back); buttonBack.setOnClickListener(mOnButtonClickEventProcess); // 停止ボタンのイベント処理 mButtonStop = (Button) findViewById(R.id.button_stop); mButtonStop.setOnClickListener(mOnButtonClickEventProcess); // 開始ボタンのイベント処理 mButtonStart = (Button) findViewById(R.id.button_start); mButtonStart.setVisibility(View.INVISIBLE); mButtonStart.setOnClickListener(mOnButtonClickEventProcess); private final View.OnClickListener mOnButtonClickEventProcess = new View.OnClickListener() { @Override public void onClick(View view) { // ビューオブジェクトの ID を取得する int id = view.getId(); switch (id) { case R.id.button_start: // Start ボタンを押下処理 mButtonStart.setVisibility(View.INVISIBLE); mButtonStop.setVisibility(View.VISIBLE); // サービスの開始 startService(); break; case R.id.button_stop: // Start ボタンを押下処理 mButtonStop.setVisibility(View.INVISIBLE); mButtonStart.setVisibility(View.VISIBLE); // サービスの停止 cancelService(); break; case R.id.button_back: // Activity の終了 finish(); break; default: break; } } } このように、Android アプリケーション開発では、様々なところでメモリやバッテリ消費を抑える必要や、ネットワーク 通信エラーなどに対して、適切にハンドリングを行う必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 42 2-3. Push 型通信処理アプリケーション開発における注意点 2-3. Push 型通信処理アプリケーション開発における注意点 2-3-1. GCM (Google Cloud Messaging) の基本 GCM は、アプリケーションサーバから Google の GCM サーバを経由し、Android アプリケーションに対してメッセ ージを送信することができるサービスです。その際、使用される認証システムやメッセージ等は、GCM サーバで管理 されており、GCM サーバがメッセージ不達時における再送処理などをすべて行っています。 また、BroadcastReceiver でメッセージ受信を実現しています。そのため、Android アプリケーションは、必要な ときのみ起動することとなります。それにより、バッテリ消費の節約が可能になります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 43 2-3. Push 型通信処理アプリケーション開発における注意点 2-3-2. GCM の仕組み メッセージを送信する仕組みは、それほど難しくありません。 アプリケーションサーバから GCM サーバに対して送信指示を送ると、GCM サーバが指示されたデバイスに対してメ ッセージを送信します。そのための準備として、API の登録等が事前に必要となっています。 GCM を使用するには、以下の条件を満たしている必要があります。 • Android バージョンが 2.2 (API Level 8)以上であること • ネットワーク通信ができる状態であること(機内モードや圏外時以外) ※ • ただし、SIM は必ずしも必要というわけではなく Wi-Fi 接続がされていれば動作します Android バージョンが 4.0.4 (API Level 16)未満の端末では Google アカウントが設定されて いること • Google Play ストアアプリ(旧 Android マーケット)がインストールされていること ※ 弊社端末 SH-05E(ジュニアスマートフォン)など、Google Play ストアアプリがインストール されていない端末では動作しません • バックグラウンドデータ使用設定が ON になっていること 【公式サイト】 http://developer.android.com/google/gcm/gcm.html#intro Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 44 2-3. Push 型通信処理アプリケーション開発における注意点 2-3-3. GCM 利用上の注意点 GCM を使用するには、以下に示すような注意点がいくつかあります。 • ユーザインターフェースや、メッセージ受信後の処理は提供されません。 開発者自身で実装が必要です。 • アプリケーションサーバから Android アプリケーションに送信するメッセージは、大きなサイズのユーザ コンテンツを送信するために設計されたものではありません。 最大送信サイズ(4KB)を意識した設計が必要になります。 • GCM サービスが実現する配信やメッセージ順序についての保証はありません。 それを理解した上で、送信ロジックを設計する必要があります。 • 登録 ID は、定期的にリフレッシュされる可能性があります。 そのために、登録 ID の認証処理などを用いた対応が必要になります。 ※ GCM は Google が無料で提供するサービスです。 そのため、Google の方針により、突然利用できなくなったり重要な仕様変更が行われたりする可 能性があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 45 2-3. Push 型通信処理アプリケーション開発における注意点 2-3-4. Android システムとの関連性 Android システムにおけるサーバ同期処理の多くは、Android アプリケーションが自らのタイミングでサーバへ情 報を取得しにいく「ポーリング型」です。 そのため、定期的にネットワーク通信が発生しており、これは電力を消費する要因の一つとなっています。また、そ れらの処理のために知らないうちにメモリを消費していることもあります。 しかし、それらの問題点は、サーバ主体で Android アプリケーションに対して情報を送信する「Push 型サービス」 によって解決できます。 「Push 型サービス」を用いることで、サーバからメッセージが送信された時にのみアプリケーションが実行され、サー バへデータを取得しにいくことができます。そうすることで、電力を消費する余計なネットワーク通信を減らすことがで きます。 また、Android の「Push 型サービス」の仕組みである GCM では、Android および Google の標準フレームワ ークを使っており、リソース消費を考慮に入れた設計になっています。これも消費電力を抑えるのに有効です。 これらにより、クライアント (Android アプリケーション)/サーバ間の同期は「ポーリング型」による同期よりも GCM による「Push 型サービス」による同期の方が望ましいと言えるでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 46 2-3. Push 型通信処理アプリケーション開発における注意点 2-3-5. 適切に実装するためのシーケンスおよびポイント GCM を利用するには SENDER_ID や API key が必要になります。まずはこれらの取得を行うことから始めます。 SENDER_ID や API key は、Google API から取得します。 下記の URL より Google APIs Console ページへアクセスします。 https://code.google.com/apis/console まだ一度も API プロジェクトを作成していない場合は「図 9 gcm-create-api-proj」が表示されるので、 [Create project...] をクリックして新規プロジェクトを作成します。 図 9 gcm-create-api-proj 「図 9 gcm-create-api-proj」 の「Create Project...」をクリックすると、API Project というプロジェクトが 作成されます。 過去に一度でも、なんらかの API プロジェクトを作成している場合は、初期表示で既存プロジェクトの Dashboard ページが表示されます。その場合は、左上の API プロジェクト名のドロップダウンメニューから Other projects > Create …で新規プロジェクトを作成できます。 追加で作成する場合は、プロジェクトの名前を入れる必要があります。図 10 からプロジェクト名を入れると新し いプロジェクトが作成されます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 47 2-3. Push 型通信処理アプリケーション開発における注意点 図 10 プロジェクトを作成すると、Services を選択する画面へ遷移します。 そのサービスの中から「Google Cloud Messaging for Android」を探し出し、Status を ON にします。 Status を ON にするとき、サービスの条項に同意する必要があるため、Accept ボタンを押下します。 同意にチェックを入れなければ Accept のボタンは disabled になっています。 図 11 すべてのサービスの条項で合意すると、Status が ON になります。 図 12 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 48 2-3. Push 型通信処理アプリケーション開発における注意点 これで、GCM のサービスを使う API プロジェクトの作成が完了しました。これらの作業によって、SENDER_ID お よび、API key が取得出来ます。 SENDER_ID は、URL の project:以下で確認することができます。下記 URL の例だと、「999999999999」 が SENDER_ID になります。 https://code.google.com/apis/console/#project:999999999999 API key を新規で取得するためには、左メニューで API Access を選択し、Create new Server key ボタンを押下します。 図 13 アクセスするサーバの IP アドレスを制限する場合は、この画面で指定します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 49 2-3. Push 型通信処理アプリケーション開発における注意点 図 14 Create ボタンを押下すると、API Access を可能にする API key が発行されます。 図 15 これで GCM を使用する準備ができました。では実際に、GCM のメッセージを送信できるアプリケーションサーバと、 そのメッセージを受信できる Android アプリケーションの開発を行います。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 50 2-3. Push 型通信処理アプリケーション開発における注意点 まずはどのような流れになるかを、図 16 で説明します。 GCMサーバ ②Push通知 APサーバ ①要求 ③登録ID通知 Androidデバイス 図 16 1. はじめに、登録 ID がすでに取得されているかを確認します。取得できない場合はあらたに、デバイスの紐付け を行う必要があります。そのため、GCM サーバに SENDER_ID の情報を送信します。 2. GCM サーバより、端末と紐付いた登録 ID が届きます。これは、GCMBroadcastReceiver から呼び出され、 onRegistered(Context context, String regId) で受け取ることができます。 3. 届いた登録 ID を、自分のアプリケーションサーバに登録します。登録を行うことで、アプリケーションサーバから デバイスを指定し、Push 配信を行うことが可能になります。 それでは、上記内容を実装するアプリケーションを説明します。 GCM 対応アプリケーションを開発するには、ヘルパーライブラリを利用します。 ヘルパーライブラリを利用するために、まず「SDK マネージャ」より「Google Cloud Messaging for Android Library」をインストールします。 インストールが完了すると、(SDK_ROOT)/extras/google/の下に gcm ディレクトリが作成され、その中に GCM を行うためのサンプル等が作成されています。 その中の、gcm-client/dist にある、gcm.jar がヘルパーライブラリですので、これを作成するプロジェクトの libs の中に入れます。 こうすることで、ヘルパーライブラリを利用できるようになります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 51 2-3. Push 型通信処理アプリケーション開発における注意点 次に、AndroidManifest.xml の編集に入ります。GCM を使用するには、Android バージョン 2.2 以上が必 要となりますので、minSdkVersion を 8 以上に設定します。 <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="xx"/> 次に、このアプリケーションのみが、この GCM メッセージを受信できるように、カスタムパーミッションを宣言して使用 します。 <permission android:name="my_app_package.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="my_app_package.permission.C2D_MESSAGE" /> my_app_package は、<manifest>タグで定義した package になります。 パーミッション名は、必ず my_app_package.permission.C2D_MESSAGE にしなければなりません。これ 以 外 の 宣 言 で は 、 動 作 し な い た め で す 。 な お 、 こ の パ ー ミ ッ シ ョ ン は Android バ ー ジ ョ ン 4.1 以 上 (minSdkVersion 16)を対象とする場合は任意の宣言となります。 GCM メッセージを受け取るには、少なくとも下記に示す 4 つのパーミッションが必要となります。 ・GCM メッセージを受け付けるためのパーミッション ・インターネットに接続するためのパーミッション ・Google アカウントにアクセスするためのパーミッション・ ・スリープ時にも GCM メッセージを受け付けるためのパーミッション 上記のパーミッションを追加します。 <uses-permission <uses-permission <uses-permission <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> android:name="android.permission.INTERNET" /> android:name="android.permission.GET_ACCOUNTS" /> android:name="android.permission.WAKE_LOCK" /> GCM のメッセージを受け付ける BroadcastReceiver が必要になるので追加します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 52 2-3. Push 型通信処理アプリケーション開発における注意点 <receiver android:name="com.google.android.gcm.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" > <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="my_app_package" /> </intent-filter> </receiver> <category>タグで定義する my_app_package は、パーミッションで指定したものと同じ<manifest>タグ で定義した package になります。こちらも、Android バージョン 4.1 以上(minSdkVersion 16)を対象と する場合は、任意の設定となります。 最後に、GCM サーバからメッセージを受け取るための Intent サービスを記述します。 この Intent サービスは、GCM ライブラリで提供される GCMBroadcastReceiver によって呼び出されます。 <service android:name=".GCMIntentService" /> これで、AndroidManifest は完了です。 次に、GcmActivity について説明します。 Android アプリケーションでは onCreate メソッドがはじめに実行されます。onCreate は、アプリケーション起動 時の初期設定等を行います。 GcmActivity では下記のような初期設定を行なっています。 • アプリケーションサーバの URL および、SENDER_ID が適切な値で設定されているかをチェック • デバイスが適切な依存関係を持っているかをチェック • マニフェストが適切に設定されているかをチェック • GCM の登録 ID が取得済みかどうかをチェックし、未取得の場合は取得 この中の「マニフェストが適切に設定されているかをチェック」のロジックは開発用です。そのため、本番運用時には 削除しても問題ありません。したがって、本番運用時のビルドでは除くことができるよう、下記のようにコードを書くこと が望ましいです。 if(BuildConfig.DEBUG) GCMRegistrar.checkManifest(this); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 53 2-3. Push 型通信処理アプリケーション開発における注意点 また、「GCM の登録 ID が取得済みかどうかをチェックし、未取得の場合は取得」のロジックはいくつかポイントがあ ります。 final String regId = GCMRegistrar.getRegistrationId(this); GCM の登録 ID 取得は、上記実装で行うことができます。登録 ID は、この後で変更することはありません。 したがって、final を指定してアクセスの向上に役立てています。 この結果で regId が空だった場合は、アプリケーションが GCM サーバに登録されていません。 GCMRegistrar.register(this, SENDER_ID); 上記実装で、アプリケーションを登録して登録 ID を取得します。 ただし、登録 ID が取得出来たからといって、アプリケーションサーバにクライアントアプリケーションが登録されている とは限りません。 また、アプリケーションサーバに登録されているかどうかをサーバに問い合わせた場合、時間も電力も消費します。 そ の た め 、 GCMRegistrar に は サ ー バ に 登 録 さ れ て い る か ど う か を チ ェ ッ ク す る GCMRegistrar.isRegisteredOnServer(this)メソッドが用意されています。 このメソッドを有効利用することで、素早く安定した確認を行うことができます。 このメソッドを使用し、アプリケーションサーバへの登録状態をチェックするためには、アプリケーションサーバに登録さ れたタイミングで、GCMRegistrar.setRegisteredOnServer(context, true)を用いて登録済みである(も しくは解除で未登録である)ことを知らせておく必要があります。 アプリケーションサーバへの登録確認については、以降で詳しく説明します。 アプリケーションサーバにクライアントアプリケーションが登録されていない場合は、アプリケーションサーバへ登録する 必要があります。アプリケーションサーバへの登録には、HTTP 通信を使用します。 HTTP 通信を使用する場合は、別スレッドで行わなければいけません。その理由は、描画スレッドで 5 秒以上応 答がない場合、ANR(Application Not Responding)と呼ばれるエラーが発生するためです。 別スレッドで通信を行う方法として、今回は AsyncTask を使用しています。 AsyncTask クラスより提供されている、doInBackground メソッドを使用し、時間のかかる処理を実行します。 登録 ID の登録は、後述する receiver でも使用するため、ServerUtilities クラスを作成して行います。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 54 2-3. Push 型通信処理アプリケーション開発における注意点 @Override protected void doInBackground(void... params) { // 端末登録 boolean registered = ServerUtilities.register(context, regId); // 端末登録に失敗した時はアプリケーション登録を解除する if (!registered) { GCMRegistrar.unregister(context); } return null; } 登録に失敗した場合は、GCMRegistrar と不整合を起こさないために登録解除を行なっています。 今回のサンプルプログラムで AsyncTask を使用するには、もう一点ポイントがあります。 それは、AsyncTask が終了するときに実行される onPostExecute メソッドです。onPostExecute で自アプリ ケーションに null を入れることにより、GC(Garbage Collection)の対象にしています。 @Override protected void onPostExecute(Void result) { mRegisterTask = null; } AsyncTask などで別スレッドを使用する場合は、処理がキャンセルされることも考慮する必要があります。 アプリケーションが終了するときにスレッドが残っている場合、アプリケーションが正しく終了できないためです。 また、GCM で GCMRegistrar を利用する場合には、不要になったタイミングで GCMRegistrar.onDestroy(this)を使用し、GCMRegistrar を破棄する必要もあります。 次の例は onDestroy でこれらを実装しています。 @Override protected void onDestroy() { // 端末登録作業中の場合はキャンセル if (mRegisterTask != null) { mRegisterTask.cancel(true); } // GCMRegister の破棄 GCMRegistrar.onDestroy(this); super.onDestroy(); } ServerUtilities クラスは、アプリケーションサーバへの登録および、登録解除などの処理を行うクラスです。 登録および、登録解除処理は、オブジェクトのフィールドにアクセスする必要がありません。したがって、static メソ ッドにしています。こうすることで、メソッドの呼び出し速度を向上しています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 55 2-3. Push 型通信処理アプリケーション開発における注意点 登録では、引数として渡ってきた登録 ID を POST していますが、登録時にエラーとなった場合に備え、リトライ処 理を入れています。 Android アプリケーションは、常に安定したネットワーク通信ができるとは限らないため、このようにして一定のリトラ イを行うことを推奨します。 また、リトライを行うときにもいくつかポイントがあります。 long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000); リトライ時に、即座にリトライしないようここで指定したミリ秒分 sleep しています。また、同時にアクセスされる場合 を考慮し、乱数で秒数をランダムにしています。 Android アプリケーション開発では、複数のデバイスで同時にサーバへアクセスされることがあります。その場合に 同じ時間の sleep を行うと、いつまでたってもすべてのデバイスが、同じ時間にアクセスします。 そのため、乱数を追加することにより、同時アクセスの時間を微妙にずらしています。 ネットワーク通信でエラーが発生するということは、ネットワーク通信が不安定な状況になっているということです。そ の場合、すぐに安定してネットワーク通信ができない可能性があります。 そのようなことを考慮するため、リトライする時間は徐々に伸ばしています。今回のプログラムでは、はじめは 2〜3 秒でリトライしますが、2 回目ではその倍の 4 秒〜6 秒後にリトライしています。それでも通信が接続不可である場 合は、さらに倍の 8 秒〜12 秒後にリトライしています。このように、リトライするまでの待機時間は徐々に伸びていき ます。 Android アプリケーション開発で、ネットワーク通信を使う場合は、上記のように通信リトライや、リトライ時間の 考慮が非常に重要となります。 登録 ID をアプリケーションサーバに登録・登録解除する場合、GCMRegistrar にも値を設定することを忘れて いけません。 GCMRegistrar.setRegisteredOnServer(context, boolean) 上記のとおり、登録の場合は true、登録解除の場合には false を、Boolean 値に登録しておきます。 登録しない場合、前述の GCMRegistrar.isRegisteredOnServer が利用できません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 56 2-3. Push 型通信処理アプリケーション開発における注意点 アプリケーションサーバとの通信結果は、ブロードキャストを使用し、通知しています。このようにすることで、メインス レッド起動している Activity に通知が可能となります。 public static void displayMessage(Context context, String message) { Intent intent = new Intent(DISPLAY_MESSAGE_ACTION); intent.putExtra(EXTRA_MESSAGE, message); context.sendBroadcast(intent); } この機能を利用する場合は、Activity で IntentFilter を指定し、BroadcastReceiver を登録する必要があ ります。 registerReceiver(mHandleMessageReceiver, new IntentFilter(DISPLAY_MESSAGE_ACTION)); private final BroadcastReceiver mHandleMessageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String newMessage = intent.getExtras().getString(EXTRA_MESSAGE); mDisplay.append(newMessage + "¥n"); } }; また、BroadcastReceiver を登録した場合は、Activity への通知が不要になったタイミングで登録解除を行 います。 unregisterReceiver(mHandleMessageReceiver); BroadcastReceiver を動的に登録・解除するタイミングは、通常 onResume メソッド・onPause メソッドで 行います。その理由は、メッセージの表示は、フォアグラウンドのみで動いていることが多いためです。もちろん、バック グラウンドにいるときにも処理を行いたい場合は、これらのメソッド内で行う必要はありません。 サンプルの GCM で通知されてきたメッセージは、最終的に mHandleMessageReceiver.onReceive で受け、 そのまま処理しています。しかし、IntentService などのサービスに、それらの処理を委譲することが推奨されていま す。その理由は、スレッドがその処理を完了させるために費やせる時間が十分であるという保証はないからです。想 定外の不具合を出さないためにも、必ず IntentService などのサービスを利用してください。 最後に GCMIntentService について説明します。 この IntentService は、ヘルパーライブラリで提供される GCMBroadcastReceiver によって呼び出されるもの です。また、GCM サーバから受信するメッセージは、すべて IntentService で受け取ることができます。 GCMIntentService クラスは、GCMBaseIntentService のサブクラスにすることで、 GCMBroadcastReceiver から呼び出されるメソッドを override します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 57 2-3. Push 型通信処理アプリケーション開発における注意点 public class GCMIntentService extends GCMBaseIntentService { @Override protected void onRegistered(Context context, String regId) { // 登録のインテントを受信した後で呼び出され、GCM サーバで割り当てられた登録 ID が渡ってきます。 // 通常はここからアプリケーションサーバに登録 ID を送ってサーバ上で登録します。 } @Override protected void onUnregistered(Context context, String regId) { // GCM サーバで登録 ID が解除された時に呼び出されます。 // 通常はここからアプリケーションサーバに登録 ID を送ってサーバ上から登録を解除します。 } @Override protected void onMessage(Context context, Intent intent) { // アプリケーションサーバが GCM にメッセージを送信したときに呼び出されます。 // メッセージがペイロードを持っている場合は、そのコンテンツは intent の Extra に入っています } @Override protected void onDeletedMessages(Context context, int total) { // GCM サーバよりメッセージが削除された時に呼び出されます。 // GCM サーバでは 100 件までしかメッセージを保持できません。 // そのために、削除されたメッセージはこちらで対応する必要があります。 } @Override public void onError(Context context, String errorId) { // アプリケーションがデバイスの登録または登録解除を試みたが、エラーが返されたときに呼び出されます。 // 通常は、errorId によって返されたエラーを確認して、問題の解決を試みることしかできません。 } @Override protected boolean onRecoverableError(Context context, String errorId) { // アプリケーションがデバイスの登録または登録解除を試みたが、 // GCM サーバが利用できないときに呼び出されます。 // GCM ライブラリはこのメソッドで false を返す場合を除いて // デバイスの登録または登録解除をリトライします。 // このメソッドはオプションですが、 // ユーザにメッセージを表示してキャンセルまたはリトライを試みさせたい場合のみ // オーバーライドすべきです。 } } なお、GCMIntentService は IntentService のサブクラスになるため、非同期で実行されます。UI スレッドを ブロックするリスクがなく、自由にネットワークの呼び出しができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 58 2-3. Push 型通信処理アプリケーション開発における注意点 GCMIntentService では、必ず public な GCMIntentService コンストラクタおよび SenderID が必要に なります。SenderID が静的である場合は、コンストラクタから super(SenderID)を呼び出しします。また、動的 である場合は、getSenderIds メソッドを Override します。 今回は SenderID を静的に保持しているため、コンストラクタから呼び出します。 public GCMIntentService() { super(SENDER_ID); } 登録 ID の登録を受け取ったときは、メッセージを表示すると同時に、アプリケーションサーバへ登録 ID を登録しま す。アプリケーションサーバへの登録は、前述の ServerUtilities を利用します。 @Override protected void onRegistered(Context context, String registrationId) { Log.i(TAG, "Device registered: regId = " + registrationId); displayMessage(context, getString(R.string.gcm_registered)); ServerUtilities.register(context, registrationId); } 登録 ID の削除を受け取ったときは、メッセージを表示すると同時にアプリケーションサーバから登録 ID を削除しま す。 アプリケーションサーバからの削除は、前述の ServerUtilities を利用します。 @Override protected void onUnregistered(Context context, String registrationId) { Log.i(TAG, "Device unregistered"); displayMessage(context, getString(R.string.gcm_unregistered)); if (GCMRegistrar.isRegisteredOnServer(context)) { ServerUtilities.unregister(context, registrationId); } else { Log.i(TAG, "Ignoring unregister callback"); } } メッセージを受け取ったときは、メッセージを表示すると共に Notification を作成して表示します。 @Override protected void onMessage(Context context, Intent intent) { Log.i(TAG, "Received message"); String message = getString(R.string.gcm_message); displayMessage(context, message); // notifies user generateNotification(context, message); } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 59 2-3. Push 型通信処理アプリケーション開発における注意点 通知できなかったメッセージがサーバ上に多数溜まり、メッセージが削除された場合は、その旨のメッセージを表示 すると共に、Notification を作成して表示します。 @Override protected void onDeletedMessages(Context context, int total) { Log.i(TAG, "Received deleted messages notification"); String message = getString(R.string.gcm_deleted, total); displayMessage(context, message); // notifies user generateNotification(context, message); } エラーを受け取ったときは、エラーメッセージを表示します。通常、この機能は開発中のみ使用されるはずであり、 本番リリース後にこのメソッドが呼び出される場合は、何らかの問題がある場合です。 @Override public void onError(Context context, String errorId) { Log.i(TAG, "Received error: " + errorId); displayMessage(context, getString(R.string.gcm_error, errorId)); } デバイス登録や登録解除の時に GCM サーバ上で問題があったとヘルパーライブラリが判断した場合、GCM サー バへの登録・解除が使用不可であることを表示します。同時に、親クラスの onRecoverableError を呼び出して、 GCM サーバーへの登録・解除が使用不可である結果を返します。 もし、再送信を行うかどうかのダイアログを表示する場合は、ここで行います。 @Override protected boolean onRecoverableError(Context context, String errorId) { // logmessage Log.i(TAG, "Received recoverable error: " + errorId); displayMessage(context, getString(R.string.gcm_recoverable_error, errorId)); return super.onRecoverableError(context, errorId); } Notification では、タップ時に表示する Activity を指定できます。Activity を呼び出す Intent には FLAG_ACTIVITY_CLEAR_TOP および、FLAG_ACTIVITY_SINGLE_TOP のフラグを付与する必要があ ります。なぜならば、フラグを付与することで同じ Activity が立ち上がっている時に、新しい Activity を生成しない ようにする必要があるからです。 なお、Notification を表示するメソッドにおいても、オブジェクトのフィールドにアクセスする必要がないために static にしています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 60 2-3. Push 型通信処理アプリケーション開発における注意点 GCM を使用するためにはアプリケーションサーバを開発する必要があります。アプリケーションサーバは以下の前提 条件を満たす必要があります。 • クライアント(Android 端末内アプリケーション)との通信が可能であること。 • 「GCM」サーバへの HTTPS リクエスト送信が可能であること。 • リクエストのハンドリングと、必要に応じて実行された連続したリトライ処理でタイムアウトが起こる場 合に、タイムアウト毎にタイムアウト値を 2 倍にして送信する機能(指数バックオフ)の使用が可能 であること。 • API Key と登録 ID の保存が可能であること アプリケーションサーバで実装しなければならない処理は、次の三つです。 • 登録処理 GCM 機能を持つ Android アプリケーションから送られてきた登録 ID を、データベースなどで保持する機 能 • 登録解除処理 GCM 機能を持った Android アプリケーションから送られてきた登録 ID を、データベースなどから削除す る機能 • メッセージ送信処理 GCM サーバにメッセージを送って Android アプリケーションに通知する機能 ※ この機能は、GCM サーバからの応答をもとに、適切に処理する必要があります。 アプリケーションサーバをサーブレットで開発する場合は、ヘルパーライブラリがクライアントプログラムと同じ場所にあ ります。 gcm-server.jar がサーバ側のヘルパーライブラリです。これをサーバのクラスパスにコピーしてインポートすることで、 容易に GCM サーバとの通信が行えます。 登録処理および登録解除処理は、特に難しくありません。しかし、通信を伴うメッセージ送信処理は続くとおり解 説します。 メッセージ通信のために、リクエストとレスポンスを組み立てる方法として、プレーンテキストと JSON の二種類が用 意されています。 しかし、複数のデバイスに同時にメッセージを送信するには、JSON を用いる必要があります。 メッセージを送信するには https://android.googleapis.com/gcm/send に POST リクエストを行います。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 61 2-3. Push 型通信処理アプリケーション開発における注意点 リクエスト内容を以下の表に示します。 HTTP HEADE R Authorization Content-Type JSON プレーンテキスト key=YOUR_API_KEY key=YOUR_API_KEY application/json (※1) application/x-www-form-urlencoded;c harset=UTF-8 メッセージを受信する登録 ID リストの文字型 registration_ids 配列。 1〜1000 個の範囲で登録する必要がある。 メッセージを受信する単一の登録 ID。[必須] [必須] 更新通知など、メッセージ内容が同じものを同 一と認識させるためのキーとなる、任意の文字 列。オフライン時などで送信されず GCM サーバ に保持されている場合、このキーと同じものが 通知されると上書きさ、同じメッセージを大量に collapse_key 送信しないようにする。ただし、メッセージが送 JSON と同様 信される順序に保証はないため、最後に送ら れたメッセージであるとは限らない点に注意が 必要。time_to_live パラメータを使用してい る場合は指定する必要があるが、使用してい ない場合はオプションになる。 JSON オブジェクトでは、メッセージのペイロード HTTP データをキーと値のペアで表現する。これが存 BODY 在する場合、ペイロードデータのキーがエクスト (※2) ラの名前となって、アプリケーションデータのイン data(※3) テントに含まれる。キーと値のペアの数に制限 (該当フィールドなし) はないが、メッセージサイズの合計は 4KB の制 限を持つ。値は文字列でなければならない。ま た、キーを予約語(from や google ではじま る任意の単語)にはできない。 data をプレフィックスとするパラメータのペイロー ド。サフィックスがキーとなる。キーと値のペアの数 data.<key> (該当フィールドなし) (※3) に制限はないが、メッセージサイズの合計は制限 を持つ。また、キーを予約語(from や google ではじまる任意の単語)にはできない。 true を指定した場合、デバイスがアイドル状態 の時にはメッセージはすぐに送信されない。サー delay_while_idle バはアクティブになるまで待ち、アクティブになる と collapse_key の最後のメッセージが送信さ 1 もしくは true を指定すると true になる。それ 以外は false になる。デフォルトは false。 れる。指定しない場合は false になる。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 62 2-3. Push 型通信処理アプリケーション開発における注意点 デバイスがオフラインの時に、GCM サーバに保 持されるメッセージの期間を秒で指定する。デ time_to_live フォルトでは 4 週間保持する。なお、0 に設定 JSON と同様 した場合には、直ちに破棄される。 クライアントアプリケーションのパッケージ名。指 restricted_packa 定した場合は、パッケージ名がマッチした ge_name registrationIDs にのみメッセージを送信す JSON と同様 る。 テストのために使用する。この値を指定した場 dry_run 合は、実際のメッセージは送信されない。デフォ JSON と同様 ルトでは false になる。 表 2. リクエスト情報 (※1) Content-Type が設定されていない場合はプレーンテキストと同じ扱いになります (※2) プレーンテキストの場合は HTTP のパラメータとしてセット (※3) JSON の場合フィールド名は「data」、プレーンテキストの場合は「data.<key>」となります メッセージの送信を試みたときのレスポンスは、正常に処理された場合と、GCM サーバがリクエストをリジェクトした 場合が考えられます。 正常に処理された場合は、200 が返ります。リクエストをリジェクトした場合は、200 以外が返ります。 レスポンスコードの値を以下の表に示します。 レスポンスコード 200 400 401 概要 正常に処理された場合に返ってくる値。レスポンスのボディにはメッセージステータスについての詳細が入る。 そのフォーマットは、リクエストが JSON なのかプレーンテキストなのかによって異なる。詳しくは後述。 JSON リクエストにおいて、JSON として解析できない場合や、無効なフィールドが含まれている場合に返っ てくる値。障害の正確な原因は、レスポンスに記述されている。 SenderId アカウントの認証エラーが発生した場合に返ってくる値。 500 や 503 のように、500〜599 の範囲で返ってくる。 5XX GCM サーバで内部エラーが発生した場合や、タイムアウト等でサーバが一時的に使用不可になった場合に 返ってくる値。この値が返ってきた場合は、指数バックオフでメッセージ送信のリトライをする必要がある。 表 3. レスポンスコード情報 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 63 2-3. Push 型通信処理アプリケーション開発における注意点 ■ 成功時のレスポンス JSON のリクエストが成功した場合、レスポンスのボディには以下のフィールドを持つ JSON オブジェクトが返ってき ます。 フィールド名 概要 multicast_id マルチキャストメッセージを識別するユニークな ID Success エラーなしで処理されたメッセージの数。 Failure 処理できなかったメッセージの数。 canonical_ids(※) 重複した registration ID の数。 Results メッセージの処理状況を表す配列オブジェクト。 並び順は、送信時の registrationID に対して同様となる。それぞれは下記のフィールドを持つ。 message_id 処理が成功したことを表す文字列。 registration_id 値があれば、canonical registration ID が存在したことを指す。 表 4. レスポンスボディ情報 (※)canonical registration ID とは、重複登録された registration ID という意味。重複登録のうち、片方を削除する必要があり ます。 failure と canonical_ids の値が 0 の場合は、レスポンスの残りを解析する必要はありません。 しかし、そうではない場合は、フィールドをすべて調べる必要があります。 message_id および registration_id がセットされている場合は、元の registration_id から新しくセットされ た registration_id に書き換えます。 元の registration_id の値は返ってこないため、リクエストで渡すときの registration_id の順番から判断する 必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 64 2-3. Push 型通信処理アプリケーション開発における注意点 Error の値がセットされている場合は、以下に示す内容によって変わってきます。 • Unavailable の場合は、別のリクエストでその送信をリトライしてみる必要がある • NotRegistered の場合は、GCM のアプリケーションがデバイスからアンインストールされているため、 サーバのデータベースから登録 ID を削除する必要がある • それ以外の場合は、リクエストで渡された登録 ID に何らかの誤りがあったことを指す。その場合はエ ラーの内容によって修正する必要がある。エラーの内容については後述。 プレーンテキストのリクエストが成功した場合は、レスポンスボディは 1 行または 2 行が、キーと値のペアのフォームに 含まれています。 最初の行は常に取得することができます。そこには、id=送信されたメッセージの ID もしくは Error=GCM のエラ ーコードが入っています。 2 行目がある場合、registration_id=canonical registration ID になります。2 行目は 1 行目がエラーで ない場合のみ、送信される可能性があります。 プレーンテキストの場合のレスポンスは、以下のようにハンドリングすることが推奨されています。 • もし 1 行目が id ではじまる場合は、2 行目をチェックします。 • 2 行目が registration_id ではじまる場合は、canonical registration ID なので、JSON と 同様にもとの、registration_id から新しくセットされた registration_id に書き換えます。 • 上記でない場合、Error の値を取得して処理を行います。 • NotRegistered の場合は、JSON と同様にサーバのデータベースから登録 ID を削除しなければ なりません。 • それ以外の場合は、JSON と同様に回復不能なエラーがあります。 エラーの内容によって修正する必要があります。 なお、プレーンテキストの場合は、Unavailable が返ってくることはありません。 その場合は、HTTP ステータス 500 が返ってきます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 65 2-3. Push 型通信処理アプリケーション開発における注意点 エラーの場合のレスポンスは、以下のようなものがあります。 エラーレスポンス 不明な登録 ID 概要 エラーコードが MissingRegistration のときに発生します。 この場合は、リクエストに登録 ID が含まれているかをチェックしてください。 エラーコードが InvalidRegistration のときに発生します。この場合は、リクエストの登録 ID が正しいかど 無効な登録 ID うかをチェックしてください。余計な文字が追加されている。もしくは、文字列が欠落している可能性がありま す。 このエラーは MismatchSenderId のときに発生します。登録 ID は Sender でグルーピングされており、 SenderId のミスマッチ GCM の登録者のみが登録 ID に対してのメッセージを送信できます。そのため、デバイスに送信するメッセー ジの SenderId が異なるとメッセージは送信出来ません。 未登録のデバイス アプリケーションが手動で 登録を解除した場合 アプリケーションが自動的に 登録を解除された場合 登録 ID が有効期限切れ になった場合 メッセージが大きすぎる エラーコードが NotRegistered のときに発生します。 既存の登録 ID が以下のような場合等で無効になった可能性があります。 ユーザが手動で登録 ID を解除した場合に発生する可能性があります。 ユーザがアプリケーションをアンインストールした場合に発生する可能性があります。 Google が登録 ID のリフレッシュを行った場合に発生する可能性があります。 エラーコードが MessageTooBig のときに発生します。メッセージのサイズが、4096 バイトを超えた場合に 返ってきます。このサイズはキーの大きさだけでなく、値の大きさも含まれます。 エラーコードが InvalidDataKey のときに発生します。ペイロードデータのキーが不正な値を指定していま 不正なデータキー す。キーに from や google のプレフィックスがついている可能性があります。これらの値は予約語のため、使 用できません 無効な有効期間 Time to Live に、0~2,419,200 秒(4 週間)以外の期間が指定されています。 HTTP ステータスコードが 401 のときに発生します。メッセージの送信を試みようとしている SenderId のア カウントが認証されません。以下のようなケースが考えられます。 認証ヘッダが欠落、あるいは無効な構文になっている 認証エラー キーとして送信されたプロジェクト ID が無効である キーは有効だが、GCM サービスでは無効になっている Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 66 2-3. Push 型通信処理アプリケーション開発における注意点 サーバ鍵の IP で、ホワイトリストされていないサーバから発信されたリクエストである HTTP ステータスコードが 503 のとき、または結果配列にある JSON オブジェクトの error フィールドが Unavailable のときに発生します。サーバが、時間内にリクエスト タイムアウト を処理できなかったことを意味します。この場合は、同じリクエストをリトライしなければ なりません。ただし、次の要件に従う必要があります。これらの要件に従わない場合 は、ブラックリストに入るリスクがあるため、注意が必要です。 Retry-After ヘッダが、GCM サーバからのレスポンスに含まれている場合は、そ のルールを順守する。 リトライのメカニズムに、指数パックオフを実装する。これは、リトライが失敗した後 で指数的に遅延を増やすことを指す。 HTTP ステータス コードが 500 のとき、または結果配列にある JSON オブジェクトの error フィールドが 内部サーバエラー InternalServerError のときに発生します。サーバがリクエストを処理中にエラーが起こると発生します。 同じリクエストをリトライできますが、タイムアウトと同じルールが適用されます。そのためタイムアウトと同じくブ ラックリストに入るというリスクがあります。 無効なパッケージ名 エラーコードが InvalidPackageName のときに発生します。 リクエストのパッケージ名が一致しない登録 ID が指定されています。 表 5. レスポンスエラー情報 【公式サイト】 http://developer.android.com/google/gcm/gs.html http://developer.android.com/google/gcm/gcm.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 67 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-1. Service のライフサイクル Activity にライフサイクルがあるのと同様に、Service にもライフサイクルが存在します。Service のライフサイクル は、Activity に比べると、シンプルな構造になっています。ただし、サービスはバックグラウンドで実行されるため、動 作していることにユーザは気づきません。そのため、Activity のライフサイクル以上に注意を払う必要があります。 Android システムの Service には、以下に示す 2 つの種類が存在します。 • startService 別のコンポーネントが startService()を呼び出すと、サービスが作成されて実行されます。 自分自身もしくは別のコンポーネントが、明示的に停止指示を出すまで動き続けます。 • bindService 別のコンポーネント(クライアント)が bindService()を呼び出すと作成されます。 クライアントとサービスは IBinder インターフェースを通じて通信します。 bind されているサービスは、すべてのクライアントから unbind されるまで動き続けます。 サービスを停止する必要はありません。 ライフサイクルの構造を「図 17 サービスのライフサイクル」に示します。 左が startService のライフサイクル、右が bindService のライフサイクルです。 図 17 サービスのライフサイクル Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 68 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 ただし、この2つは完全に分離している訳ではありません。すでに、startService しているサービスに対して bindService を実行することも可能です。 このようなケースでは、すべてのクライアントが unbind するまで、stopService()や stopSelf()が、実際にサービ スを停止することはありません。 サービス全体の生存期間は、onCreate メソッドが呼び出されたタイミングから onDestroy メソッドが呼び出され るまでとなります。Activity と同じく、onCreate メソッドで初期設定を行い、onDestroy でリソースの開放を行い ます。これは、startService、bindService ともに共通です。 サービスのアクティブな生存期間は、onStartCommand()または、onBind()のいずれかの呼び出しで開始し ます。各メソッドはそれぞれ、startService()または、bindService()のどちらかに渡された Intent によりハンドル されます。 startService の場合は、アクティブな生存期間の完了が、全体の生存期間の完了と同じになります。つまり、 stopService が呼び出されるタイミングでサービスが終了され、onDestroy が実行されます。 bindService の場合は、onUnbind から戻ったときに完了します。 【公式サイト】 http://developer.android.com/guide/components/services.html#Lifecycle Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 69 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-2. バッテリ バッテリに対する考慮は、モバイルアプリケーション開発と Web アプリケーション開発における大きな違いの一つで す。 特に Android アプリケーションの場合は、マルチタスクで実行されるため、バッテリ消耗が激しいと言えるでしょう。 そのため、常に低消費電力での開発を心がける必要があります。 低消費電力の具体例については、後述の[2-4-10.低消費電力を意識した実装方法]もご参照ください。 バッテリに関する考慮では、自アプリケーションが低消費電力での開発を心がけるという点以外にも、他アプリケー ションが電力を消費おり、自アプリケーションが起動された時には、すでにバッテリ容量がないといった可能性もありま す。そのような場合に、続けて通常の動作を行わせようとすると、アプリケーションが通信中にバッテリが切れることも 考えられます。 それに伴い、適切な処理が実装されていなければ、データが破壊される可能性があります。しかし、そのエラーハン ドリングは非常に困難です。そこで、バッテリ残量が 5%以下ならネットワーク通信はしない、といった処理を入れる ことで対応することが可能となります。 アプリケーションから、現在の電力の状態(充電中か放電中かなどの状態や、バッテリの残量など)を確認するこ とができます。そこから処理を変えるという実装は、バッテリの有効利用になります。 例えば、以下のような手段が考えられます。 ・充電中もしくは、バッテリの残量が 30%以上なら通信間隔を、15 分に 1 回 ・放電中でバッテリの残量が、15%超 30%以下なら通信間隔を、15 分から 30 分に変更 ・放電中でバッテリの残量が、5%超 15%以下なら通信間隔を、30 分から 1 時間に変更 ・放電中でバッテリの残量が、5%以下なら通信しない といった実装が考えられるでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 70 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-3. 通信間隔 ネットワークを使用して通信を行う際は、送信するパケットサイズがたとえ小さくても、バッテリを大きく消費します。 それは、ネットワーク接続のための電力消費が大きいためです。したがって、通信間隔を極端に短くすると、バッテリ の消耗が激しくなります。 ネットワーク通信間隔とバッテリの消費は、トレードオフの関係になります。 通信間隔が短いとバッテリの消費が早くなり、バッテリの消費を抑えようと思うと、通信間隔は必然的に長くなりま す。そのため、バッテリの消費量と通信結果に対する正確性から、最も適していると思われる間隔を選択するべき です。 定期的な通信を行う場合に最適な方法は、AlarmManager.setInexactRepeating を使用することです。 繰り返し処理で setInexactRepeating を使用した場合、数秒程度の誤差が生じる可能性があります。しか し、他の通信とまとめて通信を行うため、バッテリの消費量を減らす効果があります。 setInexactRepeating を使用して、繰り返し時間を指定する場合、次のいずれかを選択します。 • INTERVAL_FIFTEEN_MINUTES 15 分 • INTERVAL_HALF_HOUR 30 分 • INTERVAL_HOUR 1 時間 • INTERVAL_HALF_DAY 半日 • INTERVAL_DAY 1日 通信を行う間隔は、最短でも 15 分に 1 回が望ましいと言えるでしょう。 また、上記の定数から「Android 端末は 15 分未満の間隔で定期通信を行うことを、Google は推奨していな い」という見方をすることもできるかもしれません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 71 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-4. ネットワークの状態 モバイルアプリケーション開発と Web アプリケーション開発において、ネットワークの状態に対する考慮は、大きな 違いの一つです。 Web アプリケーション開発では、ネットワークは有線で常に安定して接続をもっていることを前提にできます。しか し、モバイルアプリケーション開発では、ネットワークは常に不安定です。そのため、「切断/再接続が繰り返される」 ということを前提に開発を行う必要があります。 例えば、地下街に入った時やエレベータに乗った時など、その直前まで安定してネットワークにつながっていたもの が突然つながらなくなる可能性があります。そのような場合に、何らかの通信に失敗することを想定し、エラーハンド リングを正しく実装しておく必要があります。その際に必要な処理とは、不要な変数のクリア、Cursor やその他クロ ーズ処理が必要なクラスの close などです。これらを、忘れずに行いましょう。 こういったクローズ処理は、正常系では実装しているが、異常系では実装していないということがよくあります。これ らの適切な実装を怠ると、メモリリークの原因となり、Android デバイス全体に悪影響を及ぼします。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 72 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-5. リトライ ネットワークの状態が不安定になる。もししくは、一時的にサーバへのアクセスが集中し、接続できなかった場合は、 通信のリトライを行う必要があります。 しかし、ネットワークの状態やサーバのアクセス状況などは、すぐには回復しない可能性があります。そのため、リトラ イは一定の時間をおいて実施することが望ましいでしょう。 また、アクセス集中を避けるために、すべてのアプリケーションで同一時間にリトライするべきではありません。定数 +乱数を用いるなどを行い、サーバへのアクセスをある程度分散させる実装が必要です。 リトライの間隔は、一定ではなく、徐々に伸ばしていくことをお勧めします。例えば、定数で 30 秒後+乱数で 10 秒後=40 秒後が、初回のリトライ開始時間になるとします。 それでもまだエラーになる場合は、次のリトライ開始時間を、40 秒後×2 の 80 秒後にすることが望ましいでしょう。 さらに、これを 3 回まで繰り返す場合や、次回リトライ開始時間が 5 分を超える場合は 5 分にするなど、リトライ時 間の調整を行うべきです。 また、サーバ側に問題があり、すぐに復旧しない可能性もあります。一定回数以上や一定時間以上が経過した 場合は、リトライをキャンセルするなども考慮する必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 73 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-6. アプリケーションの状態を正しく管理する Android システムは、Linux カーネルに LowmemoryKiller と呼ばれる独自の仕組みを追加しています。 Lowmemorykiller は、端末全体で空きメモリが少なくなると、重要度の低い順にアプリケーションのプロセスを強 制終了していきます。重要度は、アプリケーションの状態に基づいて 5 つのレベルに分類されます。 アプリケーションの 5 つの分類を、重要度の高い順に以下に示します。 1. フォアグラウンド(前面)プロセス 「ユーザが実行中の処理」に対して、必要とされるプロセスです。 フォアグランドプロセスとみなされる条件は、次のとおりです。 1-1. フォアグラウンドで動作中の Activity(OnResume が呼び出された状態)を実行しているプロセ ス 1-2. フォアグラウンドで動作中の Activity からバインドされている Service を実行しているプロセス 1-3. startForeground をコールしている Service を実行しているプロセス 1-4. Service のライフサイクルコールバック(onCreate、onStart、onDestroy)を実行しているプロ セス 1-5. BroadcastReceiver の onReceive を実行しているプロセス 2. 可視プロセス フォアグラウンドではありませんが、ユーザの目に見える画面(可視領域)に影響を及ぼすプロセスです。 可視プロセスとみなされる条件は次のとおりです。 2-1. フォアグランドではないが、ユーザにまだ見えている(OnPause が呼び出された)Activity を実 行しているプロセス 2-2. 可視プロセスの Activity にバインドされている Service を実行しているプロセス 3. サービスプロセス startService メソッドで開始された Service を実行中で、なおかつ上記の二つに分類されないプロセス 4. バックグラウンドプロセス ユーザから完全に見えない Activity(onStop が呼び出された)を保持するプロセス 5. 空のプロセス 5-1. アクティブなコンポーネントを保持していないプロセス。プロセスをキャッシュする目的だけに用いられ、 Activity や Service が、一切立ち上がっていないプロセスのことです。 5-2. このプロセスにより、次回のコンポーネント起動時間を短縮することが可能。 Lowmemorykiller によって、メモリ不足が発生した時に Activity が破棄される可能性があります。 例えば、SNS アプリケーションの Activity から、Intent を使ってカメラアプリケーションを起動したと考えてみましょ う。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 74 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 この時点で、カメラアプリケーションがフォアグラウンドプロセスになり、呼び出し元の Activity はバックグラウンドプロ セスになります。一般的に、カメラアプリケーションは大量のメモリを使用します。そのため、バックグラウンドプロセスで ある呼び出し元の Activity は一度破棄されて、カメラアプリケーションから戻ってきた際に Activity が再構築されま す。 もし、呼び出し元の Activity で状態などの値をクラス変数で保持していた場合、それらもすべて初期化されてし まいます。そのため、その値が重要なデータであった場合は、望まない動作を引き起こしたり、異常終了の原因とな ります。 Android システムでは、このような状況を回避するための方法が用意されています。 メモリ不足で破棄される前に、onSaveInstanceState メソッドが呼び出されます。onSaveInstanceState は Bundle を引数にもっており、この値は Activity が破棄されても保持されます。そのため、このメソッドで状態など を Bundle に保持させます。 例として、Activity のライフサイクルと SNS アプリケーションの画面遷移についての動作イメージを、以下の「エラ ー! 参照元が見つかりません。」に示します。 ※以下のライフサイクルは、Android 3.x (Honeycomb)以降の OS バージョンのものとなります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 75 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 図 18 SNS アプリケーション画面サイクル Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 76 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 LowMemoryKiller でア プ リ ケー シ ョン が kill さ れ た場合 、 Activity のラ イ フ サ イク ルの onStop() ~ onDestory()が呼ばれない可能性があります。onSaveInstanceState()は onStop()の前に呼ばれます。し たがって、LowMemoryKiller 対策としては有効です。 保持した Bundle は onCreate の引数として渡ってくるので、ここで元の変数にセットします。 public class HogeActivity extends Activity { private int mState = 0; // なんらかの重要な変数 @Override public void onCreate(Bundle savedInstanceState) { if( savedInstanceState == null ) { // 初期処理 mState = 1; } else { // mState に savedInstanceState で保持した値を戻す mState = savedInstanceState.getInt("STATE_KEY"); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 重要な変数を保持する outState.putInt("STATE_KEY", mState); } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 77 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-7. アプリケーション内で扱うデータ量を少なくする方法 ネットワーク通信で利用するデータ量が多い場合は、当然のこととして時間がかかる処理になります。加えて、ネッ トワークの圧迫にも繋がります。そのため、通信量はできるだけ少なくするべきです。 例えば、通信の応答は gzip で圧縮されたものを受け取るようにするなどです。 HttpURLConnection で通信する場合は、デフォルトで gzip 圧縮されていますが、Apache HTTP Client で通信する場合は、以下のように Header に gzip の指定をします。 request.setHeader("Accept-Encoding", "gzip"); また、写真を転送する場合には、サイズを下げるなども有効な手段です。 例えば、デバイスで撮った写真が、サーバでは 100×100 のサイズで必要だとします。Android 端末で撮った写 真をそのままアップロードしてサーバ上で加工するのは、ネットワーク通信量が増えてしまい、非効率的です。 このような場合は、アプリケーションでサイズを変更してサーバにアップすることが望ましいでしょう。ただし、サーバ上 でもオリジナルデータが必要な場合は、必ずしもサイズを下げなければならないというわけではありません。アプリケー ションの機能に応じて、適切なサイズ変更などを行なってください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 78 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-8. 適切なコネクションの管理方法 Android システムでは、HTTP 通信を行うための HTTP クライアント実装は、HttpURLConnection と Apache HTTP Client の二つがあります。 Apache HTTP Client の DefaultHttpClient と、そのサブクラスの AndroidHttpClient は、Web ブラウザ に適するように拡張された HTTP Client です。そのため、実装は安定していますがサイズも大きめになっています。 それに対して、HttpURLConnection は、汎用的で軽量な HTTP Client です。しかし、Android2.2 以前で はいくつかのバグがありました。Android2.3 以降の HttpURLConnection からは、デフォルトで gzip 圧縮が付 与されていることや、現在も開発が進んでいることから、利用が推奨されています。 これら 2 つの HTTP クライアントのうち、どちらが適しているかの判断は、アプリケーションでサポートとする Android のバージョンで判断するとよいでしょう。Android2.2 以前のバージョンを含むのであれば、動作が安定している Apache HTTP client を、2.3 以降のみをサポートする場合は HttpURLConnection を選ぶことが望ましいで しょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 79 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-9. 不安定状態を考慮したネットワークの使用方法 ネットワークが不安定で通信エラーによって送信できなかった場合は、再送処理が必要になります。しかし、再送 は即座に行うべきではありません。なぜならば、ネットワークが不安定なままである可能性があるからです。そのため、 再送は一定時間後に行うべきです。 ネットワークが不安定な場合においては、再送時でもエラーになる可能性は十分に考えられます。そのため、再送 処理にはバックオフ制御等を行うべきです。バックオフ制御とは、初回のリトライ処理は 1 分後、次のリトライ処理は その 2 分後、次はさらに 4 分後に、というように、リトライ回数が増えるにしたがって、処理を行う間隔を徐々に広げ ていく制御です。 また、通信が成功するまでリトライ処理を繰り返すといった実装はせず、リトライ回数制限や経過時間期限を設 定し、条件を満たした場合に以後のリトライ処理を行わないようにする制御も検討してください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 80 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 2-4-10. 低消費電力を意識した実装方法 Android アプリケーション開発で、低消費電力を意識した実装方法は多岐に及びます。以下の内容がすべてで はありませんが、特に有意差につながるポイントを挙げています。どうぞ、開発の際のチェックとしてもご活用ください。 • ローカル変数を、なるべく使用しない 文字列を返すメソッドがあり、その結果は常に StringBuilder や StringBuffer 7 に追加する処 理である場合、その関数の結果をローカル変数に格納してからではなく、直接追加する。 入力データのセットから文字列を抽出する際、コピーを作成するのではなく、オリジナルデータの subString を返却するようにする。 • 多次元配列を、一次元配列に分解する (Foo , Bar) オブジェクトの要素を保持するオブジェクトの実装が必要な場合、可能であれば Foo[] と Bar[] の二つの配列に分解し、処理効率良を向上させる。なお、他のコードから(Foo , Bar) オブジェクトの要素を保持するオブジェクトにアクセスする必要がある場合は、この限りでな い。 • static メソッドにすることが可能なものは、static メソッドにする オブジェクトのフィールドにアクセスする必要がない場合は、そのメソッドを static にすることで、呼び 出しスピードを向上させる。 • 可能な限り、フィールドアクセスを行う フィールドアクセスは、Android システムにおいて、フィールドメソッド呼び出しに比べ負担が少ない。 public なフィールドを用意し、内外問わず値を変更および、参照する際は、直接フィールドアクセス を行う。private なフィールドを用意し、値を変更および参照する際、内部からは直接フィールドアク セスを行い、外部からは Getter および Setter を使ってアクセスする。 • 定数には、static final を使用する final 属性をつけることでアクセス速度が向上する。 • for 文には、拡張 for ループ構文を使用する for (int i = 0; i < mArray.length; ++i) のような書き方よりも、for (Foo a : mArray) の 方が実行速度は 3 倍早い。 • 浮動小数点の使用は、十分に検討する 浮動小数点よりも int 型の方が演算速度は約 2 倍早いので、int 型で扱えるものは int 型にする。 なお、浮動小数点では、float 型と double 型の速度には、差はない。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 81 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 • 自作のメソッドよりも、標準ライブラリを使用する 自作メソッドよりも、標準ライブラリの方が最適化されている場合が多い。 String.indexOf とその仲間や、System.arraycopy は典型的な例で、実行速度が早くなる。 • 定期処理は、AlarmManager を適切に使用する AlarmManager はシステムが管理している。(Timer は、自アプリケーションが管理) ウェイクアップ状態で処理する必要がない場合は、スリープ状態で処理を行う。 スケジュールは、時刻指定ではなく間隔指定にする。 AlarmManager.setInexactRepeating を使用することで、多少時刻が不正確認になるが、 消費電力が抑えることができる。、実行間隔は AlarmManager クラスで定数化されている値を使 用する。 • バッテリ消費量(残量)に注意する バッテリの消費量を常にチェックして、バッテリが少ない場合は実行頻度を下げる処理を実装する。 • アプリケーション内で扱うデータ量を小さくする どこからも参照されないフィールドおよびメソッドを用意しない。 特定フィールドおよびメソッドについて、似たような役割を持つものが存在する場合、可能であれば 統合を検討する。 サーバへのリクエスト送信時に、不要なデータを送信しない。 サーバからのレスポンス返却時に、不要なデータを受け取らない。 • デバッグ時のみ必要な Log 出力などは、BuildConfig.DEBUG を条件として if 文で括る BuildConfig.DEBUG は、リリース時のビルドで削除される。 • • ネットワーク接続に注意する • 頻繁にネットワーク接続しない。 • ローミング時はネットワーク接続しない。 • サーバからのレスポンスは圧縮形式で受け取る。 • なるべくバックグラウンド常時動作状態にしない。 • 3D やアニメーションを過度に挿入しない。 不要な処理を行わない Service や BroadcastReceiver、Listener など、不要になった場合はすぐ設定内容の登録解 除およびキャンセル等を行う。 • 位置情報を、頻繁に更新しない アプリケーション上で位置情報を使用する場面を考慮し、適切な更新頻度に合わせる。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 2 章 クライアント/サーバモデルを利用したアプリケーション開発 | 82 2-4. Android 端末の“性能”“特性”を考慮したアプリケーション開発 • 位置情報を取得する際は、目的に合わせて使用するプロバイダを変更する 非常に正確な位置情報が必要な場合は、GPS プロバイダを利用し、おおよその位置が取得できれ ば良い場合は、ネットワークプロバイダを利用する。また、頻繁に位置情報を更新する必要がある 場合は、PASSIVE_PROVIDER を利用する。 • 位置情報を取得する際は、端末の状態に合わせて使用するプロバイダを変更する 屋内に移動したときは、GPS プロバイダからネットワークプロバイダに切り替える。 • 位置情報を定期取得する必要がなくなれば、取得しないように直ちに変更する • WakeLock を適切に使用する WakeLock 使用する際は、アプリケーションの状態に合わせて取得および開放を行う。 WakeLock の開放が必ず行われるよう、WakeLock を取得する際に、タイムアウト時間を設定す る。 • 不安定なネットワーク状態を考慮する ネットワーク未接続状態に変更した場合、ネットワーク接続処理を行わないようにするなど、ネットワ ーク接続状態に応じた動的実装を行う。 【公式サイト】 http://developer.android.com/training/articles/perf-tips.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 第3章 端末依存を意識したアプリケーション開発の注意点 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. | 83 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 84 3-1. 概要 3-1. 概要 Android システムのソースコードは、オープンソースで自由にカスタマイズができます。 そのため、各メーカがさまざまなカスタマイズを行いながらデバイスを発売しています。その結果、各デバイスによって 動きが違うといった機種依存が発生します。Java で実装する通常の処理部分は、大きな問題になるような機種 依存はほとんどなく、特に意識する必要はありません。 しかし、ハードウェアが関係する部分、具体的にはカメラ・位置情報・加速度センサなどは、メーカやデバイスによっ て異なる可能性があります。そのため、Android アプリケーション開発に際しては、端末に対する機種依存を可能 な限り起こさないように注意する必要があります。 本章では、2 章で取り上げた「メモリ・バッテリを効率的に使う実装」を織り交ぜつつ、得てして機種依存の原因と なりやすい機能について解説します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 85 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 3-2-1. 位置情報取得の基本と実践 - LocationManager Android システムでは、位置情報取得機能を持つアプリケーション開発のために、「GPS」や「ネットワーク」といっ たインフラを利用し、端末の位置情報を取得するための手段が提供されています。 しかしながら、Android 端末より位置情報を取得する際、エラー発生が原因で正確でない値を取得する場合 があります。エラーが発生する原因としては、下記 3 つが挙げられます。 • 多くの位置情報源が存在する 位置情報の手掛かりは、GPS、基地局および Wi-Fi などから提供されます。そのため、位置情報の 正確さ、通信速度およびバッテリ効率がトレードオフの関係にあることを考慮してどのシステムを信頼 し、使用するかを決定する必要があります。 • ユーザは移動しながら利用する ユーザの位置は変化します。そのため、開発者はタイミングを見計らって繰り返し位置を測定し、ユ ーザの所在を確認する必要があります。 • 位置情報の精度は常に変化する 各位置情報源から送られてくる位置情報の測定値は、精度が一貫していません。 例えば、情報源 A より誤差 10m の位置情報を取得した後に、別の情報源 B より誤差 100m の 位置情報を取得するような事態が発生する可能性があります。 これらの問題により、信頼できる位置情報取得は困難であると言えるでしょう。そのため、アプリケーションを開発 する際は、上記 3 点を考慮に入れ、より正確な位置情報を取得できるようにする必要があります。 位置情報の取得は、利用するインフラにより取得可能な位置情報の精度や、バッテリ消費量などが異なります。 GPS プロバイダは、後述のネットワークロケーションプロバイダに比べ正確な位置情報を提供します。しかしながら、 屋外のみの利用となります。また、バッテリの消費が早く、位置情報を返却するのにも時間がかかります。 ネットワークロケーションプロバイダは、携帯電話基地局の位置情報を利用するため屋内外問わず利用でき、バ ッテリの消費も GPS プロバイダより抑えられ、位置情報も早く返却されます。しかし、電波は建物によって吸収・反 射されるため、誤差の範囲が GPS プロバイダよりも大きいとされます。 【公式サイト】 http://developer.android.com/guide/topics/location/strategies.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 86 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 これらは、アプリケーションの使用目的や状況などに合わせ、GPS プロバイダかネットワークロケーションプロバイダ、 もしくはその両方を使い分ける必要があります。 これらのインフラは、両者とも「android.location」パッケージ内にあるクラスを使用することで利用可能となりま す。中核となる「LocationManager」クラスを使用すると、以下のことが可能になります。 • 端末の位置情報を定期的に報告するロケーションプロバイダリストの検索 • 検索条件や名前で特定したロケーションプロバイダより、ユーザの最新位置情報を定期的に更新す る処理の登録および解除 • 指定された緯度および経度の近似値(メートル単位の半径で特定)の範囲内に端末が入ってき た際に、インテントを発行する処理の登録および解除 「LocationManager」をアプリケーションにて取得する際は、他のサービス同様に「getSystemService()」メ ソッドを使用します。 LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); 「LocationManager」の準備ができると次にロケーションプロバイダを設定します。 ロケーションプロバイダとは、定期的に更新された位置情報を取得するための手段であり、GPS プロバイダとネット ワークロケーションプロバイダが存在します。前者は GPS を利用し、後者はネットワークを利用することで位置情報 を取得します。 さらに API レベル 8(Android2.2)以降であれば PASSIVE_PROVIDER を利用することが可能です。これ は、他のアプリケーションが取得した位置情報の値を流用します。たとえば、Google Map やドコモ地図ナビなどで、 あらかじめ位置情報を取得していた場合、その値を流用することが可能です。そのため、バックグラウンドで動くアプリ ケーション等でこの値を使用すると、バッテリの節約になります。 Android システムでは、位置情報の精度や消費電力など、様々な条件を指定することができます。条件を指 定することで、適切なロケーションプロバイダを検索し、取得することが可能です。これは「Criteria」クラスのメソッド を使用して指定します。 // 位置情報の精度や、費用を許可するか否かを指定し、ロケーションプロバイダのリストを検索する Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); criteria.setCostAllowed(false); // 適切なロケーションプロバイダを取得する String providerName = locManager.getBestProvider(criteria, true); // もし適切なロケーションプロバイダが見つからない場合は、null が返却される if (providerName != null) { ... } 「Criteria」クラスで指定することが可能な条件を、「表 6.位置情報取得における指定可能条件」に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 87 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 各々の ※精度 の指定は、「表 7.各精度の設定値」をご参照ください。 void setAccuracy (int accuracy) 位置情報の精度 ※ 精度 void setBearingAccuracy (int accuracy) 方位情報の精度 ※ 精度 void setHorizontalAccuracy (int accuracy) 経度および緯度情報の精度 ※ 精度 void setSpeedAccuracy (int accuracy) 速度情報の精度 ※ 精度 void setVerticalAccuracy (int accuracy) 高度情報の精度 ※ 精度 高電力消費を指定する場合は POWER_HIGH、中電力消費を指定す void setPowerRequirement (int level) 消費電力レベル る場合は POWER_MEDIUM、低電力 消費を指定する場合は POWER_LOW を指定する void setAltitudeRequired (boolean 高度情報を取得するか否か altitudeRequired) 取得する場合は true、しない場合は false void setBearingRequired (boolean 方位情報取得を取得するか否 取得する場合は true、しない場合は bearingRequired) か false void setCostAllowed (boolean costAllowed) 費用使用を許可するか否か void setSpeedRequired (boolean 速度情報を取得するか否か speedRequired) 許可する場合は true、しない場合は false 取得する場合は true、しない場合は false 表 6.位置情報取得における指定可能条件 ACCURACY_COARSE 極めて低い精度を指定する ACCURACY_FINE 極めて高い精度を指定する ACCURACY_HIGH 高い精度を指定する ACCURACY_LOW 低い精度を指定する ACCURACY_MEDIUM 中程度の精度を指定する NO_REQUIREMENT 条件を指定しない 表 7.各精度の設定値 (※精度) ロケーションプロバイダが確定したら、次は現在地の値を受け取る部分を指定します。この部分は、「Listener」 で受け取る方法と、「PendingIntent」で受け取る方法があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 88 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 「Listener」で現在地を受け取る場合は、「LocationListener()」で「onLocationChanged」メソッドを実装 します。 private final LocationListener listener = new LocationListener() { @Override public void onLocationChanged(Location location) { // 最新位置情報を取得した際に実施する処理を記載する // 最新位置情報は location を使用して取得する } }; 「listener」は「requestLocationUpdates」を使用して更新通知を行うようにします。 「requestLocationUpdates」で指定するのは、使用するロケーションプロバイダ、最低経過時間、最低移動距 離、通知する Listener です。 // listener を mLocationManager(「LocationManager」インスタンス)に登録する mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, // GPS から取得 10000, // 最低経過時間(ここでは最低 10 秒は間隔をあけて更新するよう指定) //(※最新位置情報より、10 メートル以上変化が無い場合は listener に通知しない) 10, // 最低移動距離(ここでは最新位置情報より 10 メートル変化した場合更新するよう指定) listener); この機能を有効にした直後は、すぐには正確な値を取得出来ません。そのため、アプリケーションがフォアグラウンド にあり位置情報を表示している場合は、早めの更新間隔は適切だと言えます。しかし、最低経過時間が短すぎる 場合、電池の消費量が多くなります。つまり、位置情報の取得では、正確さ・スピード・バッテリ効率がトレードオフ の関係になっています。 位置情報取得機能を利用したアプリケーションを作成する場合は、上記の点をしっかりと念頭に置き、利用シー ンに応じて「何が重要なのか」を考えて、位置情報の取得時間を設定する必要があります。また、最低移動距離 の設定を適切な値にすることも重要です。 しかし、最低移動距離を使用して、バッテリを節約することも困難です。なぜならば、位置の正確性が異なること で移動距離がずれる可能性があるためです。そのため、最低経過時間の方が、バッテリ節約のためには有用でしょ う。 アプリケーションがフォアグラウンドにない場合、可能な限り GPS_PROVIDER や NETWORK_PROVIDER の ようなアクティブプロバイダを使用するべきではありません。 どうしても、GPS_PROVIDER や NETWORK_PROVIDER をバックグラウンドで使用したい場合は、5 分以上 の間隔で取得するべきです(※)。なお、そのような場合は別プロセスで動作させている Service を使用して実 行しますが、下記のように Looper を指定してエラーを回避する必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 89 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 【公式サイト】 http://developer.android.com/reference/android/location/LocationManager.html#req uestLocationUpdates(long,float,android.location.Criteria,android.app.PendingIntent) Looper looper = Looper.getMainLooper(); mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, // GPS から取得 300000, // 最低経過時間(ここでは最低 5 分は間隔をあけて更新するよう指定) //(※最新位置情報より、10 メートル以上変化が無い場合は listener に通知しない) 10, // 最低移動距離(ここでは最新位置情報より 10 メートル変化した場合更新するよう指定) listener, looper); 「PendingIntent」で現在地を受け取る場合は「requestLocationUpdates」で直接指定します。 // 発行するインテントを用意する Intent intent = new Intent(this, <更新された位置情報を処理するアクティビティクラスを指定>); PendingIntent pendingintent = PendingIntent.getActivity(this, 0, intent, 0); // 上記 pendingintent を mLocationManager(「LocationManager」インスタンス)に登録する mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, // ネットワークから取得 10000, // 最低経過時間(ここでは最低 10 秒は間隔をあけて更新するよう指定) //(※最新位置情報より、10 メートル以上変化が無い場合は pendingintent を発行しない) 10, // 最低移動距離(ここでは最新位置情報より 10 メートル変化した場合更新するよう指定) pendingintent); // 使用するロケーションプロバイダの条件を指定する Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); // (1) listener を指定する場合 mLocationManager.requestLocationUpdates( 300000, // 最低経過時間(ここでは最低 5 分は間隔をあけて更新するよう指定) //(※最新位置情報より、10 メートル以上変化が無い場合は listener に通知しない) 10, // 最低移動距離(ここでは最新位置情報より 10 メートル変化した場合更新するよう指定) criteria, listener, looper); ロケーションプロバイダ、最低経過時間、最低移動距離は、Listener の場合と同じです。 // (2) PendingIntent を指定する場合 mLocationManager.requestLocationUpdates( 上記で挙げたものの、requestLocationUpdates はロケーションプロバイダを直接指定します。しかし、このロケ 10000, // 最低経過時間(ここでは最低 10 秒は間隔をあけて更新するよう指定) //(※最新位置情報より、10 メートル以上変化が無い場合は pendingintent を発行しない) ーションプロバイダの代わりに「Criteria」を指定することも可能です。「Criteria」を指定する場合は、以下のコード 10, // 最低移動距離(ここでは最新位置情報より 10 メートル変化した場合更新するよう指定) 内で示す 2 種類のパターンが存在します。 criteria, pendingintent); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 90 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 これらの登録を行うタイミングは、バッテリの節約やユーザの使い勝手に繋がります。アプリケーション起動時や、位 置情報が必要な画面の起動時など、適切なタイミングで登録してください。このとき、「LocationListener」の動 作時間について以下の点を考慮に入れてください。 • 「LocationListener」の動作時間が長いほどバッテリを消費する • 「LocationListener」の動作時間が短い場合、正確な位置情報が得られない可能性がある これらで登録した位置情報更新処理は、位置情報を表示する「Activity」がフォアグラウンドでなくなった場合な どのタイミングで停止するべきです。位置情報の精度を上げるためにも「最適な位置情報」の取得タイミングと、それ を内部処理にて使用するタイミングになるべく差がないことが望ましいと言えるでしょう。 したがって、「最適な位置情報」を取得してから余計な処理がなるべく発生しないように、必要な位置情報を取 得した後は、下記のように removeUpdates を使用して停止する必要があります。 // listener の場合 mLocationManager.removeUpdates(listener); // PendingIntent の場合 mLocationManager.removeUpdates(pendingintent); 一般的に、より正確な位置情報を取得する GPS プロバイダでは、ネットワークロケーションプロバイダに比べ、位 置情報の返却に時間を要します。しかし、アプリケーションによっては、GPS プロバイダを使用してより正確な位置 情報に更新したい場合があります。もしくは、ネットワークロケーションプロバイダを使用し、より早く位置情報を画面 上に表示したい場合もあります。 このような場合は「LocationManager」に対し、「LocationListener」およびインテントを登録する際に、GPS プロバイダとネットワークロケーションプロバイダの両方を使用することで、状況に応じたロケーションプロバイダの使い 分けが可能になります。 // 「GPS プロバイダ」を使用して位置情 o 報を更新するよう、listener を登録する mLocationManager.requestLocatinUpdates(LocationManager.GPS_PROVIDER, 20000, 20, listener); // ネットワークロケーションプロバイダを使用して位置情報を更新するよう、listener を登録する mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 10000, 10, listener); このように、取得した位置情報の精度は常に変化するため、取得した最新の位置情報が「現時点における最適 な位置情報」であるとは限りません。そこで開発者は、取得した位置情報に対し、開発者側で設けたいくつかの基 準を満たしているか否かを常に確認することで、最適な位置情報であるかを判断する仕組みを実装しなければな りません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 91 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 基準例として、以下のようなものが挙げられます。 • 取得した位置情報が、前回取得した位置情報に比べて非常に新しいものであるか • 取得した位置情報の精度が、前回取得した位置情報の精度に比べて良いか • 位置情報取得に使用したロケーションプロバイダが、信用できるものであるか ※ ロケーションプロバイダ内に用意されている getAccuracy()メソッドを用いることで取得した位置情 報内に存在する精度情報を取り出すことが可能 参考) 【公式サイト】 http://developer.android.com/reference/android/location/LocationProvider.html#get Accuracy() Android システムでは、ユーザが自由に設定を変更することが可能です。したがって、アプリケーションの起動時は GPS が ON になっていたにも関わらず、途中で OFF になるということも考えられます。そういった場合のために、ロケ ーションプロバイダの変更情報を受け付ける BroadcastReceiver を用意すると良いでしょう。 //現在使用中のロケーションプロバイダが使用不可になったときのためのレシーバの登録 IntentFilter intentFilter = new IntentFilter(PlacesConstants.ACTIVE_LOCATION_UPDATE_PROVIDER_DISABLED); registerReceiver(< 「BroadcastReceiver」継承クラス>, intentFilter); // より良いロケーションプロバイダが利用可能になるのを監視 // 最良のロケーションプロバイダの条件を指定する Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); // 最良のロケーションプロバイダを取得 String bestProvider = locationManager.getBestProvider(criteria, false); // 現在使用可能なロケーションプロバイダで最良のものを取得 String bestAvailableProvider = locationManager.getBestProvider(criteria, true); // 最良のロケーションプロバイダが使えるようになった場合、listener を登録しなおす if (bestProvider != null && !bestProvider.equals(bestAvailableProvider)) { locationManager.requestLocationUpdates(bestProvider, 0, 0, listener); } 位置情報は、GPS やネットワークを用いて取得します。ただし、状況によっては定期的に更新された位置情報を 取得することができない場合があります。 このような場合は、「getLastKnownLocation()」メソッドを使用することで、ロケーションプロバイダによって最後 に受け取った位置情報を取得することが可能です。上記のメソッドを使用する場合は、取得した値は古い情報で ある可能性が高いため、「いつ取得したものか(=Location.getTime())」や、「位置情報の正確性(= Location.getAccuracy())」などを調べる必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 92 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 また、「getLastKnownLocation()」メソッドにより取得した値を破棄し、ロケーションプロバイダによる新たな位 置情報の取得には、時間がかかる場合があります。そのとき開発者は、時間がかかる旨を告げる適切なメッセージ を、ユーザに対し表示すべきか否か検討する必要があります。なぜならば、位置情報の更新を取得する際には、ア プリケーションはユーザにストレスを感じさせるほどの時間を要する場合があるからです。 ここまでで、位置情報の精度とバッテリ消費量は、トレードオフの関係にあると説明して来ました。そのため、アプリ ケーションにて正確な位置情報を取得し、かつ優れたパフォーマンスを得るためには、何らかの調整が必要になりま す。それらに対する最適なバランスを見つけるために、以下のことを確認する必要があります。 • ウィンドウサイズを小さくする より小さいウィンドウで位置情報更新処理を行うと、ロケーションサービスとのやりとりを減らすことがで きます。それにより、バッテリの節約が可能になりますが、それは同時に取得する位置情報が少なく なることでもあります。そのため、開発者は取得した位置情報の中から新しい情報か、精度は高い か、位置情報取得に使用したロケーションプロバイダが信頼できるものかといった、最適な情報を選 択する必要が出てきます。 • ロケーションプロバイダが位置情報を更新する頻度を下げる 位置情報の更新頻度を下げることにより、位置情報の精度と引き換えにバッテリ効率を上げること ができます。このためには、requestLocationUpdates()メソッドで指定する最低経過時間と最 低移動距離を増やします。また、仕様上定期更新する必要がない場合もあります。そのような場 合には requestLocationUpdates()メソッドの代わりに requestSingleUpdate()メソッドを使 用して取得します。 requestSingleUpdate()は、requestLocationUpdates()メソッドの最低経過時間と最低 移動距離を除いたものに近いです。ロケーションプロバイダ名か Criteria を第 1 引数に指定し、 LocationListener か PendingIntent を第 2 引数に指定します。LocationListener を指定 した場合は、第 3 引数に Looper を指定する必要があります。 • ロケーションプロバイダのセットを制限する 使用するロケーションプロバイダの種類が多ければ多いほど、精度は向上します。しかし、その分バッ テリの使用量も多くなります。よって、アプリケーションを使用する場所の環境や目標とする精度レベ ルによって、 使用するロケーションプロバイダを限定することにより、バッテリ消費量を抑えることが可 能になります。 例えば、屋内では GPS プロバイダが使えません。そのため、屋内ではネットワークロケーションプロバイ ダのみ使用するといった方法です。 PASSIVE_PROVIDER を指定することも、バッテリの節約になります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 93 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 3-2-2. 適切に実装するためのシーケンスおよびポイント 位置情報を取得するためには、GPS プロバイダもしくはネットワークロケーションプロバイダが必要ですが、まずはそ れらが使用可能かを調べる必要があります。 Web アプリケーションなどの場合は、サーバ側で設定を行うことができます。そのため、一度プロバイダが使用可能 かを確認すれば問題ないかもしれません。しかし、Android システムはユーザが設定を自由に設定できるため、位 置情報の取得をユーザが無効にしている可能性があります。そのため、こういったチェックは必ず毎回行う必要があ ります。サンプルアプリケーションでは GPS プロバイダが無効の場合はダイアログを表示します。 @Override protected void onStart() { super.onStart(); LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); final boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); if (!gpsEnabled) { new EnableGpsDialogFragment().show(getSupportFragmentManager(), "enableGpsDialog"); } } また、ダイアログから直接設定画面に遷移させるようにすると、ユーザビリティが向上します。 private class EnableGpsDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()).setTitle(R.string.enable_gps) .setMessage(R.string.enable_gps_dialog) .setPositiveButton(R.string.enable_gps, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { enableLocationSettings(); } }).create(); } } private void enableLocationSettings() { Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivity(settingsIntent); } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 94 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 各ロケーションプロバイダが使用可能な場合、それらに対して位置情報更新が通知されるように設定します。以 下の例では、10 秒毎および 10 メートル差で通知されるように設定します。 初回の通知は時間がかかることがあるため、「getLastKnownLocation()」メソッドを利用して最後の位置情 報を取得しておきます。 gpsLocation = requestUpdatesFromProvider(LocationManager.GPS_PROVIDER, R.string.not_support_gps); networkLocation = requestUpdatesFromProvider(LocationManager.NETWORK_PROVIDER, R.string.not_support_network); private Location requestUpdatesFromProvider(final String provider, final int errorResId) { Location location = null; if (mLocationManager.isProviderEnabled(provider)) { mLocationManager.requestLocationUpdates (provider, TEN_SECONDS, TEN_METERS, listener); location = mLocationManager.getLastKnownLocation(provider); } else { Toast.makeText(this, errorResId, Toast.LENGTH_LONG).show(); } return location; } 「mLocationManager.requestLocationUpdates」で listener を指定すると、位置情報が listener に コールバックされます。位置情報は「onLocationChanged()」メソッドで受け取ります。そこで受け取った Location をもとに、位置情報の更新が可能です。 画面上に位置情報を表示する例を以下に示します。 private final LocationListener listener = new LocationListener() { @Override public void onLocationChanged(Location location) { updateUILocation(location); } // ==== 省略 ==== }; 位置情報を受け付けたら、必ず解除する必要があります。これを忘れた場合、ずっと位置情報が通知され、バッ テリの浪費につながります。Activity が stop されたタイミングで、位置情報の受付を解除するコードを以下に示し ます。 @Override protected void onStop() { super.onStop(); mLocationManager.removeUpdates(listener); } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 95 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 位置情報を取得する場合、複数のロケーションプロバイダが通知されてきますが、どのロケーションプロバイダを信 頼するべきかを判断する必要があります。これは、取得時間と誤差で判断することができます。 それぞれを取得した時間に一定以上の開きがある場合は、新しいロケーションプロバイダを有効にします。 以下の例では 2 分を基準とし、それ以上の差がある場合には新しいロケーションプロバイダを有効にしています。 2 分以内の場合は誤差で比較し、誤差が小さい方を有効なロケーションプロバイダとします。 protected Location getBetterLocation(Location newLocation, Location currentBestLocation) { if (currentBestLocation == null) { return newLocation; } long timeDelta = newLocation.getTime() - currentBestLocation.getTime(); boolean isSignificantlyNewer = timeDelta > TWO_MINUTES; boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES; boolean isNewer = timeDelta > 0; if (isSignificantlyNewer) { return newLocation; } else if (isSignificantlyOlder) { return currentBestLocation; } int accuracyDelta = (int) (newLocation.getAccuracy() - currentBestLocation.getAccuracy()); boolean isLessAccurate = accuracyDelta > 0; boolean isMoreAccurate = accuracyDelta < 0; boolean isSignificantlyLessAccurate = accuracyDelta > 200; boolean isFromSameProvider = isSameProvider(newLocation.getProvider(), currentBestLocation.getProvider()); if (isMoreAccurate) { return newLocation; } else if (isNewer && !isLessAccurate) { return newLocation; } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { return newLocation; } return currentBestLocation; } private boolean isSameProvider(String provider1, String provider2) { if (provider1 == null) { return provider2 == null; } return provider1.equals(provider2); } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 96 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 このような比較を行う理由は何でしょうか。それは、位置情報を取得してコールバックされてくる時間は不定になる からです。したがって、2 分前に取得した位置情報が、1 分前に取得した位置情報よりも遅れてコールバックされる 可能性があるということです。そのため、取得した時間が新しいのはどちらか?という比較を行なってから、誤差が小 さいのはどちらか?という比較をしなければいけません。 信頼するロケーションプロバイダが決定したら、その位置情報を更新します。更新は Handler で行う必要がありま す。なぜならば、この更新はコールバックされた時に呼ばれることもあるからです。 Android システムでは、画面の表示更新を UI スレッド以外から行った場合エラーになるため、Handler を使用 して更新を行います。 mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_ADDRESS: mAddress.setText((String) msg.obj); mAddress.setVisibility(View.VISIBLE); break; case UPDATE_LATLNG: mLatLng.setText((String) msg.obj); break; } } }; private void updateUILocation(Location location) { Message.obtain(mHandler, UPDATE_LATLNG, location.getLatitude() + ", " + location.getLongitude()).sendToTarget(); } GPS 等で位置情報を取得しましたが、この数値だけではどこを指しているのかは分かりません。そこで、この位置 情報をもとに逆ジオコーディングで住所を表示します。 Geocoder API を使用することで逆ジオコーディングが可能です。しかし、これは Web サービスに依存しており、 状況によっては使用できない場合もありえます。Android OS 2.3(API LEVEL 9)からは isPresent()で、サ ービスの利用が可能かどうかをチェックできます。以下の例では、あらかじめ boolean 値を持っています。 mGeocoderAvailable d i () = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD その後、画面更新時にこの値をチェックして有効である場合は、逆ジオコーディングで住所を求めています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. && 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 97 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 if (mGeocoderAvailable) doReverseGeocoding(location); private void doReverseGeocoding(Location location) { (new ReverseGeocodingTask(this)).execute(new Location[] { location }); } ReverseGeocodingTask は、AsyncTask のサブクラスです。ここは、必ず非同期処理にする必要があります。 なぜならば、「getFromLocation()」メソッドは HTTP 通信を行なっているからです。そのため、直接 UI スレッドで 処理を行うと、画面が停止したかのように見えます。また、その状態が 5 秒以上続くと、応答が無い ANR エラーに なります。 private class ReverseGeocodingTask extends AsyncTask<Location, Void, Void> { Context mContext; public ReverseGeocodingTask(Context context) { super(); mContext = context; } @Override protected Void doInBackground(Location... params) { Geocoder geocoder = new Geocoder(mContext, Locale.getDefault()); Location loc = params[0]; List<Address> addresses = null; try { addresses = geocoder.getFromLocation(loc.getLatitude(), loc.getLongitude(), 1); } catch (IOException e) { e.printStackTrace(); // Update address field with the exception. Message.obtain(mHandler, UPDATE_ADDRESS, e.toString()).sendToTarget(); } if (addresses != null && addresses.size() > 0) { Address address = addresses.get(0); // Format the first line of address (if available), city, and // country name. String addressText = String.format("%s, %s, %s", address.getMaxAddressLineIndex() > 0 ? address.getAddressLine(0) : "", address.getLocality(), address.getCountryName()); // Update address field on UI. Message.obtain(mHandler, UPDATE_ADDRESS, addressText).sendToTarget(); } return null; } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 98 3-2. 位置情報取得機能を持つアプリケーション開発における注意点 サンプルアプリケーションでは、mUseBoth というクラス変数を持っています。この変数は、Activity を長時間放 置して破棄された場合や、画面を回転させる場合などに初期化されます。そのため、初期化される前に値を別で 保持しておく必要があります。 Activity が破棄される場合は、破棄される前に「onSaveInstanceState()」メソッドが必ず呼び出されるので、 ここでその値を保持します。 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(KEY_BOTH, mUseBoth); } ここで保持したデータは、Activity が再構築される時に「onCreate」メソッドの引数である Bundle から取得す ることができます。したがって、onCreate メソッドでは以下のような処理が必要になります。 if (savedInstanceState != null) { mUseBoth = savedInstanceState.getBoolean(KEY_BOTH); } else { mUseBoth = false; } このように、Android でクラス変数を使用する場合は初期化の注意が必要です。 【公式サイト】 http://developer.android.com/guide/topics/location/index.html http://developer.android.com/guide/topics/location/strategies.html http://developer.android.com/reference/android/location/LocationManager.html#req uestLocationUpdates(long, float, android.location.Criteria, android.app.PendingIntent) http://android-developers.blogspot.jp/2011/06/deep-dive-into-location.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 99 3-3. カメラを利用したアプリケーション開発における注意点 3-3. カメラを利用したアプリケーション開発における注意点 3-3-1. Android システムとの関連性 Android 端末向けに、カメラを利用したアプリケーションを開発する場合、特に機種依存問題を意識する必要 があります。なぜならば、カメラ自体はハードウェアであり、これを制御するために各メーカが様々なカスタマイズを行っ ているからです。 例えば、カメラのレンズはデバイスごとに異なります。そのため、そのレンズを通して見える映像の解像度も違います。 さらに、保存した画像のサイズも異なります。画像のアスペクト比も変わってきます。加えて、デバイスによってはズー ム機能がある場合やオートフォーカス機能がある場合など、様々な機能の違いが存在します。また、前面と背面の ツインカメラの場合もあります。 そのため、カメラのレンズを通して特殊な機能を実現するには、機種毎に特別な対応をしなければいけないことも あります。しかし、カメラの基本は同じです。Android アプリケーション開発では、そのカメラの基本を正しく押さえて おくことが重要です。 カメラを使用するためには、AndroidManifest.xml でその権限が必要になります。 <uses-permission android:name="android.permission.CAMERA" /> SD カード等の外部ストレージに画像を保存する場合は、以下の権限も必要です。 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> Android 端末においては、デバイス毎に搭載されている機能が異なる可能性があります。そのため、カメラが搭 載されていないデバイスでは、カメラ機能を要求するアプリケーションがインストールできないように AndroidManifest.xml で制御できます。 <uses-feature android:name="android.hardware.camera" /> 上記のパーミッションを指定した場合、標準のカメラとしてバックカメラが必要になります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 100 3-3. カメラを利用したアプリケーション開発における注意点 カメラを利用する場合の uses-feature には、他にも以下のようなパラメータが指定できます。 • android.hardware.camera.autofocus オートフォーカス機能が必須になります • android.hardware.camera.flash フラッシュ機能が必須になります • android.hardware.camera.front フロントカメラが必須になります バックカメラではなく他のカメラ(実質的にはフロントカメラ)を使用する場合、android.hardware.camera の代わりに android.hardware.camera.any を指定します。 カメラアプリケーションを構築する手順は、以下のとおりです。 • カメラの検出とアクセス カメラインスタンスを作成し、作成したインスタンスにアクセスをリクエストするコードを作成します。 • プレビュークラスの作成 SurfaceView を拡張し、SurfaceHolder インターフェースを実装するカメラプレビュークラスを作 成します。このクラスで、カメラからのプレビューを表示します。 • プレビューレイアウトの構築 カメラプレビュークラスを作成し、そのプレビューに必要なインターフェースを統合したビューを作成しま す。 • キャプチャ用リスナのセットアップ ボタンの押下など、ユーザのアクションでイメージのキャプチャを開始するインターフェースコントロール用 のリスナを作成します。 • キャプチャとファイルの保存 画像をキャプチャし、出力を保存するコードを作成します。 • カメラの解放 カメラの使用後に、他のアプリケーションが使用できるように適切に解放します。 • カメラの検出 カメラの使用をマニフェストに宣言しないアプリケーションの場合は、カメラが使用可能かどうかを実行 時にチェックすべきです。 PackageManager.hasSystemFeature()メソッドを用いてチェックを行う方法を以下に示しま す。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 101 3-3. カメラを利用したアプリケーション開発における注意点 /** Check if this device has a camera */ private boolean checkCameraHardware(Context context) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){ // this device has a camera return true; } else { // no camera on this device return false; } } • カメラのアクセス デバイス上でカメラが使用可能であることを判断したら、Camera のインスタンスを取得して、アクセ スをリクエストします。 /** A safe way to get an instance of the Camera object. */ public static Camera getCameraInstance(){ Camera c = null; try { c = Camera.open(); // attempt to get a Camera instance } catch (Exception e){ // Camera is not available (in use or does not exist) } return c; // returns null if camera is unavailable } • カメラ機能のチェック カメラインスタンスへのアクセスを取得後、「Camera.getParameters()」メソッドから返された Camera.Parameters オブジェクトを参照することで、使用中の Android 端末上でサポートされ るカメラ機能をチェックし、さらに詳しい情報を取得することができます。 • カメラプレビュークラスの作成 カメラの使用準備完了後、次はカメラプレビュークラスを作成します。カメラプレビュークラスは、 SurfaceView を使用してカメラからのイメージデータを表示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 102 3-3. カメラを利用したアプリケーション開発における注意点 カメラプレビューの入力を SurfaceView に割り当て、そのビューを作成もしくは破棄した場合に、コールバックイベ ントを受け付けるためのメソッド SurfaceHolder.Callback を使用したコード例を以下に示します。 /** A basic Camera preview class */ public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private Camera mCamera; public CameraPreview(Context context, Camera camera) { super(context); mCamera = camera; // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. mHolder = getHolder(); mHolder.addCallback(this); // deprecated setting, but required on Android versions prior to 3.0 mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } public void surfaceCreated(SurfaceHolder holder) { // The Surface has been created, now tell the camera where to draw the preview. try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { Log.d(TAG, "Error setting camera preview: " + e.getMessage()); } } public void surfaceDestroyed(SurfaceHolder holder) { // empty. Take care of releasing the Camera preview in your activity. } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { // If your preview can change or rotate, take care of those events here. // Make sure to stop the preview before resizing or reformatting it. if (mHolder.getSurface() == null){ // preview surface does not exist return; } // stop preview before making changes try { mCamera.stopPreview(); } catch (Exception e){ // ignore: tried to stop a non-existent preview } // make any resize, rotate or reformatting changes here // start preview with new settings try { mCamera.setPreviewDisplay(mHolder); mCamera.startPreview(); } catch (Exception e){ Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 103 3-3. カメラを利用したアプリケーション開発における注意点 このプレビュークラスは基本的なものであり、実際には機種毎の対応が必要です。例えば、出力する画像サイズ などがそれにあたります。Android 端末のカメラ機能は、機種毎に画像のサイズが異なっており、これは機種依存 問題の一つになっています。 機種ごとの画像サイズを設定するには、使用中の Android 端末でサポートされている画像サイズの一覧を Camera.Parameters オブジェクトから取得して、その中から最適な画像サイズを指定します。 // サポートされている画像サイズのリストを取得する List<Size> mSupportedPictureSizes = mCamera.getParameters().getSupportedPictureSizes(); // サイズを決定するときに、適切な画像サイズを取得しておく @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); setMeasuredDimension(width, height); if (mSupportedPictureSizes != null) { mPictureSize = getOptimalPictureSize(mSupportedPictureSizes, width, height, getMaxPictureSize()); } } private Size getOptimalPictureSize(List<Size> sizes, int w, int h, int max) { // 横幅、高さ、最大値などから一番最適な画像サイズを sizes の中から選ぶ // これはサンプルのため大きさでチェックしているのみですが、 // 実際はアスペクト比などもチェックしなければいけない Size optimalSize = null; for (Size size : sizes) { if (Math.max(size.width, size.height) > max) { continue; } if (optimalSize == null || optimalSize.height < size.height) { optimalSize = size; } return optimalSize } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { // ==== 省略 ==== Camera.Parameters parameters = mCamera.getParameters(); parameters.setPictureSize(mPictureSize.width, mPictureSize.height); mCamera.setParameters(parameters); mCamera.startPreview(); // ==== 省略 ==== } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 104 3-3. カメラを利用したアプリケーション開発における注意点 また、使用中の Android 端末ごとに、個別のカメラ機能の有無を判断する必要があります。例えば、特定のメー カの端末以外ではマクロモードに設定する、というようにします。 // 特定のメーカ以外はマクロモード設定 mDeviceManufacturer = android.os.Build.MANUFACTURER; if (!mDeviceManufacturer.equals(”特定のメーカ名”)) { for (String focusList : parameters.getSupportedFocusModes()) { if (focusList.equals(Parameters.FOCUS_MODE_MACRO)) { parameters.setFocusMode(Parameters.FOCUS_MODE_MACRO); break; } } } このように、カメラプレビューには多数の機種依存があり、どの機能を使うかはアプリケーションの要件によって変わり ます。 • プレビューのレイアウトでの配置 カメラプレビュークラスは、画像等を撮影するためのユーザインターフェースを Acitivty の Layout に 配置する必要があります。カメラプレビューの表示で使用できる基本的なビューの layout の例を、 以下に示します。この例では、FrameLayout がカメラプレビュークラスのコンテナになっています。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" > <FrameLayout android:id="@+id/camera_preview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> <Button android:id="@+id/button_capture" android:text="Capture" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 105 3-3. カメラを利用したアプリケーション開発における注意点 ほとんどの Android 端末において、カメラプレビューのデフォルトの向きは横向きです。しかし、端末の姿勢により、 縦向き/横向きにレンダリングされます。AndroidManifest.xml で、Activity の screenOrientation を landscape にすると、横向き固定になり、切り替え処理が不要となるため、カメラプレビューのレンダリングを簡素化 できます。 <activity android:name=".CameraActivity" android:label="@string/app_name" android:screenOrientation="landscape"> <!-- configure this activity to use landscape orientation --> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 106 3-3. カメラを利用したアプリケーション開発における注意点 Activity では、layout で定義した FrameLayout にプレビュークラスを追加します。また、Activity がフォアグラ ウンドでなくなった場合、Camera.release()を呼び出してカメラを解放しなければなりません。 解放を忘れると、カメラを使用するすべてのアプリケーションはもちろんのこと、自アプリケーションの処理にも失敗し、 強制終了してしまいます。 public class CameraActivity extends Activity { private Camera mCamera; private CameraPreview mPreview; private FrameLayout mPreviewFrame; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mPreviewFrame = (FrameLayout) findViewById(id.camera_preview); } @Override public void onResume() { super.onResume(); // Create an instance of Camera mCamera = getCameraInstance(); // Create our Preview view and set it as the content of our activity. mPreview = new CameraPreview(this, mCamera); mPreviewFrame.addView(mPreview); } @Override protected void onPause() { super.onPause(); // カメラ周りをすべて解放する if (mCamera != null) { mPreviewFrame.removeView(mPreview); mPreview.setCamera(null); mCamera.setPreviewCallback(null); mCamera.release(); // これを忘れると他のアプリケーションにも影響を与える mCamera = null; } } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 107 3-3. カメラを利用したアプリケーション開発における注意点 • 画像のキャプチャ ここまでの過程で、カメラプレビューができました。あとは、ボタン押下時に画像のキャプチャを取り出 すのみです。画像を取り込むには、Camera.takePicture()メソッドを使用します。 JPEG フォーマットでデータを受信するには、イメージデータを受信してファイルに書き込むための Camera.PictureCallback インターフェースを実装しなければなりません。 その一例を以下のコードに示します。 private PictureCallback mPicture = new PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE); if (pictureFile == null){ Log.d(TAG, "Error creating media file, check storage permissions: " + e.getMessage()); return; } try { FileOutputStream fos = new FileOutputStream(pictureFile); fos.write(data); fos.close(); } catch (FileNotFoundException e) { Log.d(TAG, "File not found: " + e.getMessage()); } catch (IOException e) { Log.d(TAG, "Error accessing file: " + e.getMessage()); } } }; ボタン押下時の処理で、PictureCallback を mCamera.takePicture の引数として渡すことで写真を保存す るコード例を以下に示します。 // Add a listener to the Capture button Button captureButton = (Button) findViewById(id.button_capture); captureButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { // get an image from the camera mCamera.takePicture(null, null, mPicture); } } ); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 108 3-3. カメラを利用したアプリケーション開発における注意点 Android のデバイスは、バージョンアップするたびに様々な改良が加えられます。これは、カメラ周りでも同様です。 例えば、Android バージョン 2.3 以降は、フロントカメラとバックカメラのように複数のカメラに対応しています。その ため、これらを使用するためにはバージョンチェックを行い、そのバージョンに適したものを使用する必要があります。 カメラを指定する一例を、以下のコードに示します。 int numberOfCameras; int defaultCameraId; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) { numberOfCameras = Camera.getNumberOfCameras(); CameraInfo cameraInfo = new CameraInfo(); for (int i = 0; i < numberOfCameras; i++) { Camera.getCameraInfo(i, cameraInfo); if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) { defaultCameraId = i; } } } else { numberOfCameras = 1; defaultCameraId = 0; } これまでで、カメラの使い方についてご説明しました。しかし、カメラで撮影した写真を使用する程度のものであれ ば、ここまでの処理は必要ありません。 カメラを扱う一番良いアプリケーションは、プリインストールされているカメラアプリケーションです。 そのため、カメラで撮影した写真を使用するアプリケーションを作成する場合は、カメラアプリケーションを Intent で 呼び出し、返却された写真データを使用するのがもっとも適した方法です。Intent で呼び出す方法は次の項で説 明します。 その他、カメラを使ったアプリケーションを作成するには注意事項があります。 カメラの写真データは、サイズがとても大きくなります。そのまま扱うと OutOfMemory と呼ばれるエラーを引き起こ す可能性が高いです。そのため、カメラを使用するアプリケーションの開発では、その点も注意しなければなりませ ん。 次の項では、業務系アプリケーションにおいて、写真撮影・縮小・送信という一連の流れを実行するシーンを想定 しています。この中で、メモリ・バッテリ・データ量の縮小化・通信処理・機種依存に対する考慮を、サンプルコードを 交えて説明していきます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 109 3-3. カメラを利用したアプリケーション開発における注意点 3-3-2. 適切に実装するためのシーケンスおよびポイント 本項の例は、Intent でカメラアプリケーションを起動、写真を撮影した後にデータを取得する仕組みを採用して います。 Intent でカメラアプリケーションを指定すると、写真撮影はもちろんのこと、各デバイスによって変わる機能(例:オ ートフォーカスなど)のすべてが利用できます。また、写真の再撮影やギャラリーへの登録も、すべてカメラアプリケー ションが行うため、開発が容易になるのはもちろんのこと、カメラを自作するよりも安定したアプリケーションになります。 そのため、標準的なカメラ機能を利用することが目的であれば、上記の仕組みを利用することが望ましいでしょう。 カメラを起動するには、MediaStore.ACTION_IMAGE_CAPTURE を Action に持つ Intent を作成して呼 び出します。Category には、Intent.CATEGORY_DEFAULT を指定します。 startActivity で暗黙的 Intent を利用する場合、Category が未指定になる(指定できない)ため、 「Intent.CATEGORY_DEFAULT」として扱われます。Extra には、MediaStore.EXTRA_OUTPUT をキーと して保存するファイルの Uri を指定します。 Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri); startActivityForResult(intent, IMAGE_CAPTURE); mImageUri は、カメラキャプチャを保存するファイルの Uri ですが、あらかじめ ContentsProvider に登録しま す。こうすることで、ギャラリーからも写真を確認することができます。 ContentValues values = new ContentValues(); values.put(Images.Media.TITLE, title); values.put(Images.Media.DISPLAY_NAME, fileName); values.put(Images.Media.MIME_TYPE, "image/jpeg"); values.put(Images.Media.DATA, mImagePath); values.put(Images.Media.DATE_TAKEN, currentTimeMillis); mImageUri = getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values); fileName には画像ファイルのフルパスが入っています。しかし、画像ファイルはサイズが大きいため、通常は SD カ ードに保存し、内部ストレージを節約する実装が一般的です。 SD カードのパスは、Environment.getExternalStorageDirectory()で指定します。 ただし、ここは機種依存があります。例えば、一部の端末(Galaxy S 等)では、上記の値で SD カードのディレ クトリが正常に取得できず、内部ストレージの一部を仮想 SD カードとして取得してしまいます。端末内での画像 保 存 で あ れ ば 、 こ れ で 良 い で し ょ う 。 し か し 、 SD カ ー ド に 保 存 し て 持 ち 運 ぶ 場 合 は 、 機 種 ご と に Environment.getExternalStorageDirectory()が異なるので、端末ごとに個別で設定をする必要があるこ とに注意します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 110 3-3. カメラを利用したアプリケーション開発における注意点 正しく SD カードのパスを取得するには、環境変数を利用する場合があります。一例を以下に示します。 System.getenv("EXTERNAL_STORAGE"); System.getenv("EXTERNAL_STORAGE2"); System.getenv("EXTERNAL_ALT_STORAGE"); これらは端末により異なるため、自身の環境ではどの環境変数が正しいパスを返すのか、個別に確認します。 その他にも、ファイルやディレクトリなどを扱う場合は、そのパスが確実に存在し書き込みが可能であるかをチェック することも重要です。 Intent 連携の場合、撮影された写真は「onActivityResult()」に戻ります。戻ってきた写真のサイズは大きい ので、このまま View に表示させると高い確率で OutOfMemory になります。そのため、デバイスで表示するには 画像を適切なサイズに縮小します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 111 3-3. カメラを利用したアプリケーション開発における注意点 この一連の流れにおけるコード例を、以下に示します。 // オプション設定用のオブジェクト BitmapFactory.Options opt = new BitmapFactory.Options(); // 実際の画像本体は読まずにサイズ情報のみ取得するフラグをセット opt.inJustDecodeBounds = true; // サイズ情報を取得する BitmapFactory.decodeFile(mImagePath, opt); // 取得した画像サイズは // BitmapFactory.Options#outWidth // BitmapFactory.Options#outHeight // にそれぞれ入り、表示サイズで割ることで縮小時の整数比率を求める //画面サイズの 90%を View のサイズとして設定する WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); Point size = new Point(); display.getSize(size); int sizeViewW = (int) (size.x * 0.9); int scaleW = opt.outWidth / sizeViewW; opt.inSampleSize = scaleW; // // // // 高さも考慮するのであればこのように計算する int scaleH = opt.outHeight / sizeViewH; int sampleSize = Math.max(scaleW, scaleH); opt.inSampleSize = sampleSize; // 今度は実際に画像を取得するためのフラグをセット opt.inJustDecodeBounds = false; // 実画像を取得 mBitmapPreview = BitmapFactory.decodeFile(mImagePath, opt); // 最終的なサイズにするための縮小率を求める int w = mBitmapPreview.getWidth(); int h = mBitmapPreview.getHeight(); float scale = (float) sizeViewW / w; // 縦横両方考慮する場合は以下のようにする // float scale = Math.min((float) sizeViewW / w, (float) sizeViewH / h); // 画像変形用のオブジェクトに拡大・縮小率をセット Matrix matrix = new Matrix(); matrix.postScale(scale, scale); // 取得した画像を元にして変形画像を生成・再設定 mBitmapPreview = Bitmap.createBitmap(mBitmapPreview, 0, 0, w, h, matrix, true); mImagePreview.setImageBitmap(mBitmapPreview); 画像を表示すると同時に、バックグラウンドではアップロード用のファイルを作成します。これは、表示と縮小を非同 期で行うことで、処理時間をユーザに感じさせにくくなり、ユーザビリティの向上につながります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 112 3-3. カメラを利用したアプリケーション開発における注意点 バックグラウンドで行う処理は、後述の例では圧縮ではなく縮小をしています。なぜならば、JPEG 画像自体がす でに圧縮されており、ZIP 形式で圧縮してもその効果は小さいからです。そのため、今回は通信容量を減らすという 観点から縮小しています。 ただし、JPEG 画像ファイル以外では、圧縮すべき場合があります。圧縮のポイントは、ZipOutputStream およ び ZipEntry です。圧縮したいファイルリストのファイルに対して、ZipEntry で Zip エントリを作成し、作成した Zip エントリを ZipOutputStream に追加することで圧縮することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 113 3-3. カメラを利用したアプリケーション開発における注意点 具体的なコード例を以下に示します。 public void compress(List<String> inputFiles, String outputFile) { InputStream is = null; ZipOutputStream zos = null; byte[] buf = new byte[1024]; // ZipOutputStream オブジェクトを作成 try { zos = new ZipOutputStream(new FileOutputStream(outputFile)); } catch (FileNotFoundException e) { // エラー処理 return; } try { for (int i = 0; i < inputFiles.size(); i++) { // Zip に圧縮するファイルの入力ストリームオブジェクトを作成 is = new FileInputStream(inputFiles.get(i)); // ファイル名の指定 String filename = new File(inputFiles.get(i)).getName(); // Zip エントリを作成 ZipEntry ze = new ZipEntry(filename); // 作成した Zip エントリを ZipOutputStream に登録 zos.putNextEntry(ze); // 入力ストリームから Zip 形式の出力ストリームへ書き出す int len = 0; while ((len = is.read(buf)) != -1) { zos.write(buf, 0, len); } // 入力ストリームを閉じる is.close(); // Zip エントリを閉じる zos.closeEntry(); } // 出力ストリームを閉じる zos.close(); } catch (FileNotFoundException e) { // エラー処理 return; } catch (IOException e) { // エラー処理 return; } } // 送信データ作成 Base64WorkerTask task = new Base64WorkerTask(mBase64Data); task.execute(mImagePath); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 114 3-3. カメラを利用したアプリケーション開発における注意点 Base64WorkerTask ク ラ ス は Base64 エ ン コ ー ド を 行 う ク ラ ス で す が 、 今 回 は こ の ク ラ ス 内 で WeakReference と呼ばれるクラスを使用しています。 private final WeakReference<String> mBase64DataReference; public Base64WorkerTask(String base64Data) { mBase64DataReference = new WeakReference<String>(base64Data); } WeakReference は、ガーベージコレクションが実行された際に、参照が一つもない状態であれば解放されるオ ブジェクトで、一時的に参照を保持しておきたい場合に有効です。これにより、メモリの節約になります。 同様に、メモリを節約するものとして SoftReference があります。 SoftReference は、オブジェクトの利用頻度とヒープ状況を判断して、アクセスの少ないオブジェクトから解放し ま す 。 ま た 、 OutOfMemoryError が 発 生 す る 前 に も 解 放 し ま す 。 た だ し 、 Bitmap オ ブ ジ ェ ク ト で は 、 SoftReference を利用せずに自分で解放するべきです。 画像サイズの縮小およびエンコードは、バックグラウンドで行ない、準備ができ次第送信ボタンを有効化しています。 送信ボタンが有効化されると、実際にデータを送信できます。 データの送信は非同期で行います。なぜならば、UI スレッドで処理を行った場合、その間は他の処理を受け付け ることができません。そのため、画面が止まってしまい、それが 5 秒以上続くと ANR(Application Not Responding)と呼ばれるエラーが発生するからです。 そのため、ネットワーク通信はもちろんのこと、それ以外でも少し時間のかかる処理、具体的には 5 秒以上を要す る可能性のある処理は、必ず非同期で実行してください。 アプリケーションを使用するユーザにとって、100 ミリ秒程度の遅延でも、一瞬画面が止まったと見えるような実装 を行わないように工夫することが望ましいでしょう。 ※ Google も公式に、「一般的に 100~200 ミリ秒が、ユーザが遅いと感じる閾値である」との見解を示してい ます。 【公式サイト】 http://developer.android.com/training/articles/perf-anr.html#Reinforcing 後述する例の通信部分は、AsyncTask を継承した HttpPostTask クラスにて行なっています。このクラスの引 数は 3 つあります。Activity、送信先 URL、Handler です。 Activity は ProgressDialog を生成するため、送信先 URL は通信での接続先を決定させるために必要です。 Handler は、UI スレッド上に通信結果を表示させるために使用します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 115 3-3. カメラを利用したアプリケーション開発における注意点 Android システムのユーザインターフェース操作は、シングル・スレッドモデルになっています。 そのため、UI スレッド以外のスレッドから View の操作を行うとエラーになります。しかし、非同期でプログラムを実 行させて結果を表示させたい場合があるでしょう。そのような場合には、Handler を使用します。 Handler で View の更新を行うと、その情報は UI スレッドのキューに入ります。そのキューは、UI スレッド上で実 行されるためエラーになりません。 下記の例では、処理結果を Handler に渡し、Handler で Toast や AlertDialog を表示しています。また、 Handler で受け取ることで、正常系と異常系を明確に分けています。そうすることで、異常系が実行された場合は、 再試行確認 Dialog を表示することで再実行するという流れが明確にできています。 // タスク完了時に呼ばれる UI のハンドラ new HttpPostHandler() { @Override public void onPostCompleted(String response) { // 受信結果を Toast 表示して終了 Toast.makeText(getApplicationContext(), "送信完了", Toast.LENGTH_LONG).show(); finish(); } @Override public void onPostFailed(String response) { // 再送信するかダイアログ表示 new AlertDialog.Builder(CameraActivity.this) .setTitle("送信失敗") .setMessage("リトライしますか?") .setCancelable(false) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { sendData(); } }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }).show(); } }); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 116 3-3. カメラを利用したアプリケーション開発における注意点 今回、例に示す処理では、データのアップロードを対話型で行います。したがって、アップロードの成功/失敗は 容易に理解できます。対話型であれば、アップロードの失敗時に行うリトライは、ダイアログで確認してから行うのが 良いでしょう。しかし、バックグラウンドで自動的にデータ等のアップロードを行いたいという要件もあるかもしれません。 その場合は、ユーザに意識させないためにも、エラーになったときは自動でリトライを行うことが望ましいでしょう。 また、失敗時にはレジューム送信したほうがよいのでは?と思われるかもしれません。しかし、今回のような画像フ ァイル程度の容量でレジューム送信を実装するのは得策ではありません。なぜならば、アプリケーションで画像を分 割して送信することになりますが、そのたびに HTTPPOST を繰り返し実行することになり、その機構をサーバが実 装している必要があるからです。 画像ファイル程度の容量であれば、送信に失敗した場合に再度初めから送信を行う方が良いでしょう。 レジューム送信は、数十 MB 以上あるファイルのアップロードで使用します。さらに、この程度のサイズのデータをレ ジューム送信することによる Android システム(メモリ、バッテリ)への影響およびサーバ側への影響もあります。な ぜならば、レジューム機能は、データを分割し複数回に分けて送信します。そのため、毎回 HTTPPOST するためブ ロック毎のヘッダが増え、メモリ・バッテリをより多く消費することになるからです。 また、端末だけでなく、サーバにもこのヘッダが増えるので、数百台ある端末からのヘッダ増加による負荷は高く、 望ましい手法ではありません。 AsyncTask を利用してバックグラウンドでネットワーク通信を行う場合、フォアグラウンドでは通信中であるというこ とを明確にすることが望ましいでしょう。 ProgressDialog を実行前に表示し、実行終了後には消す方法が一般的です。その一例を以下に示します。 @Override protected void onPreExecute() { dialog = new ProgressDialog(parent_activity); dialog.setMessage("通信中・・・"); dialog.show(); } @Override protected void onPostExecute(Void unused) { // ダイアログを消す dialog.dismiss(); } 「onDestroy()」メソッドでは、最終的なクローズ処理を行います。カメラを使用した場合、様々なところでメモリ を使用している可能性が高いため、すべて解放し他のアプリケーションに影響がないようにします。特に、Bitmap に 関しては、必ず recycle を行わなければメモリリークの原因となり OutOfMemory が発生しやすくなります。 ただし、recycle を行ったからといって直ちにメモリが解放されるわけではありません。以下の例では、元写真、圧 縮写真、BASE64 データ、プレビュー画像などが対象になります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 3 章 端末依存を意識したアプリケーション開発の注意点 | 117 3-3. カメラを利用したアプリケーション開発における注意点 @Override protected void onDestroy() { super.onDestroy(); // 先に ImageView#setImageDrawable(null)をしてから Bitmap#recycle() しなければならない。 // なぜなら ImageView に参照されている状態で recycle()を行なっていると ImageView の中で使われている Canvas が時々bitmap を読み込もうとしてエラーを出すためである mImagePreview.setImageDrawable(null); if (mBitmapPreview != null) { mBitmapPreview.recycle(); mBitmapPreview = null; } mBase64Data = null; } また、同じプレビュー画像を ListView や GridView などで繰り返し使用する場合は、LruCache や DiskCache を使うと有効です。 ここまで紹介したように、Android 端末には多くの便利な機能が用意されています。しかし、Android システムが オープンソースである以上、異なるデバイスやハードウェアで稼働することで、さまざまな課題を招きます。このことを、 アプリケーション開発者は常に意識し、端末の特性に沿った実装を行う必要があります。 【公式サイト】 http://developer.android.com/guide/topics/media/camera.html http://developer.android.com/guide/topics/manifest/uses-feature-element.html#feat ures-reference http://developer.android.com/intl/ja/guide/components/intents-filters.html#ifs Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 第4章 常時ディスプレイ ON を維持するための アプリケーション開発 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. | 118 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 119 4-1. 概要 4-1. 概要 Android システムは通常、バッテリの消費を防ぐことを目的として、一定時間を経過するとディスプレイが OFF に なるよう設計されています。 しかし、デジタルサイネージアプリケーションなど、常時ディスプレイを ON にした状態での動作が必要な場合があり ます。そのような場合には、Android システムがディスプレイを OFF にする機能を無効にする必要があります。 常時ディスプレイを ON にしてアプリケーションを稼働させ続ける場合、メモリの適切な管理が求められます。なぜ ならば、不適切な実装は時間をかけて徐々にメモリリークを引き起こし、最終的にはメモリ不足によるアプリケーショ ンの強制終了を引き起こすからです。 また、デジタルサイネージアプリケーションの場合は、誰も操作しない環境で常に誰かの目にさらされています。強 制終了が頻繁に起きるようなシステムは、顧客にとってマイナスな印象を与えます。そのようなことがないよう、メモリ 確保や開放を適切に対処する必要があります。 しかし、実のところ Android システムにおいて、24 時間 365 日連続稼働するようなアプリケーションを構築する のは望ましいことではありませんし、非現実的と言えるでしょう。Android システムは、特定のアプリケーションを長時 間にわたり連続稼働させることを前提として設計されているわけではないからです。そのため、人為的に再起動させ る運用を計画することが求められます。重要なことは、この運用のサイクル内では止まることなく動き続ける状態を 担保する実装です。 本章では、常時ディスプレイを ON にするための注意点および適切な実装と、端末を自動で定期的に再起動す ることによって、システムをリフレッシュする手法について説明します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 120 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2-1. Android システムとの関連性 通常、Android のアプリケーションは、複数の Activity で構成されており、Activity が変わるとフォアグラウンドの Activity がバックグラウンドに入ります。 バックグラウンドに入った Activity は、すぐには開放されません。しかし、Android システムはメモリを回復させるた めに、バックグラウンドの Activity の破棄を自動で行う場合があります。それは、実行中の Activity やバックグラウ ンドで動作している Service、あるいは BroadcastReceiver で受け取ったアプリケーションなどが多くのメモリを必 要としている場合に、Android システムが Activity の破棄を自動で行います。 また、ユーザが長時間端末を放置した場合にも、Android システムはフォアグラウンドで実行中の Activity 以外 の Activity を破棄していきます。その後、破棄された Activity が復帰する時には、破棄される時に保持した情報 を復帰させるようになっています。そのため、Activity が破棄される時には、Activity が持つ情報を正しく保持する 必要があります。 Android システムでは、Activity がバックグラウンドへ遷移する時に、onSaveInstanceState メソッドが呼び 出され、引数の Bundle にデータを保存することができます。Bundle に保存されたデータは、Activity が破棄さ れて再構築される時に onCreate の引数として渡されます。もしくは、onRestoreInstanceState メソッドの引 数でも受け取ることが可能です。 onCreate と onRestoreInstanceState は呼び出される順序が違います。 Activity の ラ イ フ サ イ ク ル で は 、 onCreate→onStart→onResume の 順 に 呼 び 出 さ れ ま す が 、 onRestoreInstanceState は onStart の後に呼び出されます。したがって、保存された値を戻す前に実行した い処理がある場合は onStart で行い、onRestoreInstanceState で元の状態に戻します。 こ の 処 理 が 漏 れ て い る と 、 Activity が 再 構 築 さ れ た 時 に 必 要 な デ ー タ が 初 期 化 さ れ る こ と や 、 NullPointerException が発生する原因になります。 ただし、Bundle はどのようなデータでも保存できる訳ではありません。Bundle で保存できるのは、リテラル型と呼 ばれる基本的な型と、Parcel 型や Serializable 型のみです。そのため、独自のクラスを Bundle へ保存するには、 独自クラスに Parcel を implements する必要があります。 onSaveInstanceState での保存および Activity 再構築時の Bundle からデータ復帰を行う場合は、 「4-2-3 適切に実装するためのシーケンスおよびポイント」にサンプル例を記述していますので、ご確認ください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 121 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2-2. 「PowerManager」 は使わない! 常時ディスプレイを ON にするアプリケーション開発というと、通常は電源に関する API を探し、電源を切らない、 もしくはつけた状態を維持する API の使用を想像するかもしれません。 Android システムでは、電源管理のために PowerManager が用意されています。そのため、この API を実行 することで常時ディスプレイを ON にしようとするケースが多く見られます。 もちろん、PowerManager API を使用して、常時ディスプレイを ON にすることは可能です。しかし、 PowerManager をこの目的のために使用することは望ましくありません。 なぜならば、PowerManager を使用すると、バッテリの寿命を著しく消耗するからです。では、バッテリの問題だ けであれば、AC アダプタに接続していれば問題ないのでは?という疑問が出てきます。しかし、バッテリの寿命を縮 めるということは、端末の寿命そのものを縮めることにつながります。また、バッテリ消費によって端末が過度の熱を帯 びていくことも考えられます。熱を帯びると、端末が予期せぬ状態(例えば、充電ができなくなるなど)になる可能 性もあります。 このことは、Google の公式リファレンスにおいても、以下のとおりアナウンスされています。 Device battery life will be significantly affected by the use of this API. Do not acquire PowerManager.WakeLocks unless you really need them, use the minimum levels possible, and be sure to release them as soon as possible. この API の使用により、端末のバッテリ寿命は重要な影響を受けます。 本当に必要とするのでない限り、PowerManager.WakeLock を実装しないでください。 また、使用を最小限にして、可能な限り速やかに開放するようにしてください。 【公式サイト】 http://developer.android.com/reference/android/os/PowerManager.html では、適切な実装とは何でしょうか? WindowManager に対して KEEP_SCREEN_ON のフラグを追加することです。また、バックグラウンドからアプ リケーションを立ち上げてキーロックを解除する場合も、フラグの追加で行います。 PowerManager は、Android システムに対して、明示的に制御要求をかけます。 一方で、WindowManager のフラグは、Android システムのライフサイクルの中で参照される設定値です。 Android は通常、このような設定値の内容に基づいて自身の挙動を決定しますが、PowerManager はそれら よりも優先度の高い制御用クラスです。Android システムに、設定と異なる動作をさせるためには、定期的に制御 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 122 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 要求をかけ続ける必要があります。このことは、Android システム全体に高い負荷をかけることになり、ハードウェア レベルでの問題を引き起こす原因ともなります。 したがって、常時ディスプレイを ON の状態にすることは、WindowManager の「設定」により実現するべきであり、 PowerManager で「制御」することは望ましくないと理解できます。 Android システム アプリケーション WakeLock KEEP_SCREEN_ON フラグセット API呼出し時に 強制制御 優先順位=高 (高負荷) WindowManager Android システムの ライフサイクルで制御 (低負荷) 参照 応答 PowerManager Android OS ハードウェア 特にバッテリ 図 21. PowerManager と WindowManager が Android システムに与える負荷 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 123 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 不適切なコードのサンプル // バックグラウンドから復帰 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "My Tag"); wl.acquire(); // キーロックを無効にする KeyguardManager km = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); KeyguardManager.KeyguardLock klock = km.newKeyguardLock("My Tag"); klock.disableKeyguard(); // 動画再生 〜略〜 @Override public void onPause() { super.onPause(); // 各処理を戻す wl.release(); klock.reenableKeyguard(); } 適切なコードのサンプル getWindow().addFlags( WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 124 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2-3. 適切に実装するためのシーケンスおよびポイント 動画再生の一番簡単な方法は、VideoView を使用することです。 Android システムが標準で対応しているフォーマットは、以下のとおりです。ただし、端末メーカの実装により、一 部の機種に関しては以下のフォーマット以外に対応している場合があります。したがって、使用するフォーマットには 注意が必要です。 • H.263 • H.264 AVC • MPEG-4 SP • VP8 【公式サイト】 http://developer.android.com/guide/appendix/media-formats.html 動画再生は、VideoView のインスタンスを取得して動画のリソースを指定します。動画リソースは、次の 3 つの パターンが考えられます。 パターン 1. SD カード等の外部ストレージの動画を再生する 2. /res/raw の動画を再生する 3. インターネット上の動画をストリーミングで再生する この他にも、assets フォルダからの取得が考えられますが、assets フォルダに保存するファイルは、圧縮しない場 合に上限が 1Mbyte という制限を持ちます。くわえて、圧縮した場合でもファイルを展開してから使用することになり ます。その場合は、圧縮ファイルを展開するとそのファイルのパスが取得できるため、1 のパターンと同じように行いま す。 パターン 1. SD カード等の外部ストレージから動画を再生する場合は、パスを指定します。コード例を以下に示します。 例:外部ストレージ(SD カード)直下の sample.mp4 を指定する場合 mVideoView = (VideoView) findViewById(R.id.video); mVideoView.setVideoPath(Environment.getExternalStorageDirectory().toString() + "/sample.mp4"); 外部ストレージ(SD カード等)のパスは、機種に依存する要素の一つです。そのため、アプリケーションが実行さ れている環境に応じて、動的に取得する必要があります。上記のコード例においては、 Environment.getExternalStorageDirectory().toString()でパスを取得しています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 125 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 パターン 2. /res/raw に保存されている動画を再生する場合は、リソースの URI を取得してセットします。 例:/res/raw/sample.mp4 を保存している場合 mVideoView = (VideoView) findViewById(R.id.video); mVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.sample)); パターン 3. インターネット上の動画をストリーミングで再生する場合も、上記と同じく URI を取得してそれをセットします。 例:http://www.domain_name.jp/sample.mp4 の動画を直接再生する場合 mVideoView = (VideoView) findViewById(R.id.video); mVideoView.setVideoURI(Uri.parse("http://www.domain_name.jp/sample.mp4")); Android アプリケーションでは、上記のコードで動画を再生できます。ただし、これだけでは、再生はできても早送 りや巻き戻しなどの制御はできません。これらの制御には、MediaController を使用します。 mVideoView.setMediaController(new MediaController(this)); 次に、動画終了時に自動で繰り返し再生する方法を説明します。これは、終了のリスナを受けとった場合に、最 初から動画を再生するようにします。終了のリスナは、OnCompletionListener#onComletion です。 動画の再生位置を指定して開始します。繰り返し再生ですので、再生位置は 0 となります。 mVideoView.setOnCompletionListener(new OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { mVideoView.seekTo(0); mVideoView.start(); } }); ここまでのコード例で、動画の繰り返し再生機能が一通り動作します。 以上が、動画を再生する実装方法の説明になりますが、これで完了ではありません。なぜならば、Android シス テムは、途中で割り込みが入る可能性を考慮する必要があるからです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 126 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 例えば、電話がかかってきた場合、電話アプリケーションがフォアグラウンドに移動します。目覚まし時計など、特定 の時間に動作するアプリケーションの場合にも、割り込みが発生します。電話機能を持っていないタブレットなどの場 合でも、ディスプレイを OFF にされる可能性があります。 このような割り込みの発生は、一般的な PC 用や Web アプリケーション開発では考慮に入れる必要はありません。 しかし、Android アプリケーション開発では、注意しなければならない点の一つです。 上記で説明したような割り込みが発生した場合、実行中のアプリケーションはバックグラウンドに入ります。バックグ ラウンドに入ったアプリケーションは、[戻る]ボタン押下などで自分のプロセスまで戻ってきた場合や、履歴ボタンから 指定された場合に、フォアグラウンドに復帰します。 そのため、Activity がバックグラウンドに入った場合には各状態を保持し、復帰した時には元の状態に戻すように します。 以下のサンプルコードでは、バックグラウンドへ入った時には動画を停止させます。そして、復帰した時には停止位 置から再生させます。Activity のライフサイクルよりバックグラウンドに入った時には onPause メソッドが実行され、 復帰した時には onResume メソッドが実行されるので、この位置でそれぞれの処理を行います。 /** * バックグラウンドに移動した時に再生していた位置を保持するための変数 */ private int mStopPosition; @Override public void onPause() { super.onPause(); // 動画を停止させる mVideoView.pause(); // 現在の位置を保持する mStopPosition = mVideoView.getCurrentPosition(); } @Override public void onResume() { super.onResume(); // 開始位置を指定位置にする mVideoView.seekTo(mStopPosition); // 動画を再生させる mVideoView.start(); } サンプルコードでは、クラス変数の mStopPosition で開始位置を保存して、再開時にはその位置から再開させ ています。これにより、割り込みがあった時に動画を停止し、フォアグラウンドに復帰した時には動画を再生します。 アプリケーションがバックグラウンドに保存されている時には、注意が必要です。なぜならば、バックグラウンドにある 状態でメモリが不足すると Acitivity が削除されてしまい、Activity で保持しているクラス変数は、すべて初期化さ れるからです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 127 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 Android システムでは、Activity がバックグラウンドへ移動する際に onSaveInstanceState メソッドが実行さ れます。そこで保存された Bundle の値は、Activity が再構築され、onCreate メソッドが再度実行された時に、 引数として渡ってきます。 そのため、onCreate メソッドでは Bundle の値が null であるかをチェックし、null でなければ初期値をセットしま す。 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); 〜略〜 // バックグラウンドから復帰する際に再生する位置が保持されている場合はその位置から再生する if (savedInstanceState == null) { mStopPosition = 0; } else { mStopPosition = savedInstanceState.getInt("StopPosition"); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 画面が復帰する時に途中から再生させるためにバックグラウンドに遷移する時には再生位置を保持しておく outState.putInt("StopPosition", mStopPosition); } クラス変数を使用する時は、保存と再設定を忘れずに行なってください。これを忘れた場合、Activity を再構築 する際に、NullPointerException 等でエラーを起こしたり、予期せず初期化されたりします。 しかし、これはメモリ不足等の場合だけに発生するため、開発時にはテストとして漏れることが多く、実運用時に 問題として発生する場合がよく見られます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 128 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2-4. アプリケーション(端末)を再起動させる デジタルサイネージアプリケーションには、「高可用性」が求められます。これは、業務システム開発において、一般 的に認識されている「高可用性」とは異なります。求められるのは、視覚的に「動き続けているように見える」ことで す。 言い換えるならば、デジタルサイネージアプリケーションにおける「高可用性」の本質は、「落ちたまま」にならない実 装および運用です。 Android 端末において、週や月、ましてや年といった単位でアプリケーションを止めずに動かし続けることは、現実 的ではありません。Android システムおよび Android 端末は、そのような連続稼働を前提として設計されたもので はありません。モバイル端末向け OS は一般的に、端末のメモリ枯渇や高負荷状態が続くといった状況に陥った場 合に、OS がシステム的に自動で再起動される設計となっているからです。 では、どのようにして、「落ちたまま」にならない状態を維持しながらリフレッシュするのでしょうか。 初めに、意図的にリフレッシュさせるための、以下の 3 種類の手法が考えられます。 1. 適切なトリガで、強制的に自アプリケーションを強制終了して再実行する 2. 毎日一回、定時に再起動する 3. 端末を人の手で、再起動する運用を行う 次にもう 1 点、考慮すべき手法があります。 4. 予期しない理由によって端末が再起動した際に、自動的にアプリケーションを再実行する 本章では、上記 4 点の手法について解説します。これにより、「落ちたまま」にならない実装および運用を目指し ます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 129 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 なお、本章の手法を実践するにあたって、予めご留意いただきたい重要なポイントがあります。 1. 正常に動作することが実証されているアプリケーションに対しては、必ずしもすべてを実践する必要はありませ ん。本章の手法の対象としているアプリケーションは、新規開発するもしくは連続稼働に問題のあるアプリケー ションです。 長期間(最低でも 5~6 日以上)に渡って正常に稼働する実績を持つアプリケーションにおいて、本章 で取り上げるアプリケーション再起動の手法を実践することは、必ずしも望ましくありません。なぜならば、 アプリケーション再起動によって、かえって不安定な動作を引き起こす可能性もあるからです。長期間に わたって正常に稼働するという事実に基づいて、その実装および環境を、一つの正解であるとみなせるで しょう。 本章の内容は、新規開発するアプリケーションもしくは連続稼働に問題(※)を抱えるアプリケーション の改修に対して、活用することをお勧めします。 ※ 発生することが考えられる問題としては、次のようなものが挙げられます • 端末が、短時間(数時間以内)でたびたび再起動を繰り返す • コンテンツの再生が、たびたび停止する • 実行中アプリケーションが、たびたび強制終了する端末本体が、著しく帯熱し予期しない動作 を発生させる 2. 最低限の実装要件および性能要件が満たされたアプリケーションが対象です。 もし、ディスプレイを常時 ON にするために PowerManager で WakeLock を維持しているならば、 WindowManager に KEEP_SCREEN_ON をセットするコードに変更してください。(114p.参照) また、標準的な動画コンテンツが、少なくとも 48 時間以上、開発されたアプリケーションにおいて連続再 生できることを確認します。この動作に問題がある場合は、再生側の実装を見直すことが望ましいでしょ う。なぜならば、本章では、毎日 1 回以上アプリケーションを再起動させる方法をご紹介しますが、24 時 間稼働する状態を担保するには、少なくとも 48 時間以上動作することを確認しておくことが適切だから です。 3. アプリケーションを起動したら、デジタルサイネージ機能が即実行される設計を必要とします デジタルサイネージアプリケーションは、起動したら即サイネージ機能が実行される設計とします。アプリケ ーション起動時に初期メニューが表示され、人が手で開始させなければならないインターフェースは望まし くありません。なぜならば、予期しない理由で端末が再起動した場合に、デジタルサイネージをスタートさ せるために、必ず人の手を介する必要が出てくるからです。 設定などの各種操作は、動作中の画面から別の割り込みをかけて行うか、設定用の別アプリケーション を開発して導入することが望ましいでしょう。 あるいは、対象となる Android 端末が多数ある場合は、端末一つ一つに対して人が手で設定するのは 現実的でないかも知れません。OTA で設定用のファイルや電文を受信して、それを反映する形にするな ら、より効率的な運用が可能です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 130 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2-5. 自アプリケーションを意図的に、適切なトリガで強制終了して、再実行する 本手法の目的は、アプリケーションの処理中に何らかの問題が発生する可能性を考慮し、自アプリケーションを定 期的に強制終了し、直ちに再起動することで、クリーンな状態で実行することにあります。 デジタルサイネージアプリケーションを、意図的に強制終了するための適切なトリガとして、以下のようなものが考 えられます。 1. 再生用コンテンツが変更されたとき 2. 再生用シーケンスが変更されたとき 上記の 2 点を強制終了のトリガとすることは、デジタルサイネージアプリケーションの可用性に対して非常に有意 義です。なぜならば、それまで再生していたコンテンツのために使用中のメモリリソースが、アプリケーションを空プロセ スにすることで開放されるからです。これは、新規コンテンツ再生開始時に発生し得る、メモリリソース確保の問題を 防ぐ上で、大きな効果があります。 再生コンテンツもしくは再生シーケンスに変更が発生した時と、毎日定時(後述)をトリガにすることにより、アプ リケーションの再起動は、毎日 1 回以上実行されます。大抵の場合、この手法は概ね効果的です。 ただし、複数のアプリケーションが動作している環境では、デジタルサイネージアプリケーションの再起動実行タイミ ングにより、正常に動作しないことが起こりえます。それを防ぐためにも、デジタルサイネージアプリケーションを実行す る Android 端末では、基本的に必須ではないアプリケーションのインストールを一切行わないことが望ましいでしょ う。これにより、可用性をいっそう高めることに寄与します。 デジタルサイネージアプリケーションを、意図的に適切なトリガで強制終了することで、再実行するための手順は、 次のとおりです。 1. デジタルサイネージアプリケーションを起動するための、ブートアプリケーションを作成する デジタルサイネージアプリケーションから BroadcastIntent を受信し、それを契機にクラスを呼び出して 再起動するアプリケーションを作成します。このクラスアプリケーションは、Activity を持ちません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 131 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 例:BroadcastIntent を受信するブートアプリケーションのコード例 public class BootReceiver extends BroadcastReceiver { @SuppressWarnings("unused") private static final String TAG = BootReceiver.class.getSimpleName(); @Override public void onReceive(Context context, Intent intent) { Intent i = new Intent(context, MainActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); } } 2. デジタルサイネージアプリケーションに、自身を強制終了するための finish()を実装する 再生コンテンツやシーケンスの変更時に finish()クラスを呼び出すよう実装します。 ↓ 上記(1.)のブートアプリケーションに対して、finish()直前に BroadcastIntent を発行します。 ↓ finish()を呼び出して、自身を強制終了します。 例 : ボタンがタップされたら BroadcastIntent を発行し、finish()を実行するサンプルコード Intent broadcastIntent = new Intent(); broadcastIntent.setAction("jp.co.developer.REBOOT"); getBaseContext().sendBroadcast(broadcastIntent); sendBroadcast(broadcastIntent); finish(); これにより、上記(1.)のブートアプリケーションが、上記(2.)で発行された BroadcastIntent を受信することによ り、デジタルサイネージアプリケーションは自身で強制終了後、直ちに再起動します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 132 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 本手法の動作イメージを、以下の図に示します。 デジタルサイネージアプリケーション ブートアプリケーション ③ リスナ もしくは AlarmManager 再スタート 契機 再生コンテンツ 変更時 再生シーケンス 変更時 onRecieve() 定時/日 ① Broadcast Receiver finish クラス Broadcast Intent ② 強制終了 図 19 自アプリケーションを強制終了し、ブートアプリケーションから再起動する なお、finish()を実行してアプリケーションを強制終了しても、プロセス上から完全に消えるとは限りません。しかし、 この点は問題とはなりません。なぜならば、このプロセスは「空のプロセス」であり、優先度はきわめて低く、ほどなくし て Android システムにより完全に Kill されるためです。また、再起動後に実行中となるデジタルサイネージアプリケ ーションは「フォアグラウンドプロセス」であり、Android システムの構造上、Kill されることはありません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 133 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2-6. 毎日一回、定時に再起動する 毎 日 一 回 決 ま っ た 時 刻 に 自 ア プ リ ケ ー シ ョ ン を 強 制 終 了 さ せ 再 実 行 す る に は 、 (1.) の 実 装 に 加 え て AlarmManager を使用します。この手法は、デジタルサイネージアプリケーションを安定稼働させる上で、とても望 ましいものといえるでしょう。なぜならば、48 時間以上連続稼働することが実証されているデジタルサイネージアプリ ケーションを、24 時間周期で再実行することで、予期しない問題が発生する蓋然性を大きく低下させることができ るためです。前述の「予め留意していただきたい重要なポイント」で、少なくとも 48 時間の連続稼働を確認すること を求めた理由は、この点にあります。 AlarmManager を実装するに際して、上記のブートアプリケーションおよびデジタルサイネージアプリケーションへ の finish()実装が、予め用意されている必要があります。AlarmManager 実装の注意点については、第 1 章を ご参照ください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 134 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2-7. 端末を人の手で再起動する運用を検討する Android 端末の再起動をアプリケーションから実行するためには、システム署名が必要です。 具体的には、端末を再起動させるための API(android.permission.REBOOT)を実行する場合、システム 権限を持つパーミッション(signatureOrSystem もしくは signature)が必要です。システム権限を持つパーミッシ ョンを宣言して端末実機で動作させるには、システム署名(Android 自体をビルドする際に使用される署名)が必 要となります。 この理由により、一般的な Android アプリケーション開発において、システム署名を必要とする API を呼び出す 処理は実装できません。無理に実行するならば、セキュリティ例外(SecurityException)が発生し、処理が停止 します。 通常の Android アプリケーション開発においては、「アプリケーションから端末を再起動」させる実装を選択肢に 含めることはできません。したがって、端末を再起動する運用は、人の手を介して行う必要があります。 ただし、前述の自アプリケーション再起動の手法によって、アプリケーションの稼働率が高く保たれている場合、人 が手で端末を再起動する運用の必要性は、それほど求められません。開発されたアプリケーションを十分に検証し た上で、端末再起動を行う運用について考えることが望ましいでしょう。端末再起動を行う間隔は、実情に応じ、 たとえば週に一度、場合によっては半月に一度などの頻度で実行することが望ましいと言えるでしょう。 あるいは、対象となる Android 端末が多数ある場合は、端末一つ一つに対し、人が手で設定することは現実 的でないかも知れません。OTA で設定用のファイルや電文を受信し、それを反映する形にする場合、より効率的 な運用が可能です。 気をつける必要があることとして、もっと短い間隔で、人の手による再起動を運用として設計しなければならない 場面もあります。テスト時には、長期間にわたって正常に動作したアプリケーションが、本番稼動時に問題を示すよ うになった場合、多くはアプリケーション以外の要因が疑われます。このような場合、人の手による端末再起動は必 須です。例として、次のようなことが挙げられます。 1. コンテンツによって、正常に動作しない場合がある 動画を生成した際のコーデックに問題がある可能性があります。 アプリケーション側での対応は、きわめて困難です。 この問題は、コンテンツファイルのフォーマット形式を変更することで、解消する場合があります。 また、不具合がたびたび発生する場合、オーサリングツールを見直すことも効果的です。 2. 端末もしくは OS バージョンの違いによって、正常に動作しない場合がある Android OS のライブラリやフレームワークレベルで、メモリリークを起こしている可能性があります。 アプリケーション側での対応は、不可能です。 上記のような問題となる事象が発生した場合、解消するための手段は、端末の再起動しかありません。自アプリ ケーションに問題がないとしても、システムが正常に動作するタイムスパンの範囲で、人の手による端末再起動を運 用として設計することが求められるでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 4 章 常時ディスプレイ ON を維持するためのアプリケーション開発 | 135 4-2. 常時電源 ON を維持するアプリケーション開発における注意点 4-2-8. 予期しない理由によって端末が再起動した際に、自動的にアプリケーションを 再実行する デジタルサイネージアプリケーションは、起動したら即サイネージ機能が実行される設計とすることが、可用性を高 めるポイントです。なぜならば、予期しない理由によって起こりえる端末再起動も、アプリケーションが自動的に再ス タートするならば、「落ちたまま」という印象を与えることはないからです。 アプリケーションの起動時に初期メニューが表示され、人が手で開始させなければならないインターフェースは、デジ タルサイネージアプリケーションにおいて望ましくありません。なぜならば、予期しない理由で端末が再起動した場合 に、サイネージ機能を再スタートさせるために、必ず人の手を介する必要が出てくるからです。それまでの時間は、 「落ちたまま」となります。設定などの各種操作は、動作中の画面から別の割り込みをかけて行うか、設定用の別ア プリケーションを実装して導入することが望ましいでしょう。 デジタルサイネージアプリケーションが、端末再起動時に自動でサイネージ機能をスタートさせるようにするには、 BootComplete で起動するように AndroidManifest を設定しておく必要があります。 android.permission.RECEIVE_BOOT_COMPLETED を 設 定 し 、 以 下 の イ ン テ ン ト を BroadcastReceiver で受信します。 <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> 例 : AndroidManifest.xml への BOOT_COMPLETED 記述サンプル <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <receiver android:name="com.example.androidDigitalSignage.BootReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> これにより、予期しない理由によって端末が再起動した場合にも、デジタルサイネージアプリケーションは自動的に 再実行され、「落ちたまま」の状態に陥ることは回避されます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 第5章 | 136 マルチデバイス対応と最適化されたアプリケーション開発 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 137 5-1. 概要 5-1. 概要 5-1-1. 画面レイアウトを取り扱う背景 Android 端末向けアプリケーションにおいては、画面レイアウトを構築するための実装方法が、アプリケーションの パフォーマンスに対して重要な影響を与えます。内部処理が正しく実装されたアプリケーションであっても、画面レイ アウトに関連した実装が、その効果をすべて打ち消しているようなプロダクトも少なくありません。画面レイアウトの不 適切な実装が原因で、CPU やメモリリソースを占有し、電力を浪費し続けることで端末を過度に帯熱させ、電話 の着信すら阻害するようなケースもあります。 したがって、Android 端末向けに有用で安定したアプリケーションを開発しようとするならば、画面レイアウトをどの ように設計し実装するかという点に、十分な考慮をする必要があります。 UI(ユーザインターフェース)を伴うすべてのシステム開発と同様に、Android 端末向けアプリケーションは、内部 処理設計と画面レイアウト設計が必要です。内部処理設計については、デバイスのリソースを考慮するための事 例を、先の章でいくつか解説しました。画面レイアウト設計についても同様の注意を払うことは、アプリケーションの 安定した動作と密接な関連を持ちます。 また、PC 用ネイティブアプリケーションにおいて、画面構成が複雑になることや画像素材が多数包含されることは、 一般的に不具合の原因に直結しにくいといえるでしょう。同様に Web アプリケーションも、複雑な画面や画像の多 用は、処理速度に対するユーザのストレスを招くことはあっても、アプリケーションの性能や安定性そのものに対する 直接的な影響は、極端な例を除いて引き起こしにくいものです。 しかし、Android 端末向けアプリケーション開発は、潤沢なリソースを持つ PC 環境の開発とは大きく異なります。 画面を構成する素材リソースの不適切な使用は、メモリ不足などによる問題を容易に引き起こします。 このように、Android 端末向けアプリケーションにおいて「UI(ユーザインターフェース)の最適化」が、アプリケーショ ンの性能に直結する必須要件なのです。 したがって、本章ではアプリケーションの安定した動作に寄与する画面レイアウトの設計について、構造の実装お よびリソースの正しい使用という二つの観点から、基本的な理論と具体的な手法を解説します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 138 5-1. 概要 5-1-2. マルチデバイス対応が求められる背景 Android OS が搭載された端末は、iOS 系とは異なり、様々なメーカによって、様々な画面サイズや解像度で 発売されています。そのため、Android 端末向けアプリケーションは、それらの様々な画面サイズや解像度に対応 することができる、画面レイアウトにすることが望ましいでしょう。反対に、特定の機種環境に依存することは望ましく ありません。 例えば、当初は 960×540 の解像度を持つ特定の端末のみの対応で、アプリケーションを開発します。その際 に、特定の画面サイズや解像度に依存したレイアウト設計を行っている場合、バージョンアップなどで 1280×720 などの別の解像度に対応しなければならない際には、余分な余白が生じるなどの理由により、修正量やテスト量が 膨れ上がり、改修コストが増大します。このような事態を防ぐためにも、当初の想定端末がシングルデバイスである 場合にも、マルチデバイス対応と同様にレイアウトを実装することが望ましいでしょう。 シングルデバイス対応とマルチデバイス対応の大きな違いは、画面要素の大きさが動的に変わることを、レイアウト 設計時に考慮に入れるかどうかという点です。大きさにピクセル指定を行う場合や、View の配置場所を数値で指 定することは望ましくありません。なぜならば、別のサイズや解像度に対応できなくなる可能性が高まるためです。し たがって、このような点に注意しながら、レイアウトを設計します。 レイアウトを正しく作成することの効果は、マルチデバイス対応につながるだけではありません。レイアウトが正しく表 示されているように見える場合にも、内部のレイアウトが複雑になっていると描画速度が遅くなり、消費電力にも影 響します。そのため、View はできるだけ単純に配置するべきです。レイアウトを正しく作成することにより、アプリケー ションを高速に安定して動作させ、消費電力を抑えるアプリケーションの開発につながります。 Android アプリケーションのレイアウトを設計する際には、さらに注意することがあります。主要な注意の一つとして は、アプリケーションの画面設計では画像を多用すべきではないということです。その理由は、モバイルデバイスは内 部の記憶領域が少ないものが多く、大きなファイルサイズのアプリケーションは容量を圧迫する恐れがあるためです。 Android の場合、単純な図形などは XML で定義することが可能です。そのため、XML で定義できるものは XML を使用することで、アプリケーションの容量を削減します。 本章では、レイアウトやリソースの観点から、マルチデバイスに対応する手法や、最適なレイアウトで消費電力を 減らす手法について解説します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 139 5-2. マルチデバイスを意識したアプリケーションにおける注意点 5-2. マルチデバイスを意識したアプリケーションにおける注意点 複数の画面サイズに対応させるために、各画面サイズに対応した画像などを用いることで、冗長なレイアウト設計 を行う開発者が、実は少なくありません。これは、注意深く使用する必要のある Android システムリソースの、致 命的な乱用につながります。 さらに、スクリーンサイズも、現行の Android 端末がサポートしているものが全てではありません。したがって、今後 のバリエーション追加を事前に想定した、柔軟な基本設計を行う必要があります。 そのためには、Android 端末向けアプリケーション開発において、マルチデバイスに対応するための基本を十分に 押さえておく必要があります。また、Android SDK に含まれる Lint の活用は、適切なレイアウトを実装する上で 不可欠です。 本項では、その具体的な手法を解説します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 140 5-2. マルチデバイスを意識したアプリケーションにおける注意点 5-2-1. • マルチデバイスのためのレイアウト構成 レイアウトの配置 Android アプリケーション開発では、基本的に画面レイアウト周りとプログラムは個別に実装します。それにより、 開発者が理解しやすい構成になるため、メンテナンス性が向上します。 Android アプリケーション開発の経験が少ない場合、画面レイアウトの作成には予想以上に苦戦するかもしれま せん。なぜならば、Android の画面レイアウトは、原則として XML で記述するものであり、XML で記述する画面レ イアウトは、HTML で記述するレイアウトとは異なります。XML では、様々な属性を付与する必要があります。それ ぞれの属性の意味を正しく知らずに記述することにより、スマートフォンでは正しく表示されるように見えても、タブレッ トでは無駄な余白が入る可能性があります。 また、シングルデバイス対応の場合はあまり注意する必要はありませんが、マルチデバイスに対応する場合は、後 述する dp や sp といった単位についても正しく理解していなければなりません。画面サイズが違う場合に表示がず れるなど、様々な弊害が発生する可能性があります。マルチデバイス対応をするためには、一つの端末だけで確認 するのではなく、様々な解像度や画面サイズで確認する必要があるということを念頭に置く必要があります。 もちろん、プログラム内部でもレイアウトを書くことは可能です。しかし、これはメンテナンス性を下げ、バグの原因に もなります。また、プログラム内部でレイアウトに関する記述を行う場合は、XML で記述するような属性等を正しく 指定しなければ、画面解像度が違う端末に対してレイアウトが崩れてしまう恐れがあります。これはプログラムを実 行するまで確認することもできないため、十分な注意が必要です。 XML で書くメリットとして、以下の三点が挙げられます。 1. 開発者が理解しやすく、メンテナンス性が向上する 2. Graphical Layout で、イメージが掴める 3. 解像度が異なる端末に対する処理を、必要以上に気にする必要がなくなる レイアウトの XML は通常、プロジェクト内の、/res/layout 配下に置きます。これにより、R.layout.(ファイル名) でレイアウトを取得することができます。しかし、画面解像度ごとにレイアウトを変えたい場合もあるでしょう。あるいは、 縦で表示する際と横で表示する際に、レイアウトを変えたい場合があるでしょう。Android のレイアウトでは、そのよ うな場合でも、ディレクトリにリソース修飾子を付与することで、簡単かつ自動的にレイアウトを切り替えられるように なっています。 また、リソース修飾子の使用は、レイアウトだけではありません。レイアウトで用いる画像を保存する /res/drawable や、文字列や数値などを保存する/res/values など、/res/配下に置くリソースにはすべてリソ ース修飾子をつけることで、各条件によって自動的に切り替わります。 シングルデバイスの場合は、標準のものを一つだけ用意すれば大丈夫ですが、マルチデバイス対応の場合は、こ れらのリソース修飾子を多用して行います。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 141 5-2. マルチデバイスを意識したアプリケーションにおける注意点 • リソース修飾子 リソース修飾子は、複数付与することが可能ですが、その場合の順序は、以下の表に記述している順番でなけれ ばなりません。 修飾子 値 説明 MCC と MNC mcc440 MCC は、mobile country code、MNC は mobile network code の mcc440-mnc10 等 略で、各国のキャリアなどを指定できます。 例えば、NTT ドコモは mcc440-mnc10 となりますが、これは NTT ドコモ のすべてを指すわけではないので、指定することはめったにありません。 日本の MNC などの割り当ては、下記 URL を参照してください。 【ITU(国際電気通信連合)公式サイト】 http://www.itu.int/pub/T-SP-E.212B-2013 言語と地域 en 2 文字で定義された ISO 639-1 言語コードで、オプションとして"r"と 2 文 ja 字の ISO 3166-1-alpha-2 の地域コードをつけます。 en-rUS 等 例えば、日本語の場合は ja-rJP であり、アメリカ英語の場合は en-rUS で す。en は、地域を問わず英語の場合です。 レイアウト方向 ldrtl レイアウトの方向です。ldrtl は layout-direction-right-to-left を意味 ldltr し、右から左に文字が表示されます。これは、アラビア文字など右から文字 を書く場合に使用します。標準は ldltr で、日本語や英語のように左から右 に文字を書く場合です。 この項目は API level17 から導入され、ldrtl を利用する場合は AndroidManifest の application で supportsRtl を true にしなけれ ばいけません。 最小幅 sw<N>dp 画面解像度の縦幅もしくは横幅の小さい方のサイズを指定します。つまり、 例) 画面の方向が縦であっても横であっても変わることはありません。例えば、 sw320dp sw320dp の場合は、縦もしくは横の小さい方の解像度が 320dp 以上の sw600dp 場合に指定するレイアウトファイルを保存します。(dp という単位は次の章 sw720dp 等 で詳しく説明します。) この項目は、よく使う重要な項目なので、詳細を後述します。 この項目は、API level 13 から導入されました。 利用可能幅 w<N>dp 画面解像度で利用可能な横幅の、最小サイズを指定します。 例) この値は、sw<N>dp とは違って画面方向が縦向きの場合と横向きの場 w720dp 合で変わってきます。例えば 640dp×360dp (960ps×540px の hdpi) w1024dp 等 の携帯電話の場合、縦方向の時は w360dp のディレクトリを見ますが横 方向の時は w640dp のディレクトリを見ます。(hdpi は後述の画面ピクセ ル密度) この項目は API level 13 から導入されました。 • 利用可能高さ h<N>dp 画面解像度で利用可能な高さの最小サイズを指定します。 例) この値は w<N>dp と同じように画面方向が縦向きの場合と横向きの場 h720dp 合で変化します。前述の 640dp×360dp の携帯電話の場合、縦方向の Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 142 5-2. マルチデバイスを意識したアプリケーションにおける注意点 修飾子 値 説明 h1024dp 等 時は h640dp のディレクトリを、横方向の時は h360dp のディレクトリを見 ます。 この項目は API level 13 から導入されました。 • 画面サイズ small small は、320×426dp を最小のレイアウトサイズとする画面になります。 normal これは低密度 QVGA サイズや高密度 VGA サイズなどが当てはまります。 large API level4 から導入されました。 xlarge normal は、320×470dp を最小のレイアウトサイズとする画面になりま す。これは低密度 WQVGA サイズや中密度 HVGA サイズ、高密度 WVGA サイズが当てはまります。API level4 から導入されました。 large は、480×640dp を最小のレイアウトサイズとする画面になります。こ れは中密度の VGA サイズや WVGA サイズが当てはまります。API level4 から導入されました。 xlarge は、720×960dp を最小のレイアウトサイズとする画面になります。 これは大きすぎるため、タブレットサイズが当てはまります。 この項目は API level9 から導入されました。 • 画面アスペクト比 long long は、WQVGA, WVGA, FWVGA のように長いものです。また、 notlong notlong は、QVGA、HVGA、VGA のように長くないものです。 この項目は API level4 から導入されました。 これは画面の純粋なアスペクト比で判断されるため、画面の向きは関係あ りません。 • 画面の向き port port は縦方向、land は横方向の場合です。 land 画面の向きを固定しない場合、この値はよく使用します。 この項目はよく使う項目なので、詳細を後述します。 • UI モード car car は車載ドッグにある場合です。 desk desk はデスクドッグにある場合です。 television television は大画面で 3 メートル程度離れた場所からでも操作する場合 appliance です。 appliance は画面がない機器などの場合です。 television は API level 13 から導入され、それ以外は API level 8 から 導入されました。 • ナイトモード night night は夜間の場合、notnight は昼間の場合です。 notnight この値は API level 8 から導入されました。 デフォルトでは自動モードになっており、この時はアプリケーションが起動中で も自動で切り替わります。 • 画面ピクセル密度 ldpi 画面ピクセル密度で切り替えて使用します。 mdpi xhdpi は API level 8 から、tvdpi は API level 13 から導入されました。 hdpi この項目は非常に重要な項目なので、詳細を後述します。 xhdpi nodpi Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 143 5-2. マルチデバイスを意識したアプリケーションにおける注意点 修飾子 値 説明 tvdpi • • タッチスクリーンタイプ キーボードの利用 notouch notouch はタッチスクリーンがない場合で、finger は指で操作する場合で finger す。 keysexposed keysexposed は、ハードウェアキーボードを持っている端末で、ソフトウェア keyshidden キーボードが使用不可かつキーボードが出されている場合のみ利用されま keyssoft す。 keyshidden は、ハードウェアキーボードを持っている端末だが、隠されてい て利用可能なソフトウェアキーボードがない場合に利用されます。 keyssoft は、ソフトウェアキーボードが表示されているかどうかに関わらず、 利用可能な場合に利用されます。 keysexposed リソースを用意して keyssoft リソースを用意しない場合、 ソフトウェアキーボードが有効であれば、キーボードが出されていようが隠され ていようが keysexposed リソースを利用します。 • 主なテキスト入力方式 nokeys nokeys は、入力するためのハードウェアキーボードがない場合です。 qwerty qwerty は、出されているか隠されているかに関わらず qwerty キーボード 12key がある場合です。 12key は、出されているか隠されているかに関わらず 12key キーボードがあ る場合です。 • ナビゲーションキーの利用 navexposed naveposed は、利用可能なナビゲーションキーがある場合です。 navhidden navhidden は、例えば蓋が閉まっているなどで利用可能なナビゲーション キーがない場合です。 • タッチ以外の主なナビゲーシ nonav nonav は、タッチスクリーン以外にナビゲーションするものがない端末の場合 ョン方式 dpad です。 trackball dpad は、ナビゲーションのために方向変更用の Pad(d-pad)がある端末 wheel の場合です。 trackball は、ナビゲーションのためにトラックボールがある端末の場合です。 wheel は、ナビゲーションのために方向変更用のホイールがある端末の場 合です。 • プラットフォームバージョン v3 端末がサポートする API level です。 v4 例えば v11 は Android 3.0(Honey Comb)以上の場合であり、v14 v7 等 は Android 4.0(ICE CREAM SANDWICH)以上です。 この項目はよく使う重要な項目なので、詳細を後述します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 144 5-2. マルチデバイスを意識したアプリケーションにおける注意点 リソース修飾子のルールは、いくつかあります。その詳細を以下に示します。 1. リソース修飾子を複数付与する場合は、ハイフンで分割して指定します。 例えば、values リソースを日本語で最小幅が 600dp の場合に適用するには、 values-ja-sw600dp となります。 2. リソース修飾子を複数付与する場合は、先に挙げたとおり、上記の表の順番に付与しなければなり ません。 例えば、最小幅が 600dp の横向きレイアウトの場合、layout-land-sw600dp ではなく、 layout-sw600dp-land となります。 さらに、日本語のみの場合では、layout-ja-sw600dp-land となります。 3. リソースディレクトリは、ネストすることができません。例えば、/res/drawable/drawable-en/とい う書き方はできません。 4. 値はすべて、大文字と小文字の区別はせず、常に小文字として扱われます。大文字にするメリット は、開発者の可読性を上げるためです。 同じタイプのリソース修飾子を、複数付与することはできません。例えば、スペインとフランス用に同じ Drawable ファイルを使用したい場合、ディレクトリを drawable-rES-rFR/と記述することはできま せん。 この場合は、drawable-rES/と drawable-rFR/を用意する必要があります。ただし、同じ画像フ ァイルを両方に置く必要はありません。この場合は、エイリアスリソースを用意します。 エイリアスリソースについては後述します。 リソース修飾子を細かくすることで、きめ細かな画面設定が可能になりますが、これは全世界の様々なタイプの端 末で様々な言語に対応するために必要であり、例えば、日本語のみで、かつ限られた端末にしか提供しない場合 は、ここまで詳しく指定する必要はありません。特に、よく使うと思われるリソース修飾子は、画面ピクセル密度・画 面の向き・最小幅・プラットフォームバージョンです。したがって、これらについては詳細を説明します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 145 5-2. マルチデバイスを意識したアプリケーションにおける注意点 • 画面ピクセル密度 画面ピクセル密度は、画面の物理的な領域内のピクセル数であり、通常は dpi (dots per inch 一イ ンチ辺りのドット数)と呼ばれ、主要なものは ldpi,mdpi,hdpi,xhdpi,xxhdpi とあります。それぞれが、 どの程度の dpi に対応しているのか、以下で示します。 ldpi は 120dpi 程度 mdpi は 160dpi 程度 hdpi は 240dpi 程度 xhdpi は 320dpi 程度 xxhdpi は 480dpi 程度 それぞれの主要なスケール割合は、3:4:6:8:12 となっています。例えば、ldpi で 9×9 のビットマップは、 mdpi では 12×12 になり、hdpi では 18×18、xhdpi では 24×24、xxhdpi では 36×36 となり ます。また、画像を drawable-mdpi に保存していて、drawable-hdpi に保存していない場合は、 hdpi の画面ピクセル密度を持つ端末で確認すると、drawable-mdpi に保存されている画像を 1.5 倍して表示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 146 5-2. マルチデバイスを意識したアプリケーションにおける注意点 画像ピクセル密度が変わるということは、同じ 1px のサイズであっても、密度によって表示される太さが変 わることを意味します。Android では、これらをすべて同じサイズにするために、px の代わりに dp(dip)と いう単位を使います。dp で指定したサイズは、どの画面ピクセル密度であっても同じように表示されます。 これは、シングルデバイスでは気にする必要はありませんが、マルチデバイス対応をする場合には、非常に 重要な項目です。 以下のサンプルは、横幅が 320dp のエミュレータに 200×200px の View を、ldpi, mdpi, hdpi, xhdpi で表示したものです。 px_ldpi px_mdpi px_hdpi px_xhdpi これらを比較すると、密度によって View の大きさが変わることが分かります。 これを、px ではなく dp にしたものを、以下に示します。 dp_ldpi dp_mdpi dp_hdpi Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. dp_xhdpi 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 147 5-2. マルチデバイスを意識したアプリケーションにおける注意点 dx 指定から dp 指定に変更することで、それぞれの画面ピクセル密度に合わせた大きさになります。 dp の計算は、それほど難しいものではありません。計算方法は、mdpi が基準となって 1px=1dp となり ます。 dp のスケールも、画像などのスケールと同じ考えで、mdpi よりも低密度の ldpi は 0.75px=1dp となり、 高密度の hdpi は、1.5px=1dp となります。 更に高密度な xhdpi は、2px=1dp となります。最近になって、xhdpi よりもさらに高密度な端末が発 表されています。これは xxhdpi となっており、3px=1dp になります。今後は、このサイズが主流になる 可能性があるため、注意が必要です。 これら以外に、tvdpi もあります。tvdpi は、mdpi と hdpi の中間で、214dpi 程度になります。スケー ルする場合は 1.33px=1dp となります。しかし、tvdpi は主にテレビのために意図されたものであり、ほと んどのアプリケーションでは必要ありません。なぜならば、mdpi と hdpi リソースがあれば、それらをスケール することで十分だからです。 画像リソースの修飾子では、他に nodpi もあります。これは、主にゲームなどで、サイズ調整を自分で行 うために、自動的にスケールさせたくない画像を保存します。 • 画面の向き 画面の向きは、縦横回転に対応するアプリケーションを作成する場合には重要です。なぜならば、縦と横 では画面のイメージが変わってくるため、レイアウトを変更する必要があるからです。レイアウトの指定は、 縦 画 面 の 場 合 は /res/layout-port/ に 保 存 し 、 横 画 面 の レ イ ア ウ ト を 指 定 す る 場 合 は 、 /res/layout-land に保存しますが、通常はどちらかを指定していれば、もう一方はあえて指定する必 要はありません。例えば、横画面の layout-land と、指定のない layout がある場合、横画面で表示す る時は layout-land の中のレイアウトが適用され、縦画面で表示する時は、横画面ではないので layout の中のレイアウトが適用されるからです。 • 最小幅 最小幅は、縦横のサイズの小さい方の幅になります。 例えば、高密度(hdpi)で 800×480px の解像度を持つ携帯電話は、320dp となります。最近では、 hdpi で 540×960px の解像度や、xhdpi で 720×1280px が携帯電話のサイズとして増えてきて います。これは、360dp となります。今後の主力となるであろうサイズの 1080×1920px も 360dp で す。この項目は、画面サイズによって異なる項目のため、シングルデバイスでは不要ですが、マルチデバイ ス対応では非常に重要です。 携帯電話サイズでは、sw320dp もしくは sw360dp、7 インチタブレットサイズでは sw600dp、10 イ ンチタブレットサイズでは sw720dp が基準になるため、携帯電話サイズと 7 インチタブレットサイズ、10 インチタブレットサイズで同じアプリケーションを動かす場合は、これらのサイズで最適化するのが望ましいで しょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 148 5-2. マルチデバイスを意識したアプリケーションにおける注意点 • プラットフォームバージョン プラットフォームバージョンは Android のバージョンごとに変更する時に指定します。例えば、Android 3.0 (API レベル 11)から、デザインスタイルは Theme.Holo.Light を使うように推奨されました。また、 Android 4.0 (API レベル 14)からのスタイルは、Theme.Holo.Light.DarkActionBar を使うよう に推奨されました。 しかし、下位互換を考えた場合には、それぞれのデザインスタイルを指定することはできません。例えば、 Theme.Holo 等の style は API レベル 11 からのものであり、API レベル 10 以前では利用することが できないためです。したがって、values-v11 の styles.xml に android:Theme.Holo.Light を指定 することで、v10 以前は標準のものを、API レベル 11 以降では Theme.Holo.Light を適用させるこ とができます。 また、さらに values-v14 に android:Theme.Holo.Light.DarkActionBar を指定することで、API レベル 14 以降のスタイルには、Theme.Holo.Light.DarkActionBar を適用することができます。 このように、API レベルが変わった時に、以前までの API レベルでは使えなかった定義を使用する場合に 利用します。 • マルチデバイスを考慮したリソース 最小幅の項目としては、携帯電話サイズ、7 インチタブレットサイズ、10 インチタブレットサイズで最適化 することが望ましいと述べましたが、最適化しない場合はどのようになるでしょうか? 例えば、以下の例です。この例では、中央に、サイズを指定したアイコンを表示するレイアウトです。 例)中央にアイコンを表示するレイアウト /res/layout/layout.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="120dp" android:layout_height="120dp" android:layout_centerInParent="true" android:contentDescription="@string/app_name" android:src="@drawable/ic_launcher" /> </RelativeLayout> これを携帯サイズ、7 インチタブレット、10 インチタブレットでそれぞれ表示したものが以下のとおりになります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 149 5-2. マルチデバイスを意識したアプリケーションにおける注意点 携帯サイズ(sw320dp) 7 インチタブレットサイズ 携帯サイズ(sw360dp) 10 インチタブレットサイズ Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 150 5-2. マルチデバイスを意識したアプリケーションにおける注意点 画像サイズを 120dp と固定で表示しているため、2 つの携帯サイズではあまりわかりませんが、タブレットサイズに なるとかなり見た目が変わっていることがわかります。 これを、携帯サイズとタブレットサイズで同じような比率で見せるために/res/values/dimens.xml、 /res/values-sw600dp/dimens.xml および values-sw720dp/dimens.xml を作成して画面調整を行 います。 # dimens.xml は各値などを保存するための xml です。 /res/layout/layout.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="@dimen/icon_size" ←dimens.xml から値を取得するように変更 android:layout_height="@dimen/icon_size" ←dimens.xml から値を取得するように変更 android:layout_centerInParent="true" android:contentDescription="@string/app_name" android:src="@drawable/ic_launcher" /> </RelativeLayout> /res/values/dimens.xml <resources> <dimen name="icon_size">120dp</dimen> </resources> /res/values-sw600dp/dimens.xml <resources> <dimen name="icon_size">225dp</dimen> </resources> /res/values-sw720dp/dimens.xml <resources> <dimen name="icon_size">270dp</dimen> </resources> これで 7 インチタブレット端末および 10 インチタブレットでも同じような比率で表示されます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 151 5-2. マルチデバイスを意識したアプリケーションにおける注意点 7 インチタブレットサイズ 10 インチタブレットサイズ タブレットのサイズである 225dp および 270dp という値は、最小幅を指定しない時を 320dp とした場合の比 率で計算しています。つまり、320dp:600dp=120dp:x という比率から x=225dp となります。10 インチタブレ ットも同様に、320dp:720dp=120dp:x という比率から x=270dp です。 携帯サイズの場合、今回は 320dp と 360dp では誤差レベルで表示はそれほど変わりませんが、デザインによっ てはきちんと揃えたい場合もあるでしょう。 その場合も、タブレットと同様に/res/values-sw360dp/dimens.xml を指定すると同じ比率になります。 設定する値は 320dp:360dp=120dp:x から x=135dp になるため、以下のように設定します。 /res/values-sw360dp/dimens.xml <resources> <dimen name="icon_size">135dp</dimen> </resources> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 152 5-2. マルチデバイスを意識したアプリケーションにおける注意点 携帯サイズ(sw360dp) このように、規定の初期値を決めて、それぞれの比率を計算した値を各リソース修飾子に入れることで、それぞれ のサイズで最適化された値を指定することができます。 上記の例では、values リソースを用いてリソース修飾子を説明しましたが、これは他のリソースであっても同様で す。例えば、layout に sw360dp、sw600dp、sw720dp のリソース修飾子を用意した場合、以下の図 20 の ような形でリソースを設定できます。 携帯サイズ layoutリソース layout layout-sw360dp 7インチタブレットサイズ 10インチタブレットサイズ layout-sw600dp layout-sw720dp 端末サイズ sw360dp sw600dp 図 20 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. sw720dp 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 153 5-2. マルチデバイスを意識したアプリケーションにおける注意点 最小幅の sw<N>dp は、Android 3.2(API レベル 13)から使用可能となっており、マルチスクリーンが対応さ れた Android 1.6 (API レベル 4)から Android 3.1 (API レベル 12)まででは利用できません。そのため、マル チデバイス対応で下位のバージョンにも対応するには、さらに画面サイズを指定するものを追加する必要があります。 画面サイズは、small,normal,large,xlarge の 4 つが定義されています。これは現在、非推奨となっていますが、 複数のバージョンに対応させるには、画面サイズを指定するものの追加が必要です。各画面サイズの最小値は以 下のとおりで、例えば Android 2.2(API レベル 8)以上で対応する場合、これらの画面サイズ用のディレクトリは 用意しなければなりません。 画面サイズ名 サイズ xlarge 960dp × 720dp large 640dp × 480dp normal 470dp × 320dp small 426dp × 320dp 上記の指定により、すべてのタブレットに対応する場合は、layout-sw720dp および layout-xlage を作成し て、同じものを登録する必要があります。なぜならば、タブレットは Android 3.0 以上となるので、Android 3.0 および Android 3.1 では sw<N>dp が使用できないためです。この場合、全く同じものを登録しますが、値など が変更された際には、それぞれのディレクトリ内の値を変更しなければいけません。そこで、このようなパターンでは、 エイリアスリソースを用いて共通化させます。下記はエイリアスリソースの設定例です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 154 5-2. マルチデバイスを意識したアプリケーションにおける注意点 例)イメージを中心に表示するレイアウト。通常は横幅を 120dp で表示するが、10 インチタブレットサイズの場合 は 270dp で表示。 /res/layout/layout.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="@dimen/icon_size" android:layout_height="@dimen/icon_size" android:layout_centerInParent="true" android:contentDescription="@string/app_name" android:src="@drawable/ic_launcher" /> </RelativeLayout> /res/values-sw720dp/dimens.xml <resources> <dimen name="icon_size">@dimen/tablet_icon_size</dimen> </resources> /res/values-xlarge/dimens.xml <resources> <dimen name="icon_size">@dimen/tablet_icon_size</dimen> </resources> /res/values/dimens.xml <resources> <dimen name="icon_size">120dp</dimen> <dimen name="tablet_icon_size">270dp</dimen> </resources> ここまでで、携帯電話サイズからタブレットサイズまで、すべてのサイズで同じようにレイアウトを組む方法を説明し てきました。しかし、アプリケーションによっては、タブレットサイズのみ対応する場合もあります。その場合は、 AndroidManifest.xml の supports-screens タグで、対象となる端末の最小幅を指定することで対応できま す。最小の画面サイズ以外にも、最大の画面サイズや、最小幅の設定が導入される Android3.2(API レベル 13)以前で指定した画面サイズ(small,normal 等)が対象かどうかなどを、指定することができます。 supports-screens タグは、Android 1.6(API レベル 4)から導入されました。設定できるものは、以下のとおり です。 android:resizeable=["true"| "false"] アプリケーションが、異なる画面サイズで表示サイズ変更が可能かを設定します。この値は現在、非 推奨であり、使用すべきではありません。元々は、Android 1.5 から Android 1.6 への移行のた めに作られました。 android:smallScreens=["true" | "false"] 小さい画面サイズをサポートするかを設定します。小さい画面サイズとは HVGA(320×480)よりも 小さいアスペクト比のものと定義されています。デフォルトは true です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 155 5-2. マルチデバイスを意識したアプリケーションにおける注意点 android:normalScreens=["true" | "false"] 標準画面サイズをサポートするかを設定します。これは HVGA(320×480)の中密度画面ですが、 WQVGA(240×400)の低密度画面や WVGA(480×800)の高密度画面もこのサイズになり ます。デフォルトは true です。 android:largeScreens=["true" | "false"] 大画面サイズをサポートするかを設定します。大画面サイズとは、標準画面サイズよりも大きいもの であり、画面いっぱいに表示するためには何らかの特別な処理をする必要があるかもしれません。こ の値を false にすると、互換モードが有効になることに注意が必要です。互換モードについては後述 します。 android:xlargeScreens=["true" | "false"] 特大画面サイズをサポートするかを設定します。特大画面サイズとは、大画面サイズよりも大きいも のやタブレット等であり、largeScreen と同様に、false にすると互換モードが有効になります。この 属性は API レベル 9 から導入されました。 android:anyDensity=["true" | "false"] 任意の画面密度に対応しているかを設定します。例えば、ゲームのようにアプリケーションから直接 すべてのビットマップを操作する場合には false としますが、基本はデフォルトの true にします。 android:requiresSmallestWidthDp="integer" アプリケーションがサポートしている最小の画面幅を指定します。この属性は API レベル 13 から導入 されました。 android:compatibleWidthLimitDp="integer" アプリケーションの互換モードを必要とせずに実行できる、最大の“最小幅”を指定します。画面が 指定した値よりも大きい場合は、システムは互換モードで表示することができます。しかし、デフォルト では通常モードとして表示し、システムバーにて互換モードに切り替えが可能です。この属性は API レベル 13 から導入されました。 android:largestWidthLimitDp="integer" アプリケーションが互換モードを必要とせずに実行できる、最大の“最小幅”を指定します。画面が 指定した値よりも大きい場合は、システムは強制的に互換モードで表示し、変更することはできませ ん。この属性は API レベル 13 から導入されました。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 156 5-2. マルチデバイスを意識したアプリケーションにおける注意点 以下の例は、supports-screens タグでタブレットサイズのみを AndroidManifest で指定する場合の設定例で す。 <supports-screens android:smallScreens="false" android:normalScreens="false" android:largeScreens="false" android:xlargeScreens="true" android:requiresSmallestWidthDp="600dp" /> このようにして、非対応の画面サイズを AndroidManifest に指定することができます。 • 互換モード 互換モードとは、タブレットなどの大画面に対応していないアプリケーションを、タブレット用にリサイズして大 きく表示するモードです。この互換モードを使用することにより、タブレットサイズに対応していないアプリケ ーションであっても、多少は操作性が向上します。しかし、可能な限り互換モードを利用せずに、それぞれ の画面サイズごとに最適化することが望ましいでしょう。 • 縦横両対応のレイアウト作成 スマートフォンのような端末では、縦に持つ場合と横にして持つ場合が考えられます。その際に表示する 画面は、イメージが大きく異なります。一例として、以下のようなレイアウトで考えてみます。 /res/layout/layout.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <FrameLayout android:layout_width="320dp" android:layout_height="320dp" > <ImageView android:layout_width="48dp" android:layout_height="48dp" android:layout_gravity="center" android:contentDescription="@string/app_name" android:src="@drawable/ic_launcher" /> </FrameLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="20dp" android:text="これはアイコンです。" android:textColor="@android:color/white" /> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 157 5-2. マルチデバイスを意識したアプリケーションにおける注意点 このレイアウトを縦画面で表示すると以下のようになります。 portrait_image このイメージの場合、縦画面では正常に表示されているように見えます。しかしこのレイアウトを横画面で表示し てみると以下のようになります。 landscape_image 文字の部分が下に切れてしまっていたり、アイコンが左に寄っていたりします。このように、縦と横でレイアウトを共 通化していると、縦横の変更で思いもよらぬ画面になります。そのため、縦画面と横画面ではレイアウトを変更する ことが望ましいでしょう。横画面のレイアウトを指定する場合は、/res/layout-land/に保存します。 先ほどの例のレイアウトにおいて、横画面の場合は文字の部分を右側に表示するようにしました。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 158 5-2. マルチデバイスを意識したアプリケーションにおける注意点 /res/layout-land/layout.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <FrameLayout android:layout_width="320dp" android:layout_height="320dp" > <ImageView android:layout_width="48dp" android:layout_height="48dp" android:layout_gravity="center" android:contentDescription="@string/app_name" android:src="@drawable/ic_launcher" /> </FrameLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="20dp" android:text="これはアイコンです。" android:textColor="@android:color/white" /> </LinearLayout> landscape_image2 上のような簡単な例では、プログラムもレイアウトも単純であり、縦横どちらの画面でも表示する情報が同じなの で、レイアウトの切り替えのみで完結します。しかし、縦画面と横画面では、表示する情報量を変えたい場合もあり ます。 例えば、ニュースを表示するアプリケーションでは、縦画面でニュースタイトルのリストのみを表示しタップで詳細を表 示する、横画面では左側にニュースタイトルを表示して、右側には詳細を表示するといったレイアウトが考えられま す。このような場合、縦画面では一覧リスト表示用 Activity に一覧を取得しつつ、横画面用に詳細も取得する、 詳細用 Activity では詳細を取得するといったプログラムを書かなければならず、無駄に処理が増える原因となりま す。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 159 5-2. マルチデバイスを意識したアプリケーションにおける注意点 このような時のために、Android 3.0(API レベル 11)からは、Fragment という機能が導入されました。 Fragment は、Activity から操作するためのもので、それ単体では動作させることはできませんが、Activity と同 様にライフサイクルをもっており、Activity のライフサイクルと連動しています。 例えば、Activity が一時停止すると、その中の Fragment もすべて一時停止します。そして、Activity が破棄 されるとすべての Fragment も破棄されます。また、Fragment は Activity と同じく、入力イベント等も受け付け ることもできます。そのため、Fragment で一つの機能として完結させることが可能です。Activity は、実行中にそ れらの Fragment を追加や削除することで、画面を切り替えることができます。さらに、Fragment は複数の Activity で使用することもできるため、無駄な処理を省くこともできます。このように、Fragment には様々なメリッ トがあります。 図 21 Activity から Fragment を呼び出すイメージ 先に例として挙げたニュースアプリケーションを Fragment にした場合、ニュース一覧 Fragment と、ニュース詳細 Fragment となります。そして、縦画面では、ニュース一覧 Fragment を表示、タップすることでニュース詳細 Fragment を表示します。横画面では、ニュース一覧 Fragment とニュース詳細 Fragment を横並びで表示す るという感じになります。これらのレイアウト例は、以下のようになります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 160 5-2. マルチデバイスを意識したアプリケーションにおける注意点 /res/layout/fragment_layout.xml <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment" android:id="@+id/titles" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> /res/layout-land/fragment_layout.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment" android:id="@+id/titles" android:layout_weight="1" android:layout_width="0px" android:layout_height="match_parent" /> <FrameLayout android:id="@+id/details" android:layout_weight="1" android:layout_width="0px" android:layout_height="match_parent" android:background="?android:attr/detailsElementBackground" /> </LinearLayout> com.example.android.apis.app.FragmentLayout$TitlesFragment という Fragment がニュース一 覧 Fragment です。そして詳細の部分は、FrameLayout の部分に当てはめます。 図 22 縦画面と横画面で表示の違い このようにして縦画面と横画面でのレイアウトを切り替えることで、表示する情報量も変えることができます。これら のプログラム部分を含んだ Fragment の詳細は次以降の章で説明します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 161 5-2. マルチデバイスを意識したアプリケーションにおける注意点 プログラム内部でのレイアウト作成 • リソース修飾子を付与することで、各パターンによってレイアウトを切り替えることができ、ほとんどの場合はこれでレ イアウトを切り替えることができます。しかし、プログラム内部でレイアウトを書かなければならない場合もあります。例 えば、データによって動的にレイアウトが変わる場合などです。 以下の例は、TableLayout へ動的に項目を追加する例です。(TableLayout については後述します) 例)奇数行は TextView、偶数行は EditText で表示する MainActivity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 動的に数が変わる何か String[] values = {"TextView で表示", "EditText で表示", "TextView で表示"}; TableLayout layout = (TableLayout) findViewById(R.id.base); for (int i = 0; i < values.length; i++) { TableRow row = new TableRow(this); TextView no = new TextView(this); no.setText(String.valueOf(i + 1)); row.addView(no); TextView value; if(i%2==0) { value = new TextView(this); } else { value = new EditText(this); } value.setText(values[i]); row.addView(value); layout.addView(row); } } activity_main.xml <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/base" android:layout_width="match_parent" android:layout_height="match_parent" /> このようにレイアウトは必ずしも xml で完結できる訳ではありません。ですが、可能な限り xml ファイル内に定義す ることが望ましいでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 162 5-2. マルチデバイスを意識したアプリケーションにおける注意点 5-2-2. レイアウトファイルの最適化 マルチデバイス対応のレイアウト設計を行うためには、レイアウトの基本を正しく知っておく必要があります。なんと なくできたレイアウトでは、スマートフォンでの表示はきちんと動いているように見えても、タブレットでは余分なスペー スが入っていたりすることもあります。また、対応すべきデバイスがシングルデバイス(特定機種)であったとしても、余 分な View を重ね合わせているために表示速度が遅くなることや、電力の浪費につながることもあります。そのため にも、ここでは基本的なレイアウトの説明から、マルチデバイスに対応するためのテクニックについて説明します。 • XML によるレイアウトの配置イメージ Android の画面は XML を使用して作成します。ですが、基本的な考え方は HTML の div タグを書いて位置 を決定し、input タグを書いて実際の部品を表示するイメージと同じです。この div タグに相当するものが ViewGroup であり、input タグに当たるものが View になります。 ただし、HTML と大きく違う部分は、すべてを View もしくは ViewGroup で記述しなければならない点です。つ まり、文字列も View になるということです。また、ViewGroup は div タグに当たると述べましたが、ListView のよ うに ViewGroup に表示する文字を直接指定する場合もあります。正確に述べると、ViewGroup の中の View に表示させているのですが、その部分は見えないため、ViewGroup に直接指定しているように見えます。 図 23 View の配置イメージ Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 163 5-2. マルチデバイスを意識したアプリケーションにおける注意点 一般的に HTML で配置を決定、装飾を行う場合、スタイルシート(CSS)を用います。しかし、Android では View のプロパティで行います。そのため、CSS でよく使われるものは、ほとんどが View のプロパティとして定義されて い ま す 。 例 え ば 、 CSS に お け る padding や margin は 、 Android で も android:padding や android:layout_margin として指定することが可能です。さらに、デザイナーがデザインする上でこだわる text-shadow なども、Android には用意されています。 Android の View や ViewGroup の配置は、基本的に View の layout_width と layout_height で大き さを指定して配置します。この時に使う単位は dp です。マルチデバイス対応のためにも、px は使わないようにしまし ょう。なぜならば、px を使うとシングルデバイス対応の場合は特に問題ありませんが、マルチデバイス対応した場合 に表示がずれてしまうからです。したがって、数値を指定する場合は、必ず dp を使用してください。なお、dp は dip と書くことも可能で、どちらも同じ意味です。 View のサイズ等の単位は dp ですが、TextView などのフォントサイズの単位は sp になります。これは dp に似 た単位ですが、ユーザが設定したフォントサイズによって自動的にスケールされます。そのため、sp で指定したサイズ は、画面密度とユーザが設定したフォントサイズに依存します。 width と height の場合は、数値指定以外にも wrap_content および match_parent を用いて指定する ことができます。wrap_content を指定した場合は、Android システムは指定した View を表示するために必要 なサイズを計算して割り当てます。match_parent を指定した場合は、自分の View の親 ViewGroup に配置 できる最大の大きさを計算して割り当てます。wrap_content と match_parent の例を以下に示します。 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#f00" android:text="text1" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#0f0" android:text="text2" /> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:background="#00f" android:text="text3" /> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 164 5-2. マルチデバイスを意識したアプリケーションにおける注意点 上の例では、3 つの TextView を並べています。一つ目は、幅・高さ共に wrap_content を指定し、二つ目は 幅 に match_parent 、 高 さ に wrap_content を 指 定 、 三 つ め は 、 幅 に wrap_content 、 高 さ に match_parent を指定しましています。 このレイアウトを実行すると、以下のようになります。 wrap_content と match_parent の実行例 このように、wrap_content は View の最小限の大きさ、match_parent は View の最大限の大きさととなり ます。なお、match_parent は Android 2.1(API レベル 7)までは fill_parent という値でしたが、現在は非推 奨となっています。もし、Android 2.1 以前にも対応するアプリケーションを開発する場合は、fill_parent とする 必要がありますが、そうでない場合は match_parent とします。 • ViewGroup ViewGroup は、View を配置するための器のようなものです。先述のとおり、HTML でいう div タグや span タ グがこれに相当します。ViewGroup の中に ViewGroup を配置することも可能ですが、ViewGroup のネストは 階層が深くなるため、できるだけ行わない方が良いでしょう。ViewGroup には様々な種類がありますが、代表的 なものを幾つか説明します。 • LinearLayout LinearLayout は 、 縦 方 向 も し く は 横 方 向 に 配 置 す る 最 も 基 本 的 な ViewGroup の 一 つ で す 。 LinearLayout の orientation に vertical を指定すると縦方向、horizontal を指定すると横方向になります。 また、LinearLayout の中に配置した View は重なることはありません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 165 5-2. マルチデバイスを意識したアプリケーションにおける注意点 vertical を指定した例を以下に示します。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#f00" android:text="text1" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#0f0" android:text="text2" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#00f" android:text="text3" /> </LinearLayout> linear_vertical Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 166 5-2. マルチデバイスを意識したアプリケーションにおける注意点 horizontal を指定した例を以下に示します。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:background="#f00" android:text="text1" /> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:background="#0f0" android:text="text2" /> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:background="#00f" android:text="text3" /> </LinearLayout> linear_horizontal LinearLayout を使用する際は、orientation と match_parent に注意してください。例えば、orientation で vertical を指定しているのに、一つ目の View の height を match_parent にしてしまうと、二つ目以降の View は画面外になって見えなくなります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 167 5-2. マルチデバイスを意識したアプリケーションにおける注意点 上記の horizontal のサンプルの orientation を vertical に変えてみると確認できます。実行した例を、以下に 示します。 linear_vertical_ng これは、レイアウト作成に慣れていない開発者が陥りやすい間違いなので、注意が必要です。 • RelativeLayout RelativeLayout は、親の View や他の View を基本として位置を指定して配置します。位置指定を間違える と、View が重なってしまうこともあるので、注意が必要です。しかし、このレイアウトを用いれば、複数の階層の View にはなりにくく、無駄な描画が抑えられます。レイアウトとして、一番よく使うべき手法です。 親の View (RelativeLayout) をベースにして TextView を、中心・左上・左中央・左下・中央上・中央下・ 右上・右中央・右下の 9 ポイントに配置したものです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 168 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/center_point" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#f00" android:text="中央" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:background="#0f0" android:text="上中央" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:background="#00f" android:text="下中央" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="#ff0" android:text="左中央" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="#0ff" android:text="右中央" /> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 169 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#f0f" android:text="左上" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:background="#800" android:text="右上" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="#080" android:text="左下" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:background="#008" android:text="右下" /> </RelativeLayout> relative_parent このように、RelativeLayout は、親 View (RelativeLayout) の中心と上下左右に置くことができます。 RelativeLayout は、この他に、同レベルの View から見て相対的に View を配置することもできます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 170 5-2. マルチデバイスを意識したアプリケーションにおける注意点 以下の例は、中心に TextView を配置し、その上下左右に TextView を配置したものです。 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/center_point" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#f00" android:text="中央" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@+id/center_point" android:layout_centerVertical="true" android:background="#880" android:text="左" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/center_point" android:layout_centerVertical="true" android:background="#088" android:text="右" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/center_point" android:layout_centerHorizontal="true" android:background="#808" android:text="上" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/center_point" android:layout_centerHorizontal="true" android:background="#888" android:text="下" /> </RelativeLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 171 5-2. マルチデバイスを意識したアプリケーションにおける注意点 relative_view このように、指定した View の上下左右も簡単に配置することが可能です。そのため、これらを駆使すると大半の View は配置できるので非常に便利です。また、これらは階層を深くすることもないので、省電力に繋がります。 • FrameLayout FrameLayout は、最もシンプルなレイアウトです。そのため、基本的に View は一つしか配置できません。つまり、 FrameLayout のすべての子 View は左上を基点とし、これを変更することはできません。複数の View を配置し た場合は一枚ずつ重ねていくので、最後に配置された View が表示されます。したがって、透過 png などを利用し て、重ねて表示させることが可能です。 以下の例は、次の 3 枚の画像を重ね合わせたものです。この 3 枚の画像はすべて同じサイズであり、それぞれ、 下が透過、上が透過、上下透過となっています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 172 5-2. マルチデバイスを意識したアプリケーションにおける注意点 top bottom <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/top"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/bottom"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/center"/> </FrameLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. center 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 173 5-2. マルチデバイスを意識したアプリケーションにおける注意点 frame 上の例では、上→下→中の順番で ImageView を配置しているので、真ん中のものが一番大きく見えます。こ れを上→中→下のように 2 番目と 3 番目の ImageView を並び替えると、以下のようになります。 frame2 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 174 5-2. マルチデバイスを意識したアプリケーションにおける注意点 これを見ると、一番下の画像が一番上に来ているので、もっとも大きく見えます。このように、FrameLayout に 複数の View を配置する場合は、配置順序が重要になります。 • TableLayout TableLayout は、テーブル形式のレイアウトです。HTML の table タグと同じですが、HTML ではできても TableLayout ではできないものが多数あります。例えば、border などは TableLayout では用意されていません。 TableLayout で border を入れたい場合は、ちょっとしたテクニックが必要になります。 以下は、最も簡単な TableLayout の例です。 <?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TableRow> <TextView android:background="#f00" android:text="text1" /> <TextView android:background="#0f0" android:text="text2" /> <TextView android:background="#00f" android:text="text3" /> </TableRow> <TableRow> <TextView android:background="#ff0" android:text="text4" /> <TextView android:background="#0ff" android:text="text5" /> <TextView android:background="#f0f" android:text="text6" /> </TableRow> <TableRow> <TextView android:background="#800" android:text="text7" /> <TextView android:background="#080" android:text="text8" /> <TextView android:background="#008" android:text="text9" /> </TableRow> </TableLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 175 5-2. マルチデバイスを意識したアプリケーションにおける注意点 table TextView を、各行に三つ入れているだけです。行の決定は TableRow タグで行います。つまり、TableRow は HTML でいうところの tr タグになります。 この例から分かるとおり、TableRow やその中の子 View(今回の例では TextView)で、layout_width や layout_height のプロパティを設定していません。 な ぜな らば 、TableLayout の場合は 各子 View で match_parent や wrap_content の値を指定することができないためです。仮に、match_parent と設定し たとしても、wrap_content と同じ効果(最小限の大きさ)になります。したがって、どこかの行を match_parent にしたい(つまり、右側の余白分を割り当てたい)場合は、TableLayout の stretchColumns にカラムインデック スで指定することで広げます。以下の画面は、上記の例、TableLayout の stretchColumns に 1 を入れたもの です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 176 5-2. マルチデバイスを意識したアプリケーションにおける注意点 table2 カラムインデックスは、カンマ区切りで複数指定することが可能です。複数を指定した場合は、余白部分をそれぞ れ同じ分量だけ割り当てます。以下の画面は、stretchColumns を 1,2 としたものです。 table3 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 177 5-2. マルチデバイスを意識したアプリケーションにおける注意点 今回の例では、TableLayout の子 View に、layout_width や layoout_height は指定しませんでしたが、 数値であれば指定することは可能です。例えば、各 TextView の layout_width に 100dp、layout_height に 50dp を指定した場合の例を以下に示します。 table4 TableLayout と HTML の table タグでの違いは、他にもあります。例えば、HTML の colspan は Android にはありますが、rowspan は Android にはないといったことなどです。 このように、TableLayout は他の View とは少し異なる扱い方になっていることや、HTML の table ではできるこ とが、Android の TableLayout ではできないといった場合が多数あります。特に、HTML に慣れている人は、 HTML の table と同じように考える傾向があります。しかし、HTML における table と、Android における TableLayout は異なるものだと考えたほうが良いでしょう。 • ListView ListView は、リスト形式で表示します。ListView は、自動的にスクロールすることが可能となっており、表示デ ータが多い場合は自動的にスクロールバーが表示されます。 もっとも簡単な ListView の例を、以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 178 5-2. マルチデバイスを意識したアプリケーションにおける注意点 MainActivity.java public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.list); // アダプターの設定 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1); // 表示アイテム追加 adapter.add("test1"); adapter.add("test2"); adapter.add("test3"); adapter.add("test4"); adapter.add("test5"); adapter.add("test6"); adapter.add("test7"); adapter.add("test8"); adapter.add("test9"); adapter.add("test10"); // リストに値をセット ListView listView = (ListView) findViewById(R.id.list); listView.setAdapter(adapter); } } /res/list.xml <?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 179 5-2. マルチデバイスを意識したアプリケーションにおける注意点 list ListView の値は、adapter と呼ばれるリストの受け渡し用クラスにセットすることで自動的に表示されます。 図 24 adapter_image adapter を用いて値を表示するのは、ListView の他にも GridView や Spinner などがあります。それらは、す べて同様の方法で使用します。 ListView を利用する場合は、ListView の項目タップ時に何らかの処理を行いたいということが多いでしょう。そ のような場合には、Button の OnClickListener と同様に、ListView に OnItemClickListener を使用しま す。例えば上記の例で、タップした値を Toast 表示を行うようにするには、以下のように行います。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 180 5-2. マルチデバイスを意識したアプリケーションにおける注意点 listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(getApplicationContext(), adapter.getItem(position).toString() + " is clicked", Toast.LENGTH_SHORT).show(); } }); OnItemClickListener ではなく OnItemLongClickListener を setOnItemLongClickListenr でセット した場合、ロングタップ(長押し)として扱われます。 単純なリスト表示の際は、今回のサンプルのように実装できますが、ListView は各行にカスタマイズした View を 入れることも可能です。このサンプルのポイントは、ViewHolder を使用することです。ViewHolder については後 述します。 • その他 ViewGroup その他に代表的な ViewGroup として、以下の様なクラスがあります。 • クラス AbsoluteLayout 説明 指定した座標軸に View を配置する ViewGroup です。現在は、非推奨となっています。その理由は、シングルデ バイス対応であれば問題ありませんが、Android ではマルチデバイス対応にすべきであり、この値を使用すること で、解像度によっては位置がずれてしまうためです。 GridView Grid 形式で表示します。ListView と同じく Adapter を利用します。 TabHost タブ形式の View を表示します。 Gallery 画像をスライドで表示できます。Android 4.1(API レベル 16)から非推奨となっています。その理由は、メモリ の持ちなどに問題があるためです。 ScrollView View にスクロールバーを表示します。ScrollView の中には View はひとつしか入れることができないため、注意が 必要です。 Spinner HTML 等でいうコンボボックスのようなものであり、複数の中から一つ選択することができます。ListView と同じく、 adapter を利用します。 SurfaceView ゲーム等のように、定期的に描画を行う場合に使用します。描画スレッドとアプリケーションスレッドが別々になるた め、通常の View よりも高速に描画することができます。 ViewFlipper フリックなどで View を切り替える場合に使用します。 ViewSwitcher 2 つの View を定期的に切り替える場合などに使用します。ViewFlipper とは似ていますが、ViewSwitcher は 2 つの View 切り替えに特化したクラスです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 181 5-2. マルチデバイスを意識したアプリケーションにおける注意点 • 最適な View の構成 Android のレイアウトは、Web の HTML と似ているところがあります。特に、どんな構成であっても、見た目が綺 麗であれば問題ないように見えます。しかし、Android のレイアウトは、見た目が綺麗であっても構成が適切でな ければ、問題の原因となります。なぜならば、Android アプリケーションは、Web とは異なりバッテリの消耗を抑える ことや描画速度をきちんと考えて構成しなければならないからです。 これは、シングルデバイスであってもマルチデバイスであっても同様です。 例えば、以下のような画面を作成すると仮定します。 multi このレイアウトを作成するには、二つのパターンが考えられます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 182 5-2. マルチデバイスを意識したアプリケーションにおける注意点 パターン 1)LinearLayout を利用する <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#f00" android:text="text1" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#800" android:text="text2" /> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#0f0" android:text="text3" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:background="#00f" android:text="text4" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:background="#008" android:text="text5" /> </LinearLayout> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 183 5-2. マルチデバイスを意識したアプリケーションにおける注意点 パターン 2)RelativeLayout を利用する <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#f00" android:text="text1" /> <TextView android:id="@+id/text2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toRightOf="@+id/text1" android:background="#800" android:text="text2" /> <TextView android:id="@+id/text3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/text2" android:background="#0f0" android:text="text3" /> <TextView android:id="@+id/text4" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_below="@+id/text3" android:background="#00f" android:text="text4" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/text3" android:layout_toRightOf="@+id/text4" android:background="#008" android:text="text5" /> </RelativeLayout> これらのレイアウトを実行すると、いずれもまったく同じものができます。しかし、View の数は明らかに、パターン 2 の RelativeLayout を用いる方が少なくなります。したがって、このパターンでは RelativeLayout を使ってレイア ウトすることが望ましいでしょう。そうすることで、描画される View の数が減り、描画速度の向上や消費電力の軽 減につながります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 184 5-2. マルチデバイスを意識したアプリケーションにおける注意点 • 余分な View の削減 LinearLayout と RelativeLayout の違いで View の削減はできますが、それ以前に無駄な View があるかど うかのチェックを行わなければなりません。 例えば、以下のレイアウト例をご覧ください。 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="text"/> </LinearLayout> </RelativeLayout> center_text このレイアウトは、画面の中心に text という文字を表示します。実行すると、確かに中心へ text と表示されます。 ただし、この Layout の作り方は正しくはありません。なぜならば、LinearLayout が余分にあるためです。 Android では、このような余分な Layout は必ず削除することによって、少しでもパフォーマンスの向上につなげる 必要があります。なお、上記例を修正したものは、以下のとおりになります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 185 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="text" /> </RelativeLayout> • Lint の活用 ADT16 から、Android Lint がリリースされました。Lint は静的解析ツールであり、問題がありそうな点に Error や Warning を 表 示可能 に な り ま し た 。 例 えば 、上 記 の 例 では 「 This LinearLayout layout or its RelativeLayout parent is useless」という Warning (警告)が表示されます。そのため、開発者はここに問題 があることを認識できます。 Lint には、「正確性」「セキュリティ」「パフォーマンス」「ユーザビリティ」「アクセシビリティ」「国際化」の 6 つのカテゴリ があり、各々で様々な問題点を指摘してくれます。上記の例であった Warning は、パフォーマンスに関する Lint であり、レイアウトの構成が悪い場合の Warning は、大半がこのパフォーマンスに関する Lint です。これについて、 いくつか説明します。 LinearLayout の子ビューの中に ImageView と TextView のみを入れた場合、Lint で Warning が出ます。 これは、TextView のみで実現できるためです。その例を以下に示します。 <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="これはアイコンです" /> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 186 5-2. マルチデバイスを意識したアプリケーションにおける注意点 この例は、以下のように書き換えると Warning が出なくなります。 <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="これはアイコンです" android:drawableLeft="@drawable/ic_launcher" /> • 子 view がなくバックグラウンドも設定していない ViewGroup を置いた場合に、Lint で Warning が出ます。 これは、ViewGroup で表示するものがないためです。以下の LinearLayout のような ViewGroup は、使 用されないため、削除してください。無駄な View は、パフォーマンスに影響します。 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> • 同列に、以下のような条件で ViewGroup を記述している場合、Lint で Warning が出ます。 View がない ScrollView がない root レイアウトでない バックグラウンドを指定していない このような ViewGroup は意味のないものであり、階層を深くするだけですので、削除して直接 View を記述 します。 余分な View の削減で挙げた例が、これに相当します。 • LinearLayout 等で View の階層を深くしすぎると、パフォーマンスに大きな悪影響を与えます。そのため、デ フォルトで 10 階層以上になった場合に、Lint で Warning が出ます。この Warning が出た場合は、 RelativeLayout や GridLayout のように、フラットに配置できるレイアウトを検討してください。 • root レイアウトに FrameLayout を利用し、バックグラウンドや padding などを一切指定していない場合は、 merge タグを用いて置き換えると、わずかながら処理効率が向上します。merge タグについては後述しま す。 このように、パフォーマンスに関する Lint だけでもまだ数多くあります。また、パフォーマンス以外にも TextView の text に 直 接 文 字 列 を 入 力 し た 場 合 に Warning( 国 際 化 ) が 出 る 場 合 や 、 ImageView に contentDescription がなければ Warning(アクセシビリティ)が出る場合、Lint は様々な問題点を指摘してく れます。Lint の Warning が出ているからといって、必ず直す必要があるとは限りませんが、意図したもの以外では 可能な限り修正することが望ましいでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 187 5-2. マルチデバイスを意識したアプリケーションにおける注意点 Lint は自動的に実行されますが、手動でも実行が可能になっています。手動で実行する場合は、 プロジェクトで右クリック > Android ツール > Lint 実行:共通エラーのチェック を選択します。 図 25 lint_eclipse • レイアウトの構成を確認する Hierarchy Viewer Lint で Warning が出ていても、何が問題なのか分からないことがあります。そういった際の確認ツールとして、 Hierarchy Viewer があります。Hierarchy Viewer を使用するとと、現在のレイアウト構成を確認でき、それに よって無駄な場所を調べることができます。 Hierarchy Viewer は下記の方法で使用します。 1. エミュレータで確認したいレイアウトのアプリケーションを実行します。 2. eclipse でウィンドウ > パースペクティブを開く > [階層] ビューを開きます。 3. Windows から確認したいパッケージを選択します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 188 5-2. マルチデバイスを意識したアプリケーションにおける注意点 例えば、以下のようなレイアウトに対して、Hierarchy Viewer を使用してみます。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="これはアイコンです。" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="デフォルトアイコンを利用しています" /> </LinearLayout> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="入力行のタイトルです" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:text="入力行です" /> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:orientation="horizontal" > <Button android:layout_width="100dp" android:layout_height="wrap_content" android:text="ボタン 1" /> <Button android:layout_width="100dp" android:layout_height="wrap_content" android:text="ボタン 2" /> </LinearLayout> </FrameLayout> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 189 5-2. マルチデバイスを意識したアプリケーションにおける注意点 hierarchy_image これに対して Hierarchy Viewer を開くと、以下のようになります。 図 26 hierarchy_viewer 画面中央のツリー・ビューでは、View の詳細を確認できます。この中の View をクリックすることで、クリックした View の詳細を確認することができます。 右上のツリー概要では、画面全体の View の構成を確認することができます。これで、全体の View を把握する ことができます。 右下のレイアウトビューでは、レイアウトのイメージを確認できます。ツリー・ビューで表示されている View の、左か ら 3 列は、Android システムが自動で作成する View です。したがって、4 列目以降で不要な View 等がないこ とを確認します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 190 5-2. マルチデバイスを意識したアプリケーションにおける注意点 これを確認すると、中段下あたりの FrameLayout では、入口と出口が一つずつで不要なレイアウトだと分かる ものがあります。 図 27 hierarchy_viewer_zoom この他にも、無駄な View がいくつかあります。これらの無駄な View をいくつか削除したものが、以下のようになり ます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 191 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/icon_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toRightOf="@+id/icon" android:text="これはアイコンです。" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/icon_title" android:layout_toRightOf="@+id/icon" android:text="デフォルトアイコンを利用しています" /> <TextView android:id="@+id/input_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/icon" android:text="入力行のタイトルです" /> <EditText android:id="@+id/input" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/input_title" android:text="入力行です" /> <Button android:layout_width="100dp" android:layout_height="wrap_content" android:layout_below="@+id/input" android:layout_toLeftOf="@+id/button_2" android:text="ボタン 1" /> <Button android:id="@+id/button_2" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@+id/input" android:text="ボタン 2" /> </RelativeLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 192 5-2. マルチデバイスを意識したアプリケーションにおける注意点 図 28 hierarchy_viewer_2 無駄な View がなくなり、階層も浅くシンプルな形になったことを確認できます。 • View の再利用 一 1 アプリケーションで複数の画面を作成している際に、それぞれの画面で共通化した View を使用することがあ ります。例えば、下部に共通のメニューを作成しておくような場合です。そのような場合には、共通のメニューを別ファ イルで作成し、利用時には、include タグや merge タグで指定します。 新規、共有、設定、ヘルプというメニューを持ったビューを include で取り込んだ例を、以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 193 5-2. マルチデバイスを意識したアプリケーションにおける注意点 /res/layout/footer.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:drawableTop="@android:drawable/ic_menu_add" android:text="新規"/> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:drawableTop="@android:drawable/ic_menu_share" android:text="共有" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:drawableTop="@android:drawable/ic_menu_manage" android:text="設定" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:drawableTop="@android:drawable/ic_menu_help" android:text="ヘルプ" /> </LinearLayout> /res/layout/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:text="何らかのコンテンツ"/> <include layout="@layout/footer"/> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 194 5-2. マルチデバイスを意識したアプリケーションにおける注意点 include include タグにより、footer.xml のレイアウトが取り込まれていることが分かります。このようにすることで、各画面 で共通の View は include タグで取り込むことができます。 include タグは、android:layout_**** などのレイアウトパラメータは override できます。例えば、上記 include を下記のようにすると、footer の高さが 100dp となります。 <include android:layout_width="match_parent" android:layout_height="100dp" layout="@layout/footer"/> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 195 5-2. マルチデバイスを意識したアプリケーションにおける注意点 include2 ただし、include する View の大きさを変える場合は、layout_height と layout_width の両方の指定が必 要になります。 共通のヘッダとして追加・削除の 2 つのボタンを縦に置いている例を、以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 196 5-2. マルチデバイスを意識したアプリケーションにおける注意点 /res/layout/header.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="追加" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="削除" /> </LinearLayout> /res/layout/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <include layout="@layout/header"/> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="何らかのコンテンツ"/> </LinearLayout> include_ng Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 197 5-2. マルチデバイスを意識したアプリケーションにおける注意点 今までに説明したとおり、include タグを使用して 2 つのボタンを表示しています。しかし、この場合は vertical 指定の LinearLayout の中に、さらに vertical 指定の LinearLayout が入ることになります。同じ orientation の LinearLayout を重ねることは、階層を深くするだけであり、何のメリットもありません。そのため、取り込まれる View を LinearLayout の代わりに merge タグで記述します。 merge タグを使用した例を、以下に示します。 /res/layout/header.xml <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="追加" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="削除" /> </merge> この例を Hierarchy Viewer で確認します。merge タグを使用した場合は、取り込まれる View がそのまま merge されていることが分かります。 図 29 merge Hierarchy Viewer は、必ず PhoneWindow$DecorView と FrameLayout から始まっており、その下に 定 義 し た layout が 表 示 さ れ て い る こ と が 見 て 取 れ ま す 。 つ ま り 、 自 分 が 定 義 し た root レ イ ア ウ ト が FrameLayout だとすると、FrameLayout が 2 つ重なることになります。これも、merge タグで置き換えることが 可能です。 では、FrameLayout の説明で使用した、FrameLayout の例(P.167)を merge タグに置き換えてみましょう。 merge タグを使用した例を、以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 198 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/top"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/bottom"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/center"/> </merge> 上記を実行して Hierarchy Viewer で確認すると、FrameLayout が正しく merge されていることを確認で きます。 図 30 before_merge root レイアウトが FrameLayout の場合 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 199 5-2. マルチデバイスを意識したアプリケーションにおける注意点 図 31 after_merge root レイアウトを merge にした場合 • 必要な時に include する ViewStub View の中には、ボタンタップなど何かのアクションがあった場合にしか使用しないものもあります。例えば、データを 保存する際に、現在の状況を表示するプログレスバーなどが、これに相当します。このような View を利用する際は、 ViewStub を利用します。 ViewStub の特徴を以下に示します。 軽量な View です 値を持たない 描画されない 上記の特徴から VireStub は、画面の表示時に端末へ負担をかけることはほとんどありません。そして、必要にな ったタイミングで include し、描画します。 下記の例はボタンを押下することで、ViewStub のエリアに文字を表示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 200 5-2. マルチデバイスを意識したアプリケーションにおける注意点 /res/layout/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/show_stub" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="stub の表示" /> <ViewStub android:id="@+id/stub" android:layout_width="wrap_content" android:layout_height="wrap_content" android:inflatedId="@+id/stub_area" android:layout="@layout/stub" /> </LinearLayout> /res/layout/stub.xml <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ViewStub からの切り替え"/> </FrameLayout> MainActivity public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.view_stub); findViewById(R.id.show_stub).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ViewStub stub = (ViewStub) findViewById(R.id.stub); if (stub != null) { stub.inflate(); // こちらでも同じ意味 // stub.setVisibility(View.VISIBLE); } } }); } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 201 5-2. マルチデバイスを意識したアプリケーションにおける注意点 ViewStub は、画面の初期表示時にインフレートしませんが、その場所は指定しておきます。その際に、 android:layout でインフレートするレイアウトを指定します。プログラムからの処理でインフレートしますが、その方 法には ViewStub.inflate と ViewStub.setVisibility(View.VISIBLE)の 2 つがあります。これはいずれも同 様の命令ですが、注意点が一つあります。ViewStub の id は、一度インフレートすると android:inflatedId で 指定した値に変更されます。なぜならば、ViewStub は以下のようなイメージで置き換わるからです。 図 32 viewstub このように、ViewStub が置き換わると id が変更されるため、インフレートする前は必ず null チェックを行なってく ださい。もし ViewStub の id が null であれば、すでにインフレートされているということです。一度インフレートされた ものは、元に戻すことができないという点も注意が必要です。 • View の margin と padding 画面デザインを良く見せるパターンの一つとして、View と View の間に余白を入れる方法があります。余白を入 れる方法は、margin を入れる方法と、padding を入れる方法があります。 例えば、以下の画面のような場合です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 202 5-2. マルチデバイスを意識したアプリケーションにおける注意点 margin_default このレイアウトは、以下のようになっています。 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginTop="8dp" android:text="氏名" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginTop="8dp" android:layout_toRightOf="@+id/title" android:text="テスト太郎"/> </RelativeLayout> 上記の例では、layout_marginLeft と layout_marginTop を指定することで余白を入れています。 このレイアウトの場合、layout_marginLeft の代わりに paddingLeft を、layout_marginTop の代わりにぁ layout_paddingTop を使用したとしても見た目は変わりません。なぜならば、TextView はバックグラウンドの指 定がないためです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 203 5-2. マルチデバイスを意識したアプリケーションにおける注意点 では、バックグラウンドに色を指定した場合の、layout_margin と layout_padding を比較してみましょう。以 下の例では、TextView のバックグラウンドに赤色と緑色を設定しています。 margin margin にした場合 padding padding にした場合 このように、margin と padding では大きな違いがあります。この違いは、バックグラウンドを指定した View を配 置した場合や、ボタンを設置した場合など、パーツの境界がわかる時にはっきりと表れます。つまり、margin と padding には以下のような違いがあることがわかります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 204 5-2. マルチデバイスを意識したアプリケーションにおける注意点 図 33 そのため、View のサイズを変えたくない場合は margin を利用します。また、余白も含めて View のサイズとした い場合は padding を利用します。 View の配置位置 • RelativeLayout に子 View を配置する場合は、他の View からの相対位置になるため、比較的簡単に位置 の指定ができます。しかし、LinearLayout の場合は縦方向もしくは横方向にしか指定できません。そのため、 LinearLayout の子 View には、LinearLayout から見てどこに配置するかという指定を行います。指定を行うに は、その子 View の layout_gravity で位置を指定します。例えば、以下の例をご覧ください。この例では、 LinearLayout の中にサイズを固定した 3 つの TextView を配置して、それぞれ 左・中央・右 を指定していま す。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 205 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="200dp" android:layout_height="100dp" android:layout_gravity="left" android:background="#f00" android:text="layout_gravity=left" /> <TextView android:layout_width="200dp" android:layout_height="100dp" android:layout_gravity="center_horizontal" android:background="#0f0" android:text="layout_gravity=center_horizontal" /> <TextView android:layout_width="200dp" android:layout_height="100dp" android:layout_gravity="right" android:background="#00f" android:text="layout_gravity=right" /> </LinearLayout> layout_gravity これで TextView の配置場所が指定できました。しかし、テキストの場所を指定するにはどうするのでしょうか?こ れは、gravity で指定します。例えば、上記の例に対して gravity を追加してみる例を、以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 206 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="200dp" android:layout_height="100dp" android:layout_gravity="left" android:background="#f00" android:gravity="right" android:text="layout_gravity=left¥ngravity=right" /> <TextView android:layout_width="200dp" android:layout_height="100dp" android:layout_gravity="center_horizontal" android:background="#0f0" android:gravity="center" android:text="layout_gravity=center_horizontal¥ngravity=center" /> <TextView android:layout_width="200dp" android:layout_height="100dp" android:layout_gravity="right" android:background="#00f" android:gravity="left" android:text="layout_gravity=right¥ngravity=left" /> </LinearLayout> gravity Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 207 5-2. マルチデバイスを意識したアプリケーションにおける注意点 このように、layout_gravity は自分自身の View の位置を指定し、gravity は自分の中身の位置を指定しま す。 今回の例では、gravity で TextView を使用したテキストの位置を指定しましたが、gravity は ViewGroup でも指定することが可能です。その場合、その ViewGroup の子 View の配置位置になります。 例えば、以下の例では、LinearLayout の gravity で TextView の配置場所を center にしています。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" > <TextView android:layout_width="200dp" android:layout_height="100dp" android:background="#f00" android:text="View is center" /> </LinearLayout> linear_gravity このように、layout_margin と margin は効果も似ているため、非常に間違いやすいポイントとなります。View 自身の位置は layout_margin、子 View の場合は margin と覚えておくと分かりやすいでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 208 5-2. マルチデバイスを意識したアプリケーションにおける注意点 • LinearLayout 配置のテクニック LinearLayout の場合、gravity や layout_gravity を利用することで配置位置を指定できます。また、その 他にも View の大きさに重み付けをつけることができます。例えば、以下の例をご覧ください。 no_weight この例では、LinearLayout で横方向にボタンを 2 つ配置しています。しかし、これではバランスに違和感を覚え るため、2 つのボタンを同じ大きさで横いっぱいに配置したいとします。シングルデバイス対応であれば、端末の大き さが固定されているため、ボタンのサイズも固定すれば横いっぱいに広がります。しかし、マルチデバイス対応となると 大きさは固定されないため、サイズを固定できません。こういった場合では、layout_weight で重み付けを行なっ て、サイズを調整します。重み付けを行うと、余分なスペース分を重み付けにそって埋めてくれます。今回の例で 1:1 に重み付けを行ってみます。 まずは、間違った使い方の例を以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 209 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="button1" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="button2" /> </LinearLayout> weight_ng 一見、何も問題のないように見えますが、このレイアウトの組み方は間違っています。このことは、2 つのボタンの text を変更すると分かります。以下の画面は、ボタンの text を OK と CANCEL に変えてみた際の画面です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 210 5-2. マルチデバイスを意識したアプリケーションにおける注意点 weight_ng_ok_cancel 2 つのボタンのサイズが変わっています。なぜならば、layout_weight は空白分に対して重みづけ分を割り当て るためです。今回の例のボタンの大きさでは、元のボタンの大きさ+空白の領域÷2 ということになります。つまり、 text の大きさが違うことにより、空白の領域に差が生まれます。その結果として、全体のボタンサイズに違いが生じ ます。これに対応するためには、2 つのボタンのサイズを固定で同じサイズにします。一般的には、大きさを 0dp とし ます。なぜならば、Android では様々な画面サイズがあって、どれだけの空白があるかは環境によって異なるため、 大きさをなくすことですべてを空白にします。その結果、大きさは空白の領域÷2 となって等間隔になります。正しい レイアウト例を、以下に示します。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="CANCEL" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="OK" /> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 211 5-2. マルチデバイスを意識したアプリケーションにおける注意点 layout_weight は非常に重要なプロパティです。layput_weight を利用することで、マルチデバイスで様々な パターンのレイアウトに対応することが可能になります。例えば、以下のようなメール送信フォームを作成するとしま す。 mail_form このフォームでは、横幅は画面いっぱいまで活用しています。したがって、layout_width は match_parent と なっています。To および subject の欄の高さは 1 行分で良いため、layout_height は wrap_content として います。残りは message および送信ボタンとなります。送信ボタンは一番下に配置し、それ以外の部分は message 入力欄とするため、message の layout_height は 0dp とし、送信ボタンの layout_height は wrap_content とします。この時点では、下には空白行ができていますが、その空白行をすべて message にあて るために、message の layout_weight を 1 とします。これで縦横すべてが埋まります。あとは、ボタンの幅を 100dp にして大きさを固定し、位置も右に寄せるために layout_gravity を right とします。フォーム全体が左右 にくっついていると見難いフォームとなるため、親の LinearLayout の paddingLeft および paddingRight に 16dp のスペースをいれます。 そのようにしてできたレイアウトを、以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 212 5-2. マルチデバイスを意識したアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fff" android:orientation="vertical" android:paddingLeft="16dp" android:paddingRight="16dp" > <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="to" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="subject" /> <EditText android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:gravity="top" android:hint="message" /> <Button android:layout_width="100dp" android:layout_height="wrap_content" android:layout_gravity="right" android:text="送信" /> </LinearLayout> layout_weight の用途は他にもあります。例えば、以下の例をご覧ください。 図 34 2pain_width_fix この例では、左のリストはサイズを固定して、右側の詳細を最大まで大きくしています。しかし、これをタブレットで 見るとどうなるでしょうか?同じレイアウトを 7 インチタブレットで表示した例を、以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 213 5-2. マルチデバイスを意識したアプリケーションにおける注意点 図 35 2pain_width_fix_tab リストの部分が小さく、詳細の部分が大きくなりすぎています。その結果、見栄えが悪く感じられます。そういった場 合には、layout_weight で割合を決めて、画面サイズが違っても共通した見栄えになるようにします。割合を 2:3 にしてみた例を、以下に示します。 図 36 2pain_width_weight 携帯サイズ Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 214 5-2. マルチデバイスを意識したアプリケーションにおける注意点 図 37 2pain_width_weight_tab タブレットサイズ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <fragment android:id="@+id/titles" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" class="com.example.androiddeal02.FragmentLayout$TitlesFragment" /> <FrameLayout android:id="@+id/details" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="3" /> </LinearLayout> このように layout_weight を利用すると、View を表示する割合を決めることができるため、様々な画面サイズ に対応することができます。 しかし、layout_weight は View を描画する際に計算を行います。そのため、layout_weight は多用すべき ではありません。特に、layout_weight で計算した LinearLayout の中で、再び layout_weight を使用する 場合(layout_weight のネストをする場合)は、二重に計算を行うため描画スピードが遅くなります。そういった場 合は、RelativeLayout などを使用することを検討してください。それ以外の場合でも、sw<N>dp などのリソース 修飾子を用いることで画面サイズごとにレイアウトの変更ができます。 そのため、RelativeLayout やリソース修飾子などの方法でマルチデバイス対応をする方が、パフォーマンスに与 えるメリットは大きくなります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 215 5-2. マルチデバイスを意識したアプリケーションにおける注意点 • Fragment による切り替え 以前の章で、Fragment を用いると、縦画面ではリストのみ、横画面ではリストと詳細を表示するといったレイア ウトが簡単に実装できることを説明しました。ここでは、具体的な例を用いて解説します。 以前にも説明したとおり、レイアウトは縦画面と横画面で分ける必要があるため、 /res/layout/fragment_layout.xml および/res/layout-land/fragment_layout.xml を作成します。 以下の例は、API デモから引用してきたものです。 /res/layout/fragment_layout.xml <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment" android:id="@+id/titles" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> /res/layout-land/fragment_layout.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment" android:id="@+id/titles" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" /> <FrameLayout android:id="@+id/details" android:layout_width="0px" android:layout_height="match_parent" android:layout_weight="1" android:background="?android:attr/detailsElementBackground" /> </LinearLayout> 通常は fragment タグのみとし、横画面では fragment と詳細表示用の FrameLayout となります。これを 実行するメインの Activity では、onCreate メソッド内 setContentView(R.layout.fragment_layout);と してこのレイアウトを呼び出すだけです。Fragment の呼び出し以外の処理は、基本的にすべて Fragment が持 つようにします。そうすることで、Activity と Fragment が疎結合となり、Fragment の再利用が容易になります。 setContentView でこのレイアウトが呼び出された時、fragment タグにより、リストの部分は FragmentLayout の中にある TitlesFragment が実行されます。Fragment のライフサイクルにより、今回の 例では onActivityCreated がはじめに実行されるメソッドとなります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 216 5-2. マルチデバイスを意識したアプリケーションにおける注意点 @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // タイトル配列からリストの設定 setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES)); // 詳細フラグメントが直接レイアウト上にあるかチェック View detailsFrame = getActivity().findViewById(R.id.details); mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE; if (savedInstanceState != null) { // 現在の選択値を取得 mCurCheckPosition = savedInstanceState.getInt("curChoice", 0); } if (mDualPane) { // 2ペインモードの場合はリストビューで選択された値を表示 getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); // 2ペインモードの場合は詳細ページを表示 showDetails(mCurCheckPosition); } } TitlesFragment は 、 ListFragment を 継 承 し た Fragment と な っ て い ま す 。 ListFragment は 、 ListActivity と同じようにリスト形式の Fragment です。そのため、ListActivity と同じように setListAdapter でリストに表示するタイトルを、配列から Adapter にセットしています。これだけならば、通常の Activity と変わりあ りません。 重要なのは、その後の mDualPane です。これは、Fragment 上で表示するのがリストだけなのか、あるいは詳 細も必要なのかを判断するフラグです。このフラグが true の場合は、詳細ページを表示するための showDetails メソッドを呼び出しています。それでは、showDetails メソッドを確認してみましょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 217 5-2. マルチデバイスを意識したアプリケーションにおける注意点 void showDetails(int index) { mCurCheckPosition = index; if (mDualPane) { // 2 ペインモードの場合はリストビューで指定された値を選択済みにする getListView().setItemChecked(index, true); // Fragment がすでに表示されているかチェックして必要があれば詳細 Fragment を表示する DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.details); if (details == null || details.getShownIndex() != index) { // 選択された詳細 Fragment を作成する details = DetailsFragment.newInstance(index); // 詳細フラグメントにする FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.details, details); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.commit(); } } else { // 2 ペインモードでない場合は新しい Activity を開始する Intent intent = new Intent(); intent.setClass(getActivity(), DetailsActivity.class); intent.putExtra("index", index); startActivity(intent); } } 詳細ページを表示するのは、初期状態の場合とリストから選択された場合の 2 種類があります。そのため showDetails では、リストから選択された時に 2 ペインモードかどうかを考慮する必要があります。2 ペインモードで あり、かつ、詳細 Fragment がまだ表示されていなければ、FrameLayout を詳細 Fragment に切り替えます。 2 ペインモードでない場合は、詳細表示 Activity を呼び出しています。 詳細表示 Fragment は、以下のようになっています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 218 5-2. マルチデバイスを意識したアプリケーションにおける注意点 public static class DetailsFragment extends Fragment { // index のテキストを保持しながら新しい DetailsFragment のインスタンスを生成する public static DetailsFragment newInstance(int index) { DetailsFragment f = new DetailsFragment(); // index の値を保持 Bundle args = new Bundle(); args.putInt("index", index); f.setArguments(args); return f; } public int getShownIndex() { return getArguments().getInt("index", 0); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (container == null) { // container がないということは View では不要なので null を返す return null; } ScrollView scroller = new ScrollView(getActivity()); TextView text = new TextView(getActivity()); int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getActivity().getResources().getDisplayMetrics()); text.setPadding(padding, padding, padding, padding); scroller.addView(text); text.setText(Shakespeare.DIALOGUE[getShownIndex()]); return scroller; } } 詳細 Fragment は、newInstance で値を渡してインスタンスを生成しています。Java では通常、このような初 期処理を行う場合は、コンストラクタで引数を渡して初期化することが多いでしょう。しかし、Fragment の場合は、 コンストラクタで引数の初期化をしてはいけません。Fragment も Activity と同様に、メモリがなくなるとが破棄され るからです。その後、Fragment が必要になった際、再生成されますが、その場合に Fragment は引数がないコ ンストラクタによって呼び出されます。そのため、引数付きのコンストラクタで初期化を行なっていると、再生成の際に 引数が渡せなくなります。また、こういった理由により、Fragment には空のコンストラクタを用意しなければいけませ ん。 詳細 Fragment は、ライフサイクルにより、onCreateView から実行されます。onCreateView は、View を 返すメソッドですが、親 ViewGroup が null の場合は View を表示することはありませんので、null を返していま す。それ以外の場合では、ScrollView の中の TextView に、詳細表示したものを返しています。ここで返す View が、詳細で表示される View になります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 219 5-2. マルチデバイスを意識したアプリケーションにおける注意点 縦画面で表示する場合の Activity は、以下のようになっています。 public static class DetailsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getResources().getConfiguration().orientation== Configuration.ORIENTATION_LANDSCAPE) { // 横画面の時にはこの Activity は必要ないので終了させます。 finish(); return; } if (savedInstanceState == null) { // 詳細 Fragment が現時点で存在しない場合は詳細 Fragment を表示する。 DetailsFragment details = new DetailsFragment(); details.setArguments(getIntent().getExtras()); getFragmentManager().beginTransaction().add(android.R.id.content, details).commit(); } } 詳細表示 Activity は、縦画面の場合のみ使用する Activity です。したがって、横画面で呼び出された時は終 了 す る よ う に し て い ま す 。 ま た 、 savedInstanceState の null チ ェ ッ ク を 行 な っ て お り 、 こ れ は savedInstanceState が null になるということは、新規で呼び出された場合、つまり Activity がメモリ不足から 復帰した場合ではないということになります。その際は、詳細フラグメントを表示するようにしています。 Fragment を使用したアプリケーションは、このようにして Fragment 内で縦画面の場合と横画面の場合で、表 示する値を設定しています。これにより、一つのアプリケーションで縦画面と横画面のレイアウトが違う場合でも、複 雑な処理をせずに行えるようになっています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 220 5-3. 最適化されたアプリケーションにおける注意点 5-3. 最適化されたアプリケーションにおける注意点 Android アプリケーション開発においては、マルチデバイス対応とあわせて、アプリケーションの容量に注意を払う 必要があります。なぜならば、モバイルデバイスは内部ストレージの容量も限られているからです。もちろん、これは 必要なものを作成しないという意味ではありません。必要なものは作成しながらも、小さなサイズで高品質な素材 を作成できるならば、アプリケーションの安定性に大きく貢献するでしょう。 本章では、そのようなテクニックについて解説します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 221 5-3. 最適化されたアプリケーションにおける注意点 5-3-1. XML で作成すべき図形と画像で作成すべき図形 Android では、四角・楕円・線・円は XML を定義するだけで描くことができます。これを応用すると、様々なもの を作成することができます。例えば、ボタンなどでよく使われる角丸四角形などは、XML を定義するだけ描くことが可 能です。その結果、角丸四角形の画像の準備をする必要がなく、アプリケーションサイズを減らすことにもつながりま す。また、色を変更するなどの修正が必要な場合においても、画像で作成していれば再作成が必要になりますが、 XML で作成すると定義した色を変更するだけで対応が可能になります。このように、XML で定義することは様々 なメリットがあります。 また、XML で作成する図形で定義できるものは、形や色だけではありません。たとえば、背景色にグラデーションを 入れることができます。そのため、これらの図形に多少のデザインを加えることも可能になっています。以下の例は、 すべて XML のみで指定した図形です。 図 38 このように、XML だけで様々な形の図形が作成できます。しかし、すべてを XML で定義できるわけではありません。 例えば、2 重の枠線を持った図形は XML で作ることができません。そのようなデザインにする場合は、画像にする必 要があります。 ただし、このような画像を作成する場合でも普通に画像を用意するわけではありません。9-patch ツールを使用 して、画像を拡大した時にも綺麗に表示されるようにします。9-patch ツールを用いると、画像サイズは小さくなりま す。さらに、角丸などを用いたものを拡大しても表示は綺麗なままです。そのため、マルチデバイス対応の Android アプリケーションのデザインをする上で 9-patch ツールを用いることは必須であり、その結果、リソースを節約しつつも 高品質な画像素材を作成することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 222 5-3. 最適化されたアプリケーションにおける注意点 5-3-2. XML の作成と Draw 9-patch XML で図形を定義する場合、その XML は/res/drawable/配下に置きます。これにより、レイアウトからは @drawable/(ファイル名)で指定することができます。指定するコーディング例を、以下に示します。 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/title_bg" android:layout_margin="10dp" android:textColor="#000" android:text="何らかのタイトル"/> </RelativeLayout> drawable_sample_title このレイアウトは 1 行の TextView ですが、backgroud には@drawable/title_bg と指定しています。これは、 ImageView の src で指定する場合と同じく、drawable のリソースを参照するということです。つまり、この例だと /res/drawable/title_bg.xml を参照します。以下は、/res/drawable/title_bg.xml です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 223 5-3. 最適化されたアプリケーションにおける注意点 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <!-- 塗りつぶし --> <solid android:color="#ddd" /> <!-- 枠線をつける --> <stroke android:width="1dp" android:color="#666" /> <!-- 上下左右にスペース --> <padding android:bottom="5dp" android:left="10dp" android:right="10dp" android:top="5dp" /> <!-- 丸角 --> <corners android:radius="10dp" /> </shape> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 224 5-3. 最適化されたアプリケーションにおける注意点 XML で図形を定義する際には、shape タグを用います。Shape 内に定義できるタグは、以下のとおりです。 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape=["rectangle" | "oval" | "line" | "ring"] > <corners android:radius="integer" android:topLeftRadius="integer" android:topRightRadius="integer" android:bottomLeftRadius="integer" android:bottomRightRadius="integer" /> <gradient android:angle="integer" android:centerX="integer" android:centerY="integer" android:centerColor="integer" android:endColor="color" android:gradientRadius="integer" android:startColor="color" android:type=["linear" | "radial" | "sweep"] android:useLevel=["true" | "false"] /> <padding android:left="integer" android:top="integer" android:right="integer" android:bottom="integer" /> <size android:width="integer" android:height="integer" /> <solid android:color="color" /> <stroke android:width="integer" android:color="color" android:dashWidth="integer" android:dashGap="integer" /> </shape> • shape タグ 図形を描画するためのタグです。 • xmlns:android XML ネームスペースの定義です。 http://schemas.android.com/apk/res/android にします。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 225 5-3. 最適化されたアプリケーションにおける注意点 • android:shape 描画する図形の種類を指定します。下記の 4 種類があります。 種類 説明 rectangle 長方形を描画します。未指定の場合のデフォルト値です。 oval 楕円形を描画します。 line 水平線を描画します。線の幅の指定として stroke タグが必要です。 ring 同心円を描画します。 android:shape に ring を指定した場合はさらに下記の属性も設定ができます。 • android:innerRadius 同心円の内側にある円の半径を指定します。 • android:innerRadiusRatio 同心円の内側にある円の半径を、幅に対する割合で指定します。値は float 指定です。 上記の android:innerRadius が指定されている時は android:innerRadius が優先され ます。デフォルト値は 9 です • android:thickness 同心円の厚みを指定します。 • android:thicknessRatio 同心円の厚みを、幅に対する割合で指定します。値は float 指定です。 上記の android:thickness が指定されている時は、android:thickness が優先されます。 デフォルト値は 3 です。 • android:useLevel boolean 値を指定します。true の場合、LevelListDrawable として扱われます。 false の場合は、LevelListDrawable として扱われません。 デ フ ォ ル ト で は true に な る の で 注 意 が 必 要 で す 。 注 意 す る 必 要 が あ る 理 由 は 、 LevelListDrawable として扱われることにより、画像が表示されない可能性があるためです。 LevelListDrawable については後述します。 • corners タグ android:shape が rectangle(長方形)の場合のみ使用します。長方形の角に丸みをつ けます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 226 5-3. 最適化されたアプリケーションにおける注意点 • android:radius すべての角の半径を指定します。この値は、以下のプロパティによって上書きされます。 • android:topLeftRadius 左上の角の半径を指定します。 • android:topRightRadius 右上の角の半径を指定します。 • android:bottomLeftRadius 左下の角の半径を指定します。 • android:bottomRightRadius 右下の角の半径を指定します。 • gradient タグ 図形のグラデーションを指定します。 • android:angle グラデーションの角度を度数で指定します。 0 を指定した場合は、左から右。90 を指定した場合は、下から上のグラデーションです。 この値は 45 の倍数で指定する必要があります。デフォルトは 0 です。 • android:centerX グラデーションの中心に対する相対的な X 座標の位置です。0〜1.0 の float 値で指定しま す。 • android:centerY グラデーションの中心に対する相対的な Y 座標の位置です。0〜1.0 の float 値で指定しま す。 • android:centerColor 開始と終了の間にくる色を、16 進数で指定します。 • android:endColor 終了の色を 16 進数で指定します。 • android:gradientRadius グラデーションの半径を float 値で指定します。 後述の android:type=”radial”の時のみ適用されます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 227 5-3. 最適化されたアプリケーションにおける注意点 • android:startColor 開始の色を 16 進数で指定します。 • android:type グラデーションの種類です。以下のものがあります。 • 種類 説明 linear 線形のグラデーションです。デフォルト値です。 radial 放射状のグラデーションです。開始位置はセンターカラーです。 sweep 弧を描くようなグラデーションです。 android:useLevel boolean 値を指定します。true の場合は LevelListDrawable として扱われます。 LevelListDrawable は後述します。 • padding タグ 含んでいる View に対して適用する padding を指定します。図形に対する padding ではな いことに注意してください。 • android:left 左の padding サイズを指定します。 • android:top 上の padding サイズを指定します。 • android:right 右の padding サイズを指定します。 • android:bottom 下の padding サイズを指定します。 • size タグ 図形のサイズを指定します。 • android:height 図形の高さを指定します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 228 5-3. 最適化されたアプリケーションにおける注意点 • android:width 図形の幅を指定します。 デフォルトでは、指定した幅に比例して View のコンテナのサイズがスケールされることに注意し てください。ImageView でこの図形を使用する時は、android:scaleType を center にす ることで幅がスケールされることを制限できます。 • solid タグ 図形を塗りつぶすために利用します。 • android:color 適用させる色を、色コードで指定します。 • stroke タグ 図形の枠線の指定です。 • android:width 線の厚みを指定します。 • android:color 線の色を、色コードで指定します。 • android:dashGap 点線の間隔を指定します。android:dashWidth がセットされている場合のみ有効です。 • android:dashWidth 点線の長さを指定します。Android:dashGap がセットされている場合のみ有効です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 229 5-3. 最適化されたアプリケーションにおける注意点 上記の例で上げた xml で作成できる図形は、以下のように指定しています。 1 段目:rectangle 左 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <stroke android:dashGap="5dp" android:dashWidth="8dp" android:width="2dp" android:color="#000" /> <solid android:color="#aaa" /> </shape> 中央 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <corners android:radius="20dp" /> <gradient android:angle="90" android:centerColor="#888" android:endColor="#ccc" android:startColor="#ccc" /> </shape> 右 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <corners android:bottomRightRadius="0dp" android:radius="20dp" android:topLeftRadius="0dp" /> <gradient android:centerX="0.5" android:centerY="0.5" android:endColor="#666" android:gradientRadius="50" android:startColor="#ccc" android:type="radial" /> </shape> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 230 5-3. 最適化されたアプリケーションにおける注意点 2 段目:oval 左 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" > <stroke android:width="5dp" android:color="#000" /> <solid android:color="#aaa" /> </shape> 中央 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" > <gradient android:centerX="0.5" android:centerY="0.5" android:endColor="#444" android:gradientRadius="50" android:startColor="#ddd" android:type="sweep" /> </shape> 右 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" > <gradient android:centerX="0.3" android:centerY="0.3" android:endColor="#666" android:gradientRadius="80" android:startColor="#ddd" android:type="radial" /> </shape> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 231 5-3. 最適化されたアプリケーションにおける注意点 3 段目:line 左 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line" > <stroke android:width="5dp" android:color="#000" /> </shape> 右 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line" > <stroke android:dashGap="2dp" android:dashWidth="5dp" android:width="1dp" android:color="#000" /> </shape> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 232 5-3. 最適化されたアプリケーションにおける注意点 4 段目:ring 左 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="ring" android:useLevel="false" > <stroke android:width="1dp" android:color="#000" /> <solid android:color="#aaa" /> </shape> 中央 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:innerRadius="10dp" android:shape="ring" android:thickness="25dp" android:useLevel="false" > <stroke android:dashGap="5dp" android:dashWidth="10dp" android:width="1dp" android:color="#000" /> <solid android:color="#aaa" /> </shape> 右 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:innerRadius="15dp" android:shape="ring" android:thicknessRatio="4" android:useLevel="false" > <stroke android:dashGap="3dp" android:dashWidth="5dp" android:width="2dp" android:color="#000" /> <gradient android:centerX="0.5" android:centerY="0.5" android:endColor="#444" android:gradientRadius="50" android:startColor="#ddd" android:type="radial" /> </shape> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 233 5-3. 最適化されたアプリケーションにおける注意点 drawable は、このような図形を記述できるだけではありません。他にも様々なものが作成できます。例えば、画 像を重ね合わせて表示することができます。通常、そのような場合には、それぞれを重ね合わせたひとつの画像を別 途作成します。しかし、その場合だと素材の再利用ができず、画像の数も増えることになります。そのため、各素材 をそれぞれ作成し、重ねあわせて表示することで、再利用が可能になりアプリケーションのサイズも減らすことができ ます。 下記の例はアイコンの画像を重ね合わせたものです。 <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item> <bitmap android:gravity="left|top" android:src="@drawable/ic_launcher" /> </item> <item android:left="10dp" android:top="10dp"> <bitmap android:gravity="left|top" android:src="@drawable/ic_launcher" /> </item> <item android:left="20dp" android:top="20dp"> <bitmap android:gravity="left|top" android:src="@drawable/ic_launcher" /> </item> </layer-list> layer 上記の例では同じ画像を重ねていますが、これを応用すると色々なことができます。例えば、本棚のイメージを作 る際に、本棚の枠だけのイメージ、本棚の奥行きのイメージ、本のイメージを用意します。それらを重ね合わせること により、本棚の画像を作成することができます。これらのイメージに、後述する 9-patch を当てると、通常のイメージ 画像よりもはるかに小さな画像ができます。このように、色々なイメージを重ねることによって、小さいサイズで綺麗な 画像を作成することが可能であり、マルチデバイスにも対応することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 234 5-3. 最適化されたアプリケーションにおける注意点 さらに、xml を指定するだけで、状態による画像の変更が可能です。例えば、バッテリの残量によって画像を変え ることができます。これを実現するために、内部では LevelListDrawable を用いています。以下の例では、ボタン を押下した時に xml で定義した line を変更するようにしています。 /res/drawable/level.xml <?xml version="1.0" encoding="utf-8"?> <level-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/level_line1" android:maxLevel="0" /> <item android:drawable="@drawable/level_line2" android:maxLevel="50" /> <item android:drawable="@drawable/level_line3" android:maxLevel="100" /> </level-list> /res/drawable/level_line1.xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line" > <stroke android:dashGap="10dp" android:dashWidth="5dp" android:width="1dp" android:color="#000" /> </shape> /res/drawable/level_line2.xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line" > <stroke android:dashGap="2dp" android:dashWidth="5dp" android:width="1dp" android:color="#000" /> </shape> /res/drawable/level_line3.xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line" > <stroke android:width="1dp" android:color="#000" /> </shape> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 235 5-3. 最適化されたアプリケーションにおける注意点 /res/layout/level.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/low" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_margin="2dp" android:text="low level" /> <Button android:id="@+id/middle" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_margin="2dp" android:text="middle level" /> <Button android:id="@+id/high" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_margin="2dp" android:text="high level" /> </LinearLayout> <ImageView android:id="@+id/level_line" android:layout_width="match_parent" android:layout_height="50dp" android:src="@drawable/level" /> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 236 5-3. 最適化されたアプリケーションにおける注意点 MainActivity.java public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.level); final ImageView levelLine = (ImageView) findViewById(R.id.level_line); levelLine.setImageLevel(0); findViewById(R.id.low).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { levelLine.setImageLevel(0); } }); findViewById(R.id.middle).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { levelLine.setImageLevel(50); } }); findViewById(R.id.high).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { levelLine.setImageLevel(100); } }); } } 初期表示および low level ボタン押下時 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 237 5-3. 最適化されたアプリケーションにおける注意点 middle level ボタン押下時 high level ボタン押下時 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 238 5-3. 最適化されたアプリケーションにおける注意点 上記の例では、ボタンでレベルを変更しています。バッテリの状態によって画像を変更する場合は、そのバッテリの 状態をそのまま setImageLevel すると、自動で画像が差し替わるため便利です。 状態による変更は、LevelListDrawable だけではありません。上記の例では、レベルによって画像を変更するよ うにしました。そのほかにも、状態によって一つの画像を非表示状態から完全に表示される状態まで、分けて表示 することもできます。これを使用すると、何らかのデータロード時に、画像による表示で分かりやすく表現することがで きます。徐々に画像を表示するような例を、以下に示します。 /res/drawable/clip.xml <?xml version="1.0" encoding="utf-8"?> <clip xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_launcher" android:clipOrientation="horizontal" android:gravity="left" /> /res/layout/clip.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/start" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="start" /> <Button android:id="@+id/stop" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="stop" /> </LinearLayout> <ImageView android:id="@+id/clip_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/clip" /> </LinearLayout> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 239 5-3. 最適化されたアプリケーションにおける注意点 MainActivity.java public class MainActivity extends Activity { Timer mTimer; int mLapTime = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.clip); final ImageView clipImage = (ImageView) findViewById(R.id.clip_image); findViewById(R.id.start).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mLapTime = 0; mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { mLapTime += 100; runOnUiThread(new Runnable() { @Override public void run() { clipImage.setImageLevel(mLapTime); } }); } }, 0, 100); } }); findViewById(R.id.stop).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mTimer != null) { mTimer.cancel(); mTimer = null; } } }); } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 240 5-3. 最適化されたアプリケーションにおける注意点 clip ボタンに関する画面デザインを行う際、多くの場合は shape タグを用いて見栄えのよいボタンにすることでしょう。 しかし、普通の shape タグだけであれば、ボタンを押下した時に、押下したとわかるよう表示することはできません。 押下したことが分かるようにするためには、ボタン押下時にバックグラウンドの色を変更したりします。その機能も、 drawable で定義することができます。shape タグで作成した OK ボタンを、通常/フォーカスが当たった時/押さ れた時、に分けて表示する例を以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 241 5-3. 最適化されたアプリケーションにおける注意点 /res/drawable/button_selector.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- フォーカスがある時 --> <item android:state_focused="true"> <shape android:shape="rectangle"> <!-- 塗りつぶし --> <solid android:color="#888" /> <!-- 枠線をつける --> <stroke android:width="2dp" android:color="#ff3" /> <!-- 上下左右にスペース --> <padding android:bottom="10dp" android:left="20dp" android:top="10dp" /> <!-- 丸角 --> <corners android:radius="10dp" /> </shape> </item> <!-- 押された時 --> <item android:state_pressed="true"> <shape android:shape="rectangle"> <!-- 塗りつぶし --> <solid android:color="#ff9" /> <!-- 枠線をつける --> <stroke android:width="2dp" android:color="#ff3" /> <!-- 上下左右にスペース --> <padding android:bottom="10dp" android:left="20dp" android:top="10dp" /> <!-- 丸角 --> <corners android:radius="10dp" /> </shape> </item> <!-- 上記以外の状態 --> <item> <shape android:shape="rectangle"> <!-- 塗りつぶし --> <solid android:color="#888" /> <!-- 枠線をつける --> <stroke android:width="2dp" android:color="#666" /> <!-- 上下左右にスペース --> <padding android:bottom="10dp" android:left="20dp" android:top="10dp" /> <!-- 丸角 --> <corners android:radius="10dp" /> </shape> </item> </selector> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. android:right="20dp" android:right="20dp" android:right="20dp" 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 242 5-3. 最適化されたアプリケーションにおける注意点 /res/layout/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="100dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="@drawable/button_selector" android:text="OK" /> </RelativeLayout> ボタン押下時など、押下したとわかるよう表示する処理は、上記のように xml で定義します。プログラムで押下し たことを受け取って変更することも可能ですが、非常に複雑なプログラムになり、その後のメンテナンスも困難になる ため、実装することは望ましくありません。 xml で指定する drawable について主要なものを説明しましたが、他にも以下のようなものがあります。 • XMLBitmap XML で定義された Bitmap ファイルで、定義元のファイルのエイリアスの効果もあります。 また、それ以外に追加でディザリングやタイル加工を行うこともできます。 • XML Nine-Patch XML で定義された 9-patch があたったファイルです。 XML では、イメージに対してディザリングの指定をすることもできます。 • TransactionDrawable 2 つの Drawable を、クロスフェードで切り替えることができるようにします。指定は2つしかできません。 • InsetDrawable 特定の距離で他の Drawable を差し込むことができます。 これは View が、View の実際の境界よりも小さい背景を必要とする場合に使用します。 • ScaleDrawable Drawable をスケールすることができます。 このように、Drawable を XML に用いて作成することは、マルチデバイス対応やアプリケーション容量の削減につ ながります。しかし、すべてがこの XML で対応できるわけではありません。XML で対応できない場合には、画像を 作成することで対応します。画像を作成する場合、レイアウトと同じくスケールされるという点に注意する必要があり ます。画像を拡大すると通常、丸角などは見た目が悪くなります。しかし、Android ではそういった場合に対応する ため、9-patch ツールと呼ばれるものが提供されています。これは、画像を分割してスケールしても良い場所とスケ ールしてはいけない場所を指定します。その結果、画像もかなり小さなサイズになり、容量の圧迫も防ぎます。例え ば、以下の様な画像をご覧ください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 243 5-3. 最適化されたアプリケーションにおける注意点 sample_image この画像をそのまま利用すると、拡大した時に角丸などがぼやけます。そのため、これに対して 9-patch を使いま す。9-patch ツールは、AndroidSDK の tools の下の draw9patch ですが、Android SDK Tools22 以降 では Eclipse から直接起動できるようになりました。画像を右クリックで次で開く(H) > その他...を選択し、 Android Draw 9-patch Editor を開きます。draw9patch を直接起動、また Eclipse から起動しても、基本 操作は同じです。 起動すると、以下の様な画面が表示されます。 draw-9patch 画面中心部分には、対象の画像が表示され、右には左右に伸びたイメージ、上下に伸びたイメージ、上下左右 に伸びたイメージの3つが表示されています。 9-patch ツールの使い方はそれほど難しくはありません。基本は下記の 2 つです。 1. 上と左の 1px に黒く塗って、伸縮してもよい部分を指定します。 2. 下と右の 1px に黒く塗って、コンテンツ部分を指定します。 これらの調整をしやすくするために、下のコントローラ部を使用します。 コントローラ部の左上にある 100%〜800%は、元画像の拡大・縮小をします。その下の 2x〜6x は、右側のイ メージのサイズです。2x であれば 2 倍に伸ばしたイメージ、6x であれば 6 倍に伸ばしたイメージを表示します。 Show Lock のチェックボックスにチェックが入っている場合、黒く塗れない部分にカーソルがあれば知らせてくれます。 Show Lock の下、show Bad patches にチェックを入れている場合は、均等に伸ばせない部分が黒く塗られて いる場合、それが分かるように表示してくれます。右上の show Patches にチェックを入れると、拡大する部分の領 域に色をつけてくれます。コンテンツの表示にチェックを入れると、右側のプレビュー画面でコンテンツ領域がどこかを 教えてくれます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 244 5-3. 最適化されたアプリケーションにおける注意点 これらを使って 9-patch をあてたものは、次のようになります。 sample_image.9. 9patch で領域を指定したファイルは、ファイル名.9.png という名前で保存します。これだけで、Android のシス テムは自動的に指定部分を伸ばしてくれます。リソース名は、通常の画像と同じくファイル名になります。したがって、 ファイル名.9.png とファイル名.png は共存できません。 下記は 9-patch をあてた画像とあてていない画像です。 9patch 画像の表示が、明らかに異なっていることが分かります。しかし、この画像が正しい訳ではありません。なぜならば、 この画像はあきらかに横に長過ぎるので、サイズが大きくなっています。同じ内容の部分は、拡大することができるこ とを考えると、ここまで長過ぎるものは必要ありません。伸ばす領域も、どこを伸ばすかということなので、広く取る必 要はありません。同じ部分であれば、1px だけで十分です。これを修正して 9-patch をあてたものが、以下の画像 です。 sample_image_min.9 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 245 5-3. 最適化されたアプリケーションにおける注意点 同じ部分であれば 1px だけで十分であると述べました。では、2px 以上ある場合はどうなるのかと疑問を持つか もしれません。2px 以上ある場合は、それぞれに等分に割り当てられます。つまり、以下のような形になります。 図 39 領域の指定は、連続する必要がありません。つまり、離して指定しても同じように動作します。離して指定した場 合も、上記と同様に、同じ割合で広がります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 246 5-3. 最適化されたアプリケーションにおける注意点 等間隔に広がる 図 40 これを流用するといろいろな形ができます。例えば、以下の画像をベースに 3 つのパターンを作成してみましょう。 基本の画像 星の上と左に 1px 指定 星の上下と左に 1px 指定 星の左に 7px、右に 1px、上に 3px、下に 1px 指定 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 247 5-3. 最適化されたアプリケーションにおける注意点 これを適切な大きさに伸ばすようなレイアウトで実行すると、以下のようになります。 9patch_result このように、9-patch の伸ばす箇所の指定によって、表示結果の見た目が変わります。ただし、9-patch が苦手 とするデザインもあります。代表的なものは以下の3つです。 • ストライプ strape Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 248 5-3. 最適化されたアプリケーションにおける注意点 • グラデーション gradient • 連続した絵柄 polka_dots Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 249 5-3. 最適化されたアプリケーションにおける注意点 • デザイナーに依頼するためのポイント Android アプリケーションの開発では、画面デザインも重要なポイントの一つです。そのため、画面デザインはデザ イナーに任せる場面も多いでしょう。しかし、何の指示もなく画面デザインを発注するなら、xml での作成が難しい デザインになることもあります。そうならないためにも、デザイナーに依頼する際は、以下のことに注意して作成しても らうようにしましょう。 1. レイアウトは拡大される可能性がある Android 端末は様々な解像度があるため、フレキシブルなデザインにしてもらう必要があります。 必ず、画面固定ではなく、拡大されても問題ないデザインを作成してもらってください。 2. 極端に画面サイズが変わる場合に対応する Android は、携帯サイズ/7 インチタブレットサイズ/10 インチタブレットサイズなど、極端に画面 サイズが変わることがあります。それぞれに対して、同じデザインでいいのか、別のデザインにするほう が良いのかを、十分に検討してもらってください。画面サイズごとにデザインを分ける場合は、それぞ れの画面サイズでデザインしてもらってください。 3. 苦手なパターンの素材は使わない これまでに説明したとおり、画像ではストライプやグラデーション等、Android が苦手とするデザイ ンのポイントがあります。これらのデザインを避けるようにして、できるだけシンプルなデザインを心がけ るようにお願いしてください。 4. 切り出し素材に注意する Android では、イメージを拡大させることが基本になります。そのため、イメージの拡大を考慮して、 素材を切り出してもらってください。単純な角丸ボタンなど、xml で作成できそうなものは、色やグラ デーションの情報をもらって xml で作成しましょう。HTML と同じように文字が入った固定のボタンな どは、決して切り出さないように依頼してください。もし仮に、フォントの関係などでどうしても必要な場 合は、透過 png で文字だけの画像などをもらいましょう。 Android では、フォントの変更もできます。もし、フォントがもらえる場合は、そのフォントをもらって ください。ボタン以外の場合でも、9-patch をあてることで綺麗になるような素材であれば、その最 小限の大きさの素材をもらってください。 5. デザインガイドラインを読んでもらう Android は、デザインガイドラインがあります。 http://developer.android.com/design/index.html ここに、ボタンの理想なサイズなどの情報が色々と書いてあります。デザイナーには、必ずガイドライン を読んでもらい、それにそったデザインにしてもらってください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 250 5-3. 最適化されたアプリケーションにおける注意点 5-3-3. データの共有による容量の最適化 Android では、各アプリケーションをインストールする時に独自の一意なユーザ ID を与えることで、他のアプリケー ションからアクセスできないようになっています。そのため、通常はアプリケーションから他のアプリケーションへのアクセス はできません。しかし、sharedUserId を用いると、異なるアプリケーション間で同じユーザ ID を設定することができ ます。その結果、2 つのアプリケーションでリソースやデータベースが共有でき、アプリケーションサイズの削減につながり ます。 sharedUserId を利用する場合は、2 つのアプリケーションの AndroidManefest.xml の android:sharedUserId を共通にします。その際、必ずピリオドを 1 つ以上入れる必要があります。他の人のア プリケーションと同じにならないように、パッケージ名などを利用すべきです。 下記に sharedUserId の例を挙げます。これは、アプリケーション A(com.example.app1)からアプリケーショ ン B(com.example.app2) の/res/drawable/sample.png を読み込んで表示しています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 251 5-3. 最適化されたアプリケーションにおける注意点 アプリケーション A(画像を読み込む側) <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.app1" android:sharedUserId="com.example.user" android:versionCode="1" android:versionName="1.0" > 〜省略〜 </manifest> /res/layout/activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/app2_image" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> Mainactivity.java public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView image = (ImageView) findViewById(R.id.app2_image); try { Context context = this.createPackageContext("com.example.app2", Context.CONTEXT_RESTRICTED); Resources res = context.getResources(); image.setImageDrawable(res.getDrawable(res.getIdentifier("sample", "drawable", context.getPackageName()))); } catch (NameNotFoundException e) { // 対象のプロジェクトがない場合のエラー処理 } } } アプリケーション B(画像共有される側) AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.app2" android:sharedUserId="com.example.user" android:versionCode="1" android:versionName="1.0" > 〜省略〜 </manifest> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 252 5-3. 最適化されたアプリケーションにおける注意点 sharedUserId を用いるには、もうひとつ注意点が必要です。それは、2 つのアプリケーションで同じ証明書を用 いて署名する必要があるということです。そのため、sharedUserId を用いている他のアプリケーションがあったとして も、そのアプリケーションと同じ証明書で署名できなければ、そのアプリケーションのリソースにはアクセスできません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 253 5-4. 適切に実装するためのシーケンスおよびポイント 5-4. 適切に実装するためのシーケンスおよびポイント 今回のサンプルアプリケーションは、横画面で用いる場合、リストと詳細を表示します。縦画面では、リストもしくは 詳細のみを表示します。また、詳細画面には最大化ボタンを用意し、ボタン押下によって、横画面固定で比較的 よく使用される ViewPager による画像入れ替え処理を実装しています。 Fragment による実装 • この構成を行うにあたって、もっとも重要なのが Fragment による実装です。縦画面と横画面でレイアウトを変更 し、さらにそれらを再利用する場合は、Fragment を利用することが望ましいでしょう。Fragment を利用すること により、リストと詳細に Fragment で機能を持たせ、縦画面の場合にも、横画面の場合にも同じ機能を一つのソ ースコードで実装することができます。Activity への記述内容は、縦画面と横画面を使い分ける処理のみに整理 することができます。今回は、対象を Android 3.2 以上としているため、通常の Fragment のみを用いて実装し ています。ただし、Compatibility package を利用することで、Android 1.6 以降でも対応することができます。 そのため、低バージョンから対応する必要がある場合は、Activity を継承するのではなく、Compatibility package の FragmentActivity を継承し、Fragment も Compatibility package のものを利用します。 ListView の利用 • Fragment を使用して、リスト形式のレイアウトを用いる場合、通常 ListFragment で実装すると比較的理解 することが容易ですが、今回は通常の Fragment にカスタム ListView を使用しました。その理由として、カスタム ListView は Fragment 以外でもよく利用します。しかし、最適な使い方をしなければ表示が遅くなり、消費電力 にも影響を及ぼします。そのため、最適な実装方法を解説する目的で、ListView を使用した実装にしています。 通常の ListFragment の使用方法は、「5-2-2.レイアウトファイルの最適化」の「Fragment による切り替え」を ご参照ください。 ListView の項目説明でも触れたとおり、ListView では Adapter を使用します。カスタム ListView の場合は、 この Adapter 内で項目の View を指定します。そのため、Adapter を作成する必要があります。今回のサンプル では、BaseAdapter を継承した ImagesAdapter を作成しました。 public static class ImagesAdapter extends BaseAdapter { 〜略〜 } ImagesAdapter は、baseAdapter を継承したことにより、下記4つのメソッドを override する必要がありま す。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 254 5-4. 適切に実装するためのシーケンスおよびポイント 1. getCount() ListView で表示する件数を返します。この件数分が ListView として表示されます。 2. getItem(int position) ListView で扱うオブジェクトを返します。Adapter 内で利用しない場合は null を返しても、特に 動作に影響しません。 3. getItemId(int position) ListView で扱うオブジェクトの ID を返します。Adapter 内で利用しない場合は 0 を返しても、特 に動作に影響しません。 4. getView(int position, View convertView, ViewGroup parent) ListView に表示すべき項目の View を返します。1 番重要なメソッドです。 ListView の項目は、getView で取得した View を表示します。したがって、このメソッドで表示する View はカ スタマイズすることができます。通常、View は xml で記載するべきと述べましたが、ここも同じく xml で View を定 義してインフレートさせることが望ましいでしょう。ただし、インフレートは毎回させるべきではありません。なぜならば、 インフレートは非常に時間のかかる処理だからです。もし、毎回インフレートさせるようにした場合、ListView で下 にスクロールすると動作が非常に遅くなり、ユーザのストレスになります。そのため、インフレートは初めの 1 回だけとし、 残りは ViewHolder として定義したクラスから値を指定します。すでにインフレート済みかどうかは、getView の第 二引数で判断できます。この引数が null の場合は、まだ View が一度も生成されていないということです。そのた め、null の場合はインフレートし、それぞれのインスタンスを ViewHolder に保存します。それを View の Tag とし て指定しておくと、次回以降は View から Tag を取得するだけとなり、時間のかかる処理は一切なくなります。サン プルアプリケーションでは以下のようにしています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 255 5-4. 適切に実装するためのシーケンスおよびポイント public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { // はじめに入ってきた時は View を inflate して ViewHolder 内の View のインスタンスを生成 LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.item_images, parent, false); holder = new ViewHolder(); holder.thumbnail = (ImageView) convertView.findViewById(R.id.thumbnail); holder.title = (TextView) convertView.findViewById(R.id.title); holder.description = (TextView) convertView.findViewById(R.id.description); // ViewHolder をタグに保存することで次回からはそれを流用 convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // 配列からサムネイルのリソース ID を取得 int resourceId = mContext.getResources().getIdentifier(IMAGES_NAME[position] + "s", "drawable", mContext.getPackageName()); // ViewHolder に値をセット holder.thumbnail.setImageResource(resourceId); holder.title.setText(IMAGES_TITLE[position]); holder.description.setText(IMAGES_DESCRIPTION[position]); return convertView; } static class ViewHolder { ImageView thumbnail; TextView title; TextView description; } ViewHolder を使用する場合と使用しない場合では、処理速度が大きく変わってきます。したがって、カスタム ListView を使用する場合は、必ず ViewHolder を使用してください。 • マルチデバイス対応 画面表示に関して、携帯サイズ、sw600dp(7 インチタブレットサイズ)、sw720dp(10 インチタブレットサイズ) に対応する場合、2 つの考え方があります。一つは、端末の大きさに合わせて View のサイズを変える考え方。もう ひとつは、同じサイズを利用するが、タブレットの場合はより多くの情報を表示する考え方です。タブレットの場合の み、多くの情報を表示するには、Fragment を使用することで各機能を分け、それぞれのレイアウトで必要な View を表示します。大きさに合わせて View のサイズを変える場合は、レイアウトは一つになります。サンプルアプリ ケーションでは、端末の大きさに合わせて View のサイズを変える方式をとっています。この場合は、サイズ指定の部 分ではすべて dimens.xml で指定しており、リソース修飾子で画面サイズごとに dimens.xml をわけています。 例えば、ListView にあるサムネイル画像を確認してみます。サンプルアプリケーションでは、以下のように指定して Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 256 5-4. 適切に実装するためのシーケンスおよびポイント います。 /res/layout/item_images.xml 〜略〜 <ImageView android:id="@+id/thumbnail" android:layout_width="@dimen/thumbnail_size" android:layout_height="@dimen/thumbnail_size" android:contentDescription="@string/thumbnail" /> 〜略〜 /res/values/dimens.xml <resources> 〜略〜 <dimen name="thumbnail_size">60dp</dimen> 〜略〜 </resources> /res/values-sw600dp/dimens.xml <resources> 〜略〜 <dimen name="thumbnail_size">90dp</dimen> 〜略〜 </resources> /res/values-sw720dp/dimens.xml <resources> 〜略〜 <dimen name="thumbnail_size">120dp</dimen> 〜略〜 </resources> こうすることで、携帯サイズでは 60dp、7 インチタブレットサイズでは 90dp、10 インチタブレットでは 120dp のサ ムネイル画像を表示するようになり、画面サイズで各画像サイズの変更が可能です。ただし、この方式は各解像度 ごとに、素材の画像が必要になります。 サンプルアプリケーションでは、画像は hdpi、mdpi、xhdpi、sw600dp-hdpi、sw720dp-mdpi を用意しま したが、これだけでは全然足りません。例えば、現在発売されている Nexus 10 では sw720dp-xhdpi を利用し ます。また、最近の主流になりつつある端末では xxhdpi を利用します。 今回のサンプルアプリケーションをそれらの端末で実行しても、エラーにはならず問題なく表示してくれます。ただし、 画像を自動的に解釈して表示サイズの調節を行います。例えば、Nexus10 では、120dp 指定の場合 240px になります。しかし、現在用意している画像は sw600dp-hdpi にある 120px です。これは、120px の画像を 240px に拡大して表示してくれることを意味します。つまり画像は、粗くなってしまいます。次に、逆の場合を確認 してみましょう。例えば、Nexus 7 は tvdpi です。しかし、画像リソースに sw600dp-tvdpi はありません。この場 合、sw600dp-hdpi から縮小して表示します。つまり、低解像度のものだけを用意するよりは、高解像度のもの だけを用意するほうが綺麗に表示することが可能です。ただし、可能な限りすべての解像度の画像リソースを用意 することが望ましいでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 257 5-4. 適切に実装するためのシーケンスおよびポイント • 画像を利用しない各種リソース xml で定義できるリソースは、xml で用意することが望ましいでしょう。今回のサンプルアプリケーションでは、タイト ル、ボタン、後述する ViewPager のマージンを xml で作成しています。 タイトルは角丸の背景とするために、shape で rectangle を指定します。その他には、塗りつぶし、パディング、 角丸指定としました。 /res/drawable/title.xml <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <!-- 塗りつぶし --> <solid android:color="@color/title_bg_color" /> <!-- 上下左右にスペース --> <padding android:bottom="@dimen/title_padding" android:left="@dimen/title_padding" android:right="@dimen/title_padding" android:top="@dimen/title_padding" /> <!-- 丸角 --> <corners android:radius="@dimen/title_corners" /> </shape> ボタンを押下した時に、背景を変えるようにするため selector を使用し、押された時とそれ以外の 2 つの shape タグで構成されています。塗りつぶし、枠線、パディング、角丸を指定していますが、ボタンの押下とそれ以外で変更 するため、塗りつぶしの色のみを変えています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 258 5-4. 適切に実装するためのシーケンスおよびポイント /res/drawable/button.xml <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <shape android:shape="rectangle"> <!-- 塗りつぶし --> <solid android:color="@color/button_pressed_color" /> <!-- 枠線をつける --> <stroke android:width="@dimen/button_stroke" android:color="@color/button_stroke_color" /> <!-- 上下左右にスペース --> <padding android:bottom="@dimen/button_vertical_padding" android:left="@dimen/button_horizontal_padding" android:right="@dimen/button_horizontal_padding" android:top="@dimen/button_vertical_padding" /> <!-- 丸角 --> <corners android:radius="@dimen/button_corners" /> </shape> </item> <item> <shape android:shape="rectangle"> <!-- 塗りつぶし --> <solid android:color="@color/button_normal_color" /> <!-- 枠線をつける --> <stroke android:width="@dimen/button_stroke" android:color="@color/button_stroke_color" /> <!-- 上下左右にスペース --> <padding android:bottom="@dimen/button_vertical_padding" android:left="@dimen/button_horizontal_padding" android:right="@dimen/button_horizontal_padding" android:top="@dimen/button_vertical_padding" /> <!-- 丸角 --> <corners android:radius="@dimen/button_corners" /> </shape> /it ViewPager のマージン用 XML は、背景の指定に用いるため塗りつぶしのみを指定しています。 /res/drawable/pager_margin_image.xml <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <!-- 塗りつぶし --> <solid android:color="@color/title_bg_color" /> </shape> • ViewPager による画面切り替え 最大化の画面では、ViewPager クラスを用いてフリックによる画面切り替えを実装しています。ViewPager クラ スは、Compatibility package にあるクラスであり、使用する場合はこのライブラリを使う必要があります。しかし、 最新の Android SDK Tools の場合に、Compatibility package は、自動的にライブラリとして登録されるた め、特に意識する必要はありません。 ViewPager も、ListView などと同様に、Adapter を用いて処理をします。ただし、ViewPager の Adapter は、PagerAdapter を継承したものになります。PagerAdapter を継承することで、getCount メソッドおよび isViewFromObject メソッドを継承しなければなりません。getCount メソッドは、ViewPager で表示する Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 259 5-4. 適切に実装するためのシーケンスおよびポイント View の数をカウントするメソッドです。また、isViewFromObject メソッドは、表示する View がコンテナに含まれ ているかどうかを判断するメソッドです。 こ れ ら 2 つ の メ ソ ッ ド 以 外 で も 、 重 要 な メ ソ ッ ド が あ り ま す 。 そ れ は instantiateItem メ ソ ッ ド お よ び destroyItem メソッドです。この 2 つのメソッドは、次に表示する View の生成または削除を行います。今回のサ ンプルアプリケーションでは、instantiateItem で ImageView を生成し、ViewPager に追加、destroyItem で ViewPager から削除します。instantiateItem が呼び出されるタイミングが、現在表示している View ではな く、その左右の View であることに注意しましょう。 図 41 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 260 5-5. WebView の使用における注意点 5-5. WebView の使用における注意点 Android では、WebView を用いて HTML を直接記述することができます。すでに作成済みの HTML をそのま ま利用することができるため、開発工数の削減にもつながり、非常に有用です。WebView を用いて HTML を表 示するには、下記3パターンの方法があります。 1. URL を指定して Web サイトを直接表示する 2. assets フォルダに保存されているローカル HTML を表示する 3. 文字列で指定した HTML を直接表示する 本項では、この各手法について説明します。 1. URL を指定して Web サイトを直接表示する場合 単純に指定した URL を表示するだけであれば、下記のように URL を指定するだけでできます。 layout.xml <WebView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/web_view" android:layout_width="match_parent" android:layout_height="match_parent" /> MainActivity.java public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout); WebView webView = (WebView) findViewById(R.id.web_view); // URL 読み込み webView.loadUrl("http://www.xxxxxxx.jp"); } この場合、インターネットを介して HTML を取得します。そのため、AndroidManifest.xml に Internet のパー ミッションが必要です。この点は、忘れがちなので注意しましょう。 AndroidManifest.xml <uses-permission android:name="android.permission.INTERNET" /> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 261 5-5. WebView の使用における注意点 2. assets フォルダに保存されているローカル HTML を表示する場合 この場合も URL と同じく、webView.loadUrl でローカルファイルを指定するのみです。また、インターネット接続 は不要なので、AndroidManifest.xml に Internet のパーミッションを定義する必要はありません。assets 直下 に保存している index.html を表示する場合の例を、以下に示します。 webView.loadUrl("file:///android_asset/index.html"); android_asset と指定すると、assets のディレクトリ配下を指すことになります。そのため、assets にディレクトリ を作成します。 たとえば、/assets/dir/index.html とした場合の loadUrl は、file:///android_asset/dir/index.html となります。 assets ファイルを指定する場合は、file:///からはじまりますが、スラッシュ(/)が3つあることに注意して下さい。 file//android_asset/index.html のようにスラッシュが 2 つだけの場合、表示することができません。 3. 文字列で指定した HTML を直接表示する 文字列で指定した HTML を表示する場合は、loadUrl ではなく loadData メソッドを使用します。 String html = "<html><body>test</body></html>"; webView.loadData(html, "text/html", "utf-8"); • WebView を用いた時の文字化け WebView を用いて日本語を表示する際、文字コードを指定しなければ文字化けを起こすことがあります。 正式に運用されている Web ページの URL を指定する場合、HTML が正しく記述されていればこの問題は 避けられます。サンプルを作成した場合、HTML を簡略化して書き、文字化けがに発生すると、原因を特定 させるまでに時間がかかり、予期せぬトラブルになることがあるため注意が必要です。以下のように、簡略化し た HTML を書く場合でも、ヘッダ部に meta タグで charset を指定してください。 <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> loadData で文字列を指定した場合の文字化けは、更に注意が必要になります。なぜならば、loadData で utf-8 と文字コードを指定しても文字化けが発生するからです。この場合は、下記のように mimeType に charset を指定します。 webView.loadData(html, "text/html; charset=utf-8", "utf-8"); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 262 5-5. WebView の使用における注意点 もしくは、loadData の代わりに loadDataWithBaseURL を使用することで、文字化けを解消することがで きます。loadDataWithBaseURL を使用する場合は下記のようになります。 webView.loadDataWithBaseURL(null, html, "text/html", "utf-8", null); loadDataWithBaseURL は、本来、基準となる URL を指定して表示するためのものです。 loadDataWithBaseURL を使用することで、第一引数で指定したドメインの相対パスで画像などを読み込 むことが可能になります。ただし、第一引数に null を指定している場合、相対パスで assets の画像読み込 みができなくなるため注意が必要です。絶対パスで指定することで、読み込むことは可能です。 WebView で JavaScript を利用する • WebView は、初期状態で JavaScript を実行できません。しかし、以下のように JavaScpript を許可する ことで、簡単に実行することができます。 webView.getSettings().setJavaScriptEnabled(true); JavaScpript を有効にすると、JavaScript と Java で連携することができます。例えば、テキストに入力した 値を、JavaScript を通じて Java で処理することができます。JavaScript の値を Java で取得するには、 addJavascriptInterface メソッドで、Java のクラスと JavaScript を指定するだけで実行でき、非常に簡 単です。 text の値を JavaScript で介し、Java でログ出力を行うサンプルを、以下に示します。 MainActivity.java public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = (WebView) findViewById(R.id.web_view); webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl("file:///android_asset/index.html"); JSObject object = new JSObject(); // JavaScript で js という名前で呼ばれると JSObject を実行する webView.addJavascriptInterface(object, "js"); } class JSObject { public void getJsString(String jsString) { Log.i("TAG", jsString); } } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 263 5-5. WebView の使用における注意点 /assets/index.html <html> <head> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> </head> <body> <form name="data"> <input type="text" name="input_text"> <!-- クリック時に指定した名称(js)でメソッドを呼び出す --> <input type="button" value="送信" onClick="js.getJsString(document.data.input_text.value)"> </form> </body> </html> 逆に、Java から JavaScript を実行する場合は、loadUrl で JavaScript を指定するだけで実行することが できます。下記の例は、Java で”Java から実行”という文字を JavaScript に渡して画面に表示します。 view.loadUrl("javascript:document.write('Java から実行');"); こうすることで、Android-JavaScript 間のデータのやり取りが可能になります。しかし、すべて実行できるわけ ではありません。例えば、下記の HTML をロードしてボタンを押下すると、alert と表示されるはずですが、表 示はされません。 /assets/index.html <html> <body> <input type="button" value="alert" onClick="alert('message');"> </body> </html> Android の WebView から JavaScript を利用する場合、初期設定では Alert は非表示となっています。 しかし、Alert は setWebChromeClient メソッドで、WebChromeClient を指定することにより表示する ことができます。 webView.setWebChromeClient(new WebChromeClient()); また、Alert は、Toast に変更することもできます。その場合は、Alert の情報を Android 側でフックし、それ を Toast で表示させます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 264 5-5. WebView の使用における注意点 webView.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); result.confirm(); return true; } }); • JavaScript を利用する場合の注意点 WebView で JavaScript を利用できると非常に便利ですが、注意する点もあります。特に、セキュリティリス クが高くなるということは、常に考えておくべきです。例えば、JavaScript を実行可能にすることで、クロスサイト スクリプティングの脆弱性を生み出す可能性があります。また、Java と JavaScript 間で通信ができるというこ とは、Context を JavaScript 側へ渡せることを意味します。その結果、アプリケーションの様々な情報が引き 出される恐れもあります。 このように、JavaScript が実行できるということは様々な処理の実行も可能にすることになります。それと同時 に、セキュリティリスクを高くする要因であるということも、十分に考慮する必要があります。 また、JavaScript は機種によって実行できないものもあります。JavaScript を利用する場合は、機種依存 があることを念頭に置いて開発・テストを行う必要があります。特に、最新の技術である HTML5 は、各メーカ によって対応状況が異なっています。マルチデバイス対応を行う場合は、対象の機種で実行できることをきち んと確認しなければなりません。 さらに、JavaScript の実行は、HTML の特性上、HTML をすべてロードしてから実行する必要があります。 例えば、下記のように HTML のロードが完了する前に、JavaScript をロードしても実行されません。 webView.loadUrl("file:///android_asset/index.html"); webView.loadUrl("javascript:document.write('Java から実行');"); このような場合は、setWebViewClient で onPageFinished を override してから実行します。 webView.loadUrl("file:///android_asset/index.html"); webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { view.loadUrl("javascript:document.write('Java から実行');"); } }); • URL のフックとバックキー WebView に表示できるのは HTML なので、リンクが存在する場合もあります。例えば、HTML に google へのリンクを埋め込む場合の例を、以下に示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 265 5-5. WebView の使用における注意点 /assets/index.html <html> <head> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> </head> <body> <a href="http://www.google.co.jp">google に遷移</a> </body> </html> これを実行してリンクをタップすると、ブラウザが起動して google のページが表示されます。本来、Web サイト を見るためにブラウザを立ち上げるのは正しい動作です。しかし、アプリケーション内で画面遷移したい場合も あります。その場合は、WebViewClient を指定します。 webView.setWebViewClient(new WebViewClient()); これにより、リンクをタップしてもブラウザが新しく立ち上がらずに、アプリケーション内で画面遷移します。しかし、 このままでは、バックキーを押下した時に前の画面に戻らず、アプリケーションが終了もしくは前の Activity に戻 ってしまいます。これに対応するためには、戻るボタンをフックして WebView の履歴から戻るようにします。 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // 戻るボタン押下で WebView にヒストリがある場合はヒストリから戻って終了 if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) { mWebView.goBack(); return true; } return super.onKeyDown(keyCode, event); } • WebView の設定 WebView を 有 効 に 活 用 す る に は 、 様 々 な 設 定 を 行 う 必 要 が あ り ま す 。 こ の 設 定 は 、 webView.getSettings()で取得した WebSettings クラスに対して行います。例えば、先に挙げた JavaScript の有効化もその一つです。 以下に、WebView で設定することができることを一部、ご紹介します。 • setBuiltInZoomControls 画面の拡大・縮小 • setSaveFormData フォームデータの保存 • setSavePassword パスワードを保存 • setDefaultFontSize デフォルト文字サイズの指定 上記のように、数多くの設定が可能です。これらを利用し、アプリケーションに合った設定を行うことで、アプリケ ーションの操作性向上につなげることができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 5 章 マルチデバイス対応と最適化されたアプリケーション開発 | 266 5-5. WebView の使用における注意点 • WebView を用いたマルチ言語対応 Android アプリケーションでは、リソースごとに言語を指定することが可能です。しかし、assets では言語の指 定はできません。そのため、マルチ言語対応をするには少し工夫が必要です。 一般的には、言語ごとにディレクトリを分け、Locale.getDefault().toString()で取得した値で表示する HTML を指定する、といった方法が考えられます。しかし、Locale.getDefault().toString()は機種依存 があります。例えば、日本語では ja と返ってくる機種と ja_JP と返ってくる機種があり、単純な比較だと全機 種に対応できません。そのため、言語の切り替えは、Android に任せるほうが問題は少なくなります。 言語の切り替えを Android に任せる方法として、/res/raw/にファイルを保存する方法があります。 /res/raw/であれば、リソース修飾子を付与することができるためです。また、/res/raw/はバイナリになるた めに、見られたくない JavaScript などがあった場合にも、簡単には見ることができません。そのため、セキュリテ ィの観点からも優れているといえます。以下に、/res/raw/index.html を読み込み、WebView で表示す る例を示します。 InputStream input = getResources().openRawResource(R.raw.index); byte[] buffer = new byte[1024 * 4]; int n = 0; StringBuilder builder = new StringBuilder(); try { while (-1 != (n = input.read(buffer))) { builder.append(new String(buffer)); } } catch (IOException e) { e.printStackTrace(); } webView.loadData(builder.toString(), "text/html; charset=utf-8", "utf-8"); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 第6章 | 267 WebView を利用したアプリケーション開発の注意点 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 268 モバイルデバイス向けの Web コンテンツは、PC 向けのそれとは大きく異なります。Web の規則を守ったコンテンツ が PC 用ブラウザで正常に動作するという事実も、その実装がモバイルデバイスにとっても適切であることを保証する 根拠とはなりません。モバイルにはモバイルの作法があり、PC における正解がいつも通用するわけではないのです。 スマートフォンのブラウザは、PC 向けの Web サイトを概ね表示することができます。そのことから、両者の違いは軽 視されがちですが、モバイルアプリケーションの観点から Web コンテンツを見るならば、不適切な実装が端末のリソ ースを消耗しトラブルの原因となっている場合が少なくありません。たとえば、PC 向けに書かれた大量のスクリプトを スマートフォンのブラウザで表示すると、メモリの圧迫・不要な通信の増大・過度なバッテリ消費を招くことがあります。 また、動作が重くなるためにユーザビリティも悪化します。 モバイルデバイスにおける Web 表示の基本を知り、特有のベストプラクティスを知ることは、トラブルを未然に防ぎ、 顧客満足度を向上することと密接な関連を持ちます。 本章は、ブラウザや Android アプリケーションの WebView でモバイルコンテンツを適切に動作させるための基本 的な注意点について解説します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 269 6-1. WebView の役割と特性 6-1-1. WebView とは 「WebView」は、簡易的なブラウザ表示機能を持つアプリケーション開発のために、Android システムによって提 供されているクラスです。 HTML を描画し表示するために提供されている標準 View コンポーネントであり、レンダリングエンジンには WebKit(※)が使用されています。たとえば、インターネット上の Web ページや、Android デバイス上に保存された ページの表示に使用します。 ※ Android 4.4 以降では Chromium が使用されています WebView には、以下のような機能を持ったメソッドが用意されています。ここでは、主なものを示します。 • ページの閲覧履歴をたどる • ページのズームイン/ズームアウト • テキスト検索を行う 各種設定は、WebSetting クラスを使用することで任意に設定できます。初期状態では、デフォルトに設定され ています。 また、WebView で表示可能なファイルを、以下に示します。 • アプリケーションのバイナリに含まれる HTML ファイル • 「assets」ディレクトリに格納されている HTML ファイルや画像ファイル • SD カード上の HTML ファイル • ソース上の HTML 文字列 WebView のレンダリング機能は、HTML だけでなく、CSS や JavaScript もサポートしています。したがって、さ まざまな WEB サービスのクライアントとして利用できます。 前述のとおり、Android の WebView ではブラウザと同様に WebKit(もしくは Chromium)が使用されていま す。WebKit は、オープンソースの HTML レンダリングエンジン群の総称であり、HTML・CSS・JavaScript・SVG、 MathML などを解釈します。もともとは、Apple 社の Mac OS X に搭載される Safari のために、Linux や BSD などの Unix 系用のレンダリングエンジンである KHTML をフォーク(分岐)して開発されました。 しかし、Google は 2013 年 4 月 3 日、WebKit を Blink にフォーク(分岐)させることを発表しました。この 発表に伴い Google は、Chromium のレンダリングエンジンを、WebKit のソースコードからフォーク(分岐)した 新たなレンダリングエンジン Blink に変更しています。Webkit から Blink に変更した理由は、WebKit と Chromium の関係が複雑になり、開発イノベーションの速度が低下傾向になったことによるボトルネックを解消す るためです。関係が複雑化した原因は、レンダリングエンジンに WebKit を使用する他のブラウザと異なり、 Chromium はマルチプロセスアーキテクチャを採用しているためです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 270 これらのことから、Google Chrome(28 以降)や Opera(15 以降)などの Web ブラウザでは、WebKit ではな く Blink が採用されています。 Google は 2013 年 10 月 31 日、Android 4.4 KitKat を発表し、WebView のレンダリングエンジンを Chromium ベースに変更しました。このことは、Google の公式リファレンスにおいても、以下のとおりアナウンスされ ています。 • Migrating to WebView in Android 4.4 Chrominium ベースの新しい WebView が、Android 4.4(API レベル 19)で発表されました。 この変更で、WebView のパフォーマンスは向上し、HTML5、CSS3 や JavaScript を標準サポートします。 WebView を使っているアプリケーションは、Android 4.4 以上を搭載している端末に限り、これらのアップデ ートの恩恵を受けることになります。 • User Agent Changes ユーザエージェントを利用している場合、変更があるため注意してください。Chrome バージョンが追加されま す。 • Multi-threading and Thread Blocking WebView でアプリケーションの UI スレッドでない他のスレッドからメソッドを呼んだ場合、予想外の結果をもた らす場合があります。 • Custom URL Handling 新しい WebView は、カスタム URL スキームを使ったリンクやリソースのリクエストに関して、制限を追加するこ とができます。 • Viewport target-densitydpi no longer supported 以前の WebView は、target-densitydpi をサポートしていました。このプロパティは指定の画面解像度に するためのものです。なお、このプロパティはサポートされていません。 • Viewport zooms in when small 以前は、viewport 設定で width を 320 以下にしていた場合、"device-width"が自動的に設定されて いました。また、height が WebView の height 以下の場合は、"device-height"が設定されました。しか し、新しい WebView では width と height の値が適用され、screen width に合うように WebView が 拡大されます。 • Multiple viewport tags not supported 以前は 1 つの WebView に 2 つの viewport タグがあった場合、そのプロパティはマージされていました。新し い WebView では、一番最後に設定された viewport 設定が適用され、それ以外は無視されます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 • | 271 Default zoom is deprecated 初期ズームレベルを設定・取得するための getDefaultZoom()と setDefaultZoom()はサポートされませ ん。また、代わりに適切な viewport 設定を Web ページに適用する必要があります。 • The background CSS shorthand overrides background-size Chrome や他のブラウザは、background を使うと background-size を上書きします。新しい WebView では background と指定したスタイルであっても、同様に background-size が上書きされてしまいます 。 • Sizes are in CSS pixels instead of screen pixels 以前は、window.outterWidth や window.outerHeight という値が、実際の画面ピクセル数を返却し ていました。新しい WebView では、先のメソッドを使うと CSS のピクセル単位の値を取得できます。 • NARROW_COLUMNS and SINGLE_COLUMN no longer supported 新しい WebView では、WebSettings.LayoutAlgorithm の NARROW_COLMUNS という値はサポ ートされません。 • Handling Touch Events in JavaScript WebView でのタッチイベントを直接ハンドリングしている場合、touchcancel イベントもハンドルすることに注 意してください。touchcancel が呼ばれる場合が有ります。もし、このイベントを受け取っていなかった場合、 以下の問題を引き起こす可能性が有ります。 • エレメントがタッチされて(touchstart と touchmove が呼ばれ)、ページがスクロールされた時、 touchcancel イベントが発生します。 • エレメントがタッチされ(touchstart が呼ばれ)るが、event.preventDefault()が呼ばれない 場合、touchcancel が発生します(WebView はタッチイベントが実行されたくない状況だと 判断します)。 【公式サイト】 http://developer.android.com/intl/ja/guide/webapps/migrating.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 272 WebView は、ブラウザの持つ機能のうち一部のみを使用することが可能です。WebView とブラウザは同等で はないことに、注意が必要です。 たとえば、存在しないサーバにアクセスした場合にブラウザがエラーページを表示するのは、ブラウザの基本的な機 能です。しかし、WebView を貼り付けただけのアプリケーションでは、存在しないサーバにアクセスした場合真っ白 (Blank)なページを表示します。エラーページの表示は WebKit のサポート外であり、アプリケーション側で実装する 必要があります。 また、他にも WebView の初期状態で注意すべき主要な点として、以下のことが挙げられます。 • ズームの際にレイアウトが崩れる(自動的に拡大されないため、HTML が崩れる) • スクロールバー分の表示幅が自動で取得される • JavaScript が実行できない • Flash(端末内の Flash Player for Android)が再生できない • Web 前提ではあるが、インターネットに繋がらない • URL のパスを入力しなければ Web ページの表示ができない 標準ブラウザを導入している場合、表示した Web ページのリンクをクリックすると、別のブラウザで処理を 行う(リンクから抽出した URL を、インスタンスとして自動的に別ブラウザに渡してしまうため) これらの機能または動作を有効もしくは無効にするためには、プログラム実装を行う場合に処理内容を考慮する ことが必要となります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 6-1-2. | 273 WebView でできること Android システムの WebView クラスでは多くの機能が提供されています。本項では、その機能を用いてできる ことをいくつか紹介します。 • 一般的な Web サイトの表示 WebView のもっとも基本的な機能として、HTML ページの表示があります。具体的には、WebView の loadUrl()メソッドに任意の URL を渡すことで、Web ページを表示させることができます。アプリケーション内で ブラウザを表示させるには、layout に WebView を使用します。なお、アプリケーションから外部にアクセスする ために、AndroidManifest.xml に INTERNET 権限の設定を追記する必要があります。 図 42 WebView • 閲覧ページのキャッシュ ネットワーク越しに Web サイトを表示する場合、キャッシュ処理は表示の高速化に必要不可欠です。 WebView の場合、裏側にいる WebKit がキャッシュ処理を実行するため、独自で実装する事はないかもし れません。 webview.getSettings().setAppCacheEnabled(true); webview.getSettings().setAppCacheMaxSize(8 * 1024 * 1024); webview.getSettings().setAppCachePath("/hoge/hoge/"); 上記のように設定を行なう場合、閲覧済みの HTML・画像・CSS 等々のキャッシュ処理は WebKit が行います。 また、キャッシュの保存先は永続的なファイルになります。WebSettings#setAppCachePath()で保存先を明 示的に変更することも可能です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 274 WebView のキャッシュ制御についても、WebSettings#setCacheMode で設定可能です。また、複数の種 類を選択することが可能です。 webview.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 具体的な種類は以下のとおりです。 • LOAD_DEFAULT デフォルトのキャッシュモード • LOAD_CACHE_ELSE_NETWORK キャッシュが存在する場合は、キャッシュを使用。キャッシュが無いまたは expire(期限切れ)した 場合はネットワーク経由でデータを取得する。 • LOAD_CACHE_ONLY キャッシュのみを使用する。 • LOAD_NO_CACHE キャッシュを使用しない。 ネットワークの接続状態によって WebView のキャッシュモードを切り替えるような実装サンプルは、stack overflow などで見かけることができます。 WebView クラスで用意されている clearCache()メソッドは、キャッシュをクリアすることができます。引数に true を渡す場合、永続的ファイルで保存されているキャッシュも含めて削除します。false を渡す場合は、RAM 上のキャ ッシュのみ削除します。 • Cookie の使用 Cookies は別のスレッドで管理されます。キャッシュや Cookie はブラウザアプリケーションのデータとは共有しな いからです。そのため、index building のような操作は UI スレッドをブロックしません。 WebView は、Cookie を自動的に処理します。また、認証情報や Form 入力値などと共に SQLite3 のデ ータベースに保存され、永続化されます。Cookie をリセットする場合は、アプリケーション管理の「データ消去」 を実施します。 • JavaScript の実行 setJavascriptEnabled メ ソ ッ ド は 、 JavaScript 使 用 の ON/OFF を 設 定 し ま す 。 addJavascriptInterface は WebView によって読み込まれたページでアプリケーション内の Java メソッド を JavaScript から使用することを可能にします。 setJavascriptEnabled メソッドは、デフォルトの状態で false(偽)値が代入されており、JavaScript は使 用できません。JavaScript を使用するには、WebSettings クラスの setJavaScriptEnabled メソッドを使 用し、JavaScript を有効にする必要があります。作成した Java メソッドは、addJavascriptInterface メ ソッドで WebView に挿入することにより、JavaScript から使用できるようになります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 275 なお、JavaScript には重要な脆弱性があるため注意する必要があります。WebView を用いてコンテンツに アクセスするアプリケーションを開発する際は、次に示す原則に従うことを意識します。 ① 自社が管理しているコンテンツにアクセスする場合に限り、JavaScript を有効にする ② 上記以外の場合には、JavaScript を無効にする JavaScript の脆弱性に対する注意点は、一般社団法人 日本スマートフォンセキュリティ協会の発行する 「Android アプリのセキュア設計・セキュアコーディングガイド」が参考になるでしょう。 【参考サイト】 http://www.jssec.org/report/securecoding.html • plug-in の使用(Flash など) setPluginState メソッドは、WebView でのプラグインが埋め込まれたコンテンツ(Flash など)を扱うことがで きます。デフォルトの状態は OFF であり、設定を変更していない場合は、Flash が起動しません。 ※ setPluginState メソッドは、API_18 から非推奨になっています 以下に示す定数は、WebKit で使用する表示プラグインを有効/無効に切り替える設定であり、それぞれ以 下の効果を持ちます。 ON:プラグインをインストールしていなければ有効にできない OFF:プラグインをインストールしてもいなくても無効にする ON_DEMAND:プラグインのインストールを自動で実行し有効にする • SSL サイトへの接続 WebView は、SSL サイトへの接続が可能です。しかし、SSL エラーについて適切な対応(ハンドリング)が 必須となります。 たとえば、HTTPS 通信で SSL エラーが発生した場合は、エラーが発生した旨をダイアログ表示などの方法で ユーザに通知し、通信を終了する必要があります。なぜならば、SSL エラーの発生は、サーバ証明書に不備 がある可能性もしくは中間者攻撃を受けている可能性を示唆しているからです。 しかし、WebView には本来、サービスとの通信時に発生した SSL エラーに関する情報をユーザに通知する 仕組みは備わっていません。したがって、SSL エラーが発生した場合にはその旨をダイアログなどで表示し、脅 威にさらされている可能性があることをユーザに通知する必要があります。 また、エラーの通知に加えて、アプリケーションはサービスとの通信を終了しなければなりません。特に、次のよう な実装を行っていないか注意する必要があります。 • 発生したエラーを無視して、サービスとの通信を継続する • HTTP などの非暗号化通信を使し、サービスと改めて通信する WebView のデフォルトの挙動として、SSL エラーが発生した場合は対象のサーバと接続を行いません。 したがって、WebView の初期接続プロセスに SSL エラーの通知機能を実装することで、通信エラーを適切に 取り扱うことができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 6-1-3. | 276 WebView でできないこと 前項では、WebView を用いてできることを紹介しました。本項では、Android システムの WebView でできな いことを紹介します。 • ナビゲーションコントロール WebView は、ナビゲーションコントロール(ブラウザの[進む][戻る])には対応していません。 • アドレスバー表示 WebView は、アドレスバーの表示に対応していません。アプリケーション内で Web ブラウザを使用できるのな ら、任意の URL を自由に入力できるようにしたいと考えることもあるでしょう。そのための UI は、開発者が独自 に実装する必要があります。 図 43 アドレスバー表示の差異 (左:ブラウザ、 右:WebView) WebView と同一の画面上でアドレスバーと同様(類似)の表示を行いたい場合は、画面のレイアウトを XML で作成する必要があります。たとえば、EditText と WebView を同じ画面レイアウト上に作成します。 次に、EditText に入力された文字列を WebView に渡すなどの処理を実装することで、アドレスバーと同等 の動作を実現することが可能です。 繰り返しとなりますが、WebView がデフォルトでできることは、Web ページの表示が全てです。ユーザが慣れ親し んだブラウザのような各機能を動作させるためには、プログラムの実装時に、設定の変更・処理の追加を行うことで 実現します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 277 6-2. ブラウザと WebView の使い分け-WebView が求められるアプリケーシ ョンの条件を見定める 6-2-1. WebView が求められる場面 モバイル向け Web コンテンツを動作させるためにブラウザではなく WebView を利用することが望ましい場面につ いて、特徴的な条件は以下のとおりです。 ① 他アプリケーションとの連携、特にデータのやり取りを行う必要がある場合 今後更新する可能性がある利用規約やユーザガイドを、アプリケーション内で提供したいような場合、 WebView を使用することは有用です。Android アプリケーションにおいて、WebView を含んだ Activity を作成することができ、作成した Activity をオンラインでのドキュメント表示に使用することができるからです。 また、E メールのようなデータを取得するためにインターネット接続を常に必要とするユーザに対してアプリケーシ ョンがデータを提供するような場合にも、WebView を使用することは有効です。 たとえば、ユーザのデータと Web ページを表示するようなアプリケーションであれば、ネットワークリクエストを送信 しデータをパースすることは、Android のレイアウトの中にレンダリングするよりも、WebView を組み込むこと のほうが容易です。あるいは、Android 用に調整された Web ページをデザインし、WebView を使用して対 象のページを読み込むように実装することもできます。 ② Android 端末のハードウェア的な機能と連携を行う場合 WebView を利用したアプリケーションは、カメラや加速度センサーなど Android 端末のハードウェア的な機能 にアクセスし制御することができます。そのため、アプリケーション内の Web ページ参照時に、カメラや加速度セ ンサーなどへの連携が必要となる場合は、WebView を使用することが有用です。 ③ ネット回線が遅い場所で利用することが多い場合 WebView は、サーバとの通信を必要最低限に抑え、UI 部品などを常にローカルのアプリケーション内に保持 することができます。したがって、通信速度の遅い回線でも比較的に素早いレスポンスが得られることから、 WebView を使用することは有用です。 ④ データ通信量を抑制したい場合 上記と同様の理由により、UI 部品などをローカルのアプリケーション内に保持することで、アプリケーションのトラ フィックを通信すべきデータにのみ限定することが容易になります。通信量の抑制は Android 端末のリソース 浪費を防ぐことと密接な関連を持つため、WebView を使用することは有用です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 6-2-2. | 278 ブラウザの使用でこと足りる場面 モバイル向け Web コンテンツを動作させるために、ネイティブアプリケーションに WebView を組み込むのではなく ブラウザを使用すれば事足りる場合もしくはブラウザを使用することが望ましい場面について、特徴的な条件は以 下のとおりです。 ① Web ページや HTML ファイルの表示処理のみ行う場合 Android アプリケーションとのやり取りや端末のハードウェア的機能に対するアクセスが発生せず、画面上に Web ページを表示させるのみであれば、WebView を使用する必要はありません。 ② Web ページ上で何らかの認証処理を行う場合(主にコンシューマ向け) アプリケーション内で WebView を使用すると、明示的に実装しない限り URL が表示されません。また、URL 表示を実装したとしても、間違いなくその URL にアクセスしているという正当性を確認することはできません。つ まり、ユーザは自分が正しい認証画面にアクセスしているかどうかを URL から判断することはできません。フィッ シング詐欺サイトなどで ID やパスワードが漏えいする事態を避けるためにも URL を目視で確認することが望ま しい状況では、WebView ではなくブラウザを使用することが有用です。 WebView と ブ ラ ウザ の どち ら を 選 択 す るこ と が ふ さ わ し い か を 判 断 す る に あ た っ て 、 案 件 に よ っ て は 「WebView を選ぶべき」もしくは「ブラウザを使うべき」状況が異なります。 たとえば、一般的なコンシューマ向けに Web サイトでの認証を伴うシステムの場合は、「ブラウザを選ぶべき」で しょう。ユーザがアクセスしている URL を自分で確認することは、フィッシング詐欺などを防ぐ上で重要な対策で あり、ブラウザで表示させることがセキュリティ面での安全につながります。 しかし、同様の認証でも法人向けの業務系システムの場合は、状況が異なるかもしれません。なぜならば、 認証サーバの URL を秘匿することがセキュリティ面で望ましい場合もあるからです。その場合は、WebView で表示することにより、要件を満たすことができます。 このように、どちらを選択することがふさわしいのか、利用シーンや要件に基づいて適切に判断することが必要 です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 279 6-3. WebView 実装の基本 本項は、WebView 実装の基本について説明します。 しかし、本項で扱うのは WebView 実装のほんの一部分です。WebView を動作させる最小限の実装方法と、 WebView のいくつかの設定項目に関する実装方法を説明します。ほかにも、各種設定や機能追加の手段が多 く存在しています。 「パーミッションの設定」 WebView を扱う上で大前提となるのが、インターネットへの接続です。マニフェストファイルにインターネット接続の 許可を追加する必要があります。 <manifest ... > // インターネット接続の権限を付加 <uses-permission android:name="android.permission.INTERNET"/> </manifest> 「ハードウェアアクセラレータの有効化」 最近のモバイルデバイスは、メインプロセッサに加えて画像処理用のコプロセッサが別途用意されていることが多く、 パフォーマンスの向上に大きく寄与しています。 この機能は、マニフェストファイル中で明示的に有効にする必要があるため注意が必要です。WebView を利用 するアプリケーションでは、必ず有効にすることを推奨します。 なぜならば、この機能を有効化していない Android アプリケーションでは、WebView でのアニメーションを動作を 伴う DOM 操作が動かないもしくは不安定な動作を示す事例が顕著に見られており、その有効性が明白だからで す。 // アプリケーションに対してハードウェアアクセラレータを有効にする <application android:hardwareAccelerated="true"> // Activity に対してハードウェアアクセラレータを有効にする <activity android:hardwareAccelerated="true"/> 「WebView の実装」 まず、WebView をレイアウトに追記します。WebView の設定自体は、以下のサンプルソースに記載された内 容で完了です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 280 <?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" > <WebView android:id="@+id/webview" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout> 次に、WebView を Activity に表示します。単純に WebView の表示を行うだけであれば、たった 2 行で実装 が可能です。以下のサンプルでは、Activity の onCreate メソッドをオーバーライドし、その中で WebView を表 示しています。 public class MyActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // WebView を作成 WebView webView = (WebView)findViewById(R.Id.webview); // URL を指定 webView.loadUrl("http://www.google.co.jp/"); } } WebView を使用して画面を表示する上記のサンプルコードは、必要最小限のものです。このままでは JavaScript は利用できませんし、viewport も開かず、WebSQL データベースも向こうのままです。それらを有効 にするには、WebView の設定項目を指定する必要があります。 WebView の基本的な設定には、WebSettings を使用します。ここでは、いくつかの設定方法を以下のサンプ ルコードに示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 281 // WebView を作成 WebView webView = (WebView)findViewById(R.Id.webview); android.webkit.WebSettings webSettings = webView.getSettings(); // JavaScript を有効にする・・・① webSettings.setJavaScriptEnabled(true); // 組み込みのズーム機能が用意されている・・・② webSettings.setBuiltInZoomControls(true); // プラグインを有効にする・・・③ webSettings.setPluginState(PluginState.ON); // スクロールバーの見た目を設定する・・・④ webSettings.setScrollBarStyle(WebView.SCROLLBAR_INSIDE_OVERLAY); // viewport を利用できるようにする・・・⑤ webSettings.setUseWideViewPort(true); // コンテンツプロバイダの仕組みを利用する場合には true を設定する・・・⑥ webSettings.setAllowContentAccess(false); // WebSQL データベースを有効にする・・・⑦ webSettings.setDatabaseEnabled(true); String databasePath = getApplicationContext().getDir( "websqldatabase", Context.MODE_PRIVATE).getPath(); webSettings.setDatabasePath(databasePath); // Web ストレージを有効にする・・・⑧ webSettings.setDomStorageEnabled(true); // window.open メソッドを利用するとき・・・⑨ webSettings.setJavaScriptCanOpenWindowsAutomatically(true); webSettings.setSupportMultipleWindows(true); // フォームの入力値やパスワードのログを保存しないようにする・・・⑩ webSettings.setSaveFormData(false); webSettings.setSavePassword(false); // URL を指定 webView.loadUrl("http://www.google.co.jp/"); } } ① JavaScript の有効/無効を設定します。WebView にロードする予定の Web ページで JavaScript を使 用する場合、WebView に対して JavaScript を有効にする必要があります。一度、JavaScript を有効に することで、アプリケーションと JavaScript コード間のインターフェイスを作成することが可能となります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 282 ② setSupportZoom()メソッドは、ズーム機能の ON・OFF を切り替えることができます。このメソッドは、初期 状態では ON(true)となっています。また、setBuiltInZoomControls()メソッドでは、組み込みのズーム機 能が有効かどうかを設定します。有効にすることで、ズームイン・ズームアウトするためのコントロールボタンが WebView 上に表示されます。 図 44 ズームボタンの表示(左:表示なし 右:表示あり) ③ setPluginState ()は、WebView でのプラグインが埋め込まれたコンテンツ(Flash)の有効/無効を設定 します。デフォルトでは OFF が入っているため、設定していない場合 Flash は起動しません。無効にしている 場合、本来プラグインが表示される場所にはプレースホルダが代入されます。有効にしている場合は、プレース ホルダの場所にプラグインマークが表示され、タップされた時点でプラグインが有効となります。ただし、有効の場 合に、必ずしもクリック無しにプラグインが起動する訳ではないということは注意しなければなりません。 ※ setPluginState ()メソッドは、API_18 から非推奨です。 ④ setScrollBarStyle()メソッドは、スクロールバーの表示方法を設定します。WebView 上にスクロールバー を重ねての表示や、View の padding にスクロールバーの幅の大きさを設けて埋め込みをすることによって、 padding 領域内もしくは、設定した View の端に描画を行う処理を挟むことが可能です。全体描画を行うコ ンテンツの場合は、コンテンツ内容が padding 領域によってずれる、画面サイズによって行う判定が困難とな る場合があるため、留意しておく必要があります。 style に入力できる値は以下の 4 種類あります。表示による効果は、各々で異なります。 (水平スクロールバーおよび垂直スクロールバーともに、この選択で変更されます) 1. SCROLLBARS_INSIDE_OVERLAY 2. SCROLLBARS_INSIDE_INSET 3. SCROLLBARS_OUTSIDE_OVERLAY 4. SCROLLBARS_OUTSIDE_INSET Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 283 INSIDE と OUTSIDE の違いは、padding 領域による影響を受ける/受けないです。 INSIDE :スクロールバーの端が padding 領域の影響を受ける。 OUTSIDE :スクロールバーの端が padding 領域の影響を受けない。 OVERLAY と INSET の違いは、スクロールバーの幅の領域確保をする/しないの違いです。 OVERLAY :スクロールバーの幅の領域を確保しない。 INSET :スクロールバーの幅の領域を確保する。 ① ② ③ ④ 図 45 setScrollBarStyle()メソッドの style にて設定できる値の比較 (① SCROLLBARS_INSIDE_OVERLAY (③ SCROLLBARS_OUTSIDE_OVERLAY ② SCROLLBARS_INSIDE_INSET) ④ SCROLLBARS_OUTSIDE_INSET) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 284 ⑤ setUseWideViewPort ()メソッドは、viewport 利用の有効/無効を設定します。無効の場合は、 viewport のスケールが設定されず、WebView はコンテンツのそのままの大きさでレンダリングします。 ① ② 図 46 viewport 利用の有無による比較 ③ (① viewport 利用無し ② viewport 利用有り ③ viewport 利用有り(縮小ボタン押下後)) ※viewport 利用有りの場合、ズームボタンの縮小が活性していることが分かります。 ⑥ setAllowContentAccess()メソッドは、コンテンツプロバイダのコンテンツを読み込むかどうかを設定します。 Android には、自アプリケーションから他のアプリケーションケーションに対して、自分が持っているデータを読み 書きする手段を提供する「コンテンツプロバイダ」という仕組みがあります。この仕組みを使用すると、アプリケー ション間でデータの受け渡しができるようになります。WebView も、コンテンツプロバイダが提供するコンテンツ を読み込むことができます。コンテンツプロバイダが提供するデータは、アドレスのスキーマが「content」で始ま る次のようなアドレスを持ちます。 コンテンツプロバイダで用いるアドレスの例 content://com.example.contentprovider/somedata/12 ⑦ setDatabaseEnabled ()メソッドは、WebSQL データベースの有効/無効を設定します。この設定を有効 にするには、データベースをどこに配置するかを併せて設定する必要があります。 したがって、setDatabasePath でパスを指定します。 ⑧ setDomStorageEnabled ()メソッドは、Web ストレージの有効/無効を設定します。DomStorage とい う名称になっていますが、setDomStorageEnabled メソッドでローカルストレージとセッションストレージを有 効にします。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 285 ⑨ setJavaScriptCanOpenWindowsAutomatically()メソッド および webSettings.setSupportMultipleWindows()メソッドは、JavaScript の windows.open メ ソッドに対する挙動を制御します。これらが有効であれば、JavaScript の windows.open メソッドを使用す ることで新しいウィンドウを開くことができます。しかし、ハイブリッドアプリケーションであれば不要でしょう。したがっ て、通常の遷移になるように、これらは無効に設定します。 ⑩ webSettings.setSaveFormData()メソッドおよび webSettings.setSavePassword()メソッドは、 WebView で読み込んだ HTML のフォームで入力された値やパスワードを保存する機能を有効/無効にしま す。有効にした場合、WebView はフォームの入力値をアプリケーションのサンドボックス内のファイルに保存し ます。アプリケーション内に XSS(クロスサイトススクリプティング)の脆弱性があった場合に、Ajax や iframe を 通じて、WebView が保存する入力値やパスワードが盗まれることがあります。セキュリティ上きわめて危険で あるため、この設定は無効にすることが望ましいでしょう。 WebSetting クラスには、他にも多くの設定項目があります。詳細は、以下のサイトをご参照下さい。 【公式サイト】 http://developer.android.com/intl/ja/reference/android/webkit/WebSettings.html それでは、次に WebViewClient の設定方法を見ていきます。 WebView 内の URL リンクをクリック(タップ)した場合、デフォルトの動作は URL を扱うアプリケーションで処理 されます。大抵の場合は、Android 標準ブラウザで開かれます。しかし、この動作を変更し、WebView 内で表 示することも可能です。WebView によって管理されるページヒストリーで、進む/戻るのナビゲーションをユーザに許 可することもできます。 実現するためには、WebView インスタンスに WebViewClient インスタンスを渡します。 これだけで、WebView 内のリンクがすべて WebView 内で処理されるようになります。サンプルコードを以下に示 します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 286 // WebViewClient クラスのインスタンスを登録する webView.setWebViewClient(new webViewClient) { // ページの読み込みが始まったときの挙動を設定する @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { // ・・・ } // ページの読み込みが終わったときの挙動を設定する @Override public void onPageFinished(WebView view, String url) { // ・・・ } // ページの読み込みに失敗したときの挙動を設定する public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { // ・・・ } // リソースを読み込んだときの挙動を設定する @Override public void onLoadResource(WebView view, String url) { // ・・・ } // a タグや location.href が変わったときの挙動を指定する @Override public boolean shouldOverrideUrlLoading(WebView View, String url) { // ・・・ } } WebViewClient クラスには、これ以外にも多くの設定項目があります。詳細は、以下のサイトをご参照下さい。 【公式サイト】 http://developer.android.com/reference/android/webkit/WebViewClient.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 287 6-4. ハイブリッドアプリケーションの基本 6-4-1. ハイブリッドアプリケーションとは 「ハイブリッドアプリケーション」は、ネイティブアプリケーションに WebView を組み込んで Web アプリケーションの実 行も可能にした形式のアプリケーションです。ネイティブアプリケーションとしての枠組みを持ちながら、Web コンテンツ の表示や Web アプリケーションの実行を行えるため、ハイブリッドの呼び方が用いられています。 アプリケーションの内部構造は、ネイティブアプリケーションと HTML5 などの Web 技術で複合的に開発されており、 両方の特性を持つことから注目を浴びています。 ハイブリッドアプリケーションが持つ主な特性を以下に示します。 • Android や iOS などの複数のプラットフォームに対応できる • HTML5 などの標準化されている Web 技術によって開発できる • モバイル OS のネイティブ API を利用できる モバイルの分野では、ネイティブアプリケーションや、Web アプリケーション、さらにはハイブリッドアプリケーションといっ た用語をよく耳にする機会が増えてきました。では、その違いは何でしょうか。 Web アプリケーションは、モバイルに最適化された Web ページであり、Android 端末にインストールされる独自の プログラムはありません。Web アプリケーションは、ブラウザアプリケーションによって実行されます。 ネイティブアプリケーションとハイブリッドアプリケーションは、apk として Android 端末にインストールして使用されま す。ハイブリッドアプリケーションは、自身に埋め込まれた WebView 機能を利用します。ハイブリッドアプリケーション も、Web アプリケーションと同様に HTML の Web ページをレンダリングします。 それでは、ネイティブアプリケーションと Web アプリケーションの特徴、メリット/デメリットについて見てみましょう。そこ から、ハイブリットアプリケーションの具体的な特徴とメリット/デメリットを説明します。 まずは、ネイティブアプリケーションの特徴、メリット/デメリットについて説明していきます。 ネイティブアプリケーションは、モバイルデバイス内にあるため、アプリケーションへのアクセスはデバイスのホーム画面 上のアイコンを通して行います。特定のプラットフォーム専用に開発されているため、デバイスのハードウェア的機能を 最大限に活用することができます。たとえば、カメラや GPS、加速度センサー、コンパス、連絡先等が利用可能で す。また、ジェスチャーの組み込みも可能です。さらに、デバイスの通知システムを利用できますし、オフラインでも機 能させることが可能です。 ネイティブアプリケーションのメリットは、モバイルデバイスのハードウェア的機能にアクセスすることができ、必要な機 能を実装したり、パフォーマンスを最大限に引き出ることができることと言えるでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 288 反対にネイティブアプリケーションのデメリットは、以下に示すものが挙げられます。 • それぞれのネイティブ環境にあわせて開発する必要がある(プラットフォーム依存性) • OS のバージョンやアップデートによる影響を受けやすい(プラットフォーム/バージョン依存性) • 実行端末によって、異なる動作を示したり、動作しないことがある(端末依存性) 次に Web アプリケーションの特徴、メリット/デメリットについて見てみましょう。 Web アプリケーションは、さまざまな点でネイティブアプリケーションのようなルックアンドフィールを持ちます。実行(起 動)はブラウザで行い、通常は HTML5 で書かれています。ユーザが初めて Web アプリケーションにアクセスするとき は、Web ページへアクセスするような形となります。つまり、特定の URL へ移動し、そのページへのブックマークを作 成することで自分のホーム画面に「インストール」する形となります。Web アプリケーションが実際に普及したのは、 HTML5 が登場し、ブラウザでもネイティブ的な機能が使用可能だと分かってからになります。現在では、HTML5 を利用するサイトは増加しており、Web アプリケーションと通常の Web ページとの違いは、曖昧になってきていま す。 Web アプリケーションのメリットとしては、以下の点が挙げられます。 • 実行コードはサーバで管理されるため、常に最新の状態である • 実行環境としてブラウザに依存するため、クロスプラットフォーム性が高い 反対に、デメリットとしてはブラウザで実行することに伴う以下の点を挙げることができるでしょう。 • 使用するブラウザにより、挙動が異なる可能性がある • デバイスへのアクセスが、ネイティブアプリケーションに比べて制限されている。(例:カメラを起動でき ない) • ネイティブアプリケーションに比べて、特に画面遷移を中心に動作速度が遅い場合がある HTML5 による Web アプリケーションは遅くて重いという否定的な見解を拡大させたできごととして、 Facebook の事例は有名です。 Facebook は、iOS 向けのアプリケーションを HTML5 ベースで開発していましたが、起動が遅い・反応が重い など不評でした。そこで方針を転換し、ネイティブアプリケーションとして開発しました。同社のザッカーバーグ氏 は、この方針転換に際して「HTML5 に賭けたのは失敗」と発言し、注目を浴びました。 しかし、この結果に疑問を持った一部の開発者たちは、HTML5 による Facebook アプリケーションを開発し、 実装手法によってはネイティブアプリケーションに匹敵もしくは上回る動作性能を持たせることができると主張し ました。したがって、Web アプリケーションは遅いという通念のみによって、選択肢から排除することはあまり賢 明ではありません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 289 両者の簡単な比較を、以下の表に示します。 ネイティブアプリケーション Web アプリケーション Objective-C、Java など HTML や CSS、JavaScript OS ごとの開発、動作環境 動作環境はブラウザだけ インストール作業が必要 インストール作業はいらない ダウンロード時間も必要 ダウンロード時間は必要 動作が速い 動作が遅め デバイスへのアクセスが可能 デバイスへのアクセスは制限が多い スマホ専門の技術者は少ない Web 技術者は多い インストールしないと使えない URL だけで拡散しやすい 最後に、ハイブリッドアプリケーションの特徴、メリット/デメリットについて説明します。 ハイブリッドアプリケーションは、ネイティブアプリケーションであり、Web アプリケーションでもあります。ネイティブアプリ ケーションのように、そのモバイルデバイスで利用可能な多数のハードウェア的機能を活用できるという利点がありま す。そして、Web アプリケーションのように HTML によってブラウザ内にレンダリングされますが、そのブラウザはアプリケ ーション内に埋め込まれていることに注意する必要があります。 既存 Web ページ用のラッパーとしてハイブリッドアプリケーションが開発されることは少なくありません。そうすることで、 別のアプリケーションを開発するための労力をあまりかけずに、アプリケーションストアでの存在感を確保したいと目論 めるからです。また、ハイブリッドアプリケーションはクロスプラットフォーム開発が可能なことからも高い人気を持ってい ます。モバイル向けのさまざまな OS 上で、同じ HTML コードコンポーネントを再利用できるので、開発コストと工数 の大幅な削減につながります。なぜならば、プラットフォームを横断したデザインやプログラムを支援するツールを活用 することも可能になるからです。 ハイブリッドアプリケーションのメリットは、主に以下のとおりでしょう。 • アプリケーションの実装に、比較的習熟が容易な言語(HTML、CSS、JavaScript)を使用すること が可能 • Web コンテンツを利用する機能においては、クロスプラットフォーム性がある • 最新の情報や機能は Web コンテンツとしてサーバから提供することができるため、保守性が高い • カメラ等の、一部のハードウェア的機能を制御することが可能 • Web コンテンツとして構成する領域では、Web デザイナを活用できることもありレイアウトの自由度 が高い (※1) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 290 反対に、デメリットは以下の点です。 • 端末のハードウェア的性能をフルに活用することが困難 • 同様に、HTML5 では原理的に実現できない機能がある ※1 専用アプリケーションとして開発する場合、レイアウト調整を行う際にアプリケーションのアップデートが必要とな ります。新機能開発→リリースにタイムラグが発生しますし、新バージョンと旧バージョンのユーザが混在する事 にもなります。一方ハイブリッドアプリケーションの場合、アプリケーションのアップデート不要で HTML や CSS を 編集する事でレイアウトを変更することが可能です。また、HTML、CSS で細部のレイアウト調整が可能なた め、複雑な UI を構築したい場合に向いています。 ここまでネイティブアプリケーション、Web アプリケーション、ハイブリッドアプリケーションについて各々の特徴やメリット /デメリットを見てきました。その中で、今後のアプリケーション開発において「どのアプリケーションタイプを選択すること が望ましいか」についても考えることにしましょう。 はじめに結論となりますが、「必ずこのアプリケーションにしなければならない」ということはありません。 ネイティブアプリケーションやハイブリッドアプリケーション、Web アプリケーションはどれも、モバイルユーザのニーズに 応えるための手段です。そこには、唯一最良の解決策というものはありません。なぜならば、それぞれに長所と短所 があるからです。したがって、どのアプリケーションを選択するかは、案件の性質に基づいて何に重点を置くかにより左 右されます。 それでは、以下の観点についてそれぞれ見てみましょう。 • デバイスの機能 GPS やカメラ、ジェスチャー、通知などのデバイス固有のハードウェア的機能のすべてに対してアクセスが可能な のは、ネイティブアプリケーションおよびハイブリッドアプリケーションです。Web アプリケーションでも一部の機能を 利用することは可能ですが、限定的なものとなるでしょう。 • オフライン機能 オフライン状態でアプリケーションを動作させる必要があるときには、ネイティブアプリケーションが最適です。 HTML5 ではブラウザ内のキャッシュが利用可能ですが、それでもネイティブアプリケーションと比較するならば、 利用できる内容は限定されたものになります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 • | 291 速度 速度の観点で比較するなら、ネイティブアプリケーションの優位性が高いと言えるでしょう。なぜならば、Web ア プリケーションやハイブリッドアプリケーションは、UI 表示においても通信環境に依存する部分が多くあるからで す。 • メンテナンス アプリケーションのメンテナンスという観点では、ネイティブアプリケーションがもっとも複雑でコストを要します。特 に、同一のアプリケーションを複数のプラットフォームに対して提供している場合、開発者の負担はとても大きな ものになります。なぜならば、アプリケーションのパッケージ全体を新しいバージョンに更新する必要があるからで す。その一方で、Web アプリケーションやハイブリッドアプリケーションの Web コンテンツ部分に係るメンテナンス は、Web サイトのメンテナンスと同じであり、必要に応じた頻度で、あるいは必要に応じて定期的に行うことが 可能です。言うまでもなく、サーバサイドの更新はただちにユーザ環境に反映されるため、UI やデータの定期 的な更新が求められる場合には有用な手法です。 • プラットフォームからの独立性 Web アプリケーションは、プラットフォーム(端末/OS/バージョン)からの高い独立性を有します。ハイブリッドアプ リケーションも、ネイティブアプリケーションと比較するなら独立性が高いといえるでしょう。ブラウザの違いは、サポ ートする HTML5 のバージョンの違いと関連を持ちます。しかし、プラットフォームからの独立性を重視するので あれば、Web アプリケーションは主要な選択肢となります。Web アプリケーションやハイブリッドアプリケーション であれば、コードの一部もしくは全部を再利用することが可能だからです。 • 開発に求められるスキル ネイティブアプリケーション向け言語に習熟した開発者は、HTML5 や JavaScript などの Web 向けマークア ップやスクリプト系言語を簡単なものとして見る傾向があります。しかし、安定した効率的な Web サイトの構 築には多大な経験値が求められます。それらは一朝一夕で身につけられるものではありません。したがって、ハ イブリッドアプリケーションや Web アプリケーションの Web コンテンツ部分に、手軽なものとして手を出すことは望 ましくありません。 さらに Web サイトの構築を行ってきた人たちがオブジェクト指向のネイティブアプリケーション向け言語を習得す るためには、さらに多くの学ぶべきことがあるでしょう。たとえば、モバイルデバイス向けプログラミングは組込み開 発(.ハードウェアを制御することを目的とする開発)であるという概念を理解するところから始まります。 これらのことから、開発工数の観点から「どのアプリケーションタイプが、開発工数を低く抑えられるのか?」との 問いには、正解がないと言えます。顧客の要求、開発チームの構成、実現すべき要件、それらに基づき、状 況は多々異なってくるため、慎重に選択することが望ましいでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 • | 292 ユーザインタフェース OS が持つ標準的な UI を確実に実現するために優位なのは、ネイティブアプリケーションです。なぜならば、 Android や iOS において標準 UI は API によって提供されており、容易に利用することができるからです。 もちろん、HTML5 でも同様の UI を実現することは可能ですが、そのためにはナビゲーションをはじめとする各 UI について正しく理解し、自身で実装することが求められます。この作業を怠るなら、ユーザが既に慣れ親し んでいる、もしくはユーザが期待している UI とは異なる危険性が高くなります。 ハイブリッドアプリケーション開発用には、多くのフレームワークが提供されています。要件の特性に応じて、適切なフ レームワークを選択することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 6-4-2. | 293 ハイブリッドアプリケーション設計について 本項は、ハイブリッドアプリケーションの設計について説明します。 ハイブリッドアプリケーションの設計を行う上で最初に考える必要があるのは、「とりあえず HTML で、ではなく、どち らで実装したほうが価値のある画面なのか?」ということです。 この点を踏まえた上で、以下の点について考察していきましょう。 ① アプリケーションの品質 言うまでもなく、経験と実績のある技術で開発することが、品質面でトラブルが起こる可能性を小さ くします。 開発手法とアプリケーションの品質は、必ずしも相関関係を持ちません。お持ちの経験と、選択が 望ましい手法とをよく比較吟味し、バランスを取った上で最適な手法を選択することが重要です。 ハイブリッドアプリケーションは、ネイティブ/Web 両アプリケーションの長所や短所をあわせ持っていま す。効果的な実装は大きな価値を生みますが、自分が詳しくないサイドの実装がおざなりになること もあります。したがって、繰り返しになりますが、どのようにバランスを保つかを十分に検討することが求 められるでしょう。 ② 実装コスト・工数 「HTML5 で開発することで、どの程度の開発コスト・工数節減が可能になるのか?」 ネイティブアプリケーション開発を行う場合、各 OS に対応した言語の知識が必要です。一方で HTML5 は、プラットフォームに依存しない技術です。6-4-1.でも述べたとおり、考慮すべき様々な 状況があるため、どちらがコスト・工数節減になるのかを一律に論じることはできませんが、複数のプ ラットフォームにネイティブ対応しなければならないとすれば、言うまでもなく開発規模は肥大化しま す。したがって、どの手法が案件にとって最適かを正しく見極める必要があります。 ③ メンテナンス・運用コスト 「各アプリケーションの開発手法で、データの更新頻度や手間はどのように変わってくるか?」 この判断も、「② 実装コスト・工数」で検討すべきことと密接な関連を持ち、一律の正解はありませ ん。しかし、メンテナンスの内容によっては、明確なメリットを持つ手法が限られてくる場合がありま す。 たとえば、システムの UI に影響を与えるような更新が頻繁に発生するのであれば、ハイブリッドアプリ ケーションもしくは Web アプリケーションが望ましい選択肢でしょう。なぜならば、Web サーバサイドの 更新を行うだけで、モバイルデバイスにインストールされたアプリケーションに影響を与えることなく UI を更新することが可能だからです。 反対に、UI が変化することなくデータの更新のみを行う場合は、ネイティブアプリケーションが有用で しょう。なぜならば、データ部のみに限定して通信を行い、UI を構成するための通信を行わなければ、 通信によるリソースの消耗を防ぎユーザの待機時間を減らすことにもつながるからです。 これら運用効率は、開発手法を選択するにあたって重視すべき観点です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 294 ネイティブアプリケーション実装と HTML による実装には、それぞれに得手不得手があります。まずその点を理解し、 ネイティブで行うことと、HTML で行うことの切り分けが必要となります。あらかじめ HTML5 で不得意な部分を理解 し、その部分はネイティブで実装して補えるようにしていくことが望ましいでしょう。 ネイティブと HTML5 が、それぞれ得意としているものを、以下の表に示します。 ネイティブ実装 HTML5 による実装 高度なグラフィック処理 開発速度 リアルタイム処理 学習コスト 端末性能を活かす クロスプラットフォーム性 メモリ節約 OS の最新機能 ネイティブと HTML5 の切り分けを行う上では、以下のような観点があります。 • パフォーマンス上の問題 • 原理的な問題 • UI 上の問題 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 295 6-5. WebView に適したコンテンツ設計 6-5-1. コンテンツ設計の考え方の基本 PC 向けとモバイルデバイス向けの Web コンテンツ設計におけるベストプラクティスは、大きく異なる要素がいくつも あります、言うまでもなく、Web 技術はマルチデバイス・マルチプラットフォームに対応するものであり、基本的には同 一の実装が利用できることに違いはありません。しかし、それぞれに適した実装には違いがあります。この違いに注 意を払ったコンテンツ設計は、顧客満足度の向上と密接な関連を持ちます。 それでは、PC とモバイルデバイスにはどのような違いがあるのかを見てみましょう。これらの違いを踏まえて、最適な 機能やコンテンツを設計する必要があります。 ハードウェア 処理速度(速い/遅い) Web ページのレンダリング速度や JavaScript の処理速度に差異があります。 これらは、ハードウェアの性能差が引き起こす差異であり、モバイルデバイスで動作させるコンテンツの 場合は特に留意する必要があります。 昨今は、JavaScript の高速化やブラウザ(レンダリングエンジン)の性能向上が顕著であり、最新 端末や将来の端末を想定する場合にはさほど問題にならないかもしれません。しかし、低スペックな 端末でも実行する可能性がある場合には、ユーザビリティの観点から無視することのできないポイン トです。 スクリーンサイズ(大きい/小さい & 縦画面/横画面) モバイルデバイスは PC と比較してスクリーンサイズが小さく、文字の視認性、ボタンやテキストリンクな どの UI の操作性に差異があります。しかし、PC とモバイルデバイスの画面解像度は、スクリーンサイ ズに比例した大きな違いはありません。このことは、解像度を基準にした画面を設計をするという落 とし穴の原因になります。PC と同じ基準で解像度による画面設計を行った場合、顧客が不満を持 つ可能性は劇的に高くなるでしょう。 加えて、モバイルデバイス特有の 縦画面/横画面 の違いを考える必要があります。この点も、PC とは大きく異なります。 PC 上での再現で満足するのではなく、操作性と視認性を実機でこまめに確認することは、モバイル デバイス向けコンテンツ設計においてとても重要なポイントです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 296 入力・操作方法(キーボード・マウス/タッチインターフェース) 画面を指先で触れて操作するもの(タッチデバイス)は、モバイルデバイスの大部分を占めます。つま り、キーボードやマウスといった機器を介しません。「マウスで操作する」という、PC 用の Web コンテン ツ開発者にとっての大前提が異なることは、軽視してはならないポイントです。 加えて、文字入力時に画面の広い範囲をソフトウェアキーボードが占めることも忘れないようにしまし ょう。PC と同じように、画面全域を常に表示/入力領域とすることはできません。初めてモバイルデバ イス向けコンテンツを作る人たちの UI 設計は、ソフトウェアキーボードを考慮していない場合がありま す。このことも、見落としがちになるため注意が必要なポイントです。 図 47 ソフトウェアキーボードが画面を占有する領域 ネットワーク 速度(速い/遅い)・ 接続(安定/不安定) モバイルデバイスが通信に使用する無線通信ネットワークは、速度の観点では高速有線に匹敵す る水準となってきています。しかし、有線に比べて無線通信は安定性を阻害する要因が多いため、 注意する必要があります。移動しながらの利用が多い場合は特に、必然的に接続が不安定になり がちです。 コンテキスト PC とモバイルデバイスとでは、利用される環境(場所・時間・社会環境・状態)が異なります。たとえば、 PC が屋内で据え置きのディスプレイに対面してまとまった時間で利用されることが多いのに対し、モバイル デバイスは屋内・屋外どちらでも、時には移動しながら、細切れの時間で利用されることが多いといった差 異です。そのアプリケーションがどういうコンテキストで利用されるのか、開発の初期段階できちんと想定す ることは、顧客満足度と密接な関連を持ちます。 ※ コンテキスト = 場所・時間・社会環境・状態 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 297 UI(ユーザインタフェース) スマホにはマウスは無い(デバイス) スマートフォンやタブレットなどのモバイルデバイスは、そのほとんどがタッチデバイスです。言うまでもなく、 タッチデバイスでは指先を画面に接触させて操作を行います。そのため、従来のポインティングデバイ ス(マウスなど)を用いる機器とは操作方法が根本的に異なります。 モバイルデバイス向けのコンテンツ設計・インターフェース設計においては、まずその点に留意する必 要があります。 マウスで操作するのか、指で操作するのか - これは、とても重要であるにもかかわらず見過ごされ やすい違いです。したがって、指先での操作に配慮した、"指先に優しい” 設計が望まれます。 ※ 指先に優しい とは? = 押しやすい&触りやすい ※ 指先に優しくないのは? = マウス前提の設計 タッチ要素のサイズと間隔 マウスは 1pixel 単位での操作も可能なのに対し、指は領域を操作します。指では、マウスのような pixel 単位の緻密な操作はできません。リンクを押すための UI 設計ガイドラインは、Google も Apple も、UI ガイドラインとして公開しています。 モバイルデバイス向けの UI としては、指でポイントした際に隠れる領域に重要な情報があったり、押している間に 変化してユーザの知りえない情報に変わる UI 設計も避けなければなりません。画面に表示されている情報が、操 作中に知るべき重要なものであるにもかかわらず見えなくなるような設計は、ユーザビリティの観点から望ましくありま せん。 そのため、次のような設計は避けなければなりません。いずれも、PC 用 Web コンテンツの設計に習熟したエンジ ニアが陥りやすい落とし穴です。 • ポインティングデバイスによる操作に特化した機能 たとえば、数ピクセル単位での緻密なポインタ操作を必要とする機能が挙げられます。タッチデバイスでは、 マウスポインタよのうな緻密な“点”の操作はできません。指で操作するのは“領域”であることを忘れない ようにしましょう。指の太さや画面に接触する指先の面積から判断して、その領域が操作可能か検討す る必要があります。 たとえば、テキストリンクが挙げられます。テキストリンクは、文字の大きさや行の高さが決まっており、押せ る領域は 1 行の高さしかありません。しかし、行を連続して個別のテキストリンクが張るような設計になっ ている場合、ユーザが誤ったリンクを押しやすいなどの問題が発生します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 298 図 48 (左:テキストリンクが詰まったタップしにくい状態 右:テキストリンクが離れたタップしやすい状態) • ポインティングデバイスによる DOM イベントに依存した機能 Web 設計が W3C の規約に準拠していることは、モバイルデバイスにおいて適切な設計であることを保 証する根拠とはなりません。 DOM イベント規則に準拠し標準プロパティをフォールバックしていれば、モバイル Web でも正常に動作 するとの前提を持った開発者も少なくありませんが、これは必ずしも正しくありません。なぜならば、PC 用 Web 設計で一般的に使われている手法のいくつかは、モバイルデバイスでは望まない動作の原因となる ことがあるからです。 ※DOM イベント - HTML とスクリプトを分離して画面を操作するための手法であり、それにより発生さ せるイベントを DOM イベントと呼ぶ。 たとえば、mouseover イベントに依存した機能が挙げられます。mouseover イベントとは、任意の要 素上にマウスポインタが乗ったときに発生するイベントですが、タッチデバイスでは期待通りに動作しません (似たものに CSS の:hover 擬似クラスがあります)。そのため、ナビゲーションなどの重要な機能を mouseover イベントに依存させてしまうと(ポップアップメニューなど)、タッチデバイスではその機能が使え なくなります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 299 こういったトラブルは、モバイル専用コンテンツではなく、PC 向けに開発されたものを互換化したコンテンツ で起こりがちです。なぜならば、元々が PC 向けのコンテンツであるため、mouseover イベントなどに紐 付いた機能がそのままタッチデバイスでも使用されることになるからです。 モバイルデバイスのブラウザによっては、これらの処理をモバイル用に変換して別のイベントを発生させるこ ともあります。たとえば mouseover なら、一回目タップ時に mouseover 時のイベントを、二回目でク リックイベントを発生させる、といった場合です。これらは、ユーザにとって「二回タップしないとボタンが押せ ない」操作を求めることになり、ユーザビリティを低下させます。あるいは、mouseover はあっても mouseout が実行されず、ボタンなどの要素が over のままになることもあります。これも、望ましいことで はありません。 このように、モバイルデバイスには専用の最適化が必要であり、PC 用コンテンツの画面サイズだけを調整 したような、いわゆる「ワンリソースの互換化」をもって最適化とするのでは不十分です。互換化はあくまで パッチであり、CSS や JavaScript を後から走らせて上書きすることでモバイル用に調整するものです。 PC 用およびモバイルデバイス用に「ツーリソース設計」を行うことが望ましいでしょう。 モバイルデバイス用コンテンツには、ポインティングデバイスとしてマウスに最適化された実装が含まれてい ないか、十分な注意を払うことが重要です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 300 マルチデバイス・マルチプラットフォーム化が容易であるということは、Web コンテンツの最大のメリットです。 つまり、互換性の高い実装が実現できるといえるでしょう。しかし、さまざまな差分要因によって、ときに互 換性が損なわれます。 MVC(Model View Controller)モデルは、互換性を維持する上でとても有用なアプローチです。マル チデバイス・マルチプラットフォーム向けに MVC モデルを活用したアプリケーション開発の知見をお持ちであ れば、Web コンテンツの開発に応用することは、有用な場合があります。 リソースに制限があることを考慮する(ネットワーク・デバイス) • ネットワーク 高速な Wi-Fi ネットワークに接続しない限り、モバイルデバイスは通常、移動回線網に接続して使 用されます。そのため、コンテンツに使用する各リソースは、不要な情報を削除したりファイルの圧縮 を行うなどして、ファイルサイズを軽減します。また、HTTP リクエスト数を減らす工夫も必要です。 • メモリ・バッテリー モバイルコンテンツでは、デバイスの処理速度に不釣り合いなスクリプトを実行させるべきではありませ ん。高負荷なスクリプトはメモリを消費し、バッテリーを消耗させます。これらは、端末全体の安定性 を低下させることにもつながります。 PC 上のエミュレータと実機は違うことをきちんと理解する(デバイス・ネットワーク・コンテキスト) 開発中に逐一実機で表示・動作検証を行うのは手間がかかります。そのため、PC で動作するエミュレー タで検証を行うことが多いでしょう(エミュレータとは、Android SDK に含まれる純正のエミュレータを指し ます。それ以外は推奨しません)。エミュレータの使用自体は問題ありませんが、検証する内容によっては エミュレータでは代用にならない場合があることに注意する必要があります。 エミュレータでなく PC 用の Web ブラウザを検証に使用するのは推奨できません。PC 用 Web ブラ ウザ(Firefox, Chrome など)を検証に用いても、Web 標準技術を仕様通りに実装する限り、 それほどの齟齬は生じません。しかし、レンダリングエンジンが異なる以上、PC 用 Web ブラウザでの 検証では不完全な結果しか得られません。あくまでそういった前提を理解し、かつその検証方法にメ リットがあるのであれば構いませんが、いずれにせよ最終的には実機での検証が必要不可欠です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 301 デバイスの処理速度 エミュレータが動作する PC とモバイルデバイスとでは、CPU やメモリなどのハードウェア構成が異なりま す。したがって、実機でなければ正確な動作検証はできません。主には高負荷なスクリプトや、スクリ プト・CSS を問わずアニメーションを伴う動きなどで差異が現れます。 ネットワーク速度 たとえば、リソースのダウンロード速度に差異があります。実機の移動通信網でなければ、実際の速 度は体感できません。通信速度が低速となる環境下でも検証できれば、より良いでしょう。 ネットワークの接続安定性 移動通信網を使用するモバイルデバイスは、接続状況が不安定になりがちです。移動などの理由 から、思わぬオフライン状態になることも珍しくありません。高速 Wi-Fi が安定して使用できる開発 環境は、顧客の利用環境とは異なることを忘れてはなりません。急なネットワーク切断によるエラー 処理や、オフラインモードでの動作状況を想定した実装を行い、実機での検証を行うことが重要で す。 実際の操作感触・スクリーン上の要素のサイズ感 エミュレータは、PC のディスプレイに表示される一ウィンドウでしかなく、実機のディスプレイを実物大 で再現したものではありません。そのため、たとえば UI 要素の各サイズの妥当性や操作性の良し悪 しは、実機に表示されたコンテンツで検証する必要があります。また、多くの人の意見を聞きながら UI を調整することは、顧客満足度の向上に大きく貢献するでしょう。 グローバルな問題(ブラウザの特定バージョンに依存するもの) 技術仕様通りにコードを書いても期待どおりの表示や動作が得られない場合は、Web ブラウザやレ ンダリングエンジンの問題が考えられます。それらブラウザの特定バージョンに依存する問題は、エミュ レータでも多くの場合再現します。しかし、内容によっては再現されない可能性もあります。普段か らの地道な情報収集は、問題の切り分けに有用です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 302 問題例 z-index で重ねた上位要素(メニューなど)を突き抜けて下位要素がタップに反応してしまう http://code.google.com/p/android/issues/detail?id=6721 モバイルコンテンツ向け UI でよく見られるポップアップメニューなどで発生します。ポップアップメニューは z-index プロパティを用いて本文となる領域の上位に配置する実装がなされますが、この上位要素 をタップした際に、それを突き抜けて下位要素がタップに反応してしまう問題です。反応する要素は フォームコントロールやリンクなどのタップ可能な要素で、通常タップされたときと同様にアウトラインを 表示させるなどの反応することがありました。 図 49 上位要素を突き抜けて、下位要素がタップに反応するイメージ ローカルな問題(機種に依存するもの) 上記のようなブラウザの特定バージョンに依存する問題は、機種を問わないグローバルな問題と呼ばれま す。それに対し、機種特有のローカルな問題も存在します。機種特有の問題は、まさしくその機種のみで 発生する問題であり、実機で検証する以外に確認する手段はありません。 機種依存問題に対する対応は、多くの場合は問題が発生してから対症療法的に行われます。しかし、 事前にデバイスを限定することが可能であるならば、必須要件が正常に動作することを検証した機種を 導入するよう顧客に提案できます。そうすることで、機種に依存した問題に後追いで対応するリスクを劇 的に低減することが可能です。 法人向けアプリケーション開発においては、コンシューマ向けアプリケーションと異なって機種を限定できる 機会が多いかもしれません。そのようなプロジェクトマネジメントも、開発上の問題を未然に防ぎ顧客満 足度を向上させる上で有効です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 303 モックアップツールの出力を本番用に流用しない モックアップツールは、顧客とのイメージ合わせを行うために使用される場合があります。しかし、それらのツ ールで表示できるからといって、実機でも正常に動作するという保証はありません。むしろ、本番環境では 問題となるソースが出力されることも少なくありません。モックアップの出力したソースを本番環境用に流 用することは、避ける方が望ましいでしょう。 上記の例のような問題はいずれも、言い方を変えると「デバイス依存」しているといえます。グローバル/ローカ ルの別を問わず、使用するデバイスに依存します。したがって、ワンリソース設計で大きなコンテンツを設計する と、問題の切り分けや分離に必要以上の労力を求められることが少なくありません。「デバイスが異なる場合は、 リソースを分離する」ことは、開発やメンテナンスの生産性を向上させる重要なポイントです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 6-5-2. | 304 PC 用コンテンツを移植する際の注意点 既存の Web コンテンツを PC 用と共有する場合、単なる画面サイズやレイアウトの調整にとどまらず、縦横画面 の違いという根本的な差異もあり、モバイルデバイス用に最適化する必要があります。本項では、その基本的な考 え方を取り上げます。 PC 用コンテンツの互換化 ■前提 Web コンテンツは本来、PC 用・モバイル用の区別はありません。いずれも、基本的に同じ規則によって表示さ れる Web コンテンツだからです。したがって、PC 用に作成されたコンテンツであっても、仕様通りに標準技術を使用 する限りは特別大きな問題は起こらずに表示されるように見えます。モバイルブラウザでも不足なく表示され、機能 することでしょう。 しかし、PC とモバイルデバイスには、6-5-1.でも述べたように、コンテンツのあり方の前提となるべき違いがありま す。それらの違いを考慮する必要があります。 もっとも重要ことの一つは、モバイルデバイス向け Web コンテンツには「何をさせないのか」ということでしょう。それら のいくつかを、以下のとおり見ていきましょう。 除外することが望ましい要素・機能 Flash コンテンツ モバイルブラウザ向けの Flash Player は、すでに開発が終了しています。したがって、使用すること は適切ではありません。なぜならば、アップデートが止まっているということは、セキュリティ面での問題 が発生した場合に開発側の対処が行われないことを意味するからです。したがって、JavaScript な ど別の技術で代替コンテンツを用意するか、致命的でなければ取り除く(非表示にする)ことを推奨 します。 PC 用ブラウザ専用プラグインに依存したリッチコンテンツ PC 用ブラウザでは、プラグインと呼ばれるソフトウェアを介して、様々なリッチコンテンツが提供されて きました。 しかし、モバイルブラウザは、そうではありません。 代わりに、HTML5 というリッチコンテンツの取り扱いを前提とした技術仕様が登場しています。そうい った技術を使用して、プラグインに依存しないコンテンツに置き換えることが必要になります。なぜなら ば、PC 用プラグインに依存した機能はモバイルブラウザでは基本的に動作しません。また、ファイル 形式自体を変更することが必要なものもあります。たとえば、動画・音声(Movie/Audio)であれば、 データ形式やコーデックが合わないということもあります。その場合は、コンバート/アウトプットからや り直し、HTML5 で対応可能な形式に作り変えるなどの作業が発生することがあるでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 305 ポインティングデバイスによる DOM イベントに依存した機能(6-5-1.参照) 6-5-1.で述べたように、mouseover イベントに依存した機能などは、タッチ操作であるモバイルデ バイスでは期待通りに動作しないおそれがあります。重要な機能がそれらのイベントに依存している 場合、改修する必要があります。 指先に細かい操作を要求するような機能・ナビゲーション 同じく 6-5-1.で述べたとおり、モバイルデバイスは指で操作するタッチデバイスであることがほとんどで す。そのため、マウスポインタを想定した細かい操作は、モバイルデバイスにおいてユーザビリティを大 きく損ねます。 指での操作に支障をきたしそうな機能やナビゲーションは、改修することが望ましいでしょう。たとえば、 PC 用固定幅レイアウトを前提とした小さなボタンや入力項目などが対象となります。PC であればマ ウスポインタを使用するため、小さなボタンなども不自由なくボタン上でのクリック操作を行うことができ ます。しかし、指で操作する場合はボタンが小さいとタッチ操作しづらい、あるいは誤タッチの原因とな ることもあります。 また、タップするためにピンチインで拡大して操作させるような UI も望ましくありません。PC では問題 なく使えていたものも、モバイルでは使いにくくなることを考慮する必要があります。したがって、PC 用 コンテンツを移植する場合は、実機での検証をこまめに行う必要があります。 Android 開発者サイトの情報は、適切な実装を行う上で有用です。 【公式サイト】 Touch Feedback http://developer.android.com/design/style/touch-feedback.html Metrics and Grids http://developer.android.com/design/style/metrics-grids.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 306 インラインフレーム(iframe) インラインフレーム(iframe)は、現ドキュメントに対して入れ子状に別ドキュメントを配置することの できる要素です。多くの場合スクロールバーを伴い、別ドキュメント上のコンテンツをコンパクトに読み 込む用途などで利用されます。 タッチデバイスで使用されるモバイルブラウザには、スクロールバーがありません。スクロールの最中に一 時的に表示されるものの、通常は表示されません(厳密には OS やブラウザによって異なります)。そ のため、「スクロールバーがあることで、それがインラインフレームであるとわかる」用途で利用されてい る iframe は、モバイルブラウザではユーザから認識されにくい傾向にあります。 インラインフレームとしての操作、つまりスクロール操作を行い、まだ表示されていないコンテンツの続 きを見るといった行動をユーザが取らない可能性があり、コンテンツの存在が無視されてしまうため、 使用しないことが望ましいでしょう。 ff 図 50 PC サイトのスクロールブロックを表示した例の比較 (左:PC ブラウザで表示、 右:同 PC サイトをスマートフォンブラウザで表示) ※上記の図のとおり、スマートフォンで表示した場合には、スクロールバーが表示されません。 スクロールブロック スクロールブロックは、ここでは「スクロールバーを持ち、中身をスクロールできるブロック」を指します。 見た目や操作方法などのユーザから見える部分は、上記のインラインフレームと同一です。抱えてい る問題もインラインフレームと同じであり、使用は避けることが望ましいでしょう。 ブラウザの判定 PC 用・モバイル用のブラウザ判定には、ユーザエージェント(User Agent)文字列を取得し、判定する 手法がよく用いられます。ユーザエージェント文字列の取得・判定は、サーバサイドでもクライアントサイド でも可能であり、要件に応じて適切な方を選択します。 ただし、いずれの場合においても、ユーザエージェント頼みのブラウザ判定は完全な手法ではないことを念 頭に置き、特にセキュリティ要件にかかわる判断には利用しないようにします。 ユーザエージェント文字列は偽装され得る ユーザエージェント文字列は、容易に偽装できます。偽装というと大げさかもしれませんが、たとえば ブラウザのアドオンソフトを使用することでも、ユーザは簡単に任意の文字列に変更できます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 307 ユーザエージェント文字列はブラウザのアップデートによって変更され得る ブラウザがアップデートされる際に、ユーザエージェント文字列が変更されることがあります。実装時の 判定式が将来も通用するのか、まずは前方互換性に注意して判定式を決定する必要がありま す。 その上で、定期的に判定式の有効性をテストし、必要であればメンテナンスを行います。 ユーザのモバイルデバイスごとにコンテンツを制御する必要がある場合は、ユーザエージェントから取 得するブラウザの種類、WebKit のバージョン、モバイル OS のバージョン、Phone/Tablet の区別 などを用いて判断を行い、これらの情報をマッピングしてどこで括るかを要件に応じて考える必要があ ります。ただし、前提としてブラウザを選ばない実装、Web 技術のためモバイル向けとしてワンリソー スであることが望まれます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 308 企業サイトなどでドメイントップページにアクセスした際に、PC であれば PC サイト、スマートフォンであれ ばモバイルサイトに自動的に振り分ける設計(1)のものがあります。加えて、最近はスマートフォンからのア クセスであっても PC サイトを表示し、「スマホ向けサイトはこちら」とのリンクを貼っている設計(2)のサイトも 見られます。 設計(1)のサイトは、スマートフォンに対して PC サイト閲覧を阻んでいるという見方もできるため、その是 非については開発者の中でも議論が分かれています。設計(2)のサイトは「PC ファースト」と呼ばれる手 法で、最近の Web 業界的には主流となりつつあります。ただし、設計(1)のサイトの場合にも、PC サイト のリンクを貼るなどの対応を行っているケースもあります。 いずれにせよ、スマートフォンでも PC サイトを見たいというニーズは多く存在しており、モバイルサイトを作 ったとしても、PC サイトへのアクセスを望むスマートフォンユーザの選択肢を残すことは重要です。 スマートフォンでの Web サイト表示切り替え(PC サイト/モバイルサイト)時に主に選択する表示形式に ついての調査では、20%のユーザが「PC サイト」と回答した例もあります。スマートフォンから PC サイトを 利用するユーザは少なくありません。したがって、スマートフォンからのアクセスだからといってモバイルサイト のみを強制的に閲覧させる設計は、避けることが望ましいでしょう。 22% 20% モバイルサイト優先(58%) 58% PCサイト優先(20%) 特に意識していない(22%) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 309 6-6. 望ましくない UI 小さく、押しにくい タップ可能なエリア(リンク領域)が狭く押しにくいナビゲーションは、ユーザに好まれません。ナビゲーションのデザ インを見直すことができれば改善することが望ましいでしょう。それが無理であれば、タップ可能なエリアを拡張するこ とで対処します。具体的には、CSS で a 要素の範囲を広げます。また、使用するマークアップ言語が HTML5 であ れば「ブロックリンク」も可能なので、情報構造上問題がなければ、リンクを包含要素に設定することで操作性を向 上させることもできます。 例)テキストリンクのリンク領域拡張(破線はリンク領域) テキストリンク テキストリンク 例)包含要素全体をリンク領域にするブロックリンク(破線はリンク領域) 見出し テキストテキストテキスト 見出し テキストテキストテキスト 見出し テキストテキストテキスト 狭い画面スペースを圧迫する 例えば、ユーザのスクロール動作に追従して、常に画面内の特定位置に被さるようなフローティングウィンドウがあ ります。フローティングウィンドウに配置されるコンテンツはナビゲーションや広告など様々ですが、いずれにおいてもユ ーザの操作の邪魔にならないよう、コンテンツの邪魔をしない UI でなければなりません。モバイルデバイスでは利用 できる画面スペースが狭いことを考慮して、本当に有益な要素のみを配置する必要があります。 ホームに追加するとか、Web コンテンツがやる仕事ではないところに手を出してユーザビリティを阻害するような、 絶えず出ていることがメリットにならない実装は行わないことが望ましいでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 310 ナビゲーションが画面の一番下に常に位置するよう、スクロール動作に追従する。 コンテンツの上位レイヤーにあるため、コンテンツが見づらく、誤操作も招きやすい。 ナビゲーション 狭いスクリーンサイズを考慮しない モバイルデバイスでは、十分なスクリーン幅が常に得られる保証はありません。例えば、複数列が並ぶカラムレイア ウトを利用する場合は、カラムの列数をいくつにするか、また、そのカラムに入る情報量はどれぐらいの量かをよく考慮 する必要があります。 スクリーンサイスが狭く、カラムレイアウトでは見づらい。 不必要な折りたたみ モバイルコンテンツでは、画面スペースが狭いゆえに折りたたまれ、コンテンツが隠された状態が初期状態となる UI が多いですが、十分な考慮なしに何もかもを隠してしまうのは良策とは言えません。省スペース化だけがモバイル UI のミッションではありません。本当に隠すべき情報か、折りたたんでおくべき情報かどうか、導入の際には検討が必要 です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 311 画像化テキストを使用する テキストの画像化は避けるべきです。画像化されたテキストは、デバイスのスクリーンサイズや解像度によっては潰れ て判読しづらい場合があります。従来は画像で表現しなければ見劣りしていたボタンや見出しなどの要素も、 CSS3 によって見栄えのする装飾を施すことができるようになりました。加えて、Web フォントを利用することでさらに リッチなビジュアルも実現可能です。 低コントラスト 屋外使用が想定されるコンテンツ・アプリケーションである場合、太陽光下などの画面の視認性が低下する状況を 考慮し、十分なコントラストを確保しておかなければなりません。 コントラストを制御するタグがあるわけではありません。ビジュアルデザインとして、ダークな色調だけでまとめてしまうと、 外に出ると暗く感じる場合があります。 レンダリングエンジンに過負荷を掛ける また、処理内容にもよりますが、短い間隔で継続的に何かを取得したり、計算する処理をさせる機能も負荷が高く、 応答性や操作性の低下につながり、ユーザ体験を阻害することになります。 ネットワークトラブルを考慮しない実装 Ajax を利用するなど頻繁な通信処理を必要とする場合、ネットワークトラブルについて十分な考慮が必要です。 モバイルデバイスでは急に回線速度が低下したり、接続が途切れたりなどのネットワークトラブルが予想されます。予 定外の通信エラーやオフライン状態を考慮しておかなければなりません。 エラーが発生した時に、継続処理をシステムが自動的に実行するのか、何らかのタイミングでユーザに促すのか、適 切なエラー処理の方針を見定めて実装することが必要です。 サンプルソース:JavaScript (JQuery) $.ajax({ type: ‘POST’, url: ‘foo.php’ }) .done(function(data) { // リクエストが成功した場合の処理 }) .fail(function(data) { // リクエストが失敗した場合の処理 // 失敗した場合の処理を書くのは当然だが、“ユーザにとって親切な”処理を書くことが重要 // ユーザに違和感を与えずに自然と“ひとつ前の状態”に復帰するような、Undo 処理を書くことが望ましい // 見た目にも機能としても問題ないようひとつ前に戻す }); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 312 正しくない互換化 PC 用コンテンツを互換化してモバイルデバイスで利用する場合、6-5.で指摘したような適切な対処を行わな いとユーザビリティを損なうおそれがあります。 個々のデバイスに対し過度な最適化にこだわる ボタンの大きさやカラムレイアウトにおけるカラムの個数など、UI のルックを決定する大きな要因にデバイスのス クリーンサイズがあります。モバイルデバイスは様々な大きさのスクリーンを持つため、UI やレイアウトを個々のデ バイス(のスクリーンサイズ)に最適化させるのは現実的に不可能です。そもそも Web の世界においては、ス クリーンサイズにこだわることに意味はありません。Web では様々な要因によって、アクセス状況やユーザ体験 が変化します。そのため、特定の環境に対して最適化を行うような設計手法は、Web の特性自体に即して いないのです。汎用的で柔軟な UI を心がける必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 313 6-7. WebKit と Chromium の違い Android OS バージョンを 4.4(Kitkat)にバージョンアップすることにより、WebView 周辺動作において挙動の 違いが発生します。その理由は、実装ベースが WebKit から Chromium になったことが原因とされています。 Chromium は、WebKit ではなく WebKit からフォークされた新たなレンダリングエンジン Blink を使用しています。 WebKit について ■概要 Apple 社が開発を主導してきたオープンソースの Web レンダリングエンジンです。 元々は、Mac OS X の Web ブラウザ「Safari」のために開発され、Linux や BSD などの Unix 系用のレンダ リングエンジンである KHTML からフォークしています。2005 年に完全にオープンソース化され、Google 社がオープ ンソースプロジェクトとしての WebKit に積極的に関わるようになったことに加え、2008 年に Android と Chrome ブラウザが採用したことにより、特にモバイル OS に関しては多大なシェアを獲得しました。 Web ブラウザ以外では、Apple 社の「iTunes」などにも採用されています。 ■特徴 iPhone/iPad の標準ブラウザである「Safari」、Android の標準ブラウザ(OS 4.3 まで)についても、WebKit が 採用されていることから、スマートフォン向けのブラウザに対して大きなシェアを持っています。ただし、Android につい ては、OS 4.4 以降、レンダリングエンジンを後述する Blink へ変更しています。 多くの開発者による開発が可能という、オープンソースのメリットを活かし、HTML、CSS、JavaScript などサポー トしている規格に対し、早い段階で新しい規格の機能がサポートされます。また、開発者による改良と共にデバッグ も繰り返され、一般ユーザ向けにバグの少ないバージョンをリリースされています。 Android では、Android OS 4.4 から Chromium のレンダリングエンジンが WebKit ではなく Blink になるこ とに伴い、Apple 社は以下に示す機能を、WebKit から削除しました。そのため、現在は主に「safari」のレンダリ ングエンジンとなっています。 • Google V8 JavaScript Engine の排除 • JavaScriptCore」以外の使用を排除 • 描画ライブラリから「Skia」を排除 • Google のビルドシステムである「gyp」を排除 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 314 Chromium について ■概要 Chromium は、Google 社によるオープンソース Web ブラウザのプロジェクト名です。Google Chrome も、 Chromium をベースに開発されています。 Chromium は、レンダリングエンジンに WebKit を採用していました。しかし、技術改良を行うにあたり WebKit と の構成の乖離、またそれを原因とした開発のイノベーション速度の低下傾向を理由に、Android OS バージョン 4.4 より、WebKit からフォークした「Blink」をレンダリングエンジンに採用しています。 ※詳細については、次ページの「WebKit から Chromium(Blink)への移行理由」を参照してください。 Chromium と Google Chrome における機能差を見てみましょう。 以下に、Chromium には含まれていない機能を記載します。 • Flash Player の同梱 • Chrome PDF Viewer の統合 • Google ブランド • 自動アップデート機能 • Google への利用状況やクラッシュレポート送信機能 • RLZ トラッキング送信機能 Chromium は、タブやアプリケーションごとにプログラムのプロセスが分離されている「分離プロセスモデル」を特徴と しています。 ■Blink の特徴 2013 年 4 月 3 日に、WebKit からフォークした新たなレンダリングエンジンです。 Google Chrome はバージョン 28 以降、Opera ではバージョン 15 以降で、WebKit から Blink へ遠田リング エンジンが変更されています。また、Android OS バージョン 4.4 の標準ブラウザにも採用されています。 Blink は、描画ライブラリを「Skia」、ビルドシステムを「gyp」のみとすることで 450 万行ものソースコード削減を実 現しました。このような改良方針により、対応プラットフォームの統一、レガシーな WebKit の API 依存から脱却し ています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 6 章 WebView を利用したアプリケーション開発の注意点 | 315 WebKit から Chromium(Blink)への移行理由 WebKit の構成に関して、Google 社側がアーキテクチャを変更したい部分と、容易に変更可能な部分に溝が あり、その溝は技術改良の阻害となっていました。 WebKit は、複数のプラットフォームに対応するため、プラットフォーム別に個別コードで WebKit API が実装され ており、共通化を図ることは容易ではなく、改良を加えるごとにメンテナンス面での負担が発生します。 また、Apple 社は Safari 向けの改良を、Google 社では Chrome 向けの改良をというように、開発思想の違 いがありました。それら、開発速度の低下を招くボトルネック解消を理由に、Chromium は WebKit から Blink へ プロジェクト分離することとなりました。 また、コードの共通化を目的とした WebKit2 の開発も行われました。しかし、旧 API の置換ではなく併存となり、 WebKit との互換性が失われ、構成の複雑化の要因(ひいては開発速度低下の要因のひとつ)となっています。 図 51 WebKit の構成図 上記の図のとおり、Apple 社が保守する部分と Google 社が保守する部分は異なります。(前述のとおり、分離 プロセスモデルも WebKit2 に組み込まれています。しかし、Chromium の分離プロセスモデルとは実装方法が異 なります) そのため、ビルドシステムの非統一が原因となり、片方の改良がもう片方に影響を及ぼす事例が過去に存在して います。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 第7章 Google サービス&ライブラリを利用した アプリケーション開発 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. | 316 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 317 7-1. はじめに 7-1. はじめに Android OS には、Google のサービスと密接な関連を持つ機能が少なくありません。トラブルを未然に防ぎ顧 客満足度を向上させるためには、それら Google のサービスを正しく理解し、適切な実装を行うことが重要です。な ぜならば、これまで煩雑になりがちだった通信や位置情報の取得などの実装について、大きな効果を期待できるか らです。 また、Android OS の標準機能として提供されてきた各種 API のいくつかが、新たにライブラリとして提供される 流れも目立ってきています。特に、通信や位置情報に関するものに、その傾向が見られます。 本章では、これらの Google サービスやライブラリを利用したアプリケーション開発における注意点を解説します。 各項の基本的な構成は以下のとおりです。 概要 ご説明する新しい機能の概要を整理します。 従前の手法 新しい機能に対応する従前の開発手法を振り返ります。アプリケーションの実装が現在は従前の 手法であったりもしくは新しい機能に移行するに際して、従前の手法が正しく実装されていることは 重要だからです。 新しい機能の基本的な実装方法 新しい機能の基本的な実装方法を、簡潔にご説明します。必要に応じて、Android 開発者サイ トへのリンクを示しています。 新しい機能の基本的な注意点 Android ビギナー向け Android アプリケーション開発に対する経験が浅い方が注意すべき基本的な内容を解説しま す。 従前の手法に習熟した開発者向け 従前の手法での開発に慣れた方が注意すべき点を解説します。 既存システムの移行シーン向け 従前の手法で開発された既存システムを移行するにあたって注意すべきポイントを示します。 本章で紹介するライブラリには、Google Play services を利用するものがあります。 【公式サイト】 http://developer.android.com/google/play-services/index.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 318 7-1. はじめに Google Play services を利用するにあたっては、利用可否の確認を必ず念頭においてください。なぜならば、 一部の Android 端末では Google Play services を利用できないものがあるからです。加えて、端末がインター ネット(WAN)に対して接続できない環境(※)で法人向けアプリケーションが運用される場合もあるため、Google Play services に依存したシステム開発を行う場合は注意が必要です。 ※ たとえば、インターネットに出ることのできない VPN や完全閉域サービスなどが考えられます。 Google Play services を利用したライブラリを使ってアプリケーションを開発する際の基本的な注意点は、以下 のとおりです。 1. Google Play services がインストールされていること 2. 端末がインターネットに接続可能な状態であること 3. Google Play services のインストール時に Google アカウントが使用可能な状態であること(※) ※ Google Play services のインストール後に Google アカウントを削除した場合でも、Google Play services を利用したアプリケーションは動作します。しかし、最新版にアップデートすることができなくなる ため、Google アカウントを設定しない状態で使用することは望ましくありません。 なお、Google Play services は 2013 年 10 月 31 日より、Android OS バージョン 2.2(Froyo)のサポー トを終了しているため、注意が必要です。各実装上の注意点は、必要に応じて本文中に記載しています。 Google Play services は、日本では「Google Play 開発者サービス」と呼ばれています。 この「開発者」という名称が、自分には不要だと多くのユーザに誤解させる原因となっています。 実際には Google の各種サービスを利用するために必要なコンポーネントです。 本書では、「Google Play services」の名称を統一して使用します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 319 7-2. GCM 7-2. GCM GCM(Google Cloud Messaging for Android)は、開発者が用意するアプリケーションサーバから GCM サ ーバを介して、Android デバイスにメッセージを Push 送信できるクラウドサービスです。 従来のは C2DM(Android Cloud to Device Messaging)として提供されていたサービスの次世代版です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 320 7-2. GCM 7-2-1. GCM の概要 GCM は、Android デバイス上の Android アプリケーションに対し、非同期でのメッセージ送信を可能にします。 新着メールやアプリケーション更新、お知らせ通知など様々な通知に利用することができます。 ■特徴 GCM の主な特徴は以下のとおりです。 C2DM と同様に、無料で利用することのできるサービスです。 メッセージ受信を行うクライアント側(Android デバイス、アプリケーション)と、メッセージ送信を行うサ ーバ側(アプリケーションサーバ)の両方で実装が必要となります。 サーバにある情報をアプリケーションから取得する Pull 型(ポーリング型)ではなく、サーバ側から情報 を送信する「Push 型」の通信方式です。 Push 型通信方式を採用していることにより、Android デバイスの電池消費量などによる負荷を抑 えつつ、任意のタイミングで情報を届けることが可能です。 (ただし、ネットワーク状況や電源といった Android デバイス側のオフライン条件によって、タイミングが リアルタイムでない可能性は存在します) メッセージ配信の確実性および順序については保証されません。そのため、ユーザに対して配信の確 実性を保証することはしないでください。また、ミッションクリティカルな目的などで用いられるアプリケー ションによっては注意する必要があります。 アプリケーションのアップデート時、新しいバージョンでの動作は保証されません。そのため、既存の Registration ID を無効化する必要があります。 ■メリット GCM サーバが JSON 形式のデータに対応するようになったため、JSON 形式でのリクエスト受付お よびレスポンスが可能です。 複数デバイス(最大 1000 台)に対して、一斉にメッセージを送信することが可能です。 メッセージの再送期間を指定することが可能です。 メッセージにデータを含めることが可能です(1 メッセージあたり最大 4Kbyte)。 メッセージ送信速度が向上しました(2013 年 Google I/O の発表では平均 60ms)。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 321 7-2. GCM 7-2-2. 従前の手法(C2DM) ○従前の手法の特徴 C2DM は、GCM の前世代サービスです。アプリケーションサーバから Android アプリケーションへのデータ送信支 援サービスという点は同一です。ただし、現状として C2DM のサポートは終了しており、新規登録は行えません(既 に利用済みで、GCM へ移行していない場合は、引き続き利用可能です)。また、GCM への移行が推奨されてい ることに伴い、C2DM のサービスが突然終了する可能性があります。 Android デバイス上の Android アプリケーションに対して、メッセージ送信が行われるまでの流れは以下のとおり です。(コードの処理は、手順 3 以降) 1. C2DM 用のアプリケーションを、Android デバイスにインストールする 2. アプリケーションを、C2DM サーバに登録する(認証 Token の発行) 3. アプリケーションと Android デバイス情報を、C2DM サーバに登録する (Registration ID(※1)取得) 4. アプリケーションサーバから、Registration ID と認証 Token(※2)、その他の情報(※3)を付与し て C2DM サーバに POST する 5. C2DM サーバが Registration ID 端末を見つけ、その他の情報を Push する 6. 端末のレシーバーが Intent を受け取り、Android アプリケーション側の動作を行う (※1) Registration ID は、メッセージ受信登録デバイス上のアプリケーションと紐付いており、メッセージ送信 時のデバイス特定に使用されます。 (※2) 認証 Token(ClientLogin Auth Token)は、あらかじめアプリケーションサーバにセットアップしておく必 要があります。デバイスにメッセージ送信を行う際に使用します。 (※3) その他の情報は、以下に示すものが対象となります。(*は必須パラメータ) registration_id:(*) 取得した Registration ID です。 collapse_key:(*) デバイスがオフラインのときなどメッセージを送信できない場合に、最後のメッセージだけがクライアントに送られる よう、類似メッセージのグループを折り畳むための文字列です。 (端末がオンラインへ復帰した際に、全てのメッセージが送信されないためのもの) data.<key>: key と値(value)で表される本体データです。 data.<key>が存在する場合は、<key>にて Intent に含まれます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 322 7-2. GCM delay_while_idle: delay_while_idle が含まれ、デバイスが動作していない場合は、メッセージを即時送信しません。デバイス が動作するまで待機し、collapse_key の値に対する最後のメッセージのみ送信されます。 Authorization:GoogleLogin auth=:(*) C2DM サーバに POST するメッセージヘッダに対し、付加する ClientLogin 認証 Token です。 図 52 C2DM の仕組み 【登録】: 1. Registration ID 登録依頼(Android デバイス⇒C2DM サーバ) 2. Registration ID 発行(C2DM サーバ⇒Android デバイス) 【メッセージ送信】: 3. メッセージ送信(アプリケーションサーバ⇒C2DM サーバ) 4. メッセージ送信(C2DM サーバ⇒Android デバイス) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 323 7-2. GCM ◆C2DM のサンプルケース: 【前提および注意点】 現 在 は C2DM サ ー バ へ ア プ リ ケ ー シ ョ ン を 新 規 登 録 す る こ と は で き ま せ ん 。 な ぜ な ら ば 、 C2DM が Deprecated(非推奨)とされ、GCM の移行が強く推奨されているからです。 新規登録が行えない場合、ユーザが用意するアプリケーションサーバから、GCM サーバへ POST したメッセージを GCM サーバが受信しません。そのため、既存の利用者以外での動作は不可能ですが、実装する際のサンプルコー ドを以下に記載します。 C2DM を利用した既存アプリケーションのメンテナンスなどにご活用ください。 C2DM(Android アプリケーション側の実装) C2DM 用アプリケーションの AndroidManifest.xml に、必要なパーミッションを追加します。 // AndroidManifest への Permission 追加 // パッケージ名 <manifest xmlns:android=http://schemas.android.com/apk/res/android package="com.example.myc2dm" // メッセージの登録、受信許可 <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> // インターネット通信許可 <uses-permission android:name="android.permission.INTERNET" /> // 他アプリケーションのメッセージ登録、受信防止 <permission android:name="com.example.myc2dm.permission.C2D_MESSAGE" /> <uses-permission android:name="com.example.myc2dm.permission.C2D_MESSAGE" /> // C2DM サーバのみアプリケーションにメッセージ送信許可 <receiver android:name=".C2DMReceiver" android:permission= "com.google.android.c2dm.permission.SEND"> // 実際のメッセージ受信 <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <category android:name="com.example.myc2dm" /> </intent-filter> // 登録 Registration ID 受信 <intent-filter> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="com.example.myc2dm" /> </intent-filter> </receiver> </manifest> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 324 7-2. GCM また、C2DM 用アプリケーションが、C2DM の機能必須という場合、以下のパーミッションを追加します。 C2DM が動作しない環境の場合は、以下のパーミッションを追加することによって、アプリケーションのインストール が行えないことを保証できます。その理由は、C2DM が Android OS バージョン 2.2 以上のデバイスでなければ 動作しないためです。 <uses-sdk android:minSdkVersion="8" /> ○Activity 側の実装(Receiver 側からのメッセージ表示処理) 1. C2DM のライブラリをインポートします。 // C2DM ライブラリ(送信用) import com.google.android.c2dm.C2DMessaging; ライブラリは、「Chrome to Phone」を使用します。Chrome to Phone は、Google が C2DM を使用して開 発した、Google Chrome から Android デバイスへ URL を送信できるアプリケーションのライブラリです。Chrome to Phone を使用することにより、Registration ID の取得処理や、メッセージ受信時の処理が簡単に実装可能 です。 下記の SVN からチェックアウトしてインポートします。 https://code.google.com/p/cloud-tasks-io/source/browse/trunk/CloudTasks-Android/lib/ ?r=5 2. Register ID を発行し、Push 通信待ちを開始します。 アプリケーションが終了する場合は、Push 通信待ちを解除します。 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); C2DMessaging.register(this, "端末利用者の Google アカウント ID"); // Push 通信待ちを開始 } @Override public void onDestroy() { C2DMessaging.unregister(this); } // Push 通信待ちを終了 } 上記のうち、Push 通信待ち開始(Registration ID の取得)や、Push 通信待ちの終了処理には、C2DM サ ーバへ通知する Intent の設定を行う必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 325 7-2. GCM しかし、「Chrome to Phone」のライブラリを取り込むことで、以下の 2 つの API をコールするだけで、処理が行 われるようになります。 C2DMessaging.register() C2DMessaging.unregister() 上記のサンプルソースでは、アプリケーション開始と終了時にそれぞれ regist、unregist 処理を行っています。た だし、開始と終了のボタンを設け、各ボタン押下時にそれぞれ API をコールする処理とした方が、より理解しやすく なります。そのため、実装方法としては、各ボタン押下時に API をコールする方が望ましいと言えるでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 326 7-2. GCM ○Receiver 側の実装(C2DM サーバからのメッセージ受信処理) 1. C2DM のライブラリをインポートします。 // C2DM ライブラリ(受信用) import com.google.android.c2dm.C2DMBaseReceiver; // C2DM サーバからのメッセージ受信 import android.content.Intent; C2DM ライブラリは、Activity 側同様、以下の SVN からチェックアウトして使用します。 https://code.google.com/p/cloud-tasks-io/source/browse/trunk/CloudTasks-Android/lib/ ?r=5 2. C2DM サーバからのメッセージ受信時の処理を記述します。 public class MainReceiver extends C2DMBaseReceiver { public void onRegistered(Context context, String registrationId) { // Register ID 通知 dispMessage(context, registrationId); // // 画面表示 } @Override public void onUnregistered(Context context) { // C2DM サービス登録解除通知 dispMessage(context, "C2DM の登録解除"); // 画面表示 } @Override public void onError(Context context, String arg1) { // C2DM サーバからのエラー通知 dispMessage(context, "error"); // 画面表示 } @Override protected void onMessage(Context context, Intent intent) { // C2DM サーバからのメッセージ通知 String str = intent.getStringExtra("message"); dispMessage(context, str); // 画面表示 } private void dispMessage(Context context, String str) { // メッセージ内容をトースト表示 Toast.makeText(context, str, Toast.LENGTH_SHORT).show(); } } C2DM サ ー バ か ら 送 信 さ れ た Registration ID は 、 onRegistered() で 受 信 さ れ ま す 。 受 信 し た Registration ID は、アプリケーションサーバから C2DM サーバへメッセージ POST された際に使用します。そのた め、ここではユーザが認識できるよう画面表示を行います。 また、onMessage()は、アプリケーションサーバから POST されたメッセージを受信する際にコールされます。 上記のサンプルコードでは、Registration ID 取得時と同様に、画面表示を行います。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 327 7-2. GCM ○アプリケーションサーバ側の実装 <前提> サーバ側の処理は、PHP にて実装します。 ※本サンプルコード上では、PHP を使用して実装を行っています。 実装時には、Java、JavaScript、Ruby on Rails など、他の手法での実装も可能です。 1. C2DM サーバへメッセージを送信します。 サンプルソース:Send.php <?php require_once "HTTP/Request.php"; // C2DM サーバにデータを POST function sendDataToC2DM($auth, $registrationID) { $rq = new HTTP_Request("https://android.apis.google.com/c2dm/send"); $rq->addHeader("Authorization", "GoogleLogin auth ={$auth}"); $rq->setMethod(HTTP_REQUEST_METHOD_POST); $rq->addPostData("registration_id", $registrationID); // registration ID の指定 $rq->addPostData("collapse_key", "1"); // collapse_key の指定 $rq->addPostData("delayWhileIdle", "true"); // delayWhileIdle の指定 $rq->addPostData("data.message", "テスト"); // データの指定 } ?> 上記の PHP サンプルコードを実行時に、アプリケーションサーバへ保存した ClientLogin 認証 Token と Registration ID を指定することで、C2DM サーバへメッセージを POST します。サンプルコードでは、「テスト」とい うメッセージを送信しています。 送信したメッセージは、C2DM サーバを介して MainReceiver クラスの onMessage()にて受信します。 本サンプルでは、C2DM サーバ登録時および Android アプリケーション側で取得した ClientLogin 認証 Token と Registration ID を、Send.php 実行時に指定することで C2DM サーバへのメッセージ POST を行っていま す。 ただし、より良い実装として、Registration ID を取得時に、アプリケーションサーバに対して Registration ID と認証 Token を併せて送信、アプリケーションサーバ側で保存を行う方法があります。(Android デバイスごとに ID が異なるため、DB 管理が望ましい) アプリケーションサーバから、C2DM サーバへメッセージを POST する際は、保存した ID と認証 Token を読み込 み、POST 時に設定するような処理をアプリケーションサーバ側で実装することで、自動化することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 328 7-2. GCM 7-2-3. GCM の基本的な実装方法 ○GCM 新規登録およびライブラリのインポート 1. 以下の URL へアクセスし、Google APIs Console から GCM の API 利用登録を行います。 https://code.google.com/apis/console/ Google アカウントにサインインします。 図 53 Google APIs console 登録開始 上記の画面が表示されるので、「Create project...」を押下します。 2. Google API サービス一覧が表示されるので、「Google Cloud Messaging for Android」に対し、「OFF」 であれば「ON」に変更します。 図 54 Google API Service 一覧 図 55 GCM サービス(OFF) ※上記の「Google Cloud Messaging for Android」を「ON」に変更します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 329 7-2. GCM 3. Google API サービス認証画面が表示されるので、利用規約に同意して Accept を押下します。 図 56 Google API サービス利用認証画面 Accept 押下後、「Google Cloud Messaging for Android」が「ON」に変更されます。 図 57 GCM サービス(ON) 4. 左側の項目から「API Access」を選択します。 図 58 API Access 画面 GCM の実装時に、上記の画面に記載されている「API key」を使用することになるため、値を保存します。 ※上記の図では、サンプル登録時の値に対して画像処理でモザイクをかけています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 330 7-2. GCM また、API Access 画面表示時の URL に「project ID」が記載されているため、こちらも確認して保存しま す。 図 59 Project ID 5. SDK マネージャーから Google Cloud Messaging for Android ライブラリをインストールします。 図 60 Google Cloud Messaging ライブラリのインストール 6. また、GCM では Google Play services ライブラリを使用します。未インストールの場合は、SDK マネージャ ーからインストールを行います。 図 61 Google Play services ライブラリのインストール Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 331 7-2. GCM 7. Google Play services ライブラリを使用するには、API Level8 の SDK が必要となります。未インストール の場合は、SDK マネージャーからインストールします。 図 62 API Leve8 SDK のインストール ※以下、Eclipse の使用を前提として記載します。 8. Eclipse にて、インストールした Google Play services ライブラリをプロジェクトとしてインポートします。 ⇒[/Android SDK インストール先/extras/google/gcm-client/dist/gcm.jar] 9. 新規プロジェクトを作成し、インポートした Google Play services ライブラリを、GCM アプリケーション用プロ ジェクトのプロパティに追加します。 図 63 Google Play services ライブラリのインポート Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 332 7-2. GCM 10. 手順 5 でインストールした GCM ライブラリを GCM アプリケーション用プロジェクトに外部 jar ファイルとして追加 します。 手順 1~10 までを実行し、プロジェクトにエラーが発生しないことを確認します。 図 64 GCM プロジェクト 事前準備は以上で完了となります。 ◆サンプルケース: ○GCM(Android アプリケーション側の実装) GCM 用アプリケーションの AndroidManifest.xml に必要なパーミッションを追加します。 // AndroidManifest への Permission 追加 // パッケージ名 <manifest xmlns:android=http://schemas.android.com/apk/res/android package="com.example.mygcm" // メッセージの登録、受信許可 <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> // インターネット通信許可 <uses-permission android:name="android.permission.INTERNET" /> // 他アプリケーションのメッセージ登録、受信防止 <permission android:name="com.example.mygcm.permission.C2D_MESSAGE" /> <uses-permission android:name="com.example.mygcm.permission.C2D_MESSAGE" /> // C2DM サーバのみアプリケーションにメッセージ送信許可 <receiver android:name=".GCMReceiver" android:permission= "com.google.android.c2dm.permission.SEND"> // 実際のメッセージ受信 <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <category android:name="com.example.mygcm" /> </intent-filter> // 登録 Registration ID 受信 <intent-filter> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="com.example.mygcm" /> </intent-filter> </receiver> </manifest> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 333 7-2. GCM GCM に関連するパーミッションについては、後述の「7-2-4 GCM 実装の基本的な注意点」で記載します。 上記のサンプルソース中で、「receiver android:name=".GCMReceiver"」の「.GCMReceiver」部分につ いては、実際にソースコードとして作成するブロードキャストを受け取るサービスクラス名を付与します。 「GCMReceiver」は、本章の次ページ以降で記載するサンプルコードで作成する Receiver サービスクラス名とな っています。 また、C2DM と同様に、Android OS バージョン 2.2 以上がインストールされている必要があります。そのため、 GCM がなければアプリケーションが動作しない場合、以下のパーミッションを追加します。 <uses-sdk android:minSdkVersion="8" /> ○Activity 側の実装(Receiver 側からのメッセージ表示処理) 1. GCM のライブラリのインポート // GCM ライブラリ(送信用) import com.google.android.gcm.GCMRegistrar;; ライブラリは、事前準備の手順 10 でプロジェクトに追加した GCM ライブラリが対象となります。 2. Register ID の発行を行い、Push 通信待ちを開始します。 アプリケーションが終了する場合は、Push 通信待ちを解除します。 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { GCMRegistrar.checkDevice(this); // デバイスチェック GCMRegistrar.checkManifest(this); // Manifest チェック } catch(Exception e) { Log.e("デバイス、Manifest チェック", e.getMessage()); } String registrationID = GCMRegistrar.getRegistrationId(getApplicationContext()); // registrationId チェック if(TextUtilis.isEmpty(registrationId) { // 未登録状態なので登録処理を行う GCMRegistrar.register(this, "登録時に発行された projectID"); // Push 通信待ちを開始 } else { // 登録済み状態 Log.i("GCMRegistrationCheck", "登録済み"); } } @Override protected void onDestroy() { GCMRegistrar.unregister(this); } // Push 通信待ちを終了 } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 334 7-2. GCM GCM アプリケーションの実装では、使用している Android デバイスや AndroidManifest が、GCM を利用可 能かチェックを行います。 GCMRegsistrar.checkDevice(); GCMRegsistrar.checkManifest(); CheckDevice(): たとえば Android デバイスが、Google APIs を含んでいないエミュレータの場合などに、エラーとして例外 をスローします。 CheckManifest(): GCM 用に AndroidManifest.xml へ追加したパーミッションが存在しない場合に、エラーとして例外を スローします。 Push 通信待ち開始(Registration ID の取得)や、Push 通信待ちの終了処理は、C2DM と同様に、GCM ライブラリが GCM サーバへの登録および解除 Intent 通知設定をサポートしています。そのため、次の API をコー ルするだけで GCM サーバに対して、登録と解除が行われます。 GCMRegsistrar.registration(); GCMRegsistrar.unregister(); また、C2DM 実装時と同様に、上記のサンプルコードではアプリケーションの開始と終了時に GCM サーバへの登 録と解除を行っています。 ただし、開始と終了のボタンを設け、各ボタン押下時にそれぞれ API をコールする処理とした方が、より理解しや すくなります。そのため、実装方法としては、各ボタン押下時に API をコールする方が望ましいと言えるでしょう。 ○Receiver 側の実装(GCM サーバからのメッセージ受信処理) 1. GCM のライブラリをインポートします。 // GCM ライブラリ(受信用) import com.google.android.gcm.GCMBaseIntentService; // GCM サーバからのメッセージ受信 import android.content.Intent; GCM ライブラリは Activity 側と同様に、事前準備の手順 10 でプロジェクトに追加した GCM ライブラリが対象 となります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 335 7-2. GCM 2. GCM サーバからのメッセージ受信時の処理を記述します。 public class GCMReceiver extends GCMBaseIntentService { public void onRegistrered(Context context, String registrationId) { // Register ID 通知 dispMessage(context, registrationId); // 画面表示 } @Override // GCM サービス登録解除通知 public void onUnregistered(Context context, String registrationId) { dispMessage(context, "GCM の登録解除"); // 画面表示 } @Override public void onError(Context context, String arg1) { // GCM サーバからのエラー通知 dispMessage(context, "error"); // 画面表示 } @Override protected void onMessage(Context context, Intent intent) { // GCM サーバからのメッセージ通知 String str = intent.getStringExtra("message"); dispMessage(context, str); // 画面表示 } private void dispMessage(Context context, String str) { // メッセージ内容をトースト表示 Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show(); } } C2DM の場合と同様に、GCM サーバから送信された Registration ID は、onRegistered()で受信が行わ れます。取得した Registration ID は、アプリケーションサーバから GCM サーバへメッセージ POST された際に使 用します。 そのため、ここではユーザが認識できるよう画面表示を行います。 また、onMessage()では、アプリケーションサーバから POST されたメッセージを受信する際にコールされます。 上記サンプルコードでは Registration ID 取得時と同様に、画面表示を行います。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 336 7-2. GCM ○アプリケーションサーバ側の実装 <前提> サーバ側の処理は、PHP にて実装します。 ※本サンプルコード上では、PHP を使用して実装を行っています。 実装時には、Java、JavaScript、Ruby on Rails など、他の手法での実装も可能です。 1. C2DM サーバへメッセージを送信します。 Send.php(プレーンテキスト形式) <?php require_once "HTTP/Request.php"; // GCM サーバにデータを POST function sendDataToGCM($apiKey, $registrationID) { $rq = new HTTP_Request("https://android.apis.google.com/gcm/send"); $rq->addHeader("Authorization", "key ={$ apiKey }"); $rq->setMethod(HTTP_REQUEST_METHOD_POST); $rq->addPostData("registration_id", $registrationID); // registration ID の指定 $rq->addPostData("collapse_key", "1"); // collapse_key の指定 $rq->addPostData("delayWhileIdle", "true"); // delayWhileIdle の指定 $rq->addPostData("data.message", "テスト"); // データの指定 $rq->addPostData("time_to_live", "120") } ?> 上記 PHP サンプルコード実行時に、アプリケーションサーバへ保存した API key と Registration ID を指定す ることで、GCM サーバへメッセージを POST します。サンプルコードでは、「テスト」というメッセージを送信しています。 送信したメッセージは、GCM サーバを介して GCMReceiver クラスの onMessage()にて受信します。 C2DM との違いは、送信先のサーバ URL の変更と、ClientLogin 認証 Token の代わりに、GCM 登録時に 取得した API key をヘッダ情報に設定する点です。その他、POST 時に必須となる情報と、オプション情報につい ては以下のとおりです。(*は必須パラメータ) registration id(s):(*) 取得した Registration ID です。 JSON 形式によるマルチキャスト送信の場合は、末尾に「s」を付与します。 collapse_key: デバイスがオフラインのときなど、メッセージを送信できない場合に、最後のメッセージだけがクライアントに送信さ れるよう類似メッセージのグループを折り畳むための文字列です。(端末がオンライン復帰した際に、全てのメ ッセージが送信されないようにするためのものです) time_to_live オプションを設定する場合は、必須となります。 data.<key>: key と値(value)で表される本体データです。 data.<key>が存在する場合には、<key>にて Intent に含まれます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 337 7-2. GCM delay_while_idle: Delay_while_idle が含まれ、デバイスが動作していない場合は、メッセージを即時配信しません。デバイス が動作するまで待機し、collapse_key の値に対する最後のメッセージのみ送信されます。 time_to_live: デバイスがオフラインの場合などに、メッセージを GCM ストレージに保存する期間を設定します(秒単位で 0~ 4 週間)。デフォルトは、4 週間に設定されています。本オプションを設定する場合は、collapse_key が必須 設定となります。 Authorization:key=:(*) GCM サーバへ POST するメッセージヘッダに、付加する APIkey です。 詳細については、以下の URL を参照してください。 【公式サイト】 http://developer.android.com/google/gcm/adv.html JSON 形式でリクエストを行う場合は、ヘッダ情報に Content-Type で JSON を指定します。 $rq->addHeader("Content-Type", "application/json"); JSON を指定しない場合、デフォルトのプレーンテキスト形式が設定されます。 また、C2DM のサンプルコードと同様に、本サンプルでは GCM サーバ登録時および Android アプリケーション側 で取得した API key と Registration ID を、Send.php 実行時に指定することで GCM サーバへのメッセージ POST を行っています。 ただし、アプリケーションサーバ内で、Registration ID と API key の保存処理、ならびに GCM サーバへメッセ ージ POST 時に、保存値の読み出しを設定する処理を実装することが望ましいでしょう。 GCM サーバにメッセージを POST した場合、レスポンスとして以下の 2 種類が発生します。 メッセージが正常に処理されました GCM サーバがリクエストをリジェクトしました 上記の場合、各 HTTP レスポンスには戻されるステータス値は以下のとおりです。 200:メッセージが正常に処理された場合。 400:JSON リクエストに対してのみ適用され、JSON として解析不可または無効な フィールドが含まれている場合。 401:アカウント認証中に、エラーが発生した場合。 500:リクエスト処理中に、GCM サーバ内でエラーが発生した場合。 503:GCM サーバが、一時的に使用不可の場合。(タイムアウトなど) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 338 7-2. GCM 詳細については、以下の URL を参照してください。 【公式サイト】 http://developer.android.com/google/gcm/http.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 339 7-2. GCM 7-2-4. GCM 実装の基本的な注意点 ■基本的な注意点 ○Android ビギナー向け AndroidManifest.xml へ、GCM を利用する際に必要なパーミッションを追加します。 AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.mygcm" // Google アカウントのアクセス許可 <uses-permission android:name="android.permission.GET_ACCOUNTS" /> // インターネット通信設定許可 <uses-permission android:name="android.permission.INTERNET" /> // メッセージの登録、受信許可 <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> // 他アプリケーションのメッセージ登録、受信防止 <permission android:name="com.example.mygcm.permission.C2D_MESSAGE" /> <uses-permission android:name="com.example.mygcm.permission.C2D_MESSAGE" /> // C2DM サーバのみアプリケーションにメッセージ送信許可 <receiver android:name=".GCMReceiver" android:permission= "com.google.android.c2dm.permission.SEND"> // 実際のメッセージ受信 <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <category android:name="com.example.mygcm" /> </intent-filter> // 登録 Registration ID 受信 <intent-filter> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="com.example.myc2dm" /> </intent-filter> </manifest> 上記のパーミッションのうち GET_ACCOUNTS は、Android OS バージョンが 4.0.4 以上の端末であれば不要 です。ただし、4.0.4 未満のバージョンでは、GCM 利用時に必須のパーミッションです。そのため、サポートする OS バージョンが 4.0.4 未満を含む場合は、忘れずに追加します。 その他、メッセージの登録・受信許可等のパーミッションについては、C2DM アプリケーションの場合と同様です。 GCM の利用は、Android 端末がインターネットに対して通信可能な状態(3G/LTE など)であることが必要 です。機内モードや圏外の場合は動作しません。ただし、必ずしも SIM は必要ではなく、Wi-Fi 接続されてい れば動作します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 340 7-2. GCM ○従前の手法に習熟した開発者向け C2DM と GCM は、C2DM の Registration ID から GCM サーバへメッセージを POST する場合や、逆に GCM の Registration ID から C2DM サーバへのメッセージを POST することはできません。そのため、両立(相互運用) は不可能です。したがって、開発者が用意するアプリケーションサーバ側は、C2DM から GCM へ移行する場合、ど ちらの Registration ID からの処理なのかを使い分ける必要があります。 <Android アプリケーション側の変更点> ・GCM サーバへ渡す値の変更 public class MainActivity extends Activity { public static Handler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 引数を GCM 登録時に発行された project ID に設定 GCMRegistrar.register(this, "発行された project ID"); } } C2DM アプリケーションの場合、「Google アカウント ID」を C2DM サーバに対し、引数として渡します。GCM ア プリケーションの場合では、GCM サーバ登録時に発行された「project ID」を渡す必要があります。 ・GCM サーバへのメッセージ送信先およびヘッダ情報の変更 <?php require_once "HTTP/Request.php"; // GCM サーバにデータを POST function sendDataToGCM($apiKey, $registrationID) { $rq = new HTTP_Request("https://android.apis.google.com/gcm/send"); $rq->addHeader("Authorization", "key ={$apiKey}"); $rq->setMethod(HTTP_REQUEST_METHOD_POST); $rq->addPostData("registration_id", $registrationID); // registration ID の指定 $rq->addPostData("collapse_key", "1"); // collapse_key の指定 $rq->addPostData("delayWhileIdle", "true"); // delayWhileIdle の指定 $rq->addPostData("data.message", "テスト"); // データの指定 } ?> アプリケーションサーバから GCM サーバへメッセージを POST する際、POST 先の URL を以下のように変更する 必要があります。 <C2DM> :"https://android.apis.google.com/c2dm/send" <GCM> :"https://android.apis.google.com/gcm/send" また、ヘッダ情報に設定する値として、C2DM で使用していた「ClientLogin Auth Token」は、GCM 登録時 に発行された「API key」に変更する必要があります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 341 7-2. GCM ○既存システムの移行シーン向け ■従前の手法との違い No 1 機能 API key 従前の手法(C2DM) GCM アプリケーションサーバから C2DM サーバへ アプリケーションサーバから GCM サーバへメッセー メ ッ セ ー ジ を POST す る 際 は 、 ジを POST する際は、「API key」が必要です。 「ClientLogin 認証 Token」が必要で GCM 登録時に発行される API key を使用しま す。 す。 C2DM 登録時に発行される Token を使 ※GCM は API key のみを受け付けるという点に 用します。 注意。 ※ただし現在は、新規登録不可。 2 3 4 センダーID JSON フォーマット マルチキャストメッセージ Push 通信開始(Regist)時に、C2DM Push 通信開始(Regist)時に GCM サーバへ送 サーバへ送信する値は「Google アカウント 信 す る 値 は 、 GCM 登 録 時 に 発 行 さ れ た ID」です。 「project ID」です。 C2DM サーバに対する HTTP リクエスト GCM サーバに対する HTTP リクエストは、プレー は、プレーンテキスト形式のみをサポートし ンテキストに加え、JSON 形式をサポートしていま ています。 す。 C2DM では、同一メッセージを受信可能 GCM では、同一メッセージを受信可能なデバイ なデバイスは 1 つだけです。 スリストを、1~1000 まで設定可能です。そのた め、マルチキャストメッセージを送信することができ ます。 その場合、アプリケーションサーバからの POST 時 に設定を行います。 ※ マ ル チ キ ャ ス ト の 場 合 は JSON 形 式 で の POST が必要。 5 有効期間メッセージ ※C2DM には該当機能なし デバイスがオフラインの場合など、受信不可な状 態である場合に備え、 GCM ストレージに保存する期間を設定可能で す(0~4 週間)。 6 7 ペイロード付きメッセージ マルチセンダー メッセージサイズは、1024 バイトまで送信 メッセージサイズは、4096 バイトまで送信可能で 可能です。 す。 C2DM では、同一アプリケーションに対し、 GCM では、同一アプリケーションに対し、複数の 複数のグループからメッセージを送信するこ グループからメッセージを送信することができます。 とはできません。 ※Regist 時に指定する project ID を複数指 定することで可能。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 342 7-3. Location 7-3. Location Location API は、Google Play services の新しいライブラリとして 2013 年の Google I/O にて発表されま した。位置情報の取得について、正確性・検出範囲・電池消費の面において改善されています。Google Play services ライブラリとして提供される理由は、プラットフォームのアップデートを待つよりも、短い間隔でアプリケーショ ンを改善することが可能となるためです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 343 7-3. Location 7-3-1. Location API の概要 ○Fused Location Provider ■概要 Fused Location Provider は、最適な方法で位置情報を取得できる API を提供します。 GPS・Wi-Fi・Sensor・Cell(基地局)などのプロバイダから、精度、電池消費量の Priority に応じて 位置情報の取得を行います。 ■特徴 位置情報取得の際に使用する4つのコンポーネントを、Android 側が使い分けます。 (これまでは、場面に応じて各コンポーネントとのやり取りを開発者が実装していました) シンプルな実装を可能にする API です。(※7-3-3.のサンプルを参照) 精度、電池消費量の Priority を加味した位置情報の測位が可能です。 ■メリット GPS、Wi-Fi、Cell、Sensor を組み合わせて利用することにより、多様な環境下で、位置情報を正 確に取得することができます。 位置情報の精度、電池消費量を、アプリケーション要件や場面に応じて使い分けることができます。そ のため、位置情報を取得するたびに、電池消費量を下げて長時間持続する、電池は消費するが精度 の高い位置情報を取得するなど、目的に合わせた位置情報の取得が可能です。 目的に合わせた位置情報の取得が可能になることで、アプリケーション内での自由度が向上します。 シンプルな API として提供されることにより、開発工数の削減に貢献します。 ○Geofencing ■概要 任意の場所に、任意の大きさの領域(フェンス)を作成します。作成した領域の中へ進入したことや、領 域から退出したことを Android デバイスが検知し、Intent を発行します。 ■特徴 PendingIntent(タイミングを指定して発行可能な Intent)での Intent 発行となり、イベントを検知 したブラウザや SNS アプリケーション、メールを呼び出すなど、様々な場面で使用することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 344 7-3. Location ■メリット 設定した領域への進入/退出を Android デバイスが検知するため、ユーザに対して負荷の少ないサービスを設 計することができます。活用することが可能と考えられるシーンの一例を、以下に示します。 店舗が設定した範囲(フェンス)に顧客が接近すると、顧客に販促情報を通知する。 観光名所付近(範囲)に接近すると、観光情報などを通知する。 社員が会社(フェンス)に接近すると、管理システムを利用して勤怠をつける。 自宅の最寄駅付近に設定した範囲(フェンス)に接近すると、自宅の電化製品を起動する。 事故多発地帯(フェンス)に接近すると、アラートを通知する。 このように、私的利用やマーケティング活動など、各種アラートシステムに活用することができます。ただし、常に位 置情報を取得して通知を行う場合には、以下のような点に注意が必要です。 消費電力が増加する懸念。 位置情報の精度と電池消費量のバランスは、ユーザ側で調整できるようにすることが望ましいでしょ う。 位置情報利用によるプライバシーが適切な範囲で公開されることに対する考慮。 図 65 特定の位置(場所)を中心にフェンスを作り、その中に入ると予め決めた処理を自動的に実行するモデル geofence は、自身で指定した特定の範囲に端末が入ったときに PendingIntent を発行し、アプリケーションに 指定した処理をさせる仕組みです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 345 7-3. Location 法人向けアプリケーションの応用例としては、以下のような処理の実装が考えられます。 勤怠管理 - 職場に出退勤したことを端末の位置情報に基づいて管理する 来店管理 - 小売店が顧客の来店ポイント加算を、顧客端末の位置情報に基づいて管理する etc... 上記図に記載されている指定座標(緯度 x , 経度 y)および半径 R については、setCircularRegion()を 用いて geofence の領域の指定を行います。 setCircularRegion(double latitude, double longitude, float radius) latitude:-90 から 90 までの間の緯度 longitude:-180 から+180 までの間の経度 radius:メートル単位の半径 特に注意する必要があるのは、半径の指定になります。 フェンスの大きさが小さすぎると、認識誤差の影響でジオフェンス領域への出入りを誤認識して頻繁に処理が作 動してしまう可能性があるため、ジオフェンス領域の半径は起点から 100m 以上にしておくことを意識する必要が あります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 346 7-3. Location サンプルソース 最短 500 ミリ秒間隔で測位。設定位置から半径 100m の円に入ったり出たときに PendingIntent を登録し、 指定した Web ページを表示する処理の例 public class MainActivity extends Activity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener , LocationClient.OnAddGeofencesResultListener private LocationClient mLocClient = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setupLoc(); } @Override protected void onResume() { super.onResume(); setupLoc(); } private void setupLoc() { if (mLocClient == null) { mLocClient = new LocationClient(this, this, this); mLocClient.connect(); } } private void requestUpdate() { ArrayList<Geofence> fenceList = new ArrayList<Geofence>(); Geofence geofence = new Geofence.Builder() .setRequestId("Fence-1") .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) .setCircularRegion(34.994913, 135.738927, 100) .setExpirationDuration(Geofence.NEVER_EXPIRE) .build(); fenceList.add(geofence); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.-----.com/")); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mLocClient.addGeofences(fenceList, pendingIntent, this); LocationRequest req = LocationRequest.create(); req.setFastestInterval(500); req.setInterval(500); req.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // 次ページへ続く Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 347 7-3. Location // 前ページの続き mLocClient.requestLocationUpdates(req, new LocationListener() { @Override public void onLocationChanged(Location loc) { } }); } // 更新された位置情報を使う処理 @Override public void onConnected(Bundle connectionHint) { requestUpdate(); } @Override public void onDisconnected() { } @Override public void onConnectionFailed(ConnectionResult connectionResult) { } @Override public void onAddGeofencesResult(int i, String[] strings) { } // addGeofences の結果がやってくる 端末での位置情報取得処理によらず、ビーコンの電波が届く範囲に端末が入ることで Intent を登録 する処理の実装も可能になりました。 この仕組みは、指定領域に専用のビーコンを設置し、端末のアプリケーションがビーコンの信号を受信 した際に任意の動作を行うように実装することで実現します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 348 7-3. Location 図 66 特定のデバイス(ビーコン端末)を中心にフェンスを作り、端末が入ると処理を実行するモデル ビーコンは、ビーコンモジュールが発信する電波を端末が受信することによって PendingIntent を発行し、アプ リケーションが所定の動作を行う仕組みです。 Android でビーコンを検出するのに必要となるメソッドは以下となります。 onLeScan(BluetoothDevice device, int rssi, byte[] scanRecoud) device はリモートデバイスを識別 rssi は Bluetooth ハードウェアによって報告されたリモートデバイスのための RSSI 値 scanRecoud はリモートデバイスによって提供される広告レコードの内容 それでは、ビーコン実装の手法を説明していきます。 「パーミッションの設定」 まず最初に Manifest ファイルに権限の追加を行います。本機能では Bluetooth を使用しますので、そのための パーミッションを追加します。 <manifest ... > // Bluetooth を使用する <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> // Bluetooth 4.0 LE 搭載機種でしか動作できないことを指定 <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> </manifest> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 349 7-3. Location 「ソースコードの記述」 ここでは、ビーコンを検出を行うための最低限の実装のみ記載します。 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // ・・・ // Bluetooth Adapter の取得 Final BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); // Bluetooth LE デバイスの検索 mBluetoothAdapter.startLeScan(mLeScanCallback); // コールバックメソッドの中身 private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi,byte[] scanRecord) { // UUID,major,miner(scanRecord より取得)、RSSI の判定処理。 } } } ビーコンにおける UUID/Major/Minor の情報について、Android では Bluetooth LE の Advertisement パケットに工夫をして、ビーコンとして利用していますので、コールバックメソッド内の onLeScan の引数である scanRecord の中に UUID/Major/Minor の信号が含まれています。 以下サンプルソースでは、scanRecord より UUID/Major/Minor の値を取り出す処理の一例を記載します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 350 7-3. Location if(scanRecord.length > 30) { if((scanRecord[5] == (byte)0x4c) && (scanRecord[6] == (byte)0x00) && (scanRecord[7] == (byte)0x02) && (scanRecord[8] == (byte)0x15)) { // UUID の値を取り出す String uuid = IntToHex2(scanRecord[9] & 0xff) + IntToHex2(scanRecord[10] & 0xff) + IntToHex2(scanRecord[11] & 0xff) + IntToHex2(scanRecord[12] & 0xff) + "-" + IntToHex2(scanRecord[13] & 0xff) + IntToHex2(scanRecord[14] & 0xff) + "-" + IntToHex2(scanRecord[15] & 0xff) + IntToHex2(scanRecord[16] & 0xff) + "-" + IntToHex2(scanRecord[17] & 0xff) + IntToHex2(scanRecord[18] & 0xff) + "-" + IntToHex2(scanRecord[19] & 0xff) + IntToHex2(scanRecord[20] & 0xff) + IntToHex2(scanRecord[21] & 0xff) + IntToHex2(scanRecord[22] & 0xff) + IntToHex2(scanRecord[23] & 0xff) + IntToHex2(scanRecord[24] & 0xff); // major の値を取り出す String major = IntToHex2(scanRecord[25] & 0xff) + IntToHex2(scanRecord[26] & 0xff); // minor の値を取り出す String minor = IntToHex2(scanRecord[27] & 0xff) + IntToHex2(scanRecord[28] & 0xff); } } ○Activity Recognition ■概要 センサーを利用し、ユーザの行動状態を判定、取得することができます。 ■概要 静止、徒歩、自転車、自動車といった行動状態を判定することができます。 ■メリット ユーザの行動状態に合わせて別のアプリケーションを起動するなど、他サービスとの連携を強化すること ができます。 (たとえば、徒歩や自動車で移動中に、設定した Geofence に入ることで、観光情報を通知するよう な Geofencing との連携などにも使用することができます) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 351 7-3. Location 7-3-2. 従前の手法(LocationManager) ○従前の手法の特徴 1. Fused Location Provider に対応する機能 Android の パ ッ ケ ー ジ に 含 ま れ て い る LocationManager ク ラ ス に て 、 GPS ま た は ネ ッ ト ワ ー ク (3G/Wi-Fi)を指定し、位置情報を取得することができます。 しかし、GPS での位置情報取得ができない環境の場合に、Wi-Fi に切り替えるなどの処理を、開発者側で 実装する必要がありました。 2. Geofencing に対応する機能 LocationManager.addProximityAlert()にて、指定のエリアに入ったときにアラートを発行することが可 能です。 3. ActivityRecognition に対応する機能 AcitivityRecognition に対応する機能は、従前の LocationManager には存在しません。そのため、記 載については割愛させていただきます。 Google は、Location API の利用を強く推奨しています。推奨に至った理由は、以下のとおりです。 1. Fused Location Provider ユーザの利用シーンや Android 端末の状態に応じたコンポーネント(プロバイダ)の選択を、設定する必 要がなくなった。 2. Geofencing 電池消費量が約 1/3 に削減され、Android 端末のリソースを保護する観点から有用である。 LocationClient を使用することで、より正確な位置座標での領域設定が可能となった。 3. ActivityRecognition 新たに設けられた機能であり、従前の LocationManager には存在しなかった。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 352 7-3. Location サンプルケース: Fused Location Provider に対応する、LocationManager(対応する従前の手法)を使用したサンプルソ ースを以下に示します。 <従前の手法> ○LocationManager による位置情報の取得 //AndroidManifest への Permission 追加 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> public class MyActivity extends Activity implements LocationListener { private LocationManager mLocationManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mLocationManager = (LocationManager)getSystemService(LOCATION_SERVICE); // GPS を使用する場合の位置情報更新 mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this); } @Override public void onPause() { super.onPause(); if(mLocationManager != null) { // 更新不要ならリクエスト破棄 mLocationManager.removeUpdates(this); } } @Override public void onLocationChanged(Location location) { // 位置情報更新時にコールされる } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // 位置情報ステータス変更時にコールされる } @Override public void onProviderEnabled(String provider) { // プロバイダ有効時にコールされる } @Override public void onProviderDisabled(String provider) { // プロバイダ無効時にコールされる } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 353 7-3. Location GPS を 使 用 し て 位 置 情 報 を 取 得 す る 場 合 、 上 記 の サ ン プ ル に あ る と お り 、 設 定 す る プ ロ バ イ ダ を 「GPS_PROVIDER」に指定します。 ネットワーク(3G/Wi-Fi)を使用する場合は、「NETWORK_PROVIDER」を指定します。ただし、アプリケーショ ンや場面などの条件によって、設定するプロバイダを使い分ける必要があります。 ※Android OS 2.2(API Level 8)から、「PASSIVE_PROVIDER」というプロバイダも存在します。 PASSIVE_PROVIDER は、他のアプリケーションやサービスが位置情報を取得した際に、位置情報を 利用するための設定値となります。 設定プロバイダを固定値で設定する場合、場面に応じた最適な使い分けを実現することは困難です。また、開 発者側で処理を実装する必要があります。そのため、位置情報を取得するためのプロバイダを取得する API を使 用することで、処理の煩雑化を防ぐことが可能です。ただし、プロバイダ取得 API は、LocationManager クラスで 複数存在し、現状は以下の API の使用が望ましいとされています。 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mLocationManager = (LocationManager)getSystemService(LOCATION_SERVICE); //プロバイダのリストを作成し、適合するプロバイダリストを取得 List<String> providers = mLocationManager.getProviders(true); for(String provider : providers) { mLocationManager.requestLocationUpdates(provider, 5000, 0, this); } LocationManager.getProviders()は、現在の位置情報を取得するためのプロバイダリストを返却する API です。 詳細については、以下の URL を参照してください。 【公式サイト】 http://developer.android.com/reference/android/location/LocationManager.html LocationManager クラスには、上記 getProviders()の API とは別に getBestProvider()という API が存 在します。 getBestProvider()は、「位置が最も正確なプロバイダ」を返却する API ですが、端末によって想定通りに動作 しない可能性があります。そのため、getBestPrivider()の使用は望ましくありません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 354 7-3. Location サンプルケース: Geofencing に対応する、LocationManager.addProximityAlert()(対応する従前の手法)を使用したサン プルソースを以下に示します。 <従前の手法> ○Geofencing に対応する機能 Activity 側(Geofence 登録設定) public class MyAlertActivity extends Activity { private LocationManager mLocationManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); MyAlert(); } MyAlert() { double latitude = XXXXX; double longitude = YYYYY; Intent intent = new Intent(PROX_ALERT_INTENT); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); mLocationManager.addProximityAlert(latitude, longitude, 100, -1, pendingIntent); IntentFilter filter = new IntentFilter(PROX_ALERT_INTENT); registerReceiver(new ProximityIntentReceiver(), filter); } } Latitude :設定領域の中心緯度 Longitude :設定領域の中心経度 Radius :設定領域の中心からの範囲(半径(m 単位) Expiration :設定領域の警告時間(ミリ秒単位、-1 の場合は期限なし) Intent :設定領域への入出時に発行される Intent を含む PendingIntent 詳細については、以下の URL を参照してください。 【公式サイト】 http://developer.android.com/reference/android/location/LocationManager.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 355 7-3. Location Service 側(Geofence 入出検知受信) private class ProximityIntentReceiver extends BreadcastReceiver { @Override public void onReceiver(Context context, Intent intent) { String keyname = LocationManager.KEY_PROXIMITY_ENTERING; Boolean transition = intent.getBooleanExtra(key, false); if(transition) { // 設定領域へ入った際の通知 } else { // 設定領域から出た際の通知 } // 通知を受けた際の処理を記載(メッセージ表示など) } } LocationManager.KEY_PROXIMITY_ENTERING: Intent に付属されている上記の値を、以下のとおり判定します。 ・true の場合 :設定領域に入った際に、通知された Intent であることを示します。 ・false の場合 :設定領域から出た際に、通知された Intent であることを示します。 <従前の手法> ○Acitivity Recognition Activity Recognition に対応する従前の手法は存在しません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 356 7-3. Location 7-3-3. 1. Location API の基本的な実装方法 Location API は、Google Play services ライブラリで提供されています。 そのため、ライブラリを SDK マネージャーからインストールします。 図 67 Google Play services ライブラリのインストール 2. また、Google Play services ライブラリを使用するには、API Level8 の SDK が必要です。 未インストールの場合は、あわせて SDK マネージャーからインストールします。 図 68 API Leve8 SDK のインストール ※以下については、Eclipse の使用を前提として記載します。 3. Eclipse にて、インストールした Google Play services ライブラリを、プロジェクトとしてインポートします。 ⇒[/Android SDK インストール先 /extras/google/google_play_services/libproject] Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 357 7-3. Location 4. 新規プロジェクトを作成します。 その後、インポートした Google Play services ライブラリをプロジェクトのプロパティに追加します。 図 69 Google Play services ライブラリのインポート 手順 1~4 までを実行し、プロジェクトにエラーが発生しないことを確認します。 図 70 Location プロジェクト Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 358 7-3. Location ◆サンプルケース: ○Fused Location Provider 1. LocationClient(Fused Location Provider)関連のライブラリを、以下のとおりインポートします。 import import import import import import import com.google.android.gms.common.ConnectionResult; com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks; com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener; com.google.android.gms.location.LocationClient; com.google.android.gms.location.LocationListener; com.google.android.gms.location.LocationRequest; android.location.Location; 2. LocationClient のインスタンスを生成します。 public class MainActivity extends Activity implements LocationListener, ConnectionCallbacks, OnConnectionFailedListener) { private LocationClient mLocationClient; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // LocationClient のインスタンス生成 mLocationClient = new LocationClient(this, this, this); } } LocationListener: 位置座標変更時に呼ばれる、コールバック用インターフェースです。 onLocationChanged() ConnectionCallbacks: connect()がコールされた後、Google Play services 接続成功時、または切断時に呼ばれるコー ルバック用インターフェースです。 onConnected()、onDisconnected() OnConnectionFailedListener: connect()がコールされた後、Google Play services 接続に失敗した際に呼ばれるコールバック用 インターフェースです。 onConnectionFailed() Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 359 7-3. Location 3. Google Play services へ接続開始、切断、失敗時などのコールバックメソッドを設け、続いて全体の処理を 記述します。 @Override public void onResume() { super.onResume(); mLocationClient.connect(); // 画面復帰時に Google Play-services 接続開始 } @Override public void onPause() { super.onPause(); mLocationClient.disconnect(); // 画面中断時に Google Play-services 接続停止 @Override public void onConnected(Bundle bundle) { // connect()のコールバック LocationRequest mLocationRequest = LocationRequest.create(); // 測位要求を生成 mLocationRequest.setInterval(5000); // 5 秒間隔の測位設定 mLocationRequest.setSmallestDisplacement(1); // 最小 1m の移動距離の精度で測位設定 mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // プライオリティ設定 mLocationClient.requestLocationUpdates(mLocationRequest, this); // 測位要求 } @Override public void onConnectionFailed(ConnectionResult connectionResult) { // Google Play-services 接続失敗時の処理を記載(ログ、トースト表示など) } @Override public void onDisconnected() { // Google Play-services 接続切断時の処理を記載(再接続など) } @Override public void onLocationChanged(Location location) { // 位置情報が変更された際の処理を記載(位置情報の表示など) } onResume()や、設置した接続ボタン押下時などの処理で、connect()がコールされた場合、コールバックであ る onConnected()が呼ばれます。その際、ブラウザを使用した検索のように、大雑把な現在地情報を取得する のであれば、getLastLocation()をコールすることで、Android 側が管理している最新の位置情報を取得するこ とができます。 上記のサンプルコードでは、インターバル時間や優先度を設定した測位要求のオブジェクトを生成した後に、測位 要求を行っています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 360 7-3. Location 位置情報の計測には、要件によってプライオリティを選択します。設定できるプライオリティを以下に示します。 ○プライオリティ プライオリティ HIGH_ACCURACY 典型的な 電池消費 正確性 インターバル時間 (1 時間毎(%)) 5 秒 7.25% ~ 10m 0.6% ~ 40m Small ~ 1 マイル(約 1.6km) (5000ms) BALANCED_POWER 20 秒 (20000ms) NO_POWER N/A 出典)Google I/O 2013 のセッション「Beyond the Blue Dot: New Features in Android Location」 【公式サイト】 ※動画サイトです https://developers.google.com/events/io/sessions/325337477 ※ 17:50~ 各プライオリティの概要は、以下のとおりです。 HIGH_ACCURACY: 屋外では GPS を使用し、屋内では Wi-Fi や Cell(基地局)を使用します。 ※なお、Wi-Fi や Cell の位置情報は、Google の位置情報データベースにおける登録内容に依存しま す。したがって、必ずしも正確な情報でない場合があるため、注意が必要です。 基本的なインターバルは 5 秒であり、マップアプリケーションやナビゲーションアプリケーション向けの設定とな ります。正確性が高い反面、比較的多くの電池を消費します。 BALACNED_POWER: GPS を使用せず、Wi-Fi や Cell を使用します。基本的なインターバルは 20 秒です。 NO_POWER: 別アプリケーションのリクエストで取得した位置情報を、取得する際に使用します。 ただし、正確性はそのときの状況に依存します。 (従前の手法における「PASSIVE_PROVIDER」に相当または類似します) LocationRequest の API についての詳細は、以下の URL を参照してください。 【公式サイト】 https://developer.android.com/reference/com/google/android/gms/location/Locatio nRequest.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 361 7-3. Location ◆サンプルケース: ○Geofencing Geofencing は、Activity 側と Service 側の両方で実装が必要です。 Activity 側は Geofence の登録設定を、Service 側は Geofence への入出検知の受け取りを実装します。 ○Activity 側(Geofence の登録設定) 1. Geofencing 関連のライブラリをインポートします。 // Location 関連 import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks; import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.LocationClient; import com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener; // Geofence リスト用 import java.util.ArrayList; import java.util.List; // PendingIntent 用 import android.app.PendingIntent; import android.content.Intent; Geofence は、Fused Location Provider と同様に、以下のライブラリをインポートします。 LocationClient ライブラリ Geofence ライブラリ Geofence リスト作成用の List ライブラリ Geofence への入出時に発行される PendingIntent ライブラリ 2. LocationClient のインスタンスを生成します。 public class MainActivity OnConnectionFailedListener extends Activity implements ConnectionCallbacks, OnAddGeofencesResutListener) { private LocationClient mLocationClient; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // LocationClient のインスタンス生成 mLocationClient = new LocationClient(this, this, this); } } ConnectionCallbacks、OnConnectionFailedListener は、記述内容と同様です。 OnAddGeofencesResultListener: addGeofences()で、Geofence が登録された際に呼ばれるコールバック用インターフェースです。 onAddGeofencesResult() Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 362 7-3. Location 3. Google Play services へ接続開始・切断・失敗時などのコールバックメソッドを設け、全体の処理を記述し ます。 @Override public void onResume() { super.onResume(); mLocationClient.connect(); // 画面復帰時に Google Play-services 接続開始 } @Override public void onConnected(Bundle bundle) { registGeofence(); // Geofence 登録 } // connect()のコールバック @Override public void onConnectionFailed(ConnectionResult connectionResult) { // Google Play-services 接続失敗時の処理を記載(ログ、トースト表示など) } @Override public void onDisconnected() { // Google Play-services 接続切断時の処理を記載(再接続など) } connect()のコールバックである onConnected()が呼ばれた際に、registGeofence()をコールします。 registGeofence()は、Geofence を作成し、登録設定を行います。 registGeofence()は、次ページにサンプル処理を記載します。 onConnectionFailed()と onDisconnected()は、Fused Location Provider のサンプルコードと同様で す。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 363 7-3. Location 4. Geofence を作成し、登録設定を行います。 public void registGeofence() { // Geofence を作成する List<Geofence> geofenceList = new ArrayList<Geofence>(); // Geofence のリスト生成 Geofence.Builder geofence = new Geofence.Builder(); // Geofence のインスタンス生成 double latitude = XXXXX; // 緯度設定 double longitude = YYYYY; // 経度設定 geofence.setRequestId(“Geofence1”); // リクエスト ID を設定 geofence.setCircularRegion(latitude, longitude, 100); // 緯度経度地点から半径 100m を範囲とする geofence.setExpirationDuration(Geofence,NEVER_EXPIRE); // 設定期間は無期限 geofence.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER); // ユーザが Geofence に入ったら geofence.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_Exit); // ユーザが Geofence から出たら geofence.setLoiteringDelay(30000); // ユーザが Geofence に入り、30 秒経過検知を設定 geofenceList.add(geofence.build()); // Geofence リストに追加 // Geofence への入出時に通知するインテントの設定 Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); // Geofence の登録 mLocationClient.addGeofences(geofenceList, pendingIntent, this); } @Override public void onAddGeofencesResult(int arg0, String[] arg1) { // addGeofences()の戻り先 } setRequestId() :今回作成・登録する Geofence のリクエスト ID を設定 setCircularRegion() :緯度経度地点から半径 Xm 以内を、Geofence 範囲として設定 setExpirationDuration() :Geofence の持続期間を設定 setTransitionTypes() :Intent が発行される契機 setLoiteringDelay() :Geofence に入った時点からのタイマーを設定 その他、API についての詳細は、以下の URL を参照してください。 【公式サイト】 https://developer.android.com/reference/com/google/android/gms/location/Geofenc e.Builder.html 上記サンプルでは Geofence のリストを生成し、複数登録可能(1 アプリケーションにつき上限 100)である処理 です。緯度経度や半径数値は、全て固定値として記載しています。ただし、registGeofence()をコール時に引 数として値を渡すことで、自由に Geofence を設定し、登録することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 364 7-3. Location ○Service 側(Geofence の入出検知受信) 1. Geofence および Intent 関連のライブラリをインポートします。 // Geofence 用 import com.google.android.gms.location.Geofence; import com.google.android.gms.location.LocationClient; // Intent 用 import android.app.IntentService; import android.content.Intent; Geofence への入出検知を受け取る際に使用するライブラリをインポートしています。 2. Geofence への入出検知を受け取る Service を生成します。 public class ReceiverTransitionIntent extends IntentService { public ReceiverTransitionIntent() { super(“ReceiverTrasitionIntent”); } @Override protected void onHandleIntent(Intent intent) { if(LocationClient.hasError(intent)) { // エラー通知 Intent 受信時の処理記載 return; } // Geofence 入出検知の受信 int transitionType = LocationClient.getGeofenceTransition(intent); // TransitionType 取 得 switch(transitionType) { case Geofence.GEOFENCE_TRANSITION_ENTER: case Geofence.GEOFENCE_TRANSITION_EXIT; // Geofence への入出検知受信時の処理 break; case Geofence.GEOFENCE_TRANSITION_DWELL; // Geofence へ入った後、指定時間経過後の通知受信時の処理 break; default: } } } 登録した Geofence への入出検知時に発行される Intent を受信し、どの契機で発行された Intent かを取 得することで、判定を行います。Intent の発行契機は以下のとおりです。 GEOFENCE_TRANSITION_ENTER :Geofence に進入した場合 GEOFENCE_TRANSITION_EXIT :Geofence から退出した場合 GEOFENCE_TRANSITION_DWELL :Geofence に入ったあと、設定時間が経過した場 合 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 365 7-3. Location ◆サンプルケース: ○Activity Recognition Activity Recognition(行動認識)は、静止中、徒歩で移動中、車で移動中などの行動を認識することがで きます。実装するにあたり、Activity Recognition では、認識を開始する Activity 側と、認識結果を受け取る Service 側の両方の実装が必要となります。 ○Activity 側(行動認識開始) 1. ActivityRecognition 関連のライブラリをインポートします。 // Location 関連 import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks; import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener; import com.google.android.gms.location.ActivityRecognitionClient; import com.google.android.gms.location.LocationClient; // PendingIntent 用 import android.app.PendingIntent; import android.content.Intent; Fused Location Provider と同様に、Activity Recognition では以下のライブラリをインポートします。 LocationClient ライブラリ Activity Recognition ライブラリ 行動認識結果を通知するための、PendingIntent ライブラリ 2. LocationClient、ActivityRecognition のインスタンスを生成します。 public class MainActivity extends OnConnectionFailedListener { private LocationClient mLocationClient; Activity implements ConnectionCallbacks, public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // LocationClient のインスタンス生成 mLocationClient = new LocationClient(this, this, this); // Activity Recognition のインスタンス生成 mActRecognition = new ActivityRecognitionClient(this, this, this); } } ・ConnectionCallbacks、OnConnectionFailedListener については、記述内容と同様です。 connect()のコールバック先で、行動認識を開始、disconnect()のコールバック先で行動認識を終了、となる ようインターフェースを実装します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 366 7-3. Location 3. Google Play services へ接続開始、切断、失敗時などのコールバックメソッドを設け、全体の処理を記載し ます。 @Override public void onResume() { super.onResume(); mLocationClient.connect(); // 画面復帰時に Google Play-services 接続開始 } @Override public void Pause() { sumepr.onPause(); mLocationClient.disconnect(); // アプリケーション終了時に Google Play-services 接続停止 } @Override public void onConnected(Bundle bundle) { // 行動認識処理を開始 } // connect()のコールバック @Override public void onDisconnected() { // 行動認識処理を終了 } @Override public void onConnectionFailed(ConnectionResult connectionResult) { // Google Play-services 接続失敗時の処理を記載(ログ、トースト表示など) } 上記のサンプルコードでは、connect()の契機が onResume()のみです。また、disconnect()の契機も onPause()のみです。 実際に Activity Recognition を組み込んだアプリケーションを作成する際は、作成するアプリケーションにもより ますが、Start/Stop のボタンを設ける方法が考えられます。Start 押下時に connect()をコール、Stop 押下時 に disconnect()をコールするような処理を行うことで、行動認識の ON/OFF を切り替えることができます。このよう に、実装を行う際は、明確に切り替え可能なことが望ましいでしょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 367 7-3. Location 4. 行動認識の開始および終了を行います。 @Override public void onConnected(Bundle bundle) { // connect()のコールバック // 行動認識処理を開始 // 行動認識結果通知の PendingIntent 生成 Intent intent = new Intent(this, ReceiverRecognitionIntent.class); pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mActRecognition.requestActivityUpdates(1000, pendingIntent); } @Override public void onDisconnected() { // disconnect()のコールバック requestActivityUpdates()を組み込むことで、行動認識の結果を取得するたびに、サービス側へ認識結果 // 行動認識処理を終了 を通知する仕組みとなっています。 if(pendingIntent != null) { mActRecognition.removeActivityUpdates(pendingIntent); ※ 本仕組みを使用するにあたり、ACTIVITY_RECOGNITION の Permission を追加する必要が pendingIntent = null; あります } if(mActRecognition != null) { mActRecognition.disconnect(); 行動認識を終了する場合は、removeActivityUpdates()を使用します。 mActRecognition = null; } } 詳細については、以下の URL を参照してください。 【公式サイト】 https://developer.android.com/reference/com/google/android/gms/location/Activity RecognitionClient.html#requestActivityUpdates(long, android.app.PendingIntent) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 368 7-3. Location ○Service 側(行動認識結果受信) 1. ActivityRecognition 関連のライブラリをインポートします。 // ActivityRecognition 用 import com.google.android.gms.location.ActivityRecognitionResult; import com.google.android.gms.location.DetectedActivity; // Intent 用 import android.app.IntentService; import android.content.Intent; Activity Recognition の結果を受け取るためのライブラリをインポートします。 2. 行動認識結果を受け取る Service を生成します。 public class ReceiverRecognitionIntent extends IntentService { // コンストラクタ public ReceiverRecognitionIntent() { super(“ReceriverRecognitionIntent”); } @Override protected void onHandleIntent(Intent intent) { if(!ActivityRecognitionResult.hasResult(intent)) { // Intent の結果通知有無確認 // 行動認識結果無し return; } // 行動認識結果の取得 ActivityRecognitionResult actRecogResult ActivityRecognitionResult.extractResult(intent); DetectedActivity detectedActivity = actRecogResult.getMostProbableActivity(); int activityType = detectedActivity.getType(); // 行動タイプの取得 // 行動タイプの確認 switch(activityType) { case DetectedActivity.IN_VEHICLE: // 車に乗車中 break; case DetectedActivity.ON_BICYCLE: // 自転車に乗車中 break; case DetectedActivity.ON_FOOT: // 徒歩中 break; case DetectedActivity.STILL: // 静止中 break; case DetectedActivity.TILTING: // デバイスが傾いている break; case DetectedActivity.UNKNOWN: // 不明 break; default: // それ以外 break; } } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. = 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 369 7-3. Location 行動認識が行われると、Activity 側で処理された requestActivityUpdates()により、Service 側へ結果が Intent 通知されます。Service 側では、通知された Intent を受け取り、getType()で行動タイプを取得するこ とができます。 行動タイプは、以下に示すような種類が存在します。 IN_VEHICLE :車 ON_BICYCLE :自転車 ON_FOOT :徒歩 STILL :静止 TILTING :傾き UNKNOWN :不明 行動タイプ取得時には、Activity 側へ再通知します。再通知することにより、デバイス画面上に行動認識の結 果を表示させることや、行動タイプごとに処理を行う(車で移動中は位置情報を取得、静止中は取得しないなど) ことで、アプリケーションとして活用することができます。 その他、詳細については以下の URL を参照します。 【公式サイト】 http://developer.android.com/reference/com/google/android/gms/location/ActivityR ecognitionResult.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 370 7-3. Location 7-3-4. Location API 実装の基本的な注意点 ■基本的な注意点 ○前提 ・Google Play services が使用できない端末では、Location API を利用できません。そのため、アプリケーショ ン起動時に以下のようなチェック処理を行います。 利用可否チェック処理のサンプルコードを以下に記載します。 public void onCreate(Bundle savedInstanceState) { // Google Play-services 利用可否チェック int retCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); if(retCode == ConnectionResult.SUCCESS) { // 利用可能な場合 } else if(GooglePlayServiceUtil.isUserRecoverableError(retCode)) { // サービス利用不可だがユーザによる設定変更可能な場合 GooglePlayServicesUtil.getErrorDialog(retCode, this, 1, new DialogInterface.onCancelListener() { @Override public void onCancel(DialogInterface dialog) { // キャンセル時の処理(finish()など) } }).show(); } else { // サービス利用不可(ユーザによる対応不可)の場合 } } protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch(requestCode) { case 1: // getErrorDialog 設定時に設定した戻り値(1) // ユーザによる設定変更(PlayStore など)後の再確認処理 // 再確認時も Google Play-services が利用不可な場合は、Activity 実行中止等の処理 default: super.onActivityResult(reqeustCode, resultCode, data); } } Location API 利用時に、使用しているデバイスが Google Play services 利用可能であるかを、毎回確認 する必要があることに注意してください。 詳細については、以下の URL を参照してください。 【公式サイト】 http://developer.android.com/training/location/retrieve-current.html#CheckServices Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 371 7-3. Location ○Android ビギナー向け AndroidManifest.xml に、GPS の利用を許可するパーミッションを追加する必要があります。 AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> </manifest> Activity Recognition 実装時は、AndroidManifest.xml に以下のパーミッションを追加する必要がありま す。 AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" < uses-permission android:name= "com.google.android.gms.permission.ACTIVITY_RECOGNITION"/> </manifest> ○従前の手法に習熟した開発者向け Geofencing および Activity Recognition の実装では、Geofence への入出や行動認識検知時に、それぞ れ Intent を発行し、Service 側で受信するといった実装を行います。その際、Service 側のソースファイルを、 AndroidManifest.xml に登録する必要があります。 AndroidManifest.xml <manifest xmlns:android=http://schemas.android.com/apk/res/android < uses-permission android:name= "com.google.android.gms.permission.ACTIVITY_RECOGNITION"/> <application 中略 <service android:name=”.ReceiverTransitionIntent”> android:name=”.ReceiverRecognitionIntent”> </service> </application> </manifest> 上記のように、Manifest へのソースファイル登録を行わない場合、IntentService の呼び出しに失敗してし まいます。 また、登録を行わないことによるエラーは発生しないため、注意が必要です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 372 7-3. Location ○既存システムの移行シーン向け ■従前の手法との差 No 1 機能 従前の手法 新規 Location API Fused Location Provider LocationManager クラスでの位置情 LocationClient クラスでの位置情報取 位置情報取得時のプロバイダ選択 報取得 得 ・ 位 置 情 報 取 得 時 、 GPS ま た は ・位置情報取得時に、指定プロバイダを Network プロバイダを個別に指定しま 利用者が気にする必要がなく、プロバイダ す。そのほかにも、getProvides()でプロ の組合せ バイダリストを取得する必要があります。 (GPS/Network/cell/sensor) (プロバイダを取得する類似 API が複数 によって、最適な方法で精度の高い位置 あり、端末によっては動作しない可能性 情報を取得することができます。 があります) ・設定する Priority により、精度、電池 消費量、インターバル時間を加味すること ができるため、場面に応じた使い分けがで きます。 2 Geofencing LocationManager クラスの LocationClinet クラスの Geofence 機 addProximityAlert()で実装 能で実装。 ・設定領域への入出時に Intent を発行 従前の手法に加え、以下機能が利用可 し、検知することができます。 能です。 ・電池消費量が約 1/3 です。 ・設定領域への入出に含め、入った時点 から指定タイマー時間経過後に、Intent を 発 行 す る こ と が で き ま す 。 (TRANSITION_DWELL) ・LocationClient での正確な位置情報 の取得により、設定領域自体も正確な 位置へ設定することができます。 3 Activity Recognition ※該当する従前の手法無し 行動認識開始時点より、利用者の行動 (車、自転車、徒歩など)を認識した時点 で Intent を発行し、検知することができ ます。 位置情報が直前の計測結果と大きくかい離する場合に誤表示を防ぐためのアルゴリズムについて。 以下の条件では、位置情報を正確に取得できなかった旨のアラートを表示し、取得した最新の位置情 報を使用しないように促す。 現在と前回の時間差分が 1 分以内で測位地点差が 10km 以上の場合 現在と前回の時間差分が 10 分以内で測位地点差が 100km 以上の場合 現在と前回の時間差分が 1 時間内で測位地点差が 500km 以上の場合 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 373 7-4. Maps 7-4. Maps Android には、「Google Map Android API」が提供されています。これは、Google Map を使用した Android アプリケーションを開発する際に使用します。 本章では現在の最新バージョンである「Google Map Android API v2」と、それ以前の「Google Map Android API v1」との差について記載します。 ※新しい Location API を Google Maps Android API と組み合わせて使用することによって、位置情報取得 精度を向上させることができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 374 7-4. Maps 7-4-1. Maps の概要(Google Map Android API v2) ■概要 Android アプリケーション上で地図の表示や、位置情報を取得するための API を提供します。2012 年 12 月に「Google Map Android API v2」が公開され、Google Play services SDK としてリ リースされています。 ■特徴 「Google Map Android API v1」との互換性はありません。 2D/3D 対応のベクターベースのマップです。 地図を回転、傾けることができます。 ズームボタンの表示、ダブルタップによるズームイン、ピンチイン/ピンチアウトなどの機能が、標準で実装さ れています。 地図に対する回転や視点変更、屋内や地下街マップの表示、その他カスタマイズ機能が、Google Map クラスの API でシンプルに提供されています。 詳細については、以下の URL を参照してください。 【公式サイト】 http://android-developers.blogspot.jp/2012/12/new-google-maps-android-api-nowpart-of.html ■メリット 独自実装や外部ライブラリによる実装を行う必要のあった、ズームイン/アウトやピンチイン/アウトなどの 機能が、API として提供されています。 Google マップの機能を組み込んだ Android アプリケーションの開発を行うことができます。 複数の地図を、1 つのアプリケーション上に表示できます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 375 7-4. Maps 7-4-2. 従前の手法(Google Map Android API v1) ○従前の手法の特徴 ■概要 Google Map Android API v1 は、2007 年 11 月に Android SDK の公開とあわせて、公開された機能で す。ラスター(小さな色の点(ドット)を集めて構成される画像)ベースのマップとして表示されます。 ■特徴 Google APIs に含まれているため、Android SDK と一緒にインストールされます。(Google Map Android API v2 は、Google Play services に含まれます。これについては、後述します。) MapView クラスを使用して地図の表示を行います。また、地図を表示する Activity は、MapActivity を継承し て実装する必要があります。 ※次ページのサンプルコードをご参照ください 上記より、別の Activity を継承して作成しなければならないようなシーンでは地図表示を併用することが困難でし た。また、1 つの画面では、1 つの地図のみの表示となります。 ○推奨に至った理由 Google Map Android API v2 は、地図の表示に Fragment を使用することによって、1 つの画面に複数の 地図を表示できます。(7-4-3.のサンプルコードで記載します) ま た 、 Google Map Android API v1 は 、 そ れ ぞ れ 個 別 の 地 図 の 操 作 を 行 う ク ラ ス (Overlay 、 MapController など)の API を使用し、地図の移動や拡大、マーカーの表示などの機能を、実装していました。そ れに対し、Google Map Android API V2 は、地図に関する操作のほとんどを GoogleMap クラスで実行でき ます。その他にも、独自実装する必要のあった機能が、標準で搭載されています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 376 7-4. Maps ◆サンプルケース: <従前の手法(Google Map Android API v1)> 【前提および注意点】 前提として、現状 Google Map Android API v2 の公開に伴い、2013 年 3 月 18 日以降は v1 用の新 しい API key を取得することができません。ですが、Google Map Android API v1 を使用した、地図を表示す るだけのサンプルコードを以下に記載します。 ○地図を表示する 1. Map 機能の使用に必要なパーミッションを、AndroidManifest.xml に追加します。 // AndroidManifest への Permission 追加 <manifest xmlns:android=http://schemas.android.com/apk/res/android // インターネット通信許可 <uses-permission android:name="android.permission.INTERNET" /> <application // Maps のライブラリを追加 <uses-library android:name="com.google.android.maps" /> </application </manifest> 2. レイアウトに地図を表示するための MapView を配置します。 // レイアウトへ MapView 配置 <RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android xmlns:tools=http://schemas.android.com/tools android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <com.google.android.maps.MapView // MapView 配置 android:id="@+id/mapview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:enabled="true" android:clickable="true" // ドラッグを有効にする android:apiKey="XXXXXXX" // Maps API key 設定 /> </RelativeLayout> Maps API key は、「Google Map Android API v1」用の API key を使用します。 ※ただし、前述のとおり「Google Map Android API v1」用の API key は取得できません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 377 7-4. Maps 3. MapActivity クラスを継承した Activity を作成します。 import com.google.android.maps.MapActivity public class MainActivity extends MapActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected Boolean isRouteDisplayed() { return false; // ルート情報を表示する場合は true を設定 } } MapActivity を継承した Acitivity クラスを生成するだけで、基本的には問題なく地図が表示されます。 ただし、MapActivity を継承する場合、isRouteDisplayed()の実装が必要になります。 返却値を true にするとルート情報を表示し、false にすると表示しません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 378 7-4. Maps 7-4-3. Maps(Google Map Android API v2)の基本的な実装方法 ○Google Play services ライブラリのインポート ※以下、Eclipse の使用を前提として記載します。 1. Maps 用のプロジェクトを作成します。 ⇒パッケージ名は、例として "com.sample.maps"とします。 2. Google Map Android API v2 は、Google Play services に含まれます。 そのため、SDK マネージャーから Google Play services ライブラリをインストールします。 図 71 Google Play services のインストール 3. また、Google Play services ライブラリを使用するには、API Level8 の SDK が必要です。 そのため、未インストールの場合は SDK マネージャーからインストールします。 図 72 API Leve8 SDK のインストール 4. Eclipse にて、インストールした Google Play services ライブラリをプロジェクトとしてインポートします。 ⇒[/Android SDK インストール先/extras/google/google_play_services/libproject] Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 379 7-4. Maps 5. 新規プロジェクトを作成し、インポートした Google Play services ライブラリを GCM アプリケーション用プロ ジェクトのプロパティに追加します。 図 73 Google Play services ライブラリのインポート 手順 1~5 までを実行し、プロジェクトにエラーが発生しないことを確認する。 図 74 Maps プロジェクト Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 380 7-4. Maps ○API key の作成 1. API key を作成するため、apk にデジタル署名を付与する証明書の SHA-1 ハッシュ値を取得します。ハッ シュ値の作成には、keystore ファイルが必要となります。 Windows PC の場合、デバッグ版 keystore ファイルは以下に格納されています。 ⇒C:\Users\ユーザ名\.android\debug.keystore ※ ただし、Android Market に公開する場合は、別途 keystore の作成が必要となります。 ◆公開用 keystore ファイル作成: ①コマンドプロンプトを開き、java のインストールフォルダ (例:"C:\Program Files\Java\jre6\bin")へ移動 ②以下コマンドを実行 "keytool -genkey -v -keyalg RSA -keystore <格納場所>\ <証明書のファイル名> -alias <エイリアス> -validity 10000" ※作成時、「keystore のパスワード」「姓名」「組織単位名」「組織名」「都市名または地域名」「州名 または地方名」「国番号」を入力します。 (公開用 keystore ファイル作成時には、適切な内容で入力すること) 2. コマンドプロンプトを開き、以下コマンドを実行してハッシュ値を生成します。 "keytool -list -v -keystore <生成したキーストアの格納場所>" 上記コマンド実行時、「SHA1: XX:XX:XX:XX……」のように出力が行われます。 出力された値を保持しておきましょう。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 381 7-4. Maps 3. 以下の URL へアクセスし、Google APIs Console から Google Maps の API 利用登録を行います。 https://code.google.com/apis/console/ その際、Google アカウントのサインインが求められるので、サインインを行います。 図 75 Google APIs console 登録開始 上記画面が表示されるので、「Create project...」を押下します。 4. Google API サービス一覧が表示されるので、「Google Maps Android API v2」に対し、「OFF」であれば 「ON」に変更する。 図 76 Google API Service 一覧 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 382 7-4. Maps ※以下、「Google Cloud Messaging for Android」を「ON」に変更します。 図 77 Google Maps Android API v2(OFF) 「OFF」押下後、「Google Maps Android API v2」が「ON」に変更されます。 図 78 Google Maps Android API v2 (ON) 5. 左側の項目から「API Access」を選択し、「Create new Android key…」を選択します。 図 79 API Access 画面 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 383 7-4. Maps 6. ポップアップウィンドウが表示されるので、手順 2 で作成したハッシュ値(16 進数部分)と作成する Maps プロ ジェクトのパッケージ名を入力し、「Create」を選択します。 図 80 ハッシュ値+パッケージ名入力画面 入力形式は「XX:XX:XX:XX…」のハッシュ値に「;」を加え、パッケージ名を追記します。 ⇒例:" XX:XX:XX:XX:XX;com.example.maps" API key の作成に成功すると、「Key for Android apps」が追加され、API key が発行されます。 図 81 API key 発行画面 作成した API key は、Android Manifest.xml への記載値となります。 事前準備は以上で完了です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 384 7-4. Maps ◆サンプルケース:(Google Map Android API v2) 以下に、Google Map Android API v2 を利用した地図表示のサンプルコードを記載します。 ○地図を表示する 1. Map 機能の使用に必要なパーミッションを、AndroidManifest.xml に追加します。 // AndroidManifest への Permission 追加 <manifest xmlns:android=http://schemas.android.com/apk/res/android // インターネット通信許可 <uses-permission android:name="android.permission.INTERNET" /> // ネットワークの状態取得許可 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> // 外部ストレージへ地図のキャッシュを許可 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> // 位置情報の取得(ネットワーク経由) <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> // 位置情報の取得(GPS 経由) <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> // Google API へのアクセス許可 <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <uses-feature android:glEsVersion="0x00020000" android:required="true" /> // 地図の描画に OpenGL ES2 を使用しているため、 // OpenGL ES2 が必要な旨を指定 <application <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="事前準備で取得した API key" /> // API key の設定 </application> </manifest> Maps に関連するパーミッションについては、後述の「7-4-4. Maps(Google Map Android API v2)」で記載 します。 2. レイアウトに地図を表示するためのフラグメントを配置します。 // レイアウトに FragmentActivity を配置 <RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android xmlns:tools=http://schemas.android.com/tools xmlns:map=http://schemas.android.com/apk/res-auto android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <fragment android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent" android:name="com.google.android.gms.maps.MapFragment"/> /> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 385 7-4. Maps ※Android OS バージョン 2.X をサポートする場合、MapFragment ではなく SupportMapFragment を使 用します。 3. FragmentActivity クラスをインポートします。 // FragmentActivity ライブラリ import android. app.Activity; // MapFragment 使用時(Android OS 3.0 以上) import android.support.v4.app.FragmentActivity; // SupportMapFragment 使用時(Android OS 2.X) 4. 通常の Activity クラスを継承した Activity を作成します。 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } Google Maps Android API v1 のサンプルと同様に、レイアウトファイルの読み込み処理だけで地図を表示で きます。また、Android OS バージョン 2.X で動作を行う際に、SupportFragment を使用している場合は、 FragmentActivity を継承します。 ・ズームボタンの実装 ・ダブルタップによるズームインが可能 ・ピンチイン/ピンチアウトが可能 上記のような機能が Mapfragment では初期実装 (v1 では独自実装が必要) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 386 7-4. Maps 7-4-4. Maps(Google Map Android API v2)実装の基本的な注意点 ■基本的な注意点 ○前提 Google Play services 非対応端末では利用できないため、アプリケーション起動時に Google Play services の利用可否チェックを行う必要があります。 ○Android ビギナー向け Google Maps Android v2 を利用する際に必要なパーミッションを、AndroidManifest.xml へ追加します。 AndroidManifest.xml // AndroidManifest への Permission 追加 <manifest xmlns:android=http://schemas.android.com/apk/res/android // インターネット通信許可 <uses-permission android:name="android.permission.INTERNET" /> // ネットワークの状態取得許可 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> // 外部ストレージへ地図のキャッシュを許可 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> // 位置情報の取得(ネットワーク経由) <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> // 位置情報の取得(GPS 経由) <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> // Google API へのアクセス許可 <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <uses-feature android:glEsVersion="0x00020000" android:required="true" /> // 地図の描画に OpenGL ES2 を使用しているため、 // OpenGL ES2 が必要な旨を指定 <application <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="事前準備で取得した API key" /> // API key の設定 </application> </manifest> "android.permission.WRITE_EXTERNAL_STORAGE" 地図のキャッシュを、外部ストレージ領域に行うことを許可するためのパーミッションです。 "android.permission.ACCESS_COARSE_LOCATION" ネットワーク(3G/Wi-Fi)を使用した位置情報取得を、許可するためのパーミッションです。 "android.permission.ACCESS_FINE_LOCATION" GPS を使用した位置情報取得を、許可するためのパーミッションです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 387 7-4. Maps android:glEsVersion 地図表示に必須となる OpenGL ES2 の使用を定義します。 android:name="com.google.android.maps.v2.API_KEY"の Value 値として、7-4-3.の 事前準備で作成した API key を記載します。 Android デバイスがネットワーク通信可能な状態(3G/LTE など)となっていること。 (機内モードや圏外状態ではないこと) ※ただし、SIM は必ずしも必要ではなく、Wi-Fi 接続されていれば動作します。 7-4-3.にて Android Market 公開用の keystore ファイルを使用して作成した API key を使用する場合、作 成する Android アプリケーションに対して免責事項(Legal Notices)の掲載が必須となります。 詳細については、以下の URL を参照してください。 【公式サイト】 https://developers.google.com/maps/documentation/android/intro 「Google Maps Android API v2」での実行には、Google Play が必要です。そのため、エミュレータでは実行 できません。現状、エミュレータで実行した場合、Google Play services のアップデート画面が表示され、アップデ ートを行おうとするとエラーが発生します。 ○従前の手法に習熟した開発者向け Google Maps Android API v2 を使用する際、インストールする Android デバイスの OS バージョンに注意し てください。 最新の Android OS バージョン 4.4 搭載端末に、7-4-3.のサンプルコードで作成した Maps アプリケーションをイ ンストールし、実行した場合、以下のエラー(例)が発生します。 Caused by: java.lang.IllegalStateException: The meta-data tag in your app's AndroidManifest.xml does not have the right value. Expected 4132500 but found 0. You must have the following declaration within the <application> element: <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> エラーを回避するには、AndroidManifest.xml へ、以下の宣言を追記する必要があります。 <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 388 7-4. Maps また、SDK マネージャーに「Google Play services for Froyo」ライブラリが存在します。Android OS バージョ ン 2.2 以下となる対象アプリケーションの場合は、上記ライブラリを使用する必要があります。 ※Google Play services は、2013 年 10 月 31 日より、Android OS バージョン 2.2(Froyo)のサポート を終了しているため、注意が必要です。 詳細については、以下の URL を参照してください。 http://android-developers.blogspot.jp/2013/10/google-play-services-40.html 図 82 Google Play services for Froyo 地図表示は、OpenGL ES2.0 を使用します(AndroidManifest に宣言)が、Android OS バージョ ン 2.1(API Level7 Eclair)を含む、それ以前の OS バージョンでは、OpenGL ES2.0 をサポートし ていないため、ビルドは可能ですが動作はしません。 「Google Maps Android API v1」で使用していた API key は使用できないため、再度 API key を取得します。以前とは、API key の取得方法が異なることに注意してください。 本章の最初に述べたとおり、「Google Maps Android API v1」と、「Google Maps Android API v2」は互換性がありません。そのため、v1 で使用していたクラスや API が、v2 では使用できないことが ほとんどです。その一例を、以下に示します。 <Google Maps Android API v2 からの変更点> 地図上へ図形(ポリゴンや線)の描画 ◆Google Maps Android API v1: Overlay の draw をオーバーライドし、Canvas の drawLine メソッドなどの、描画メソッドを呼び出すことで 実装します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 389 7-4. Maps ◆Google Maps Android API v2: Overlay、Canvas の呼び出し元が存在しません。 v2 で は 、 「 Polygon 」 「 Polyline 」 ク ラ ス が 用 意 さ れ て い ま す 。 そ の た め 、 GoogleMap ク ラ ス の 「addPolygon」や、「addPolyline」メソッドにて実装します。 ズームボタンの表示 ◆Google Maps Android API v1: MapView に対し、setBuiltInZoomControls()で設定を行います。 その後、再描画(invalidate)することで表示を行います。 ◆Google Maps Android API v2: レイアウトに記載する MapFragment の定義にて、[map:uiZoomControls=”true”]の属性定義を行 うことで画面表示されます。デフォルトの値は「true」であるため、明記せずとも表示は行われます。また、 GoogleMap クラスの getUiSettings()メソッドで取得できる UiSettings の値を使用し、動的に定義値を 変更することも可能です。 動的に変更する値の詳細については、以下の URL を参照してください。 http://developer.android.com/reference/com/google/android/gms/maps/UiSettings. html#setCompassEnabled(boolean) その他、コンパスや MyLocation ボタンについてもレイアウトにて定義が可能です。 詳細については、以下の URL を参照してください。 https://developers.google.com/maps/documentation/android/map 現在地の取得方法 ◆Google Maps Android API v1: isMyLocationEnable()メソッドにて、現在地取得が可能かを判定します。 有 効 な 場 合 は 、 getMyLocation() メ ソ ッ ド で 現 在 地 を 取 得 し ま す 。 有 効 で な い 場 合 は 、 enableMyLocation() メ ソ ッ ド で 現 在 地 を 取 得 し ま す 。 ま た 、 取 得 し た 現 在 地 を 取 得 す る に は 、 MapView に対し、現在地を表示するためのオーバーレイを追加します。 ◆Google Maps Android API v2: GoogleMap ク ラ ス の setMyLocation() メ ソ ッ ド に て 現 在 地 取 得 を 有 効 に し 、 Location API(requestLocationUpdates)を使用して、現在地を取得します。また、現在地の表示も Location API(LocationClient クラス)にて実装することができます。 ※getMyLocation()は、Location API の公開により Depricated(非推奨)となりました。 getMyLocation()に比べ、Location API を使用することで、精度の高い位置情報を 取得できます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 390 7-4. Maps 1 画面に表示できる地図の数 ◆Google Maps Android API v1: 1 画面(1Activity)につき、1 地図(1MapView)のみ配置可能です。 ◆Google Maps Android API v2: 1 画面(1Activity)につき、複数画面(Fragment)を配置可能です。 ※ただし、表示地図の数が増えるほどパフォーマンスは低下します。 1 画面に複数の地図を表示した場合のキャプチャイメージを、以下に示します。 図 83 複数地図配置イメージ Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 391 7-4. Maps ○既存システムの移行シーン向け ■従前の手法との差(Google Maps Android API) No 従前の手法 機能 (Google Maps Android API v1) Google Maps Android API v2 通常の Activity を継承した Activity クラスを作成 し、MapFragment で地図を表示します。 1 地図の表示 MapActivity を継承した Activity クラスを作成 1 画面に対して、複数のマップを表示できます。 し、MapView で地図を表示します。 (Fragment 化されたため) 1 画面に対して、1 つの地図を表示できます。 ※API v2 にも、MapView クラスは存在します。し かし、同名であるだけで、地図表示とは異なる機能 のクラスです。 地図の移動、拡大、縮小、マーカーの表示には、 2 地図の操作 それぞれ個別クラスの機能を使用して実装しま す。 3 導入ライブラリ 4 地図の範囲 Android SDK と共にインストールされ、Google APIs に含まれています。 屋外のみとなります。 地図の操作に関して、ほとんどが GoogleMap クラ スの機能を使用することで実行できます。 ズームボタンやコンパスの表示/非表示を、レイアウト の xml 内で定義可能です。 Google Play services ライブラリに含まれます。 空港や地下街など、屋内にも対応しています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 392 7-5. Volley 7-5. Volley Volley は、Android アプリケーション向けに http 通信機能を提供するためのネットワークライブラリです。2013 年の Google I/O で発表されました。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 393 7-5. Volley ■概要 Volley ライブラリはサーバとデバイス間のデータ通信を容易に実現するためのライブラリです。 ■特徴 データのリクエスト データのレスポンス 上記機能が Volley 内でライブラリ化されており、Volley を組み込むことで自動的に処理を行います。 図 84 Volley 概要 ◆データのリクエスト 図 85 Volley リクエスト概要 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 394 7-5. Volley ◆データのレスポンス 図 86 Volley レスポンス概要 Volley ライブラリは、以下に示す機能をカバーしています。 Request:Image、JSON などレスポンス種別に応じたリクエスト RequestQueue:キューの管理 CacheDispatcher:ディスクキャッシュ検索 NetworkDispatcher:ネットワークアクセス ExecutorDelivery:Handler でレスポンスを UI スレッドに post Response.Listener:リクエスト発行時に登録したリスナーに通知 ●Volley ライブラリで可能な機能(詳細) リクエストのコールバックが UI スレッドで処理されます リクエストのスケジューリングを行うことができます リクエストキャンセルを行うことができます Activity が存在しない時に自動キャンセルできます ネットワーク通信が非同期で処理されます メモリキャッシュ、ディスクキャッシュを行うことができます リクエストの優先順位を設定できます ※本機能を利用するためには、リクエストクラスの拡張が必要です。手法は本文中で説明します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 395 7-5. Volley ◆サーバとデバイス間のデータ通信フロー 図 87 Volley 処理フローA (ディスクキャッシュから対象データを発見した場合) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 396 7-5. Volley 図 88 Volley 処理フローB (ディスクキャッシュに対象データが存在せず、ネットワークからデータを取得した場合) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 397 7-5. Volley 図 89 Volley 処理フローC (ディスクキャッシュを使用せず、ネットワークから対象データを取得した場合) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 398 7-5. Volley Volley がレスポンスを返す流れは、以下に示すとおりです。 (A)ディスクキャッシュに対象データが存在した場合の例 1. リクエストを生成する 2. リクエストをリクエストキューに格納する 3. ディスクキャッシュの CacheDispatcher にキューを追加する 4. ディスクキャッシュに対象データが存在したため、レスポンスを ResponseDelivery に渡す 5. レスポンスを返す (B)ディスクキャッシュに対象データが存在せず、ネットワークから取得した場合を例 1. リクエストを生成する 2. リクエストをリクエストキューに格納する 3. ディスクキャッシュの CacheDispatcher にキューを追加する 4. ディスクキャッシュに存在しないため、NetworkDispatcher にキューを追加する 5. ネットワークから検索して、レスポンスを ResponseDelivery に渡す 6. レスポンスを返却する 2. でキューを格納した後、レスポンスが返ってくるまでは別スレッドで動作します。そのため、UI スレッド が停止することはありません。ただし、大量のリクエストを短期間で行った場合、キューが溜まってしまいま す。それは、リクエストのディスパッチャ(キャッシュ/ネットワーク)が一つだけであり、並列化されていないため です。場合に応じて、必要なものだけをリクエストすることで、より速い通信を実現することが可能です。 (C)ディスクキャッシュを使用せず、ネットワークから対象データを取得した場合の例 1. リクエストを生成する 2. リクエストをリクエストキューに格納する 3. ネットワークの NetworkDispatcher にキューを追加する 4. ネットワークから検索して、レスポンスを ResponseDelivery に渡す 5. レスポンスを返却する Volley を使用するメリットを、以下に示します。 特徴で挙げたような、ネットワーク通信における複雑な処理を、ライブラリの機能を 使用することで簡単に行うことができます Image データに特化した処理では、内部にメモリキャッシュをもつため、パフォーマンスの 向上につながります Android OS バージョン毎の、HTTP リクエスト通信クラスの使い分けは不要です バックグランド処理を意識する必要はありません Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 399 7-5. Volley 7-5-1. 従前の手法 ○従前の手法における特徴 Android は、メインスレッドである UI スレッドが他のスレッドからの操作を受け付けません。そのため、 AsyncTask を用いた非同期処理を行います。 Android OS バージョン毎に、HTTP リクエスト通信クラスを使い分ける必要があります。 Default HTTP Client、HttpURLConnection などの HTTP 通信は、非同期でのキャンセル処理や スレッド、メモリ、キャッシュ方法など、周辺技術を設計して実装する必要があります。 ※Image データはキャッシュを組まない限り、毎回アクセスします。アプリケーションを再起動した場合も同様 に、キャッシュがなければ Image データの再取得を実行します。 ○推奨に至った理由 メリットで挙げたとおり、ネットワーク通信の複雑な処理をライブラリ化していることで、利用者が簡単に使 用することが可能です。 オープンソースとなっているため、利用者によるカスタマイズが可能です。 リクエストのデータ種別に影響を受けやすい、単純な文字列をリクエストする場合、キャッシュ設定を意識 した実装は不要です。 その他、「7-5-4 既存システムの移行シーン向け」を参照してください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 400 7-5. Volley サンプルケース: <従前の手法> ○キャッシュの設定(Image データ) public class ImageDataCache { private static HashMap<String, Bitmap> cache = new HashMap<String, Bitmap>(); // キャッシュから Image データを取得する public static Bitmap getImage(String key) { if(cache.containsKey(key)) { return cache.get(key); // 存在する場合はキャッシュデータを返却 } return null; // 存在しない場合は Null を返却 } // キャッシュに Image データを設定する public static void setImage(String key, Bitmap imageData) { cache.put(key, imageData); } } 【キャッシュ設定処理】 URL の文字列(String)を key、Image データ(Bitmap)を value とした HashMap を用意します。ネットワ ーク通信を行う前に HashMap を参照し、データがあれば通信せずにキャッシュデータを返却、存在しなければ Null を返却します。また、通信してデータを取得した際に、HashMap にデータを設定します。 また、キャッシュを設定する場合は、上記のような実装に加え HTTP 通信時のキャッシュデータチェック処理が必 要となります。 HTTP 通信時のキャッシュデータチェック処理については後述します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 401 7-5. Volley ○URL 文字列(String)から Image データ(Bitmap)を取得 import import import import java.net.HttpURLConnection; java.net.MalformedURLException; java.net.URL; android.os.AsyncTask; public class MyHttpActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); URL url = null; try { url = new URL(“http://www.XXX”); } catch(MalformedURLException e) { Log.e(“URL”, e.getMessage()); return; } new HttpTask().execute(url); } Class HttpTask extends AsyncTask<String, Void, Bitmap> { @Override protected Bitmap doInBackground(String... str) { Bitmap imageData; try { InputStream inputstream = str.openStream(); imageData = BitmapFactory.decodeStream(inputStream); return imageData; } catch(IOException ioe) { Log.e(“doInBackground”, ioe.getMassage()); } return null; } @Override protected void onPostExecute(Bitmap response){ // 非同期処理の戻り先、画像表示 } 【HttpURLConnection での通信】 指定した URL に対し、Image データのリクエストを行います。 取得処理は、AsyncTask によってバックグランドで処理されます。処理を行う際、キャッシュの存在を確認し、存 在する場合はキャッシュデータを返却します。存在しない場合は通信を行い、Image データを取得します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 402 7-5. Volley 【Android OS バージョンによる通信クラスの使い分け】 上記にサンプルとして記載した HTTP 通信処理において、Android OS により次の HTTP 通信クラスを使い分 ける必要があります。 ・HttpURLConnection(java.net パッケージ) ・Apache HTTP Client(org.apache.http パッケージ) 現状での推奨される通信クラスの使い分けは、以下のとおりです。 ・Froyo(Android OS 2.2)以前 :Apache HTTP Client ・Gingerbread(Android OS 2.3)以降 :HttpURLConnection Froyo 以前は、HttpURLConnection に、コネクションプールを汚染する深刻なバグが含まれていました。その ため、Apache HTTP Client が推奨されていました。 Gingerbread 以降では、HttpURLConnection のサポートが強化され、使用が推奨されています。ただし、 Froyo 以前の OS バージョンサポートが必須の場合は、HTTP Client の使用が推奨されています。 詳細については、以下の URL を参照してください。 【公式サイト】 http://android-developers.blogspot.jp/2011/09/androids-http-clients.html <Volley> ○キャッシュの設定(Image データ) public class ImageDataCache implments ImageCache { private LruCache<String, Bitmap> cache; public BitmapCache() { int maxSize = 10 * 1024 * 1024; cache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; } @Override public Bitmap getBitmap(String url) { return cache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { cache.put(url, bitmap); } } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 403 7-5. Volley 【キャッシュ設定処理】 Volley は、リクエストキューがディスクキャッシュを使用しています。そのため、Image データの取得時にメモリキャ ッシュを行うことで、よりパフォーマンスを向上させています。 上記では、Least Recently Used Cache クラスを使用することで、メモリキャッシュ処理を行っています。 メモリサイズは 10MB とし、Image データの URL をキーに Bitmap データをキャッシュに保存、またはキャッシュ から取得しています。 ※キャッシュ量を制限、またはキャッシュしない処理の実装も可能です。 ○URL 文字列(String)から Image データ(Bitmap)を取得 import import import import import com.android.volley.RequestQueue; com.android.volley.toolbox.ImageLoader; com.android.volley.toolbox.ImageLoader.ImageCache; com.android.volley.toolbox.NetworkImageView; // 画像取得リクエスト用 com.android.volley.toolbox.Volley; Public class MyHttpActivity extends Activity { @Override Protected void onCreate(Bundle savedInstanceState) { Super.onCreate(savedInstanceState); setContentView(R.layout.main); // HTTP リクエストキューの生成 mRequestQueue = Volley.newRequestQueue(this); // URL を指定して URL から BitmapImage データを取得 String url = “http://www.XXX”; // Image データのリクエスト NetworkImageView nImageView = new NetworkImageView(this); nImageView.setImageUrl(url, new ImageLoader(mRequestQueue, BitmapCache())); } } 【NetworkImageView クラスで Bitmap 画像を取得】 指定した URL に対し、Image データのリクエストを行います。 取得処理は、NetworkImageView クラスを使用します。setImageUrl で URL を指定し、同時にキャッシュの 指定も行います。 Volley を使用することで、キャッシュの存在の有無による処理を、利用者が気にする必要はなくなります。 【Android OS バージョンによる違いは無い】 従前の方法のように、利用者は OS バージョンによる推奨通信クラスを考慮することは不要となります。Volley では、データ種別によるリクエストメソッドが異なることにのみ、注意します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 404 7-5. Volley 7-5-2. Volley の基本的な実装方法 Volley ライブラリは、Git リポジトリまたは jar ファイルで公開されています。 Git からのインポート: ※Git をインストールしていない場合は、以下の URL からインストールを行います。 git GUI Clients :: http://git-scm.com/downloads git clone https://android.googlesource.com/platform/frameworks/volley 上記 Clone で取得したプロジェクトを、Android プロジェクトとしてインポートします。 インポート時の注意点を、以下に示します。 Eclipse の使用が前提です インポート後に、プロジェクトのプロパティからチェックを行います (インポートしたプロジェクトは、「Is Library」にチェックが入っていないため) (以下の図 Is Library チェック参照) Volley プロジェクトをライブラリ化したあと、新規 Android プロジェクトのプロジェクト設定を開きます。そこで、 Volley プロジェクトの src ディレクトリも、ソースディレクトリとして追加設定を行います。 図 90 Is Library チェック または、インポートした Volley プロジェクトをビルドし、jar ファイルを生成、新規プロジェクトから外部ライブラリとし てインポートします。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 405 7-5. Volley ビルド手順は、以下に示すとおりです。 1. Volley プロジェクトの build.xml を右クリックし、「1 Ant ビルド」を選択する 2. 「volley.jar」を、新規プロジェクトの libs 配下に格納して使用する (「volley.jar」は、ビルドに成功した際、bin ディレクトリ配下に生成されています) 図 91 Ant ビルド実行 jar のインポート: ※上述のとおり、Volley プロジェクトから「volley.jar」を生成します。 もしくは、Google I/O 公式サイトより、Volley の jar ファイルを用意します。 ◆jar ファイルのダウンロード先 URL(一覧から volley.jar を選択してダウンロード) http://iosched.googlecode.com/git-history/ac2ec5c3c70b0b81df407fde11a0f1354aa 9cd4a/android/libs/ 用意した jar ファイルを外部 jar としてプロジェクトにインポートします。 ※Git からのインポート同様、Eclipse を使用していることを前提とします。 ○Volley 適応ケースモデル 図 92 Volley 適応ケースモデル Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 406 7-5. Volley ① 認証 HTTP 通信部分がブラックボックスとなっているため、通信先によって認証処理が必要となります。 (Volley での認証については、7-5-4.で後述します) ② Request/Response/Cancel Request :Json,xml などのメタ情報と、Image,Text などの表示情報の要求 Response :リクエストされた情報を受け取る Cancel :リクエストや、リクエストキューなど、全てのリクエストの中止 ③ UI 処理 レスポンスを受け取ったあと、UI 処理(画面表示やリトライなど) ■リクエスト(String)~レスポンス <HttpURLConnection+AsyncTask> 1. HttpURLConnection ライブラリ、AsyncTask ライブラリのインポート import import import import java.net.HttpURLConnection; java.net.MalformedURLException; java.net.URL; android.os.AsyncTask; 2. 非同期処理の設定 public class MyHttpActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); URL url = null; try { url = new “http://www.XXX”; catch(MalformedURLException e) { Log.e(“URLConnection”, e.getMessage()); } New HttpTask().execute(url); } Class HttpTask extends AsyncTask<URL, void, String> @Override protected String doInBackground(String... str) { // 非同期処理を行う } @Override protected void onPostExecute(String response){ // 非同期処理の戻り先 } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 407 7-5. Volley doInBackground()内で、非同期で行う処理を記載します。 非同期処理の戻り先として、onPostExecute()がコールされるため、失敗時のリトライなど、メインスレッドへ反映 させる処理を記載します。 ⇒AsyncTask についての詳細は、以下の URL を参照してください。 http://developer.android.com/reference/android/os/AsyncTask.html 3. HttpURLConnection での HTTP リクエストとレスポンス取得(AsyncTask での非同期処理) Class HttpTask extends AsyncTask<URL, void, String> @Override protected String String doInBackground(String... str) { String result = “”; // HttpURLConnection を使ったデータ取得 HttpURLConnection urlConnection = null; try { urlConnection = (HttpURLConnection)str[0].openConnection(); result = IOUtilis.toString(urlConnection.getInputStream()); } catch (Exception e) { Log.d(“HttpRequest”,”Exception”); } finally { if(urlConnection != null) { urlConnection.disconnect(); } } return result; } @Override protected void onPostExecute(String response){ // シーンによって UI 処理を行う(画面表示、失敗時のリトライなど) } ⇒HttpURLConnection については、以下の URL を参照してください。 http://developer.android.com/reference/java/net/HttpURLConnection.html <Volley> 1. Volley ライブラリのインポート import import import import import com.android.volley.Request; com.android.volley.RequestQueue; com.android.volley.Response; com.android.volley.toolbox.StringRequest; // String のリクエスト用 com.android.volley.toolbox.Volley; Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 408 7-5. Volley 2. リクエストキュー生成 public class MyActivity extends Activity { private RequestQueue mRequestQueue; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // HTTP リクエストキューの生成 mRequestQueue = Volley.newRequestQueue(this); } } 3. HTTP リクエストを行い、レスポンス文字列を取得 // HTTP リクエストキューの生成 mRequestQueue = Volley.newRequestQueue(this); // URL を指定して URL から文字列を取得 String url = “http://www.XXX”; mRequestQueue.add(new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { // レスポンス時の処理 @Override public void onResponse(String response) { // シーンによって UI 処理を実装 } }, null)); 上記は、StringRequest で文字列をリクエストしています。ただし、Volley がサポートするリクエスト種別ごとの メソッドを使用することで、他のデータ種別のリクエストが可能です。 Volley がサポートするリクエスト種別ごとのメソッドを、以下に示します。 ClearCacheRequest :キャッシュのクリア要求 ImageRequest :Bitmap 用 JsonRequest :Json 処理用の抽象クラス JsonArrayRequest :JsonArray 用 JsonObjectRequest :Json オブジェクト用 StringRequest :文字列用 また、Request クラスを継承することで、個別に Request クラスを作成することが可能です。そのため、リクエス トメソッドには存在しないデータ種別についても、拡張クラスを作成することでリクエスト可能です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 409 7-5. Volley ■xml のリクエストサンプルコード Request 拡張クラス(InputStreamRequest クラス) public class InputStreamRequest extends Request<InputStream> { private final Listener<inputstream> xmlListener; public InputStreamRequest(int method, String xml_url, Listener<InputStream> listener, ErrorListener errorListener) { super(method, xml_url, errorListener); xmlListener = listener; } public InputStreamRequest(String xml_url, Listener<InputStream> listener, ErrorListener errorListener) { this(Method.GET, xml_url, listener, errorListener); } @Override protected void deliverResponse(InputStream response) { xmlListener.onResponse(response); } @Override protected Response<InputStream> parseNetworkResponse(NetworkResponse response) { InputStream inputStream = new ByteArrayInputStream(response.data); return Response.success(inputStream, HttpHeaderParser.parseCacheHeaders(response)); } } 指定された URL から xml を取得する場合、Request クラスを拡張した、上記 InputStremeRequest クラ ス(サンプル)を作成します。実際に取得した xml の Parse 処理は、以下のとおり別途処理を作成する必要があり ます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 410 7-5. Volley xml の Parse、リクエスト(リクエストキュー生成は共通の処理とする) Request<InputSream> inputstreamRequest = new InputStreamRequest(Method.GET, url, new Listener<InputStream>() { @Override public void onResponse(InputStream inputStream) { BufferInputStream bfStream = new BufferInputStream(inputStream); try { XmlPullParser xmlPullParser = Xml.newPullParser(); xmlPullParser.setInput(inputStream, “UTF-8”); int eventType = xmlPullParser.getEventType(); while(true) { if(eventType == XmlPullParser.END_DOCUMENT) { break; } // 他のイベントタイプの場合の処理は利用者各自で行う。 eventType = xmlPullParser.next(); } } catch(Exception e) { Log.e(“XmlPullParser”, e.getMessage()); } finally { if(bfStream != null) { try { bfStream.close(); } catch(IOException ioe) { Log.e(“XmlPullParser”, ioe.getMessage()); } } if(inputStream != null) { try { bfStream.close(); } catch(IOException ioe) { Log.e(“XmlPullParser”, ioe.getMessage()); } } } } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // error } }); mReauestQueue.add(inputstreamRequest); } Xml の Parse については、XmlPullParser クラスを使用します。 文字列解析時にイベントタイプを取得し、読み込んだドキュメントが終了後、自動で終了します。 ⇒XmlPullParser についての詳細は、以下の URL を参照してください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 411 7-5. Volley 【公式サイト】 http://developer.android.com/intl/ja/reference/org/xmlpull/v1/XmlPullParser.html ⇒これまでの Volley に関するコードについて、公式サイトにサンプルコード等の情報はありません。バージョンアップ 情報が、時系列で記載されているだけとなっています。 https://android.googlesource.com/platform/frameworks/volley/ 4. リクエストの開始、停止、キャンセル protected void onPause() { super.onPause(); mRequestQueue.stop(); } protected void onResume() { super.onResume(); mRequestQueue.onStart(); } protected void cancelRequest(StringRequest request) { // 文字列リクエスト単位でのキャンセル request.cancel(); } // リクエストキューの全キャンセル // リクエストのキュー前にリクエストにオブジェクトの TAG を設定 String TAG = "通信を実行する画面のクラス名" mRequestQueue.setTag(TAG); queue.add(mRequestQueue); ~~~ ~~~ mRequestQueue.cancelAll(TAG); ■リクエストの開始: 上記は、Android アプリケーションの画面復帰時に自動でコールされる onResume()時に、明示的にリクエス トを開始しています。これまで記述したサンプルコードでは、明示的にリクエストを開始していません。Volley クラスの newRequestQueue メソッドにて、暗黙的にリクエストのキューを開始しているためです。したがって、順次、リクエ スト処理が開始されています。 明示的なリクエストの開始は、onResume()時だけでなく、リクエストの優先順位設定時の開始で使用します。 ■リクエストの停止: Android アプリケーションがバックグランドに遷移する際、自動でコールされる onPause()時にリクエストの停止 を行っています。 ■リクエストのキャンセル: Volley では上記のように、リクエスト単位で簡単にキャンセル処理を行うことができます。 また、リクエストキュー上のタスクをキャンセルするには cancelAll()を使用し、リクエストをキューイングする前に設 定した TAG を指定します。そうすることで、TAG 設定されたリクエストキュー上のタスクが、全てキャンセルされます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 412 7-5. Volley 7-5-3. Volley 実装の基本的な注意点 ■基本的な注意点 ○Android ビギナー向け Android バージョンが、Android2.2(API レベル 8)以上であること <manifest xmlns:android="http://schemas.android.com/apk/res/android" <uses-sdk android:minSdkVersion="8" /> </manifest> Android デバイスが、ネットワーク通信可能な状態(3G/LTE など)となっていること (機内モードや圏外状態ではないこと) ※ただし、SIM は必ずしも必要ではありません。Wi-Fi 接続でも動作可能です。 AndrroidManifest.xml に、ネットワーク通信を許可するパーミッションを追加していること (Volley はネットワーク通信を行うため、ネットワーク通信を許可するパーミッションが必要です) <manifest xmlns:android="http://schemas.android.com/apk/res/android" <uses-permission android:name="android.permission.INTERNET" /> </manifest> Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 413 7-5. Volley ○従前の手法に習熟した開発者向け ■認証(Basic) ※7-5-3 Volley 適応ケースモデル① Basic 認 証 が 必 要 な URL へ ア ク セ ス す る 場 合 、 DefaultHttpClient を 使 用 し た 通 信 で は 、 「 UsernamePasswordCredentials() 」 、 HttpURLConnection を 使 用 し た 通 信 で は 、 「setRequestProperty()」といった、HTTP ヘッダを追加設定する処理が必要となります。 Volley ライブラリでは、HTTP 通信のリクエストが簡略化され、HTTP ヘッダについてはブラックボックスとなってい ます。そのため、Basic 認証を行う場合は、必要に応じて拡張を行います。具体的には「getHeaders()」をオー バーライドし、以下のような処理を組み込みます。 mRequestQueue = Volley.newRequestQueue(this); // URL を指定して URL から文字列を取得 String url = “http://www.XXX”; mRequestQueue.add(new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { // レスポンス時の処理 @Override Public void onResponse(String response) { // シーンによって UI 処理を実装 } }, null) { @Override Public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> headers = super.getHeaders(); // Basic 認証ヘッダ追加 Map<String, String> mHeaders = new HashMap<String, String>(); mHeaders.putAll(headers); mHeaders.put(“Authorization”, “Basic password“); return mHeaders; }; }; ■Request/Response/Cancel ※7-5-3 Volley 適応ケースモデル② データのリクエストを行う際、Volley の基本的な仕組みとしてリクエストに対するレスポンス全体がメモリに確保さ れ、UI スレッドで実行されます。そのため、大きなデータをリクエストする場合や、ストリーミング処理を行う場合、現 在行っている処理が完了するまでは、他の処理が実行できなくなります。そのため、データのリクエストには不向きと いう点には注意が必要です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 414 7-5. Volley 一度に要求するデータ量があまりにも多いと、トラブルの原因となることがあるので注意が必要です。 たとえば、Image データのリクエスト時に使用される ImageLoader クラスでディスクキャッシュを行うことは望 ましくありません。なぜならば、ディスクキャッシュは、リクエストに対するレスポンスを受け、UI スレッド上のシーケ ンスとして動作するからです。仮に、大きなデータのキャッシュ検索が実行され、検索に時間がかかった場合、 画面は表示上固まることがあります。これは、必ずしも ANR(Activity Not Respond)エラーを引き起こすわ けではありませんが、ANR の原因とはなりえます。 ディスクキャッシュだけでなく、ImageLoader 使用時のコールバック処理内で重い処理を行うことも、同様に 望ましくないとされています。 優先度の設定は、以下のような場合に行うことが適しているでしょう。 複数の画像を取得して表示する 表示するページで優先して取得し、表示しなければならない画像がある メタデータのリクエストで取得した情報の中から、その画像の Image データリクエストを行う Cancel については、7-5-3.でコードを交え、全てのリクエストを中止できると説明しました。しかし、リクエスト単 位やリクエストキュー上のタスク単位での中止も可能です。 Cancel 処理を受け取った場合、実際にキャンセルを行う訳ではありません。処理としては、リクエストの内部フラ グ(mCanceled)を true に設定しており、「キャッシュ検索」「ネットワーク通信」「レスポンス通知」といった Volley の各工程が行われる前に、内部フラグのチェックを行います。そこで、フラグが true であれば、キャンセルされることに なります。これは、Cancel 処理を通知し、リクエストがキャンセルされるまで待っている場合、UI スレッド側の動作が 滞るためです。(7-5-3.で説明しているため、ここではコードを記載しません) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 415 7-5. Volley ■リクエストクラスの拡張(優先度設定) public class RequestPriority extends StringRequest { private Priority mPriority = Priority.LOW; public RequestPriority (String url, Listener<String> listener, ErrorListener errorListener) { super(url, listener, errorListener); } @Override // 優先度を設定する public void setPriority(final Priority priority) { this.mPriority = priority; } @Override public Priority getPriority() { return mPriority; } } 優先度設定には、Request クラスの拡張が必要となります。 拡張クラスに Priority のメンバ変数を所持し、setPriority()メソッドで優先度を設定することで、getPriority() メソッドをオーバーライドします。 (RequestQueue 内部で、getPriority()がコールされます) ■リクエストの優先度設定 mRequestQueue = Volley.newRequestQueue(this); // URL を指定して URL から文字列を取得 String url = “http://www.XXX”; RequestPriority reqPriority = new RequestPriority(url, new Listener<String>() { // レスポンス時の処理 @Override public void onResponse(String response) { // シーンによって UI 処理を実装 } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError error) { int errorCode = error.networkResponse.statusCode; Log.e(“Priority”, errorCode); } }); reqPriority.setPriority(Priority.IMMEDIATE); mRequestQueue.add(reqPriority); Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 416 7-5. Volley 設定できる Priority は、以下に示す 4 種類です。(上から順に優先度低となります) LOW NORMAL HIGH IMMEDIATE (標準の Request クラスのデフォルト Priority) RequestQueue 内部に、キューイングされたリクエストの順番を動的に変更したい場合、優先度設定を利用しま す。ただし、複数のリクエストに対して同じ優先度(たとえば、IMMEDIATE)を設定した場合は、あくまでキューイン グ順にリクエストが処理されます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 417 7-5. Volley ■UI 処理 ※7-5-3 Volley 適応ケースモデル③ リクエストのコールバック処理は、UI スレッド上で処理されます。 ここでは、取得した文字列や画像の表示、失敗時のリトライ処理を行うことができます。しかし、別途記述したと おり、UI スレッドでの処理であるため、時間がかかる処理を行わないよう注意が必要となります。 ○既存システムの移行シーン向け ■従前の手法と Volley の差 No. 1 機能 キャッシュ 従前の手法 Volley キャッシュ処理は全て利用者が実装を行 リクエストキューにディスクキャッシュが設定さ う必要がある。 れている。 (キャッシュの有効期限はサーバ指定) Image データのリクエスト時、利用者がメモ リキャッシュ処理を実装することで、さらにパ フォーマンスを向上できる。 2 3 リクエスト順序 通信クラス リクエスト処理が行われた順に処理され リクエストに対し、優先順位を設定して処 る。 理させることができる。 Android OS バージョンによって使い分け Android OS バージョンによる使い分けは る必要がある。 不要。 Froyo(Android OS 2.2)以前: ⇒Apache HTTP Client Gingerbread(Android OS 2.3) 以 降: ⇒HttpURLConnection 4 キャンセル 非同期処理の中でキャンセル処理をコー リクエスト単位キャンセル、オブジェクト TAG ルするだけでは、処理が中断されず、キャ 設定されたリクエストキューの全キャンセルが ンセルイベントを受けた際に、非同期処 行える。 理自体を抜ける必要がある。 View が非表示になった場合、リクエストの 自動キャンセルが行われる。 キャンセルされた場合、リクエストは応答され ない。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 418 7-5. Volley No.1:キャッシュ ⇒表に記載のとおり、従前の手法は、キャッシュ処理は利用者が独自に組む必要がありました。 しかし、Volley は、リクエストキュー時のディスクキャッシュは利用者が意識する必要はありません。そのため、 Volley 側の実装で複雑な処理を組む必要はなく、RAM 領域削減にも繋がります。 また、Image データリクエスト時に、利用者がメモリキャッシュ処理を組むことで、さらにパフォーマンスを向上 させることもできます。キャッシュについては、従前の手法および Volley の仕組みそのものに関わっているため、 キャッシュ部分だけを移行することはできません。 No.2:リクエスト順序 ⇒従前の手法は、ページに表示する Image データの中で一番重要なデータがある場合でも、リクエスト順で しか Image データの取得ができませんでした。Volley は、リクエストに対して優先度(4 段階)を設定すること ができます。 複数スレッドで複数のリクエストを実行した場合にも、表示したいデータを優先的に設定することや、画面 遷移時に前画面のリクエストをキャンセルすることなく、次画面のリクエストを優先させたい場合などに活用する ことができます。 利用者が表示するページによりますが、Request クラスの拡張を行うだけで、優先度設定が可能な Volley は、従前の手法に比べ、処理における利便性が向上します。リクエスト順序については、従前の手法 および Volley の仕組みそのものに関わっているため、リクエスト順序部分だけを移行することはできません。 No.3:通信クラス ⇒何度も記述のとおり、従前の手法は Android OS バージョンによって推奨される HTTP 通信クラスが異な ります。そのため、サポートする OS バージョンによって、HTTP 通信クライアントを使い分ける必要があります。 Volley は、通信部分がライブラリによって簡略化されているため、OS バージョンを気にすることなく使用する ことができます。 ただし、使い分けが必要な DefaultHTTPClient については、OS バージョンが Froyo(OS 2.2 以前)と なります。そのため、Gingerbread(OS 2.3 以降)だけをサポートするのであれば、従前の手法を使うことに、 特に弊害は存在しません。 No.4:キャンセル ⇒従前の手法は、非同期で処理実行中にキャンセル処理をコールするだけでは、実際にキャンセルが行われ ません。キャンセル通知が行われた際に、非同期処理自体を抜ける必要があります。 Volley は、リクエスト単位やリクエストキュー単位でのキャンセルが可能な他、非同期中であることを意識す る必要はなく、キャンセルされたリクエストのレスポンスもありません。そのため、利用者が必要とするキャンセル 後の処理のみを記載するだけでよいのです。 キャンセルについては、従前の手法および Volley の仕組みそのものに関わっているため、キャンセル部分だ けを移行することはできません。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 419 7-5. Volley ■ネットワーク設定の変更 Volley は、RequestQueue 生成時に HttpStack を引数として渡すことにより、ネットワーク設定の変更を行う ことができます。 HttpStack の実装方法は、標準で 2 種類あります。それらは、API レベルによって使い分けます。 HttpURLConnection を使用した、HurlStack による実装 ⇒API レベルが 9 以上 Apache Http Client を使用した、HttpClientStack による実装 ⇒API レベルが 9 未満 以下のサンプルコードは、HurlStack を使用しています。 public class MainActivity extends Activity { private RequestQueue mRequestQueue; @Override protected void onCreate(Bundle SavedInstanceState) { super.onCreate(savedInstaceState); // HTTP リクエストキューの生成 mRequestQueue = Volley.newRequestQueue(this, new sampleHurlStack()); // URL を指定して URL から文字列取得 String url = "http://www.XXX"; mRequestQueue.add(new StringRequest(Request.Method.GET, url new Response.Listener<String>() { // レスポンス時の処理 @Override public void onResponse(String response) { // シーンによって UI 処理を実装 } }, null)); } Class sampleHurlStack extends HurlStack { @Override protected HttpURLConnection createConnection(URL url) throws IOException { // プロキシサーバを指定して接続開始 Proxy proxy = new Proxy(Proxy.Tyep.HTTP, InetSocketAddress.createUnresolved("proxy",8080)); HttpURLConnection uConnection = (HttpURLConnection)url.openConnection(proxy); // ユーザエージェントを設定 uConnection.setRequestProperty("User-Agent", "YourLikeBrowser/2.00"); return uConnection; } } 上記サンプルコードは、RequestQueue 生成時に HurlStack を設定し、引数として渡すことで、Proxy 設定と ユーザエージェント設定を行っています。Proxy も、必要に応じて引数を渡すだけで容易に設定できることに注目し てください。これは、セキュリティを保ちつつ Volley を組み込む上で有用です。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 7 章 Google サービス&ライブラリを利用したアプリケーション開発 | 420 7-5. Volley ■Volley の最適化について スレッドプール数の変更 ⇒Volley ライブラリは、通信スレッドの数が通常「4つ」です。しかし、RequestQueue クラスのコンストラクタ にて、スレッド数の設定が可能です。 基本的にスレッド数は、デフォルトの「4」で問題ないと考えられます。しかし、低速環境下における処理を 行う場合、設定変更によって改善が可能な場合もあるでしょう。 RequestQueue.java public class RequestQueue { public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } } レスポンスタイミングのチューニング ⇒Volley ライブラリは、リクエスト処理に対してレスポンスを通知する際、処理完了と共に行っていま す。しかし、ImageLoader クラスでは、このレスポンス通知を遅延させる処理が存在します。 (初回レスポンス時以降、100ms 単位) これにより、ListView や GridView でのサムネイル表示時など、リクエストが大量に存在する場合に、繰り 返して実行を行うよりも、効率よくまとめて描画更新を反映させることが可能です。 ImageLoader.java public class ImageLoader { public void setBatchedResponseDelay(int newBatchedResponseDelayMs) { mBatchResponseDelayMs = newBatchedResponseDelayMs; } } デバッグの有効化 ⇒Volley ライブラリそのものに対し、不具合時の解析用にデバッグ機能を有効にできます。 デバッグ機能を有効にするには、以下の adb コマンドを実行します。 adb shell setprop log.tag.Volley VERBOSE Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 第8章 Android アプリケーション開発に必須の デバッグツール利用法 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. | 421 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 422 8-1. はじめに 8-1. はじめに 一般的に、「デバッグ」はソフトウェアの動作において再現性を持つ「バグ」を解消するための修正作業を指して用 いられます。バグの多くは、UI や動作を通じて人間が認識できるものです。そして、この修正作業工程が「デバッグ 工程」と呼ばれます。 言うまでもなく、これらは必須の作業であり工程です。しかし、モバイルアプリケーションの品質を高めるためには、 パフォーマンスや安定性を測定し問題を予防するためのデバッグ工程も重要です。発見されたバグに対して対症療 法的に行うデバッグのみではなく、必ずしもバグではない不適切な実装を改善することは、軽視されがちですが重 要なポイントです。 なぜならば、モバイルデバイスは一般的な PC 環境と比べて、アプリケーションの正常に見える動作に隠れた小さな 問題の蓄積が、端末全体に影響を与えるトラブルにつながる場合があるからです。明確なバグの改修を目的とし ないデバッグ工程はときとして軽視される傾向がありますが、このことはトラブルの原因の一つともなっています。 ユーザから報告される問題には、通常の動作検証時には見過ごされるものが少なくありません。しかも、再現性 が不明確な抽象的表現でその問題が示されることも多いでしょう。例えば、以下のようなケースが考えられます。 事例① [8-7. デバッグツールでの解析事例]で紹介 ユーザの報告 :「気づいたら止まっている」 実際の事象 :バックグラウンドで動作するサービスアプリケーションが、起動後一定時間(2 日な ど)経過した時点で必ず異常終了する 想定される原因 :ごくわずかなメモリリークが見過ごされていた こういった事態を防ぐためにも、いわゆる「バグ修正」の観点だけではなく、正常な動作を継続させるためのパフォー マンス「最適化」という観点からもデバッグ工程を明確に計画し、開発プロセス内に設けることは重要です。 本章では、Android アプリケーションを開発する際に利用できるデバッグツールについて解説します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 423 8-1. はじめに 8-1-1. 本章で取り扱うデバッグツールについて 以下に、後述するデバッグツールを示します。 DDMS ■概要 Android SDK Tools に標準で搭載されているデバッギングツールです。エミュレータや Android デバイス上で、ア プリケーションを動作させながらデバッグを行うことができます。また、画面キャプチャの撮影や、ログの参照などを行う こともできます。 Systrace ■概要 Android SDK Tools の Revision 20 より提供されたトレースツールです。Linux のカーネルから直接データを収 集し、システム全体のプロセスを把握することができます。 Procstats ■概要 メモリ使用状況を確認するためのツールであり、メモリ使用状況の詳細を確認し、分析することができます。本ツー ルは、Android OS 4.4(KitKat)より追加されました。メモリ使用状況を確認できる対象は、フォアグランドのアプリ ケーション、バックグランドのサービスアプリケーションです。 StrictMode ■概要 メインスレッドの監視機能を持ったツールです。Android OS 2.3(Gingerbread)より追加されました。ディスクや ネットワークへのアクセスを検知することができます。Android OS 4.0(Ice Cream Sandwitch)からは、OS の デバッグ支援機能としてサポートされています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 424 8-2. DDMS について 8-2. DDMS について 8-2-1. DDMS とは DDMS(Dalvik Debug Monitor Service)は、Android に用意されたデバッグ用開発支援およびプロセス管 理を行うツールです。Eclipse またはコマンドラインから利用することができます。 DDMS の実行ファイルは、Android SDK インストール先の「…/tools/」ディレクトリ配下に存在します。そのため、 コマンドラインから実行する場合は、上記の「…/tools/」ディレクトリに入り、「ddms」コマンドを入力し実行しま す。 DDMS は、エミュレータと Android デバイス両方で動作しますが、両方とも同時に接続、実行されている場合は、 エミュレータ側をデフォルト実行先とします。 Eclipse から実行する場合は、Eclipse のメニューバーから[ウィンドウ]->[パースペクティブを開く]を選択します。 表示された一覧に「DDMS」が存在しない場合は、[その他]を選択します。選択後に表示された一覧から、 「DDMS」を選択し「OK」を押下すると、Eclipse が DDMS 用の画面に切り替わります。 図 93 DDMS(Eclipse) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 425 8-2. DDMS について DDMS で表示される画面の各タブを、以下に示します。 (使用する Eclipse によって日本語表記ではない部分が存在する場合があります) Devices 接続されているデバイスと、動作中のデバッグ可能なプロセス一覧を表示します。 スレッド 選択しているプロセスのスレッド構成(ID や Tid などのタスク状況)を表示します。 Heap 選択しているプロセスのメモリ状況(空きサイズなど)を表示します。 Allocation Tracker 選択しているプロセスのメモリ確保時の動作を追跡し、表示します。 Network Statistics 選択しているプロセスのネットワーク通信状況を表示します。 ファイルエクスプローラー 選択している接続デバイス内部のファイルを表示または操作を行います。 Emulator Control エミュレータの電話状態、位置情報を表示または操作を行います。 System Information 選択している接続デバイスの CPU やメモリ、ハードウェアリソースの使用状況を表示します。 LogCat 選択している接続デバイス内のログを表示します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 426 8-2. DDMS について 8-2-2. DDMS の使用方法 ■各機能使用シーン DDMS で使用可能な機能について、各機能ごとの使用方法と使用シーン、または使用上の注意点について記 載します。 ○Devices タブ 図 94 DDMS(Devices タブ) Debug the selected Process: プロセス一覧から、デバッグを行いたいプロセスを選択し、本ボタンを押下します。 選択したプロセスに マークが表示されると準備完了です。 接続デバイスを操作する際、Eclipse のソースコード側で設定したブレークポイントで処理が止まります。 そのため、ソースコードをトレースしたい場合に使用することができます。 注意点として、Eclipse で表示しているソースコードと、接続デバイスで操作しているアプリケーションの 間に差異がある(別の apk やソースコードを使用している)場合、正常にトレースすることができない 可能性があります。 Update Heap: Heap タブにて使用可能な、メモリ状況の確認を行うためのボタンです。 詳細については、Heap タブの項目で記載します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 427 8-2. DDMS について Dump HPROF files: 使用しているアプリケーションにメモリリークが発生している場合、メモリダンプを行い、ダンプしたファイルで リーク状況を確認することができます。 ダンプファイルを可視化するには、「Memory Analyzer」ツールが必要になります。そのため、別途 インストールを行います。また、メモリダンプは SD カードに書き出されるため、メモリダンプの対象となる アプリケーションの AndrodManifest.xml に、以下のパーミッションの追加が必要となります。 <manifest xmlns:android="http://schemas.android.com/apk/res/android" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> </manifest> ※Memory Analyzer およびメモリダンプファイルについては、Heap タブ項目で併せて記載します。 詳細については、Heap タブの項目で記載します。 Cause GC: Heap タブ内の、「Cause GC」ボタンと同一のものとなります。「Update Heap」で、メモリ状況の確認 を開始した後、本ボタンを押下することでメモリの使用状況を表示することができます。また、一度 ガーベジコレクションを行い、未使用のメモリ回収を行うことで、メモリ使用状況の更新を行う際にも使用 することができます。 詳細については、Heap タブの項目で記載します。 Update Threads: スレッドタブにて使用可能な、スレッド状況の確認を行うためのボタンです。 詳細については、スレッドタブの項目で記載します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 428 8-2. DDMS について Start Method Profiling: プロセスを選択し、「Start Method Profiling」ボタンを押下した際に、再度同じボタン (押下後は Stop Method Profiling と表示される)を押下するまでの間、選択したプロセスのメソッド プロファイリングを行うことができます。 各メソッドごとに、実行時間や実行回数、全体から見た使用率の表が表示されます。また、メソッド単体 の実行時間を確認することもできます。 例えば、アプリケーションを動作させている際に、特定のシーンで処理のレスポンスが遅い場合などに、 本機能を使用します。そうすることで、どのメソッドに関して処理時間が掛かっているのかを確認できます。 そのため、ボトルネックとなっている箇所への対策を行うことが可能となります。 図 95 DDMS(Start Method Profiling) Stop Process: プロセスを選択した状態で「Stop Process」ボタンを押下することにより、選択したプロセスを 強制的に終了することができます。 例えば、メモリが逼迫している状況でアプリケーションを動作させている場合、Android OS が自動で アプリケーションを強制終了させることがあります。このような動作のデバッグを行いたい場合に、 本ボタンを使用することで状況を再現かつデバッグ行うことが可能です。 ただし、プロセスを強制的に終了させるため、アプリケーションのシーンによっては正しく終了しない ことによる影響が発生する可能性があります(アプリケーションの次回起動時、正常に起動しないなど)。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 429 8-2. DDMS について Screen capture: 接続デバイスに表示されている画面をキャプチャし、画像として保存することができます。Android OS バ ージョン 4.0 以前の OS は、デバイスに画面キャプチャ機能が標準では搭載されていません。そのため、 DDMS の Screen capture 機能を使用することで、表示中の画面を保存することができます。 Android Market にアプリケーションを登録する場合、登録対象アプリケーションの画面キャプチャ画像 が必要になります。そのため、デバイスに画面キャプチャ機能が搭載されていない場合に使用します。 リフレッシュ :画面更新 Rotate :画面回転 保管 :画面保存(png 形式) コピー :画面コピー 終了 :画面終了 図 96 DDMS(Screen capture) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 430 8-2. DDMS について Dump View Hierarchy: 接続デバイスに表示されている画面を対象に、画面内のレイアウト構造を階層化して表示することがで きます。 注意点として、Root を取得したデバイス、またはエミュレータでしか使用することができません。 図 97 DDMS(Dump View Hierarchy) 上記の図に記載しているとおり、選択した画面の部品に対するレイアウト、座標位置を確認することがで きます。 Android systrace: Systrace と同一の機能となります。Systrace 実行時にイベントタイプを指定し、アプリケーションを動作 させることで、指定したイベントタイプに関してのプロファイリングを行うことができます。 詳細については Systrace で記載します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 431 8-2. DDMS について Trace OpenGL calls: アプリケーションに組み込まれた Open GL API のトレーサーです。選択したプロセスに対し、Devices タ ブの「Trace OpenGL calls」を押下することでトレースを開始します。トレースは、「Stop Tracing」が 押下されるまで記録され、「Stop Tracing」が押下されると、自動的にトレースファイルが Eclipse 上で 表示されます。 注意点として、OpenGL のトレースは、接続デバイス側の許可が必要です。接続デバイスのトレース許 可は、開発者向けオプションから OpenGL トレースを有効にすることで設定することができます。ただし、 OpenGL トレースの有効かは、Android OS バージョン 4.2 から設定することができます。 図 98 ○スレッドタブ 図 99 DDMS(スレッドタブ) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 432 8-2. DDMS について スレッドタブは、選択したプロセスのスレッド状況を確認することができます。アプリケーションを操作する際に、リアル タイムなスレッドの状況を確認できるため、デバッグに役立ちます。 以下に、スレッドの状況を表示するための手順を示します。 1. Devices タブの「Update Threads」を押下します。 2. アプリケーションを操作します。 3. スレッドタブの「リフレッシュ」を押下します。 4. 表示されたスレッド一覧から任意のスレッドを選択します。 Devices タブの「Update Threads」を押下した後、アプリケーション操作によりスレッドの状況が記録され、スレ ッドタブの「リフレッシュ」を押下するとスレッド構成が表示されます。 スレッド構成で表示される情報は以下のとおりです。 項目名 詳細 状況名 詳細 Dalvik VM に割り当てられたユニーク runnable 実行中 なスレッド ID sleeping Thread.sleep()中 Tid プロセスのメインスレッド monitor モニターロック待ち 状況 スレッド状態(右図を参照) wait Object.wait()中 utime ユーザコードの実行に費やした累積時 native ネイティブコード実行中 間。単位は 10ms。 vmwait VM リソース待機中 システムコードの実行に費やした累積 zombie スレッド終了中 ID stime 時間。単位は 10ms。 名前 スレッド名 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 433 8-2. DDMS について Heap タブ 図 100 DDMS(Heap タブ) Heap タブは、選択したプロセスのメモリ状況を確認することができます。Android は PC と異なり、メモリ不足が発 生しやすい傾向にあります。メモリ不足に陥ると「OutOfMemory」エラーが発生し、起動中のアプリケーションが強 制終了します。 作成したアプリケーションを動作させていると、メモリリークが発生し、システム全体のパフォーマンスや様々なアプリケ ーションに影響を与える可能性があります。そのため、アプリケーション開発時のメモリ管理に対する考慮を行う場合 や、メモリリークによるメモリ不足が発生した際のメモリ状況を確認する場合に、Heap タブ内の機能を使用します。 ■メモリ使用量の確認 1. Devices タブの「Update Heap」を押下します。 2. Device タブの「Cause GC」または Heap タブの「Cause GC」を押下します。 3. メモリリークが発生するアプリケーションを操作します。 4. メモリリークが発生するアプリケーションを終了します。 5. メモリリークが発生するアプリケーションを操作します。 メモリリークが発生するアプリケーションを終了した場合でも使用するメモリ量が減らず、次のアプリケーション操作時 以降も使用メモリ量が増え続ける場合は、メモリリークが発生しているといえます。 図 101 DDMS(メモリ使用状況) ※アプリケーションの起動と終了を行った場合にも、Allocated しているメモリ量が減らずに増え続ける場合、メモリ リークが発生しているといえます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 434 8-2. DDMS について 実際にメモリリークが発生している箇所の特定を行うには、メモリのダンプを行います。 ■メモリのダンプ 1. Memory Analyzer ツールを別途インストールします(※)。 2. Devices タブの「Dump HPROF file」を押下します。 3. 自動的に Dump ファイルが Eclipse 上で表示されます。 Dump ファイルでは、メモリ使用量が多い、存在するオブジェクト数が多いなどの理由から、メモリリーク の疑いがあると判断されたオブジェクトの分布が表示されます。 図 102 DDMS(Dump HPROF file) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 435 8-2. DDMS について 4. 「Open Dominator Tree for entire heap」を選択します。 図 103 DDMS(Dominator) Shallow Heap :オブジェクト単体のメモリサイズ Retaimed Heap :オブジェクトが内部で確保した総メモリサイズ 割合(%) :メモリ使用率 メモリの使用率(割合)の高いクラス名が存在する場合などは、メモリリークの可能性があります。 ※Memory Analyzer ツールのインストール手順 1. 2. Eclipse を起動し、メニューバーから[ヘルプ]->[新規ソフトウェアのインストール]を選択します。 「追加」を選択し、ロケーションに下記 URL を入力した後に、OK を押下します。 http://download.eclipse.org/mat/1.3.1/update-site/ Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 436 8-2. DDMS について 3. インストール画面へ遷移し、「Memory Analyzer for Eclipse IDE」にチェックを入れ、「次へ」を 押下します。 図 104 DDMS(Memory Analyzer インストール) 4. ライセンスのレビュー画面で「使用条件の条項に同意します」を選択し、「完了」を押下します。 図 105 DDMS(ライセンスレビュー画面) 5. インストール完了後、Eclipse の再起動を促されますので、再起動を行います。 メモリリークを解析する手法としては、上記手順がアプローチの一つとなります。しかし、メモリリークの可能性があると いうだけで、メモリ使用率が高いことが原因とならないこともあります。原因と考えられる箇所を解析することが、メモ リリークの直接的な解消にはならないことに注意してください。あくまで、開発者によるソースコードの修正が必要とな ります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 437 8-2. DDMS について ○Allocation Tracker タブ メモリ解析の手法として、Heap タブからのメモリダンプと、Allocation Tracker タブから、メモリの確保状況を追跡 する手法があります。 Allocation Tracker は、選択したプロセスに対してどのクラス、スレッドがオブジェクトに割り当てられたかをトラッキ ングすることができます。 1. Allocation Tracker タブから「Start Tracking」を選択します。 図 106 DDMS(Allocation Tracker) 2. メモリリークが発生するアプリケーションを操作します。 3. Allocation Tracker タブから「Get Allocations」を選択します。 図 107 DDMS(Get Allocations) 4. メモリリークが発生するアプリケーションを操作します。 5. 終了する場合、「Stop Tracking」を選択します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 438 8-2. DDMS について 「Start Tracking」を選択した時点から、プロセスに対するメモリのトラッキングを開始します。その後、アプリケーショ ン操作を行うたびに、メモリ割り当てが記録されます。そのため、メモリリークが発生すると考えられるアプリケーション 操作を行います。 アプリケーション操作を行った後、「Get Allocations」を選択することで、記録されたメモリ割り当ての履歴が表示 されます。 図 108 DDMS(メモリ割り当て履歴) メモリ割り当ての履歴は、選択したオブジェクトに対し、メモリ確保までのメソッドおよび行番号が表示されます。 Allocate されているメモリサイズが大きいオブジェクトに対し、詳細なメソッド履歴を確認することで、例えば冗長な メモリ確保の繰り返しが、ガーベジコレクションの頻繁な発生の原因となっていることを、履歴から読み取ることができ ます。そこから、メモリ確保と管理の方法の再考が必要であることが分かります。 ただし、Allocation Tracker は、処理中のリアルタイムなメモリ確保状況やオブジェクトの生成タイミングの最適化 について確認することはできますが、メモリダンプと異なり、アプリケーション全体のメモリ使用状況を確認することはで きません。詳細な箇所が特定できていない状態で、アプリケーション全体からメモリリークの原因を解析する場合は、 Heap タブからのメモリダンプ機能を使用して確認するほうが望ましいと考えられます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 439 8-2. DDMS について ○Network Statistics タブ 図 109 DDMS(ネットワーク通信量) Network Statistics タブは、選択したプロセスのネットワーク通信量を表示することができます。 上の図で灰色の部分は、プロセス全体のネットワーク通信量を表しています。アプリケーション内のネットワーク処理 に対してタグ設定を行うことにより、個別に通信量をモニタリングすることができます。 以下に、プロセスのネットワーク通信量を表示する方法を示します。 1. モニタリングしたいプロセスを選択し、Network Statistics タブから「開始」を選択します。 2. アプリケーションで、ネットワーク通信を含めた操作を行います。 3. Network Statistics タブから「停止」を選択して終了します。 上記の手順にて、「図:DDMS(ネットワーク通信量)」の画面が表示されます。 また、ネットワーク処理の前後にタグ設定とタグ消去を行うことで、Total(灰色部分)とは別でモニタリングしたいネッ トワーク処理を表示することができます。 ネットワーク通信時のデータ転送頻度と通信量を確認することで、最適化を行います。 タグ設定のサンプルコードは、以下のような処理となります。 <Network Statistics タグ設定サンプルコード> TrafficStats.setThreadStatsTag(0xFFFFFF00); // ()内は設定するタグ try { // ネットワーク処理 } finally { TrafficStats.clearThreadStatsTag(); } Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 440 8-2. DDMS について ○ファイルエクスプローラータブ 図 110 DDMS(ファイルエクスプローラー) ファイルエクスプローラータブは、選択している接続デバイスのディレクトリ構成やファイルを確認し、操作することが できます。ただし、ファイル操作を行うには、接続デバイスが root を取得しており、権限があるディレクトリが対象で あることが必要となります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 441 8-2. DDMS について アプリケーション開発におけるデバッグで、ファイルエクスプローラーの機能を使用する場合、ファイルの生成や外部か らの取得および接続デバイス内部へ保存した後、正常にファイル保存が行えているかをファイルエクスプローラー上か ら確認する手段として使用することができます。また、アプリケーション操作を行う際に、ファイルの pull/push で動 作が変更されることを確認する場合などにも使用することができます。 Push a file onto the device Pull a file from device 選択範囲を 削除します 図 111 DDMS(ファイル操作) Pull a file from device: 接続デバイス内のディレクトリからファイルを取得し、保存します。 Push a file onto the device: 接続デバイス内部のディレクトリへファイルを格納します。 選択範囲を削除します: 選択したディレクトリまたはファイルを削除します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 442 8-2. DDMS について ○Emulator Control タブ 図 112 DDMS(Emulator Control) Emulator Control タブは、起動しているエミュレータに対してデバイスの状態を設定することができます。 設定可能な項目は、以下のとおりです。 音声、データ通信の電波状態と通信情報 音声、SMS での着信 位置情報の変更 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 443 8-2. DDMS について 例として、エミュレータに対し、音声着信を行う場合の手順を以下に記載します。 1. デバイス一覧から、音声着信を行うエミュレータを選択します。 2. Emulator Control タブの「Incoming number」へ、任意の番号を入力します。 3. Emulator Control タブの着信種別を「Voice」に設定し、「呼び出し」を押下します。 図 113 DDMS(音声着信設定) 図 114 DDMS(エミュレータ音声着信画面) 電波状態による条件変更または音声着信の他に、SMS 受信や位置情報の更新も行えます。そのため、デバッグ 設定と合わせることにより、アプリケーション開発において利用することができます。 ただし、エミュレータを対象とした機能となるので、実機デバイスでのデバッグではないことに注意してください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 444 8-2. DDMS について ○System Information タブ 図 115 DDMS(System Information 画面) System Information タブは、選択している接続デバイスの CPU 使用率を表示することができます。 上記の図は、接続デバイスを起動時に CPU 使用率の確認を行っています。そのため、他のアプリケーションがあまり 動いていないことにより、Idle が CPU 使用率の半分以上を占めています。 この CPU 使用率を、開発アプリケーション起動時や操作時に更新することで、CPU 使用率が高くなるアプリケーシ ョンを確認することができます。 CPU 使用率を更新する場合は、System Information タブの「Update from Device」を選択します。 ○LogCat タブ 図 116 DDMS(LogCat) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 445 8-2. DDMS について LogCat タブは、接続デバイスのログを取得することができます。 取得できるログには、ログレベルが存在し、Log クラスを使用して出力することができます。 Log クラスを使用するには"android.util.Log"クラスをインポートします。 ○ログレベル verbose :Log.v(String tag, String message) debug :Log.d(String tag, String message) info :Log.i(String tag, String message) warn :Log.w(String tag, String message) error :Log.e(String tag, String message) assert :Log.a(String tag, String message) ログレベル低 ログレベル高 図 117 DDMS(ログレベル) ○ログフィルタ LogCat タブ内で表示されるログに対し、以下のフィルタを設定することができます。 ログタグ ログメッセージ プロセス ID(PID) アプリケーション名 ログレベル LogCat タブの Saved Filters に対し、「Add a new logcat filter」を押下してフィルタを追加します。 図 118 DDMS(フィルタ新規追加) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 446 8-2. DDMS について 図 119 DDMS(フィルタ新規追加) 図 120 DDMS(フィルタ設定時のログ出力) 上記では、ログタグに"DEBUG"を設定した「Filter1」というフィルタを追加しています。出力ログされるは、ログタグ が"DEBUG"で出力されているもののみとなります。追加したフィルタは、削除および編集を行うことができます。 また、メッセージについてはフィルタだけでなく、ログ出力ボックスに直接キーワードを入力することでフィルタを設定する ことができます。 図 121 DDMS(フィルタ設定時のログ出力(メッセージ)) ○コマンドラインからのログ表示 これまでは、Eclipse から LogCat 機能を使用することでログ出力を行っていました。しかし、コマンドラインからもロ グ出力を行うことができます。 出力方法は、テキストファイルを指定し、テキストファイルに出力および保存を行います。 Eclipse で LogCat 機能を使用た場合に、ログが出力されないことがあるということに注意してください。 出力されない原因は、ログ出力を行う接続デバイスが Eclipse 上で正しく選択されていない可能性があります。そ のため、ログが表示されない場合は、Devices タブのデバイスを確認し、デバイス接続および選択が行われているこ とを確認してください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 447 8-3. Systrace について 8-3. Systrace について 8-3-1. Systrace とは Systrace は、Android SDK Tools の Revision 20 から提供された、選択中のアプリケーションに対するパフ ォーマンス解析ツールです。Android OS バージョン 4.1(Jelly Bean)は、Systrace を使用することを考慮して 設計されています。Systrace は、Linux のカーネルから直接データを収集します。そのため、システム全体のプロセ スを確認することができます。 また、コマンドライン上からの実行だけでなく、Eclipse 上からも実行することができます。Eclipse 上から実行する 際は、Devices タブの「Capture system wide trace using Android systrace」を選択します。 図 122 Systrace(Eclipse からの実行) また、Android OS バージョン 4.3 からは、新しいバージョンの Systrace がサポートされています。新しいバージョ ンの Systrace は、Dalvik VM など様々なオブジェクトに対してトレースすることができます。新しく追加された Trace API(Systrace begin/end)により、アプリケーション内の特定コード部分が実行されると、Trace ログに 開始と終了イベントを書き出すことができます。そのため、特定のイベントに対して注視することが可能となりました。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 448 8-3. Systrace について 8-3-2. Systrace の使用方法 以下に Systrace を Eclipse から実行する方法と、その実行結果について記載します。 1. 「8-3-1 Systrace とは」で記載のとおり、Eclipse の DDMS 画面上に存在する Devices タブから 「Capture system wide trace using Android systrace」を実行します。 今回は、「メモ帳」アプリのトレース結果を例に説明します。 図 123 Systrace(Eclipse からの実行(メモ帳)) 2. 実行時に表示される下記画面にて、必要な情報を設定します。 図 124 Systrace(設定画面) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 449 8-3. Systrace について 設定可能な項目は以下の項目となります。 設定項目 設定内容 Destination File トレース結果ファイルの出力先指定(,html) Trace duration(秒) トレース時間指定(秒単位) Trace Buffer Size(kb) トレース結果ファイルサイズ指定(kb 単位) Enable Application Traces from トレース対象アプリケーション指定 Select tags to enable トレース対象オブジェクト指定 図 125 Systrace(設定画面) 3. 「メモ帳」アプリに対し、「5 秒間」のトレース時間を指定して実行します。その後、5 秒の間に「メモ帳」アプリを 操作します。 上記の画面が表示されている間にアプリケーションを実行することで、時間内のトレース結果が出力されることにな ります。(今回は 5 秒) 4. トレース結果の出力ファイルを確認します。 図 126 Systrace(トレース結果出力画面) 出力結果の html ファイルは、Google Chrome ブラウザの「trace-viewer」というツールを使って表示します。そ そのため、「Google Chrome」で確認してください。 ※その他のブラウザによっては表示できないことがあります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 450 8-3. Systrace について Systrace は、カーネルから直接データを収集します。そのため、詳細な情報のトレース結果を確認することができ まが、今回は「メモ帳」アプリに関連するプロセスが出力されています。 図 127 Systrace(トレース結果(メモ帳)出力画面) トレース結果は実行時間の秒数ごとに記録し、出力されています。数秒ごとに記録されていることにより、アプリケー ションが実際に開始される時間や、アプリケーション内のプロセスを時系列で確認することができます。 1.トレース出力結果の左右に移動させます(時系列移動) 2.選択した範囲でのプロセスを表示する 3.出力結果を拡大/縮小し、全体または詳細を表示します 上記のうち、「2.選択した範囲でのプロセスを表示する」を実施した場合は下記の画面が表示されます。 選択範囲 図 128 Systrace(トレース結果(メモ帳)詳細出力画面) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 451 8-3. Systrace について 上記のうち、「3.出力結果を拡大/縮小し、全体または詳細を表示します」を実施した場合は、下記の画面が表 示されます。 図 129 Systrace(トレース結果(メモ帳)拡大出力画面) プロセスの実行時間があまりに短く、初期出力結果画面は「秒単位」で表示されているため、細かい秒数単位で の表示ができていません。上記のように拡大を行うことで、細かいプロセスを表示することができ、プロセス詳細につ いても個別に確認できるようになります。 これらの出力結果を元に、「メモ帳」アプリの動作が不安定であれば、出力結果を見てプロセスが異常になっていな いか、実行時間が異常に長くなっていないか、周りのプロセスが異常に動作していないか、といった観点で確認を行 い、不具合を特定する際の支援ツールとして利用することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 452 8-4. Procstats について 8-4. Procstats について 8-4-1. Procstats とは Procstats(Process + Statistics を合わせた造語)は、Android OS バージョン 4.4(KitKat)から追加され たメモリの使用状況を分析するツールです。システム上で動作している他のアプリケーションやサービスについても確 認することができます。Android SDK に含まれる adb ツールより、以下のコマンドを実行してアクセスします。 “adb shell dumpsys procstats” また、開発者向けオプション機能の一つとして「プロセスの統計情報」という機能が提供されています。「プロセスの 統計情報」は、Procstats 機能を使用して収集されたアプリケーションのメモリ状態を画面で確認することができま す。 図 130 プロセスの統計情報 図 131 使用状況の詳細 プロセスの統計情報画面にて表示されるメモリ状態バーは、緑色、赤色、黄色の 3 色で分布が行われています。 各分布の内容は、以下に示すとおりです。 緑色:メモリ使用量が低い期間 赤色:メモリ使用量が高い期間 黄色:メモリ使用量が適切な期間 使用状況の詳細画面にて表示される事項は、選択したアプリケーションが消費した「メモリ量」と「実行時間の割 合」の 2 つです。アプリケーションの実行時間やメモリ消費量を確認することで、メモリに関する最適化を行う際に使 用することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 453 8-4. Procstats について 8-4-2. Procstats の使用方法 プロセスの統計情報で表示しているメモリデータは、Procstats の機能で収集したデータを使用しています。コマ ンドラインから Procstats 機能を実行した場合に表示されるメモリデータは、以下のように表示されます。 ■Procstats 実行結果サンプル AGGREGATED OVER LAST 24 HOURS: * com.android.systemui / u0a21: TOTAL: 100% (35MB-48MB-66MB/32MB-42MB-61MB over 118) Persistent: 100% (35MB-48MB-66MB/32MB-42MB-61MB over 118) Imp Fg: 0.00% * com.google.process.location / u0a17: TOTAL: 100% (7.8MB-9.7MB-12MB/6.2MB-7.7MB-9.5MB over 122) Imp Fg: 63% (9.6MB-11MB-12MB/7.5MB-8.3MB-9.5MB over 81) Imp Bg: 37% (7.8MB-8.2MB-8.3MB/6.2MB-6.4MB-6.5MB over 41) * com.google.android.gms / u0a17: TOTAL: 100% (5.2MB-13MB-16MB/3.3MB-11MB-14MB over 122) Imp Fg: 63% (5.2MB-13MB-16MB/3.3MB-10MB-14MB over 82) Imp Bg: 37% (12MB-13MB-13MB/10MB-11MB-11MB over 40) Service: 0.00% Service Rs: 0.00% Receiver: 0.01% (Cached): 0.01% ・ ・ ・ Run time Stats: SOff/Norm: +12h8m59s983ms SOn /Norm: +10h20m41s182ms TOTAL: +22h29m41s165ms Start time: 20XX-XX-2XX XX:XX:XX Total elapsed time: +22h30m1s732ms (partial) libdvm.so chromeview Procstats 結果(過去 24 時間以内) Procstats を実行した場合、まず過去 24 時間以内と 3 時間以内に起動、また現在起動しているアプリケーシ ョンやサービスの一覧と実行時間やメモリ使用量が表示されます。各アプリケーションに対し、フォアグラウンドとバック グラウンドで動作しているサービスごとに表示が行われますので、アプリケーション全体のメモリプロファイリングに役立 ちます。 また、Procstats のオプションコマンドとして、表示するアプリケーション状況に対し、過去何時間までの記録を表 示するか指定することができます。 “adb shell dumpsys procstats --hours X” ※X に数字を指定することで設定が可能です。(1 を指定すれば過去 1 時間) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 454 8-4. Procstats について AGGREGATED OVER LAST 3 HOURS: * system / 1000: TOTAL: 100% (35MB-43MB-45MB/32MB-40MB-41MB over 12) Persistent: 100% (35MB-43MB-45MB/32MB-40MB-41MB over 12) * com.android.systemui / u0a21: TOTAL: 100% (44MB-45MB-51MB/39MB-40MB-46MB over 12) Persistent: 100% (44MB-45MB-51MB/39MB-40MB-46MB over 12) Imp Fg: 0.01% * com.android.phone / 1001: TOTAL: 100% (10MB-12MB-12MB/9.2MB-11MB-11MB over 12) Persistent: 100% (10MB-12MB-12MB/9.2MB-11MB-11MB over 12) ・ ・ ・ Run time Stats: SOff/Norm: +2h20m58s448ms SOn /Norm: +7m24s855ms TOTAL: +2h28m23s303ms Start time: 20XX-XX-XX XX:XX:XX Total elapsed time: +2h28m28s195ms (partial) libdvm.so chromeview Procstats 結果(過去 3 時間以内) CURRENT STATS: * com.android.chrome / u0a55: TOTAL: 6.1% (57MB-57MB-57MB/39MB-39MB-39MB over 1) Top: 5.6% (57MB-57MB-57MB/39MB-39MB-39MB over 1) Imp Fg: 0.02% Imp Bg: 0.01% Service: 0.22% Receiver: 0.21% (Last Act): 0.28% (Cached): 90% (45MB-45MB-45MB/32MB-32MB-32MB over 2) ・ ・ ・ Run time Stats: SOff/Norm: +2s341ms SOn /Norm: +7m3s498ms (running) TOTAL: +7m5s839ms Start time: 20XX-XX-XX XX:XX:XX Total elapsed time: +7m11s738ms (partial) libdvm.so chromeview Procstats 結果(現在) Procstats 機能を使用することで、アプリケーションやサービスがどれくらいの時間実行され、実行時にどのくらいの メモリを消費し、バックグラウンドで消費しているメモリ量を確認することができます。そのため Procstats 機能は、ア プリケーションやサービスの稼働時間とメモリ効率を把握し、改善に繋げることができるツールと言えます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 455 8-4. Procstats について ■プロセス統計情報の使用方法について 「プロセスの統計情報(Procstats)」機能について説明しました。 ここでは、「プロセスの統計情報」を表示する際の、フィルタの設定方法について説明します。 期間を選択 統計タイプを選択 図 132 プロセスの統計情報(フィルタ設定画面) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 456 8-4. Procstats について フィルタ名 フィルタ内容 3 時間以内のデータを表示する 6 時間以内のデータを表示する 期間 12 時間以内のデータを表示する 1 日以内のデータを表示する システムを表示 Uss を使用 システムプロセスを含める システムプロセスを含めない Uss メモリを含める Uss メモリを含めない バックグラウンドのプロセスのみ表示する 統計タイプ フォアグラウンドのプロセスのみ表示する キャッシュされたプロセスのみ表示する プロセスの統計情報(フィルタ可能項目) アプリケーション名を選択すると、選択したアプリケーションの実行時間、メモリ使用量が表示されます。 選択したアプリケーションが長時間に渡りメモリを大量に使用している場合、Android デバイス自体のメモリ容量 にもよりますが、アプリケーションのパフォーマンスを妨げる要因となっている可能性があります。そのため、選択したア プリケーションに対し、最適化を行う必要があることを確認することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 457 8-5. StrictMode について 8-5. StrictMode について 8-5-1. StrictMode とは StrictMode は、Android OS バージョン 2.3(Gingerbread)より追加された、パフォーマンス改善を行うため の機能です。Android OS バージョン 4.0(Gingerbread)からは、開発者向けオプションの機能の一つとして、 「厳格モード」という名称で独立して提供されるようになりました。 図 133 開発者向けオプション(厳格モード設定) StrictMode をアプリケーションに実装することで、パフォーマンスの劣化につながる要因を特定することができます。 UI スレッド上でネットワークアクセスやデータベースアクセスを行う場合など、設定したポリシーへの違反を検知するこ とができるため、解析手法として使用することが可能です。 ○StrictMode のメリット パフォーマンスの劣化につながる要因(ディスクアクセス、ネットワークアクセスなど)を検知し、特定すること ができます。 特定結果は、LogCat 上で表示することができます。そのため、Analyzer ツールなどの特殊解析ツール は不要になります。 ○StrictMode のデメリット パフォーマンスの劣化につながる要因を全て検知し、特定できるわけではありません。 本来は正しいはずの処理(アクセス)が、ポリシーに違反する処理として検知されてしまうことがあります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 458 8-5. StrictMode について 8-5-2. StrictMode の使用方法 Android OS バージョン 4.0(Ice Cream Sandwitch)より、開発者向けオプション機能で「厳格モード」として 提供されています。厳格モードを有効にする場合は、Android デバイスの[設定]から[開発者向けオプション]を選 択し、「厳格モードを有効にする」にチェックを入れます。有効にすることで、メインスレッドの処理が長くなった場合に 画面に対して赤枠の点滅が行われます。 (チェックを入れた後、端末の再起動(厳密には各アプリケーションのプロセス再起動)が必要です) StrictMode は、スレッドや仮想マシンのポリシーとして何を検知するかを設定することができます。何を検知する か設定することにより、ポリシーに違反する動作を行った原因の特定が可能となります。 たとえば、UI スレッド上でのネットワークリクエストが該当します。このように、ネットワークリクエストを行った場合、 Android OS バージョン 3.0(Honnycomb)以降は、Fatal エラーが発生します。そのため、仮に Android OS バージョン 2.3(Gingerbread)以前をサポートしているアプリケーションを、3.0 以降もサポートさせたい場合などに、 StrictMode でポリシー違反を特定し、アプリケーションのバージョンアップに役立てることができます。今後の OS バ ージョンアップに伴う変更においても同じことが言えます。 また、ネットワークアクセスによる処理の遅延が発生させる ANR についても、StrictMode による検知で防ぐことが できます。 StrictMode の設定サンプルコードは以下のとおりです。 ■StrictMode のサンプルコード (メインスレッド上でのネットワークリクエストに対するポリシー設定) 1. AndroidManifest.xml に API レベルと必要なパーミッションを記載します。 <manifest xmlns:android="http://schemas.android.com/apk/res/android" <uses-sdk android:minSdkVersion="9" /> <uses-permission android:name="android.permission.INTERNET" /> </manifest> StrictMode は、Android OS バージョン 2.3(API レベル 9)以上から使用可能です。そのため、SDK バージョ ンを 9 とします。また、ネットワークリクエストを行うためインターネット接続用のパーミッションを追加します。 2. StrictMode およびインターネット接続(HttpURLConnection)用のライブラリをインポートします。 import import import import import android.os.StrictMode; java.net.HttpURLConnection; java.net.URL; java.io.IOException java.net.MalformedURLException Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 459 8-5. StrictMode について 3. onCreate()で StrictMode のポリシー設定を行います。 public class MainActivity extends Activity { private Boolean strictMode = true; if(strictMode) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() // 疑いのある違反を全て検知する .penaltyLog() // 検知をシステムログに出力する .build(); // ThreadPolicy のインスタンスを生成する StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectAll() // 疑いのある違反を全て検知する .penaltyLog() // 検知をシステムログに出力する .penaltyDeath() // 違反検知により、プロセスを終了させる .build(); // VmPolicy のインスタンスを生成する } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); networkAccessSample(); } } StrictMode のポリシー設定は、アプリケーションの onCreate()で行います。 上記のコードは、detectAll()で全ての違反を検知する処理を記載しています。ただし、「ディスクアクセスのみ検 知する」「ネットワークアクセスのみ検知する」、または「ディスクアクセスは検知しない」などの個別設定も行うことがで きます。 個別設定についての詳細は、「8-6. StrictMode の関連のクラスおよび API 一覧」を参照してください。 setThreadPolicy()は、スレッド上でのポリシーを設定し、setVmPolicy()は VM(Virtual Machine、仮想 マシン)上でのポリシーを設定します。サンプルコードは、ポリシーの設定を行った後に、ネットワークアクセス処理を行 うための networkAccessSample()メソッドをコールしています。 networkAccessSample()の詳細については、次ページで記載します。 4. HttpURLConnection によるネットワークアクセス処理を行います。 public void networkAccessSample() { URL url = null; String accessUrl = http://www.XXX try { url = new URL(accessUrl); // ネットワーク接続開始 HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection(); // ネットワーク接続確立 urlConnection.connect(); } catch(MalformedURLException e) { e.printStackTrace(); } catch(IOException e) { e.printStackTrace(); } } 上記のサンプルコードは、ネットワークアクセスに HttpURLConnection を使用して確認を行っています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 460 8-5. StrictMode について StrictMode を設定している状態で、本処理を行うことによりポリシー違反ログが出力されるようになります。 5. StrictMode のログを確認します。 図 134 StrictMode ログ StrictMode ログは、”StrictMode policy violation”と出力されます。上記のログは、ネットワーク関連の処理 でポリシー違反が発生しています。原因と考えられる箇所に、UI スレッド上でのネットワークアクセスを記載したサン プルコードの networkAccessSample()メソッドが列挙されています。 メインスレッド上でのネットワークアクセスは、パフォーマンスを下げる原因の一つとなるります。そのため、ネットワークア クセスは、AsyncTask を使用した非同期処理で行うことが改善方法として考えられます。 ※Android OS バージョン 3.0(Honnycomb)以降は、Exception が発生するためサンプルコード上では try ~catch を使用しています。 ■StrictMode における注意点 StrictMode は、設定したポリシーに違反する処理が行われた際に検知を行う仕組みです。しかし、厳密にはセキ ュリティという観点で作られた仕組みではなく、全てのディスクアクセス、ネットワークアクセスを検知する訳ではありま せん。また、ポリシーの違反を検知した場合にも、検知箇所の全てが改善しなくてはならない処理という訳でもあり ません。あくまで、パフォーマンス改善手法の一つとして使用するようにしてください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 461 8-6. 「StrictMode」関連のクラスおよび API 一覧 8-6. 「StrictMode」関連のクラスおよび API 一覧 ○StrictMode クラス メソッド名 機能 static StrictMode.ThreadPolicy allowThreadDiskReads() 追加 OS Ver getThreadPolicy()メソッドで現在のポリシーを取得し、ディ スクアクセス(読み取り)の許可を行います。その後、 2.3 setThreadPolicy()で新しくポリシーの設定を行うラッパーで (Gingerbread) す。 static StrictMode.ThreadPolicy allowThreadDiskWrites() getThreadPolicy()メソッドで現在のポリシーを取得し、ディ スクアクセス(読み取りと書き込み)の許可を行います。その 2.3 後、setThreadPolicy()で新しくポリシーの設定を行うラッパ (Gingerbread) ーです。 static void 推奨とされている StrictMode のデフォルト設定です。ポリシー enableDefaults() static StrictMode.ThreadPolicy に違反している場合は、ログ出力されます。 (Gingerbread) 現在のスレッド上のポリシーを取得します。 2.3 getThreadPolicy() (Gingerbread) static StrictMode VmPolicy 現在の VM 上のポリシーを取得します。 getVmPolicy() 2.3 (Gingerbread) static void ログ出力時に、処理が遅い箇所について引数に設定したメッ noteSlowCall(String name) static void セージを出力します。 スレッドに対し、引数に指定した内容でポリシーの設定を行い setThreadPolicy(StrictMode.Thr ます。 eadPolicy policy) static void VM に対し、引数に指定した内容でポリシーの設定を行いま setVmPolicy(StrictMode.VmPoli す。 cy policy) 詳細については、下記 URL を参照してください。 【公式サイト】 2.3 http://developer.android.com/reference/android/os/StrictMode.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 3.0 (Honeycommb) 2.3 (Gingerbread) 2.3 (Gingerbread) 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 462 8-6. 「StrictMode」関連のクラスおよび API 一覧 ○StrictMode.ThreadPolicy.Builder クラス: スレッドに適用されるポリシー設定を以下に示します。 メソッド名 機能 StrictMode.ThreadPolicy ThreadPolicy クラスのインスタンスを生成します。 build() 追加 Ver 2.3 (GB) StrictMode.ThreadPolicy.Builder 潜在的に疑いのある全ての事象を検知します。 detectAll() 2.3 (GB) StrictMode.ThreadPolicy.Builder 処理が遅い箇所についての検知を行います。 detectCustomSlowCalls() StrictMode.ThreadPolicy.Builder 3.0 (HC) ディスクアクセス(読み込み)についての検知を行います。 detectDiskReads() 2.3 (GB) StrictMode.ThreadPolicy.Builder ディスクアクセス(書き込み)についての検知を行います。 detectDiskWrites() 2.3 (GB) StrictMode.ThreadPolicy.Builder ネットワークアクセスについての検知を行います。 detectNetwork() 2.3 (GB) StrictMode.ThreadPolicy.Builder penaltyDeath() ポリシーに違反した場合に、全てのプロセスをクラッシュします。ク ラッシュは、他のペナルティ処理の最後に行われます。 StrictMode.ThreadPolicy.Builder penaltyDeathOnNetwork() 2.3 (GB) 何らかのネットワークアクセスが発生した場合に、全てのプロセス をクラッシュします。 Android OS バージョン 3.0 以降は、本ポリシーがデフォルトで 3.0 (HC) 設定されます。 StrictMode.ThreadPolicy.Builder ポリシーに違反した場合に、ダイアログを表示します。 penaltyDialog() 2.3 (GB) StrictMode.ThreadPolicy.Builder penaltyDropBox() ポリシーに違反した際に、スタックトレースとタイミングデータを DropBox にログ出力することを有効にします。 StrictMode.ThreadPolicy.Builder penaltyFlashScreen() ポリシーに違反している間、画面をフラッシュします。 ※厳格モードで発生する赤枠。 StrictMode.ThreadPolicy.Builder ポリシー違反の検知ログを、システムログに出力します。 penaltyLog() 2.3 (GB) 3.0 (HC) 2.3 (GB) StrictMode.ThreadPolicy.Builder 全ての検知を無効にします。 permitAll() 2.3 (GB) StrictMode.ThreadPolicy.Builder 処理が遅い箇所についての検知を無効にします。 permitCustomSlowCalls() StrictMode.ThreadPolicy.Builder 3.0 (HC) ディスクアクセス(読み込み)についての検知を無効にします。 permitDiskReads() 2.3 (GB) StrictMode.ThreadPolicy.Builder ディスクアクセス(書き込み)についての検知を無効にします。 permitDiskWrites() 2.3 (GB) StrictMode.ThreadPolicy.Builder ネットワークアクセスについての検知を無効にします。 permitNetwork() 2.3 (GB) 【公式サイト】 http://developer.android.com/reference/android/os/StrictMode.ThreadPolicy.Builder. html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 463 8-6. 「StrictMode」関連のクラスおよび API 一覧 ○StrictMode.VmPolicy.Builder クラス: VM プロセス中の全てのスレッドに適用されるポリシーを以下に示します。 メソッド名 機能 StrictMode.VmPolicy VmPolicy クラスのインスタンスを生成します。 build() 追加 OS Ver 2.3 (Gingerbread) StrictMode.VmPolicy.Builder アクティビティのサブクラスで発生したメモリリークを検知します。 detectActivityLeaks() 3.0 (Honeycommb) StrictMode.VmPolicy.Builder 潜在的に疑いのある全ての実証を検知します。 detectAll() 2.3 (Gingerbread) StrictMode.VmPolicy.Builder detectFileUriExposure() file://Uri(ファイルのUri)がアプリケーション外へ公開される場 合に検知します。 StrictMode.VmPolicy.Builder detectLeakedClosableObjects() StrictMode.VmPolicy.Builder クローズ可能または明示的な終了方法で、他のオブジェクトが クローズされなかった場合に検知します。 3.0 (Honeycommb) 3.0 (Honeycommb) BroadCastReceiver または ServiceConnection が終了 時に発生したメモリリークを検知します。 detectLeakedRegistrationObjects 3.0 (Honeycommb) () StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects() StrictMode.VmPolicy.Builder penaltyDeath() SQLiteCursor または他の SQLite のオブジェクトがクローズさ れずにファイナライズされた場合に検知します。 ポリシーに違反した場合、全てのプロセスをクラッシュします。 クラッシュは他のペナルティ処理の最後に行われます。 StrictMode.VmPolicy.Builder penaltyDropBox() ポリシーに違反した際、スタックトレースとタイミングデータを DropBox へログ出力することを有効にします。 StrictMode.ThreadPolicy.Builder ポリシー違反の検知ログをシステムログに出力します。 penaltyLog() 2.3 (Gingerbread) 2.3 (Gingerbread) 2.3 (Gingerbread) 2.3 (Gingerbread) StrictMode.ThreadPolicy.Builder setClassInstanceLimit(Class 一度に設定するクラスのインスタンス数を超えた場合に検知し ます。 class, int instanceLimit) 3.0 (Honeycommb) 詳細については、下記 URL を参照してください。 【公式サイト】 http://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 464 8-7. デバッグツールでの解析事例 8-7. デバッグツールでの解析事例 これまで記載したデバッグツールのいくつかを使用して、実際に解析および修正に繋がる例を紹介します。 ◆ユースケース(メモリリーク): サーバに保存された複数の画像データをサムネイル表示する UI を持ったアプリケーションがあり、ユーザ操作による 選択で画像を表示する際に画面遷移が発生します。 ユーザが今までと同じようにアプリケーションを起動し、画面遷移をいつもより多く繰り返していたところ、アプリケー ションが強制終了しました。10 回や 20 回の画面遷移では発生せず、何度も繰り返すことで発生した、という報告 から画面遷移によるメモリリークが疑われましたので、本現象について調査することになりました。 ○メモリリーク Java 実行環境では、ガーベジコレクションによってメモリ管理が行われていますが、例えば静的に確保したリソー スや確保したリソースを参照したままになっていると、ガーベジコレクションはメモリを解放してくれません。そのため、解 放されないまま操作を続けていくうちにメモリリークが発生します。またリークするメモリ量によっては「OutofMemory」 による Exception が発生し、アプリケーションが終了することもあります。 メモリリークの解消は、本ドキュメントで記載したデバッグツールを使用することで原因解析を行い、改善することがで きます。 ■メモリリークサンプル 今回のメモリリークサンプルで使用するデバッグツールは以下の機能です。 <8-2-2. DDMS の使用方法> Devices タブ Heap タブ メモリリーク解析に、まず「DDMS の Heap タブ」の機能を使用します。 1. PC と Android デバイスを接続し、Eclipse を起動します。 図 135 メモリリーク解析実例(Eclipse) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 465 8-7. デバッグツールでの解析事例 メモリリークサンプルアプリケーションを起動し、Devices タブのプロセス一覧からメモリリークサンプルアプリケーション を選択し、起動時のメモリ確保容量を確認します。 図 136 メモリリーク解析実例(DDMS(Heap)) プロセス一覧からメモリリークサンプルアプリケーションを選択後、「Update Heap」を押下し、Heap タブの 「Cause GC」を押下すると、現在のメモリリークサンプルアプリケーション起動時のメモリ容量が表示されます。 2. メモリリークサンプルアプリケーションに対して画面遷移を行い、同じように Heap タブから「Cause GC」を押下 します。 図 137 メモリリーク解析実例(DDMS(Heap)画面切り替え後) 画面遷移によって元画面の Activity は終了されるはずですが、メモリリークサンプルアプリケーション起動時に確 認したメモリ確保容量から増えたまま、元の確保容量に戻っていません(約プラス 6MByte 増)。ガーベジコレクショ ンを行っても解放されないことから、メモリリークが発生しています。 メモリリークが発生していることが分かったら、次に Devices タブの「Dump HPROF file」機能を使用します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 466 8-7. デバッグツールでの解析事例 3. Devices タブのプロセス一覧からメモリリークサンプルアプリケーションを選択し、「Dump HPROF file」を押下 します。 図 138 メモリリーク解析実例(DDMS(Heap) HPROF file 押下) 4. 下記画面で「Leak Suspects Report」が選択されていることを確認し、「完了」を押下します。 図 139 メモリリーク解析実例(HPROF file) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 467 8-7. デバッグツールでの解析事例 5. メモリリークの疑いのあるオブジェクトが Eclipse 上に表示されますので各オブジェクトを確認します。 図 140 メモリリーク解析実例(Leak Suspect Report 画面) 図 141 メモリリーク解析実例(Problem Suspect 画面 1) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 468 8-7. デバッグツールでの解析事例 図 142 メモリリーク解析実例(Problem Suspect 画面 2) Report 画面では、メモリリークの疑いのあるオブジェクトを表示します。概要図の下に表示される Problem Suspect を確認すると、同じ LinearLayout 内、Activity 内で同じサイズの byte 配列が作成され、保持され ていることが分かります。ただし、必要な分だけ個別に byte 配列を生成し、使用する可能性もありますので、あくま でメモリリークを疑うためのツールにすぎないのは「Heap」タブで説明したとおりです。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 469 8-7. デバッグツールでの解析事例 6. 「Open Dominator Tree for entire heap」を押下し、メモリ使用量の多い順にオブジェクトの一覧を表示 します。 図 143 メモリリーク解析実例(Dominator 画面) 約 3MByte という同じ容量のメモリを 2 種類のオブジェクトで消費していることが分かります。手順 3 で確認した際 に、メモリリークサンプルアプリケーション起動時より約 6MByte の確保領域が増え、ガーベジコレクションでも解放さ れない、ということを記載しましたが、ここで約 6MByte(3MByte×2)という数値で類推することができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 470 8-7. デバッグツールでの解析事例 7. オブジェクトに対して右クリックから「Path to GC Roots」を選択し、「with all references」を選択し、byte 配列の参照を表示します。 図 144 メモリリーク解析実例(Path to GC Roots 選択画面 1) 図 145 メモリリーク解析実例(Path to GC Roots 選択画面 2) 「Path to GC Roots」では選択したオブジェクトを参照しているクラスが表示されます。ここで参照されていると、 ガーベジコレクションでメモリ解放が行われないため、メモリリークの要因になります。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 471 8-7. デバッグツールでの解析事例 図 146 メモリリーク解析実例(Path to GC Roots 画面 1) 図 147 メモリリーク解析実例(Path to GC Roots 画面 2) 上記サンプルでは、ソースコード上の話になりますが、インナークラスとして生成した「sampleClass」から参照され ています。 「MainActivity.java」「byte 配列(3MByte)」「sampleClass」というキーワードを元に、ソースコードを確認し ます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 472 8-7. デバッグツールでの解析事例 8. ソースコードを確認し、メモリリーク要因となる箇所を修正します。 MainActivity.java(メモリリーク発生) public void MainActivity extends Activity { private static SampleClass sampleClass; // インナークラス private byte[] LeakObject = new byte[3 * 1024 * 1024]; // 3MByte の Object @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView imageView = new ImageView(this); imageView.setImageBitmap(BitmapFactory.decodeResource( getResources(), R.drawable.ic_launcher)); if(sampleClass == null) { sampleClass = new SampleClass(); } sampleClass.doing(); } Button bt = (Button) findViewById(R.id.link); bt.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent(getApplicationContext(), MainActivity2.class); startActivity(intent); finish(); } }); class SampleClass { public void doing() { Toast.makeText(getApplicationContext(), “Sample MemoryLeak”, Toast.LENGTH_SHORT).show(); } } MainActivity.java ファイルの中で、LeakObject という byte 配列が 3MByte 分確保されていますので、 HPROF file で確認したオブジェクトと同じものと考えられます。 次に sampleClass というインナークラスを確認すると、非 static のクラスとして宣言されています。非 static のイ ンナークラスはアウタークラスのインスタンスへの参照を暗黙的に持つという特性があるため、今回のガーベジコレクシ ョンを妨げる要因となっています。今回のメモリリークは上記が原因です。 (画面遷移が発生していますので、画面自体のレイアウトと Activity とでそれぞれ 3MByte 分のリークが発生しま した。) メモリリークの原因が判明したところで、修正点を説明します。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 473 8-7. デバッグツールでの解析事例 MainActivity.java(メモリリーク修正後) public void MainActivity extends Activity { private static SampleClass sampleClass; // インナークラス private byte[] LeakObject = new byte[3 * 1024 * 1024]; // 3MByte の Object @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView imageView = new ImageView(this); imageView.setImageBitmap(BitmapFactory.decodeResource( getResources(), R.drawable.ic_launcher)); if(sampleClass == null) { sampleClass = new SampleClass(); } sampleClass.doing(this); } Button bt = (Button) findViewById(R.id.link); bt.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent(getApplicationContext(), MainActivity2.class); startActivity(intent); finish(); } }); static class SampleClass { public void doing(Context context) { Toast.makeText(context, “Sample MemoryLeak”, Toast.LENGTH_SHORT).show(); } } 修正点は、赤字で示した部分です。 非 static のインナークラスが原因だったため、まず static のインナークラスに変更します。static に変更することで doing()のメソッドは Context が必要になるため、呼び元からの引数と doing()の引数に Context を追加し、 受け取った Context を Toast.makeText()で使用します。 また、もう一点、上記サンプルコードの青字で示した部分(finish())について注意点を説明します。 ボタンクリックによる画面遷移時、遷移元画面の Activity を finish()メソッドによって終了させていますが、ここで 終了を行わないと再度遷移元画面に戻ってくる際に新しく Activity を生成してしまうため、遷移元画面への遷移 が発生するたびに Activity が生成され直すことになります。当然ながら Activity が保持するオブジェクトサイズ分 が全てメモリ消費されますので、Android デバイスの最大ヒープ容量を超えるまで、増え続けることになります。 遷移元画面を終了させないようなアプリケーションを作成する場合は別ですが、画面遷移時の finish()メソッド についても注意してください。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 474 8-7. デバッグツールでの解析事例 上記の修正により、メモリリークは解消されているはずですので、実際に修正後のアプリケーションで動作確認を 行います。 9. 修正後のアプリケーションで、手順 2~7 を実施し、結果を確認します。 図 148 メモリリーク解析実例(DDMS(Heap) 修正後) 上の図は、修正後のアプリケーションを起動し、画面遷移を行った後のメモリ確保容量を表示しています。 修正前は byte 配列分の 3MByte×2 の 6MByte 分が解放されないままとなっていましたが、修正後では解放 され、アプリケーション起動時と変わらない確保容量となっています。 図 149 メモリリーク解析実例(Leak Suspect Report 画面 修正後) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 8 章 Android アプリケーション開発に必須のデバッグツール利用法 | 475 8-7. デバッグツールでの解析事例 図 150 メモリリーク解析実例(Dominator 画面 修正後) HPROF file でも確認します。修正前ではメモリリークの疑いのあるオブジェクトとして byte 配列が表示されていま したが、修正後は表示されなくなっています。 (同じ 3MByte のオブジェクトが表示されていますが、レイアウトと Activity の 2 種類が表示されているだけですの で、問題ありません) 上記により、メモリリークが解消されていることが確認できました。 画面遷移時の finish()メソッドについては既に述べましたが、今回のサンプルにおけるメモリリークでは画面遷移 時に finish()メソッドを確実にコールしています。この処理を行わないだけで、非 static のインナークラスによる参照 問題に加え、画面遷移に伴って無限にメモリ容量を増加させることになり、Android デバイスの状況によってはすぐ OutofMemory が発生し、様々なアプリケーションやサービスが正常に動作しなくなる可能性があります。 以上で、Android に用意されたデバッグツールにより、メモリリークの疑いのある箇所を特定し、解消することがで きる一例を紹介しました。 ソースコードを作成する上で注意する内容でもありますが、メモリリークに限らず、開発工程の中でデバッグツール を適切に使用することで、発生した問題の早期解決を行うことができます。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 第9章 補足情報 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. | 476 第 9 章 補足情報 | 477 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」 (補足:API 情報) クラス名 Address API クラス メソッド レベル 概要 1 住所を Address (Locale locale) 1 コンストラクタ。 表す際 clearLatitude () 1 アドレスに紐づいた緯度を削除する。 に使用 clearLongitude () 1 アドレスに紐づいた経度を削除する。 describeContents () 1 Parcelable にマーシャルされた型の ス。 住所のフ 種類を表す。 getAddressLine (int index) 1 など)を返却する。 界中の アドレスの構成データが存在しない 住所を きる 場合は「null」を返却する。 getAdminArea () 1 例としては「CA」などがあり、不明の 簡易バ に従う。 アドレスの持っている行政区域名 (州・都道府県等)を返却する。 XAL の ージョン インデックス(0 から始まる)でナンバリ ングしたアドレスの構成データ(国名 トは世 表現で メソッド概要 レベル するクラ ォーマッ API 場合は「null」を返却する。 getCountryCode () 1 アドレスの持っている国コードを返却 する。 例としては「US」などがあり、不明の 場合は「null」を返却する。 getCountryName () 1 アドレスの持っている国名を返却す る。 例としては「アイスランド」などがあり、 不明の場合は「null」を返却する。 public Bundle getExtras () 1 アドレスに関して追加的に提供され た特定情報を「Bundle」として返却 する。 getFeatureName () 1 アドレスの持っている建造物の名前 を返却する。 例としては「ゴールデンゲードブリッジ」 などがあり、不明の場合は「null」を 返却する。 public double getLatitude () 1 アドレスの緯度を返却する。 public Locale getLocale () 1 アドレスの位置情報を返却する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 478 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) getLocality () 1 アドレスの持っている市区町村名を 返却する。 例としては「マウンテンビュー」などがあ り、不明の場合は「null」を返却す る。 public double getLongitude () 1 アドレスの経度を返却する。 getMaxAddressLineIndex () 1 アドレスの構成データ(国名など)を特 定するために現在使用しているイン デックスの最大値を返却する。 getPhone () 1 アドレスの持っている電話番号を返 却する。 不明の場合は「null」を返却する。 getPostalCode () 1 アドレスの持っている郵便番号を返 却する。 例としては「94110」などがあり、不 明の場合は「null」を返却する。 getPremises () 4 アドレスの持っている建物の名称を 返却する。 不明の場合は「null」を返却する。 getSubAdminArea () 1 アドレスの持っている副行政区域名 (郡等)を返却する。 例としては「サンタナクララ郡」などがあ り、不明の場合は「null」を返却す る。 getSubLocality () 4 アドレスの持っている副市町村名 (町・字等)を返却する。 不明の場合は「null」を返却する。 getSubThoroughfare () 4 アドレスの持っている所番地の名前 を返却する。 不明の場合は「null」を返却する。 getThoroughfare () 1 アドレスの持っている区画の名前を 返却する。 例として「アンフィテアトルムパークウェ イ 1600」などがあり、不明の場合は 「null」を返却する。 getUrl () 1 アドレスの持っている URL を返却す る。 不明の場合は「null」を返却する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 479 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) hasLatitude () 1 アドレスに緯度がある場合、true を 返却する。 それ以外は false を返却する。 hasLongitude () 1 アドレスに経度がある場合、true を 返却する。 それ以外は false を返却する。 setAddressLine (int index, String line) 1 インデックス(0 から始まる)でナンバリ ングしたアドレスの構成データ(国名 など)を 引数に指定した String に設定す る。「null」を設定することも許可され る。 setAdminArea (String adminArea) 1 アドレスに行政区域名を設定する。 「null」を設定することも許可される。 setCountryCode (String countryCode) 1 アドレスに国コードを設定する。 「null」を設定することも許可される。 setCountryName (String countryName) 1 アドレスに国名を設定する。「null」を 設定することも許可される。 setExtras (Bundle extras) 1 引数に指定した「Bundle」に対し位 置情報に関連する追加的に提供さ れた特定情報を設定する。 setFeatureName (String featureName) 1 アドレスが持つ建造物の名前を引数 に指定した String に設定する。 setLatitude (double latitude) 1 アドレスに緯度を設定する。 setLocality (String locality) 1 アドレスに市区町村名を設定する。 「null」を設定することも許可される。 setLongitude (double longitude) 1 アドレスに経度を設定する。 setPhone (String phone) 1 アドレスに電話番号を設定する。 「null」を設定することも許可される。 setPostalCode (String postalCode) 1 アドレスに郵便番号を設定する。 「null」を設定することも許可される。 setPremises (String premises) 4 アドレスに建物の名称を設定する。 「null」を設定することも許可される。 setSubAdminArea (String 1 subAdminArea) アドレスに副行政区域名(郡等)を 設定する。「null」を設定することも 許可される。 setSubLocality (String sublocality) 4 アドレスに副市町村名(町・字等)を 設定する。「null」を設定することも 許可される。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 480 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) setSubThoroughfare (String 4 subthoroughfare) アドレスに所番地の名前を設定す る。「null」を設定することも許可され る。 setThoroughfare (String thoroughfare) 1 アドレスに区画の名前を設定する。 「null」を設定することも許可される。 setUrl (String Url) 1 アドレスに URL を設定する。「null」 を設定することも許可される。 toString () 1 このオブジェクトの内容物を読みやす いように String の形に変換したもの を返却する。 Criteria 1 使用す writeToParcel (Parcel parcel, int flags) 1 オブジェクトを「Parcel」に書き込む。 Criteria() 1 新しい「Crireria」オブジェクトを作成 るロケー ションプ するためのコンストラクタ。 Criteria(Criteria criteria) 1 既に存在している「Crireria」オブジ ロバイダ ェクトをコピーする際に使用するコンス の選定 トラクタ。 条件を describeContents() 1 「Parcelable」インターフェースによっ 指定す て整理された表示の形で含む特別 るための なオブジェクトの種類を表す。 クラス。 getAccuracy() 1 指定されている位置情報の精度を 返却する。 getBearingAccuracy() 9 指定されている方位情報の精度を 返却する。 getHorizontalAccuracy() 9 指定されている平面情報(緯度およ び経度)の精度を返却する。 getPowerRequirement() 1 指定されている消費電力レベルを返 却する。 getSpeedAccuracy() 9 指定されている速度情報の精度(高 い、低いおよび指定されていない)を 返却する。 getVerticalAccuracy() 9 指定されている高度情報の精度を 返却する。 isAltitudeRequired() 1 高度情報を提供すべきか否かを返 却する。 isBearingRequired() 1 方位情報を提供すべきか否かを返 却する。 isCostAllowed() 1 費用発生を許可するか否かを返却 する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 481 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) isSpeedRequired() 1 速度情報を提供すべきか否かを返 却する。 setAccuracy(int accuracy) 1 望ましい位置情報の精度を指定す る。 setAltitudeRequired(boolean 1 altitudeRequired) setBearingAccuracy(int accuracy) 望ましい高度情報の精度を指定す る。 9 望ましい方位情報の精度を指定す る。 setBearingRequired(boolean 1 bearingRequired) setCostAllowed(boolean costAllowed) 方位情報を取得するか否かを指定 する。 1 費用発生を許可するか否かを指定 する。 setHorizontalAccuracy(int accuracy) 9 望ましい平面情報(緯度および経 度)の精度を指定する。 setPowerRequirement(int level) 1 望ましい消費電力レベルを指定す る。 setSpeedAccuracy(int accuracy) 9 望ましい速度情報の精度を指定す る。 setSpeedRequired(boolean 1 speedRequired) setVerticalAccuracy(int accuracy) 速度情報を取得するか否かを指定 する。 9 望ましい高度情報の精度を指定す る。 toString() 1 このオブジェクトの内容物を読みやす いように String の形に変換したもの を返却する。 writeToParcel(Parcel parcel, int flags) 1 このオブジェクトを「Parcel」オブジェク トの形に圧縮する。 Geocoder 1 緯度お Geocoder(Context context, Locale よび経 locale) 1 れたレスポンスを持つ Geocoder の 度から住 所を割り 取得した位置によってローカライズさ コンストラクタ。 Geocoder(Context context) 1 デフォルトの位置によってローカライズ 出した されたレスポンスを持つ Geocoder り、住所 のコンストラクタ。 から緯度 getFromLocation(double latitude, および経 double longitude, int maxResults) 度などに 1 引数に指定された経度と緯度周辺 のアドレスを配列型で複数個返却す る。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 482 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) 変換す getFromLocationName(String locationN 1 引数に指定された緯度、経度以内 る際に ame, int maxResults, double にあるロケーション名周辺のアドレス 使用す lowerLeftLatitude, double を配列型で複数個返却する。 るクラ lowerLeftLongitude, double ロケーション名は、「アイスランド、ダル ス。 upperRightLatitude, double ビック村」のような場所名や「カリフォ upperRightLongitude) ルニア州マウンテンビュー アンフィテア トルムパークウェイ 1600」のような住 所名、「SFO」のような空港コードな どが利用可能。 getFromLocationName(String locationN 1 ame, int maxResults) 引数に指定されたロケーション名周 辺のアドレスを配列型で複数個返 却する。 ロケーション名は、「アイスランド、ダル ビック村」のような場所名や「カリフォ ルニア州マウンテンビュー アンフィテア トルムパークウェイ 1600」のような住 所名、「SFO」のような空港コードな どが利用可能。 isPresent() 9 「Geocoder」にて getFromLocation()および getFromLocationName()メソッ ドが実装されている場合は true を 返却する。 GpsSatellite 3 GPS 衛 getAzimuth() 3 衛星の方位角を返却する。 星の現 getElevation() 3 衛星の仰角を返却する。 在の状 getPrn() 3 衛星の PRN 番号(疑似雑音系列 態を示 ID)を返却する。 すクラス であり、 「GpsSt getSnr() 3 衛星の SN 比を返却する。 hasAlmanac() 3 GPS エンジンが衛星用の Almanac データ(軌道情報)を持っている場合 atus」ク ラスと共 true を返却する。 hasEphemeris() 同で使 3 GPS エンジンが衛星用の Ephemeris データ(軌道情報)を持 用する。 っている場合 true を返却する。 usedInFix() 3 最新の GPS 位置情報を計算する 際に GPS エンジンによって衛星が使 用された場合 true を返却する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 483 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) GpsStatus 3 GPS エ getMaxSatellites() 3 getSatellites()メソッドによって返 ンジンの 却され得る衛星リストにおける最大 現在の 衛星数を返却する。 状態を getSatellites() 3 GPS エンジンの現在の状態を示す 示すクラ GPS 衛星オブジェクトの配列を返却 スであ する。 り、 getTimeToFirstFix() 3 最近再起動した GPS エンジンから 「GpsSt 最初の位置情報を受け取るために atus.Li 要求される時間を返却する。 stener 」インター フェースと 共同で 使用す る。 GpsStatus.L istener 3 GPS の onGpsStatusChanged(int event) 状態に 3 GPS の状態に変更があった際に呼 び出される 変更が あった際 に通知 を受ける ために使 用するク ラス。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 484 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) GpsStatus.N 5 meaListener MNEA( onNmeaReceived (long timestamp, GPS 受 String nmea) 5 NMEA データを GPS エンジンから受 け取った際に呼び出される。 信機の 出力す るメッセ ージであ り、海上 電子装 置と対 話するた めの標 準フォー マット)を 受け取 るために 使用す るクラ ス。 Location 1 地理上 Location(String provider) 1 の位置 プロバイダの名前を引数に指定する コンストラクタ。 を示すデ Location(Location l) 1 ータクラ 既にある Location オブジェクトをコピ ーする際に使用するコンストラクタ。 ス。位置 bearingTo(Location dest) 1 現在の「Location」と引数に指定し は緯 た「Location」間の最も短い距離に 度、経 おける方位角を返却する 度、タイ (真北から東に向かう角度で指定)。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 485 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) ムスタン convert(String coordinate) 1 「FORMAT_DEGREES」、 プのほ 「FORMAT_MINUTES」および か、方位 「FORMAT_SECONDS」のフォー や高 マットに従って 度、速 表記された String 型の座標を 度などか double 型に変換する。 ら成り立 つ。 【フォーマット詳細】 「FORMAT_DEGREES」 →[+-]DDD.DDDDD(D は角度) 「FORMAT_MINUTES」 →"[+-]DDD:MM.MMMMM(D は角度/M は分) 「FORMAT_SECONDS」 →DDD:MM:SS.SSSSS(D は角 度/M は分/S は秒) convert(double coordinate, int 1 座標を String 型に変換する。 1 Parcelable にマーシャルされた型の outputType) describeContents() 種類を表す。 distanceBetween(double startLatitude, 1 2 つの位置(緯度と経度で指定)間 double startLongitude, double における最も短い距離を計算し、そ endLatitude, double endLongitude, の結果を格納したリストを作成する float[] results) (計算結果はメートル単位/複数結 果が出る場合有)。 distanceTo(Location dest) 1 現在使用している「Location」と引 数に指定した「Location」との距離 (メートル単位)を返却する。 dump(Printer pw, String prefix) 3 toString()メソッドで取得可能な内 容(「Location」オブジェクトが保持 する情報)をログ出力する。 getAccuracy() 1 ロケーションでの誤差範囲をメートル で取得する。 getAltitude() 1 可能である場合、海抜高度情報を メートルで取得する。 getBearing() 1 方位情報を水平角度で取得する。 getElapsedRealtimeNanos() 17 システム起動からの経過時間を基準 とした位置情報の時間を返却する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 486 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) getExtras() 1 位置情報に関して追加的に提供さ れた特定情報を「「Bundle」として 返却する。 getLatitude() 1 緯度を角度で取得する。 getLongitude() 1 経度を角度で取得する。 getProvider() 1 位置情報を提供したロケーションプロ バイダの名前を返却する。 getSpeed() 1 可能であれば、速度情報をメートル/ 秒で取得する。 getTime() 1 1970 年 1 月 1 日からミリ秒単位で 位置情報の UTC 時刻を返却する。 hasAccuracy() 1 位置情報に精度情報がある場合 true を返却する。 hasAltitude() 1 位置情報に高度情報がある場合 true を返却する。 hasBearing() 1 位置情報に方位情報がある場合 true を返却する。 hasSpeed() 1 位置情報に速度情報がある場合 true を返却する。 removeAccuracy() 1 位置情報から精度情報を削除す る。 removeAltitude() 1 位置情報から高度情報を削除す る。 removeBearing() 1 位置情報から方位情報を削除す る。 removeSpeed() 1 位置情報から速度情報を削除す る。 reset() 1 位置情報から構成要素をすべて削 除する。 set(Location l) 1 引数にて指定された「Location」に 対し位置情報の全ての構成要素を 設定する。 setAccuracy(float accuracy) 1 位置情報に対しメートルで見積もっ た精度情報を設定する。 setAltitude(double altitude) 1 高度情報をメートルで設定する。 setBearing(float bearing) 1 方位情報を水平角度で設定する。 setElapsedRealtimeNanos(long time) 17 システム起動からの経過時間を基準 とした位置情報の時間を設定する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 487 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) setExtras(Bundle extras) 1 引数に指定した「Bundle」に位置 情報に関する追加情報を設定す る。 setLatitude(double latitude) 1 緯度を角度で設定する。 setLongitude(double longitude) 1 経度を角度で設定する。 setProvider(String provider) 1 位置情報を提供した位置情報プロ バイダの名前を設定する。 setSpeed(float speed) 1 速度情報をメートル/秒で設定す る。 setTime(long time) 1 1970 年 1 月 1 日からミリ秒単位で 位置情報の UTC 時刻を設定する。 toString() 1 このオブジェクトの内容物を読みやす いように String の形に変換したもの を返却する。 LocationList ener 1 現在位 writeToParcel(Parcel parcel, int flags) 1 オブジェクトを「Parcel」に書き込む。 onLocationChanged (Location location) 1 現在位置が変更されたときに呼び出 置が更 される。 新された onProviderDisabled (String provider) 1 際、 プロバイダが使用不可である場合に ユーザによって呼び出される。 「Locati onMan onProviderEnabled (String provider) 1 プロバイダが使用可能である場合に ユーザによって呼び出される。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 488 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) ager」か onStatusChanged (String provider, int らの変更 status, Bundle extras) 1 プロバイダの状態に変更があった場 合呼び出される。 通知を 受け取 るために 使用す るクラ ス。 このクラ スのメソ ッドは Locatio nMana ger サー ビスに対 して本ク ラスのオ ブジェク トを登録 した場合 にコール される仕 組みに なってい る。 LocationMa nager 1 システム addGpsStatusListener(GpsStatus.Listen ロケーシ er listener) ョンサー addNmeaListener(GpsStatus.NmeaList ビスにア ener listener) 3 使用する「GpsStatus.Listener」 を追加する。 5 使用する 「GpsStatus.NmeaListener」を クセスす 追加する。 るための addProximityAlert(double latitude, 手段を double longitude, float radius, long 半径より算出された位置に近づいた 提供す expiration, PendingIntent intent) ら発動するアラート(発行するインテン るクラ 1 緯度、経度および引数に指定された ト)を設定する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 489 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) ス。 addTestProvider(String name, boolean 3 こういっ requiresNetwork, boolean し、起動中のプロバイダのセットに追 たサービ requiresSatellite, boolean requiresCell, 加する。 スを利 boolean hasMonetaryCost, boolean 用する supportsAltitude, boolean と、定期 supportsSpeed, boolean 的に更 supportsBearing, int 新される powerRequirement, int accuracy) 現在位 clearTestProviderEnabled(String provid 置をアプ er) 3 除去する。 ン側で clearTestProviderLocation(String provid 取得した er) 3 する。 した現在 clearTestProviderStatus(String provider 位置が ) getAllProviders() 3 1 用可否は問わない) getBestProvider(Criteria criteria, 入ったと 定のイン できる。 1 boolean enabledOnly) きに特 ることが 全てのロケーションプロバイダの名前 のリストを返却する。(プロバイダの使 囲内に 発行す 引数に指定した擬似ロケーションプロ バイダの擬似的な状態を除去する。 値の範 テントを 引数に指定した擬似ロケーションプロ バイダの擬似的な位置情報を除去 り、取得 た近似 引数に指定した擬似ロケーションプロ バイダが使用可能か否かの設定を リケーショ 定められ 擬似ロケーションプロバイダを作成 引数に指定された「Criteria」の条 件に最も適合したプロバイダの名前 を返却する。 getGpsStatus(GpsStatus status) 3 GPS エンジンの現在の状態に関する 情報を検索する。 getLastKnownLocation(String provider) 1 引数に指定されたプロバイダより最 近取得した位置情報を返却する。 getProvider(String name) 1 引数に指定されたロケーションプロバ イダに関する情報を返却する。 (引数はプロバイダ名にて指定し、名 前がない場合は null を指定する) getProviders(boolean enabledOnly) 1 ロケーションプロバイダの名前のリスト を返却する。(プロバイダの使用可否 を引数にて指定可能) getProviders(Criteria criteria, boolean enabledOnly) 1 引数にて指定した「Criteria」の条 件を満たすロケーションプロバイダの 名前のリストを返却する。 (プロバイダの使用可否を引数にて 指定可能/「Criteria」がない場合 は null 指定) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 490 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) isProviderEnabled(String provider) 1 引数に指定されたプロバイダが現在 使用可能か否かを返却する。 removeGpsStatusListener(GpsStatus.Li 3 stener listener) 使用していた 「GpsStatus.Listener」を除去す る。 removeNmeaListener(GpsStatus.Nmea 5 Listener listener) 使用していた 「GpsStatus.NmeaListener」を 除去する。 removeProximityAlert(PendingIntent in 1 tent) 端末が範囲内に入ってきた際に発 動するよう設定したアラート(引数に 指定した「PendingIntnet」)を除 去する。 removeTestProvider(String provider) 3 引数にて指定された擬似ロケーショ ンプロバイダを除去する。 removeUpdates(PendingIntent intent) 3 引数にて指定した 「PendingIntent」を使用した全て の位置情報更新処理を除去する。 removeUpdates(LocationListener listen 1 er) 引数にて指定した 「LocationListener」を使用した全 ての位置情報更新処理を除去す る。 requestLocationUpdates(long minTime, 9 「Criteria」と「PendingIntent」を float 使用した位置情報更新処理を登録 minDistance, Criteria criteria, PendingI する。 ntent intent) requestLocationUpdates(long minTime, 9 「Criteria」と引数に指定した float 「Looper」におけるコールバックを使 minDistance, Criteria criteria, Location 用した位置情報更新処理を登録す Listener listener, Looper looper) る。 requestLocationUpdates(String provide 1 プロバイダと「PendingIntent」を使 r, long minTime, float 用した位置情報更新処理を登録す minDistance, LocationListener listener) る。 requestLocationUpdates(String provide 1 プロバイダと引数に指定した r, long minTime, float 「Looper」におけるコールバックを使 minDistance, LocationListener listener, 用した位置情報更新処理を登録す Looper looper) る。 requestLocationUpdates(String provide 3 プロバイダと「PendingIntent」を使 r, long minTime, float 用した位置情報更新処理を登録す minDistance, PendingIntent intent) る。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 491 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) requestSingleUpdate(String provider, L 9 ocationListener listener, Looper looper) プロバイダと引数に指定した 「Looper」におけるコールバックを使 用した位置情報更新処理(1 回分) を登録する。 requestSingleUpdate(Criteria criteria, L 9 ocationListener listener, Looper looper) 「Criteria」と引数に指定した 「Looper」におけるコールバックを使 用した位置情報更新処理(1 回分) を登録する。 requestSingleUpdate(String provider, P 9 endingIntent intent) プロバイダと「PendingIntent」を使 用した位置情報更新処理(1 回分) を登録する。 requestSingleUpdate(Criteria criteria, P 9 endingIntent intent) 「Criteria」と「PendingIntent」を 使用した位置情報更新処理(1 回 分)を登録する。 sendExtraCommand(String provider, St 3 ring command, Bundle extras) setTestProviderEnabled(String provider ロケーションプロバイダに対し追加コマ ンドを送信する。 3 , boolean enabled) 引数に指定された擬似ロケーションプ ロバイダが使用可能か否か設定す る。 setTestProviderLocation(String provide 3 r, Location loc) 引数に指定された擬似ロケーションプ ロバイダに対し擬似的な位置情報を 設定する。 setTestProviderStatus(String provider, LocationPro vider 1 端末の 3 引数に指定された擬似ロケーションプ int status, Bundle extras, long ロバイダに対し擬似的な状態を設定 updateTime) する。 getAccuracy() 1 地理的 プロバイダにおける、平面情報の精 度情報を返却する。 な位置 getName() 1 プロバイダの名前を返却する。 を定期 getPowerRequirement() 1 プロバイダにおける、消費電力レベル 的にレポ ートする を返却する。 hasMonetaryCost() 1 ユーザ側に料金が発生する可能性 クラス。 のあるプロバイダを使用している場 なお、こ 合、true を返却する のクラス (false の場合は、無料) は抽象 クラスに あたる。 meetsCriteria(Criteria criteria) 1 プロバイダが引数にとられている 「Criteria」オブジェクトの条件と合 致する場合、true を返却する (false の場合は合致しない) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 492 9-1. 「3-2.位置情報取得機能を持つアプリケーション開発における注意点」(補足:API 情報) requiresCell() 1 プロバイダが適切なセルラー方式のネ ットワーク(例:セルタワーの ID を使 用する)への アクセスを必要とする場合 true を返 却する(false の場合は必要としな い) requiresNetwork() 1 プロバイダがデータネットワーク(例: インターネット)へのアクセスを必要と する場合 true を返却する (false の場合は必要としない) requiresSatellite() 1 プロバイダが衛星を基にした位置特 定システム(例:GPS)へのアクセス を必要とする場合 true を返却する(false の場合は必 要としない) supportsAltitude() 1 プロバイダが高度情報を提供可能で ある場合 true を返却する(false の 場合は提供不可) supportsBearing() 1 プロバイダが方位情報を提供可能で ある場合 true を返却する(false の 場合は提供不可) supportsSpeed() 1 プロバイダが速度情報を提供可能で ある場合 true を返却する(false の 場合は提供不可) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 493 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足: API 情報) クラス名 Camera.Ar API クラス概要 メソッド API レベ レベ ル ル 14 ea 自動露出、オートホワイト Camera.Area(Rect rect, int weight) 14 メソッド概要 コンストラクタ(具体的 バランスおよびオートフォー に座標と重さを引数に カスを計算する際、フォー 指定して作成)。 カスや測定される現在の equals (Object obj) 14 ビューの領域を指定する Area オブジェクトを比 較する。 ために使用するクラス。 Camera.Au カメラのオートフォーカスの onAutoFocusMoving (boolean start, toFocusMov 起動および停止を通知す Camera camera) eCallback るために使用するコールバ れたりした際に ックインターフェース。 呼び出される。 Camera.Au 16 1 カメラのオートフォーカスが onAutoFocus(boolean success, toFocusCall 完了したことを通知するた Camera camera) back めに使用するコールバック 16 カメラのオートフォーカス が起動されたり停止さ 1 カメラのオートフォーカス が完了した際に呼び出 される。 インターフェース。 Camera.Ca 9 meraInfo Camera.Err Camera.CameraInfo () 9 コンストラクタ。 onError (int error, Camera camera) 1 発生したカメラ関連エラ 供するクラス。 1 orCallback Camera.Fa カメラに関する情報を提 14 ce カメラのエラー通知を受け るためのコールバックインタ ーのためのコールバッ ーフェース。 ク。 カメラの顔探知機能を通 Camera.Face() 14 して識別した顔に関する 空の顔関連情報を作 成する。 情報を提供するクラス。 Camera.Fa プレビューのフレームにおい onFaceDetection(Face[] faces, Camera ceDetection て顔が検出された際のコ camera) Listener ールバックインターフェー ことをリスナに通知す ス。 る。 Camera 14 1 イメージキャプチャの設 addCallbackBuffer(byte[] 定、プレビューの開始/停 callbackBuffer) 14 プレビューのフレームに おいて顔が検出された 8 プレビューのコールバック バッファのキューに対し、 止、写真撮影およびビデ あらかじめ割り当てられ オのためのエンコーディング たバッファを追加する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 494 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) を行うためにフレームを回 収する際に使用するクラ autoFocus(Camera.AutoFocusCallback 1 cb) カメラのオートフォーカス を起動し、カメラがフォ ス。 ーカスされた際に起動 また、このクラスは実際の するようにコールバック カメラハードウェアを操作 機能を登録する。 するカメラサービスのための cancelAutoFocus() 5 クライアントとなる。 進行中の全てのオート フォーカス機能をキャン セルする。 enableShutterSound(boolean enabled) 17 写真を撮った際にデフ ォルトのシャッター音を 有効にするか無効にす るか設定する。 getCameraInfo(int 9 cameraId, Camera.CameraInfo camer 特定のカメラに関する 情報を返却する。 aInfo) getNumberOfCameras() 9 デバイス上において実 際に使用可能なカメラ の台数を返却する。 getParameters() 1 カメラサービスにおける 現在の設定を返却す る。 lock() 5 他のプロセスがカメラに アクセスするのを防ぐた めにカメラに再度ロック をかける。 open(int cameraId) 9 特定のカメラハードウェ アにアクセスするための カメラオブジェクトを作 成する。 open() 1 デバイス上における背 面カメラ(一番最初に 指定されるもの)にアク セスするためのカメラオ ブジェクトを作成する。 reconnect() 8 他のプロセスがカメラを 使用した後にカメラサー ビスに再び繋げる。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 495 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) release() 1 カメラサービスを切断 し、カメラオブジェクトリ ソースを開放する。 setAutoFocusMoveCallback(Camera.A 16 utoFocusMoveCallback cb) カメラのオートフォーカス の動きを感知するコー ルバックを設定する。 setDisplayOrientation(int degrees) 8 プレビュー表示における 回転角度(時計回りを 基準に指定)を設定す る。 setErrorCallback(Camera.ErrorCallbac 1 k cb) エラーが発生した際に 発動させるコールバック を登録する。 setFaceDetectionListener(Camera.Fac 14 eDetectionListener listener) プレビューのフレームに おいて顔が検知された ことに関する通知を受 けるためのリスナを登録 する。 setOneShotPreviewCallback(Camera.P 3 reviewCallback cb) スクリーン上に次のプレ ビューのフレームを表示 することに加え、その際 に発動するコールバック を設定する。 setParameters(Camera.Parameters pa 1 rams) setPreviewCallback(Camera.PreviewC カメラサービスの設定を 変更する。 1 allback cb) スクリーン上に全てのプ レビューのフレームを表 示することに加え、その 際に発動するコールバ ックを設定する。 setPreviewCallbackWithBuffer(Camera .PreviewCallback cb) 8 全てのプレビューのフレ ーム (addCallbackBuffe r()メソッドにより提供さ れたバッファを使用)をス クリーン上に表示するこ とに加え、その際に発 動するコールバックを設 定する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 496 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) setPreviewDisplay(SurfaceHolder hold 1 er) ライブプレビューのために 使用される SurfaceHolder を設 定する。 setPreviewTexture(SurfaceTexture sur 11 faceTexture) ライブプレビューのために 使用される SurfaceTexture を 設定する。 setZoomChangeListener(Camera.OnZ 8 oomChangeListener listener) ズーム中にカメラドライ バによってズームの値が 更新された際に通知を 受けるためのリスナを登 録する。 startFaceDetection() 14 顔の検出を開始する。 startPreview() 1 キャプチャ取得とスクリ ーンへのプレビューのフ レームの描画を開始す る。 startSmoothZoom(int value) 8 指定された値でスムー ズにズームを行う。 stopFaceDetection() 14 顔の検出を停止する。 stopPreview() 1 キャプチャ取得と SurfaceView に対す るプレビューのフレーム の描画を停止し、カメラ オブジェクトが startPreview()メソッ ドを呼ぶのをリセットす る。 stopSmoothZoom() 8 滑らかなズームを停止 する。 takePicture(Camera.ShutterCallback s 1 写真を撮影する。 5 写真を撮影する(非同 hutter, Camera.PictureCallback raw, C amera.PictureCallback jpeg) takePicture(Camera.ShutterCallback s hutter, Camera.PictureCallback raw, C 期的なイメージキャプチ amera.PictureCallback postview, Cam ャをトリガとする)。 era.PictureCallback jpeg) Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 497 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) unlock() 5 他のプロセスがカメラに アクセスすることを許可 するためにカメラのロック を解除する。 clone() 1 オブジェクトのコピーを 作成して返却する。 equals(Object o) 1 インスタンスと別のイン スタンスを比較し、等し いか否かを示す。 finalize() 1 ガーベッジコレクタもは や使わないインスタンス を検出した際に実施す る。 getClass() 1 オブジェクトのクラスを表 す特定のクラスインスタ ンスを返却する。 hashCode() 1 オブジェクトのハッシュコ ード値を返却する。 notify() 1 (wait()メソッドを呼ぶ ことにより)オブジェクト のモニター上で止められ ているスレッドを起こ す。 notifyAll() 1 (wait()メソッドを呼ぶ ことにより)オブジェクト のモニター上で止められ ている全てのスレッドを 起こす。 toString() 1 このオブジェクトのコンテ ンツを読みやすいように String の形に変換し たものを返却する。 wait() 1 他のスレッドがオブジェク トの notify() メソッド または notifyAll() メ ソッドを呼び出すまで、 呼んでいるスレッドを待 機させる。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 498 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) wait(long millis, int nanos) 1 他のスレッドがオブジェク トの notify() メソッド または notifyAll() メ ソッドを呼び出すまで、 または指定したタイムア ウト時間が終了するま で呼んでいるスレッドを 待機させる。 wait(long millis) 1 他のスレッドがオブジェク トの notify() メソッド または notifyAll() メ ソッドを呼び出すまで、 または指定したタイムア ウト時間が終了するま で呼んでいるスレッドを 待機させる。 Camera.On ズームの操作中にズーム onZoomChange(int zoomValue, ZoomChan の値が変更した際のコー boolean stopped, Camera camera) geListener ルバックインターフェース。 Camera.Par ameters 8 1 カメラサービスの設定クラ 8 ズームの操作中にズー ムの値が変更した際に 呼ばれる。 flatten() 1 ス。 Parameter オブジェク トに設定された全ての パラメータを構成要素 とした 1 つの String を 作成する。 get(String key) 1 String 型のパラメータ をキーに指定した値を 取得する。 getAntibanding() 5 現在の階調の落差に 関する設定を取得す る。 getAutoExposureLock() 14 自動露出のロック状態 を取得する。 getAutoWhiteBalanceLock() 14 オートホワイトバランス のロック状態を取得す る。 getColorEffect() 5 現在の色合いの設定 を取得する。 getExposureCompensation() 8 現在の露出補正のイ ンデックスを取得する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 499 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) getExposureCompensationStep() 8 現在の露出補正のス テップを取得する。 getFlashMode() 5 現在のフラッシュモード の設定を取得する。 getFocalLength() 8 カメラのフォーカスの長さ (ミリメートル)を取得す る。 getFocusAreas() 14 現在のフォーカスエリア を取得する。 getFocusDistances(float[] output) 9 カメラからフォーカスされ た物体までの距離を取 得する。 getFocusMode() 5 現在のフォーカスモード の設定を取得する。 getHorizontalViewAngle() 8 水平的なビューのアン グルを取得する。 getInt(String key) 1 Int 型のパラメータの値 を返却する。 getJpegQuality() 5 JPEG 方式の画像に 対するクオリティの設定 を返却する。 getJpegThumbnailQuality() 5 JPEG 方式の画像にお ける Exif のサムネイル に対するクオリティの設 定を返却する。 getJpegThumbnailSize() 5 JPEG 方式の画像にお ける Exif のサムネイル の大きさを返却する。 getMaxExposureCompensation() 8 最も大きい露出補正 のインデックスを取得す る。 getMaxNumDetectedFaces() 14 サポートされている顔 認識可能な最大数を 取得する。 getMaxNumFocusAreas() 14 サポートされているフォ ーカス可能なエリアの最 大数を取得する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 500 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) getMaxNumMeteringAreas() 14 サポートされている軽 量可能なエリアの最大 するを取得する。 getMaxZoom() 8 スナップショットにおいて 許可されているズーム の最大値を取得する。 getMeteringAreas() 14 現在の計量エリアを取 得する。 getMinExposureCompensation() 8 最も小さい露出補正 のインデックスを取得す る。 getPictureFormat() 1 写真に対するイメージ フォーマットを返却す る。 getPictureSize() 1 写真の大きさの設定を 返却する。 getPreferredPreviewSizeForVideo() 11 ビデオの記録において 好ましい、もしくは推奨 されるプレビューサイズ (横幅と高さ/ピクセル 指定)を返却する。 getPreviewFormat() 1 Camera.PreviewC allback より取得した プレビューのフレームに おけるイメージフォーマッ トを返却する。 getPreviewFpsRange(int[] range) 9 現在の最大および最 小フレームレートを返 却する。 getPreviewFrameRate() 1 API レベル 9 より非推 奨です。代わりに getPreviewFpsRan ge(int[])メソッドを使 用してください。 getPreviewSize() 1 プレビュー写真の大きさ の設定を返却する。 getSceneMode() 5 現在のシーンモードの 設定を取得する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 501 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) getSupportedAntibanding() 5 サポートされている階 調の落差の値を取得 する。 getSupportedColorEffects() 5 サポートされている色 合いを取得する。 getSupportedFlashModes() 5 サポートされているフラッ シュモードを取得する。 getSupportedFocusModes() 5 サポートされているフォ ーカスモードを取得す る。 getSupportedJpegThumbnailSizes() 8 サポートされている JPEG 形式のサムネイ ルサイズを取得する。 getSupportedPictureFormats() 5 サポートされている写 真のフォーマットを取得 する。 getSupportedPictureSizes() 5 サポートされている写 真のサイズを取得す る。 getSupportedPreviewFormats() 5 サポートされているプレ ビューのフォーマットを取 得する。 getSupportedPreviewFpsRange() 9 サポートされているプレ ビューのフレームレート (フレーム/秒)を取得す る。 getSupportedPreviewFrameRates() 5 API レベル 9 より非推 奨です。代わりに getSupportedPrevi ewFpsRange()メソッ ドを使用してください。 getSupportedPreviewSizes() 5 サポートされているプレ ビューサイズを取得す る。 getSupportedSceneModes() 5 サポートされているシー ンモードを取得する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 502 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) getSupportedVideoSizes() 11 Gets the supported video frame sizes that can be used by MediaRecorder. サポートされたビデオの フレームサイズ (MediaRecorder に よって使用される)を取 得する。 getSupportedWhiteBalance() 5 サポートされているホワ イトバランスを取得す る。 getVerticalViewAngle() 8 垂直方向におけるビュ ーのアングルを取得す る。 getVideoStabilization() 15 現在のビデオの安定化 の状態を取得する。 getWhiteBalance() 5 現在のホワイトバランス の設定を取得する。 getZoom() 8 現在のズームの値を取 得する。 getZoomRatios() 8 全てのズームの値にお けるズームの割り当てを 取得する。 isAutoExposureLockSupported() 14 自動露出のロックがサ ポートされている場合、 true を返却する。 isAutoWhiteBalanceLockSupported() 14 オートホワイトバランス のロックがサポートされ ている場合、true を返 却する。 isSmoothZoomSupported() 8 滑らかなズームがサポー トされている場合、 true を返却する。 isVideoSnapshotSupported() 14 ビデオのスナップショット がサポートされている場 合、true を返却する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 503 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) isVideoStabilizationSupported() 15 ビデオの安定化がサポ ートされている場合、 true を返却する。 isZoomSupported() 8 ズームがサポートされて いる場合、true を返 却する。 remove(String key) 1 String 型のパラメータ をキーにで指定した値 を除去する。 removeGpsData() 5 パラメータから GPS の 緯度、経度、高度およ びタイムスタンプを除去 する。 set(String key, String value) 1 String 型のパラメータ をキーに値を指定す る。 set(String key, int value) 1 Int 型のパラメータをキ ーに値を指定する。 setAntibanding(String antibanding) 5 階調の落差を設定す る。 setAutoExposureLock(boolean toggle) 14 自動露出のロック状態 を設定する。 setAutoWhiteBalanceLock(boolean 14 toggle) オートホワイトバランス のロック状態を設定す る。 setColorEffect(String value) 5 現在の色合いを設定 する。 setExposureCompensation(int value) 8 露出補正のインデック スを設定する。 setFlashMode(String value) 5 フラッシュモードを設定 する。 setFocusAreas(List<Camera.Area> 14 focusAreas) setFocusMode(String value) フォーカスエリアを設定 する。 5 フォーカスモードを設定 する。 setGpsAltitude(double altitude) 5 GPS の高度を設定す る。 setGpsLatitude(double latitude) 5 GPS の緯度(座標)を 設定する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 504 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) setGpsLongitude(double longitude) 5 GPS の経度(座標)を 設定する。 setGpsProcessingMethod(String proce 8 ssing_method) setGpsTimestamp(long timestamp) GPS の進行中のメソッ ドを設定する。 5 GPS のタイムスタンプを 設定する。 setJpegQuality(int quality) 5 キャプチャ写真における JPEG 形式のクオリティ を設定する。 setJpegThumbnailQuality(int quality) 5 JPEG 形式の写真にお ける Exif のサムネイル のクオリティを設定す る。 setJpegThumbnailSize(int width, int 5 height) JPEG 形式の写真にお ける Exif のサムネイル の大きさを設定する。 setMeteringAreas(List<Camera.Area> 14 meteringAreas) setPictureFormat(int pixel_format) 計量しているエリアを設 定する。 1 写真におけるイメージフ ォーマットを設定する。 setPictureSize(int width, int height) 1 写真の大きさを設定す る。 setPreviewFormat(int pixel_format) 1 プレビュー写真における イメージフォーマットを設 定する。 setPreviewFpsRange(int min, int max) 9 最大および最小のプレ ビューのフレームレート を設定する。 setPreviewFrameRate(int fps) 1 API レベル 9 より非推 奨です。代わりに setPreviewFpsRan ge(int, int)メソッドを 使用してください。 setPreviewSize(int width, int height) 1 プレビュー写真の大きさ を設定する。 setRecordingHint(boolean hint) 14 記録モードのヒントを設 定する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 505 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) setRotation(int rotation) 5 カメラの方向に関係す るアングルの角度(時 計回りを基準に指定) を設定する。 setSceneMode(String value) 5 シーンモードを設定す る。 setVideoStabilization(boolean toggle) 15 ビデオの安定化が可能 か不可能か設定する。 setWhiteBalance(String value) 5 現在のホワイトバランス を設定する。 setZoom(int value) 8 現在のズームの値を設 定する。 unflatten(String flattened) 1 パラメータを構成要素 とした String より取り 出した各パラメータを Parameter オブジェク トに追加する。 clone() 1 オブジェクトのコピーを 作成して返却する。 equals(Object o) 1 インスタンスと別のイン スタンスを比較し、等し いか否かを示す。 finalize() 1 ガーベッジコレクタもは や使わないインスタンス を検出した際に実施す る。 getClass() 1 オブジェクトのクラスを表 す特定のクラスインスタ ンスを返却する。 hashCode() 1 オブジェクトのハッシュコ ード値を返却する。 notify() 1 (wait()メソッドを呼ぶ ことにより)オブジェクト のモニター上で止められ ているスレッドを起こ す。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 506 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) notifyAll() 1 (wait()メソッドを呼ぶ ことにより)オブジェクト のモニター上で止められ ている全てのスレッドを 起こす。 toString() 1 このオブジェクトのコンテ ンツを読みやすいように String の形に変換し たものを返却する。 wait() 1 他のスレッドがオブジェク トの notify() メソッド または notifyAll() メ ソッドを呼び出すまで、 呼んでいるスレッドを待 機させる。 wait(long millis, int nanos) 1 他のスレッドがオブジェク トの notify() メソッド または notifyAll() メ ソッドを呼び出すまで、 または指定したタイムア ウト時間が終了するま で呼んでいるスレッドを 待機させる。 wait(long millis) 1 他のスレッドがオブジェク トの notify() メソッド または notifyAll() メ ソッドを呼び出すまで、 または指定したタイムア ウト時間が終了するま で呼んでいるスレッドを 待機させる。 Camera.Pic 1 写真のキャプチャからイメ onPictureTaken(byte[] data, Camera tureCallbac ージデータを提供するため camera) k に使用するコールバックイ 1 写真を撮った後にイメ ージデータが利用可能 なときに呼ばれる。 ンターフェース。 Camera.Pre 1 プレビューのフレームのコピ onPreviewFrame(byte[] data, Camera viewCallbac ーを表示する際にそれらを camera) k 運ぶために使用するコー ルバックインターフェース。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 1 プレビューのフレームが 表示される時にに呼ば れる。 第 9 章 補足情報 | 507 9-2. 「3-3.カメラを使用したアプリケーション開発における注意点」(補足:API 情報) Camera.Sh 1 イメージキャプチャが実際 onShutter() 1 写真がセンサからキャプ utterCallba にとれた瞬間に合図を送 チャとして取れた際、で ck るために使用するコールバ きる限りその瞬間に呼 ックインターフェース。 ばれる Camera.Siz 1 e イメージサイズのクラス(横 Camera.Size(int w, int h) 1 幅と高さ)。 コンストラクタ(写真の 大きさに関するセット情 報)。 equals(Object obj) 1 サイズでオブジェクトを 比較する。 hashCode() 1 オブジェクトのハッシュコ ード値を返却する。 CameraProf ile 8 事前定義されたカメラアプ getJpegEncodingQualityParameter(int リケーションの静止画キャ quality) 8 カメラアプリケーションに おける背面カメラのクオ プチャ(JPEG)の品質レベ リティレベルに対応す ル(0~100)を品質設 る、あらかじめ定義され 定ごとに取得する。 ているイメージキャプチ ャのクオリティレベルを返 却する。 背面カメラがない場合 は 0 を返却する。 getJpegEncodingQualityParameter(int cameraId, int quality) 8 カメラアプリケーションに おける指定されたカメラ のクオリティレベルに対 応する、あらかじめ定 義されているイメージキ ャプチャのクオリティレベ ルを返却する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 508 9-3. 「4-2.常時電源 ON を実装する」(補足:API 情報) 9-3. 「4-2.常時電源 ON を実装する」(補足:API 情報) クラス名 API クラス概要 メソッド レベル Window 1 API メソッド概要 レベル ウィンドウ addFlags(int flags) 1 ウィンドウに指定されたフラグを有効にする。 の外観と FLAG_KEEP_SCREEN_ON 1 常時スクリーン ON に関連するフラグ。 動作を制 このフラグがセットされているウィンドウが前面にいる 御するクラ 限り、スクリーンを ON にする。 ス clearFlags(int flags) 1 ウィンドウに指定されたフラグを無効にする。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 509 9-4. 参考:Google の推奨する「Performance Tips」 9-4. 参考:Google の推奨する「Performance Tips」 Google は、Android アプリケーションのパフォーマンスを高めるための Tips を公開しています。その内容の参考 和訳を下記に示します。 【公式サイト】 http://developer.android.com/training/articles/perf-tips.html 観点 注意事項 概要 詳細 具体例 通常 短命なオブジェクトを ローカル変数をなるべく使用しない 文字列を返すメソッドがあり、その結果は常に StringBuilder や 時 使用しない StringBuffer に追加する処理である場合、その関数の結果を ローカル変数に格納してから追加するのではなく、直接追加す る。 入力データのセットから文字列を抽出する際、コピーを作成する のではなく、オリジナルデータの subString を返却するようにす る。 多次元配列を一次元配列に分解す (Foo , Bar) オブジェクトの要素を保持するオブジェクトを実装 る する必要がある場合、効率よくするため(可能であれば※) Foo[] と Bar[] の二つの配列に分解するようにする。 (※)…他のコードから(Foo , Bar) オブジェクトの要素を保持す るオブジェクトにアクセスする必要がある場合は、その限りでない。 static を優先する static メソッドにすることが可能なもの 呼び出しスピードを上げるため(15→20%)、オブジェクトのフィー は static メソッドにする ルドにアクセスする必要がない場合は、そのメソッドを static にす る。 内部の Getter や 可能な限り、フィールドアクセスを行う public なフィールドを用意し、内外問わず、値を変更および参 Setter は避ける (※) 照する際は直接フィールドアクセスする。 (※)…Android ではフィールド呼び出 private なフィールドを用意し、値を変更および参照する際は下 しより、メソッド呼び出しの方が負担が 記のようにする。 大きい ・内部で呼び出す場合 →直接フィールドアクセスする ・外部から呼び出す場合 →Getter および Setter を使ってアクセスする 定数には static final - - 拡張 For ループ構文 Iterable インターフェースを実装したコ Foo[]配列の mArray に対し for 文を使用する場合は、拡張 を使用する レクションや配列に対する for 文の使 for 文「for (Foo a : mArray)」を使用する。 を使用する 用方法に気をつける Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 510 9-4. 参考:Google の推奨する「Performance Tips」 プライベートのインナー ※詳細は下記 URL より「Consider Package Instead of クラスを使ったプライベ Private Access with Private Inner Classes」参照 ートアクセスの代わりに http://developer.android.com/training/articles/perf パッケージアクセスを行 -tips.html う よく考えて浮動小数点 - 効率化のため、int 型で代用できる部分は、int 型を使用する。 - short 型で事足りる場合、int 型ではなく short 型を使用する。 ライブラリを理解して使 自作のメソッドを使用するより、ライブラ - 用する リを使用するようにする を使用する 処理要件を満たせる 最小の型を使用する JIT コンパイラ(Android 搭載)を使用 Service を適切に使 左記ライブラリ例は下記のとおり。 した場合、高速化するライブラリを使 ・String.indexOf メソッドおよびその仲間 用するようにする ・System.arraycopy メソッド - 用する AlarmManager を使用し、IntentService が定期的に呼び 出されるようにする(※) (※)の目的: ・ポーリング通信使用時 →サーバへ定期的にアクセスしに行く際に Service を必要とす るため。 ・Push 通信使用時(GCM 使用時) →GCM から通知を受け取る際に Service(GCMIntentService)を必要とするため。 処理上、GCMIntentService が死んでしまっていると、通 知を受け取ることができない バッテリ消費量に注意 アプリケーション起動中はバッテリ状態 する を常に見張る バッテリ消費量の変動に応じた処理を行う。 例: ・バッテリ残量が少なくなったとき →バッテリ消費量が多い処理(ネットワーク接続など)を行わない ようにしたり、実行頻度を下げる アプリケーション内で扱 うデータ量を少なくする 不要なデータを使用しない どこからも参照されないフィールドおよびメソッドを用意しない。 ※使用するかどうか検討中である場合はコメントアウトしておく。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 511 9-4. 参考:Google の推奨する「Performance Tips」 特定フィールドおよびメソッドについて似たような役割を持つフィー ルドおよびメソッドは存在しないか確認し、可能であれば統合さ せることを検討する。 例: class Person(){ int age; // 年齢 boolean isChild; //子供であるか否か →age を確認し、 「子供であるか否か」を検討すればよいため、不要 … } (サーバへのリクエスト送信時)不要なデータを送信しない。 (サーバからのレスポンス返却時)不要なデータを受け取らない。 ※サーバから不要なデータが返却される場合は、そのデータを無 視し、内部で処理しないようにする。 低消費電力になるよう 頻繁にネットワーク接続しない。 意識する ※アプリケーション上でネットワーク接続を使用する場面を考慮し た適切な接続頻度に合わせる。 ローミング時はネットワーク接続しない。 サーバからのレスポンスは圧縮形式で受け取る。 なるべくバックグラウンド常時動作状態にしない。 ※端末の状態に合わせてバックグラウンド処理の起動および停止 処理を適切に行う。 例: ・バッテリ残量が少なくなったとき →バックグラウンド処理を停止するか、あるいはユーザに停止す るか否か問う 3D やアニメーションを過度に挿入しない。 使用する画像リソースファイルはなるべく PNG ではなく、JPEG(圧 縮画像形式)を使う。 不要な処理を行わない BroadcastReceiver や Listener など、不要になった場合はす ぐ設定内容の登録解除およびキャンセル等を行う。 AlarmManager を適切に使用する いたずらにスリープ状態を開場することを避けるため、 AlarmManager を使用する際は、アラームの種類に「スリープ 状態を開場するもの」を指定しない。 ※ただし、使用場面による。例えば目覚まし時計アプリであれば、 上記のようなアラームを指定する必要がある。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 512 9-4. 参考:Google の推奨する「Performance Tips」 AlarmManager のスケジュールは「時刻」ではなく AlarmManager クラスで定数化されている「間隔」で指定す る。 ※複数のアプリケーションで普遍的に使用されているスケジュール に合わせることで、 一度の AlarmManager 起動で、自身のアプリケーションの処 理を含む複数処理に対応することが可能になる。 位置情報を適切に取得する 頻繁に位置情報を更新しない。 ※アプリケーション上で位置情報を使用する場面を考慮した適切 な更新頻度に合わせる。 位置情報の使用目的に合わせて、位置情報を取得する際に使 用するプロバイダ(※1)を変更する。 (※1)…位置情報を取得する際に GPS を利用するか、 Google Maps などのサービスを使用するか 選択する際に使用する。プロバイダの種類によって、位置情報の 精度や動作環境などが異なる。 例: ・非常に正確な位置情報が必要な場合 →GPS プロバイダを利用する ・大体の位置が掴めればいい場合 →ネットワークプロバイダを利用する ・頻繁に位置情報を更新する必要がある場合 →パッシブプロバイダ(※2)を利用する (※2)…他のアプリケーションにて取得した位置情報を流用する プロバイダ。 端末の状態に合わせて、位置情報を取得する際に使用するプロ バイダを変更する。 例: ・屋内に移動したとき →GPS プロバイダからネットワークプロバイダに切り替える (位置情報を定期的に取得するよう設定されている場合) 位置情報を取得する必要が無くなれば、直ちに位置情報を定 期的に取得しないように処理する。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 第 9 章 補足情報 | 513 9-4. 参考:Google の推奨する「Performance Tips」 WakeLock を適切に使用する アプリケーションの状態に合わせて WakeLock の取得および開 (常時スクリーン ON 状態に対する制 放を行う 御を適切に行う) 例: ・ビデオ再生時は WakeLock を取得し、一時停止になったとき は WakeLock を一度開放する WakeLock の開放が必ず行われるよう、WakeLock を取得す る際に、タイムアウト時間(※)を設定する (※)…時間になると、必ず wakeLock の開放を行う。例えばビ デオ再生に WakeLock を使用する場合、タイムアウト時間に「ビ デオ再生時間」を指定する。 ネット 適切なコネクションの アプリケーション起動中は接続状態を ワーク 管理を行う/不安定な 常に見張る 使用 ネットワーク状態を考 時 慮する 下記のように、ネットワーク接続状態の変動に応じた処理を行う ・ネットワーク未接続状態に変更した場合、ネットワーク接続処 理を行わないようにする。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved. 法人向け Android アプリケーション開発実践手引書 ~ トラブルの未然防止とお客様満足度向上に向けて ~ Ver.1.0 2014 年 10 月 21 日 Ver.1.0 発行 発行者 法人事業部 ソリューションビジネス部 株式会社 NTT ドコモは、本書の記述が正確なものとなるように最大限努めましたが、本書に含まれるすべての情報が完全に正確であると 保証することはできません。また、本書の内容に起因する直接的および間接的な損害に対して一切の責任を負いません。 本書は著作権上の保護を受けています。本書の一部あるいは全部(ソフトウェアおよびプログラムを含む)について、株式会社 NTT ドコモか ら文書による許諾を得ずに、いかなる方法においても無断で複写・複製することは、法律により固く禁じられています。 Copyright ©2014 NTT DOCOMO, INC. All Rights Reserved.
© Copyright 2024 Paperzz