スマートフォンやタブレットをはじめとするモバイル端末向けのアプリケーションを開発する場合にまず問題になるのが、プラットフォームごとに異なるプログラミング言語を用いなければならないということである。開発者は、複数のプラットフォームに対してそれぞれ個別にアプリケーションを開発しなければならない。そのための技術の習得にかかる学習コストも馬鹿にはできない。そこで注目されているのが、、単一のコードで開発を行い、それをベースとして各プラットフォームで実行できる形式に変換するというアプローチだ。今回は、そのようなアプローチを実現するためのフレームワークである「PhoneGap」の使い方を紹介する。

そもそもPhoneGapとは

PhoneGapは、HTMLやCSS、JavaScriptといったWeb技術によってネイティブのモバイルアプリを開発することができるオープンソースのフレームワークであり、米Adobe Systems社(以下、Adobe)によって提供されている。オープンソースのプロジェクト名は「Apache Cordova」。AdobeではCordvaベースのディストリビューションのひとつとして「PhoneGap」の提供を行っている。

モバイルプラットフォーム向けのアプリを開発する場合、通常はプラットフォームごとに異なるプラグラミング言語を用いなければならない。それに対してPhoneGapを利用すれば、使い慣れたWeb標準技術によってネイティブで動作するアプリを開発することが可能となる。作成されたアプリは各プラットフォームのWebレンダリング機能(WebView)を利用して実行されるため、開発者はWebアプリを開発するのとほぼ同様の手順で、スマートフォン用のアプリを作ることができるのだ。

このようなフレームワークを使用する場合の不安要素としては、カメラやストレージなどといったプラットフォーム固有の機能が利用できるかということが挙げられる。PhoneGapの場合には、プラットフォームごとに用意されたJavaScriptライブラリによってネイティブAPIにアクセスすることができるため、その点は心配がいらない。対応プラットフォームの多彩さもPhoneGapの魅力であり、このページにはサポートするプラットフォームおよびそれぞれの固有機能がまとめられている。iOSやAndroidをはじめとして、BlackBerry、WebOS、Windows Phoneなど、主要なモバイルOSが網羅されていることがわかる。

では、早速PhoneGapを使ってみよう。

PhoneGapのダウンロードとAndroidアプリの作成

PhoneGapは公式サイトのダウンロードページよりダウンロードすることができる。配布ファイルはzip形式になっており、解凍するとlibディレクトリ以下に各プラットフォーム向けのライブラリが格納されている。本稿では、Android向けのアプリ開発を例にとってPhoneGapの使い方を説明する。

PhoneGapを使ってモバイルアプリを開発するには、PhoneGap本体とは別に各プラットフォーム向けの開発ツールが必要となる。Androidの場合にはAndroid SDKが必要。統合開発環境としてはEclipse(3.4以降)と、Eclipse用のプラグインであるAndroid Development Tools(ADT)を使用する。

環境が用意できたら、Eclipseを起動してプロジェクトを作成しよう。プロジェクトエクスプローラーから[新規]-[Android Project]を選択し、図2.1のようにアプリ名やプロジェクト名、ターゲットにするプラットフォームを選択する。

図2.1 プロジェクトの作成

[次へ]をクリックして、アイコン画像を選択する(図2.2)。試すだけであればデフォルトのままでもよい。

図2.2 アプリアイコンの選択

次の画面(図2.3)では、主となるアクティビティを作成できる。[Create Activity]にチェックを入れ、テンプレートの種類を選択する(本稿の例ではBlankActivityを選択)。続いて、アクティビティ名などの設定を記入する(図2.4)。すべて完了したら[完了]ボタンをクリックすればプロジェクトが作成されるはずだ。

図2.3 アクティビティの作成

図2.4 メインのアクティビティの設定

図2.5 プロジェクト作成直後の画面例

PhoneGapを使うためには、必要なファイルをプロジェクト内にコピーしておく必要がある。まず、/libsフォルダに「cordova-1.9.0.jar」を、/assetsフォルダに「www」という名前のフォルダを作成し、そこに「cordova-1.9.0.js」をコピーする。また、/resフォルダに「xmlフォルダ」をフォルダごとコピーする。この段階で、プロジェクト内のフォルダ構成は図2.6のような形になっているはずだ。

図2.6 PhoneGapの関連ファイルをプロジェクトにコピー

続いて、/libsフォルダを右クリックして[ビルド・パス]-[ビルド・パスの構成]を選択し、図2.7のように[ライブラリー]タブに「cordova-1.9.0.jar」が追加されていることを確認する。もし追加されていない場合には、[追加]ボタンをクリックして手動で追加しておく必要がある。

図2.7 ビルド・パスの構成を確認

以上でライブラリの準備は完了だ。次に、自動生成されたJavaのソースコードをPhoneGap用に編集する必要がある。/srcフォルダにあるアクティビティ用のjavaファイル(ファイル名はプロジェクト作成時に指定したもの)を開き、中身を次のように修正する。

package jp.co.mynavi.sample.phonegapsample;

import android.os.Bundle;
import android.view.Menu;
import org.apache.cordova.*;

public class PhoneGapSampleActivity extends DroidGap {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_phone_gap_sample);
        super.loadUrl("file:///android_asset/www/index.html");
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_phone_gap_sample, menu);
        return true;
    }

    
}

修正内容は以下の通り。

・「org.apache.cordova.*」パッケージをインポート
・extendsするクラスをActivityから「DroidGap」に変更
・setContentView()メソッドの呼び出し部分を、「super.loadUrl("file:///android_asset/www/index.html"」に変更

続いて、AndroidManifext.xmlを開いて、中身を次のように修正する。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.mynavi.sample.phonegapsample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="9"
        android:targetSdkVersion="15" />

<!-- ここから -->
        <supports-screens 
        android:largeScreens="true" 
        android:normalScreens="true" 
        android:smallScreens="true" 
        android:resizeable="true" 
        android:anyDensity="true" />
        <uses-permission android:name="android.permission.VIBRATE" />
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.RECEIVE_SMS" />
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
        <uses-permission android:name="android.permission.READ_CONTACTS" />
        <uses-permission android:name="android.permission.WRITE_CONTACTS" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 
        <uses-permission android:name="android.permission.GET_ACCOUNTS" />
        <uses-permission android:name="android.permission.BROADCAST_STICKY" />
<!-- ここまで追加 -->

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".PhoneGapSampleActivity"
            android:label="@string/title_activity_phone_gap_sample" 
            android:configChanges="orientation|keyboardHidden" > <!-- この属性を追加 -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

修正内容は以下の通り。なお、この例ではデバイスの全ての機能に対するパーミッションを要求するようになっているが、本番用に開発する際には使用する機能の設定を追加するだけでよい。

・<uses-sdk>タグと<application>タグの間にパーミッションの設定を追加
・<activity>タグにandroid:configChanges属性の設定を追加

以上でソースの修正も完了だ。上記の通りに設定した場合、アプリの起動時には/assets/wwwフォルダのindex.htmlの内容が呼び出されるようになっている。このindex.htmlは、通常のWebページとまったく同じように作ればいい。特別なのは、<script>タグで「cordova-1.9.0.js」を読み込む必要があるという点だ。試しに、次のようなコードでindex.htmlを作ってみよう。

<!DOCTYPE html>

<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
  <title>PhoneGapのサンプル</title>
  <script type="text/javascript" charset="utf-8" src="cordova-1.9.0.js"></script>
</head>

<body>
  <h1>Hello PhoneGap!</h1>
</body>

</html>

実行するには、プロジェクト名を右クリックして、[実行]-[Androidアプリケーション]を選択する。ネイティブアプリの生成に成功すれば、図2.11のようにデプロイするデバイスの選択画面が表示される(ただし、デバイスが1つのみの場合には自動でそのデバイスが選択される)。この例は、エミュレータ用のADV(Android Virtual Device)と、USB接続された実機が表示されている状態である。ここでAVDを選択すると、Androidエミュレータが起動してアプリがデプロイされ、図2.12のようにindex.htmlの内容が表示されることを確認できる。なお、AVDが用意されていない場合には[Launch new Android Virtual Device]にチェックを入れ、右下の[マネージャー]ボタンからAVD Managerを起動して作成すればよい。

図2.11 実行するデバイスの選択

図2.12 Androidエミュレータ上で実行した例

実機上で実行するには、端末のUSBデバッグモードをオンに設定([設定]-[アプリケーション]-[開発]メニューから設定可能)した状態でUSB接続して、図2.11の画面で実機を選択すればよい。すると実機にデプロイされて、図2.13のようにネイティブのアプリとして実行される。

図2.13 Androidの実機上で実行した例

カメラAPIを利用する

Webアプリをネイティブアプリ化するだけであれば、上記の手順の延長でHTMLやCSS、JavaScriptを使って通常通りにWebアプリを作成し、それをネイティブアプリ化すればよい。カメラや各種センサーなどといったネイティブの機能を使いたい場合には、PhoneGapに用意されたAPIを利用する。これらのAPIはJavaScriptのコードで利用することができる。どのプラットフォームに対しても共通のAPIが提供されているため、開発者はプラットフォームごとの差異を気にする必要がない(ただし、プラットフォームによっては一部サポートされていない機能もある)。

ここでは、カメラ機能を利用して写真を撮影してみよう。カメラ機能を利用するには、まずカメラにアクセスするためのパーミッションを取得する必要がある。本稿の例では、その設定はすでAndroidManifest.xmlに記載している。実際に写真の撮影を行うには、navigator.camera.getPicture()メソッドを利用する。このメソッドは、第一引数として撮影成功時のコールバック関数を、第2引数として撮影失敗時のコールバック関数を、第3引数としてカメラの設定を指定して実行する。ただし、第3引数は省略することもできる。

具体的には、次のようにして利用すればよい。

// 写真の撮影
function capture() {
    navigator.camera.getPicture(cameraSuccess, cameraError, { 
        quality: 50,
        destinationType: navigator.camera.DestinationType.DATA_URL
    });
}
// 撮影に成功
function cameraSuccess(imageData) {
    document.getElementById("status").innerHTML = "撮影成功";
    var image = document.getElementById("photo");    // id:"photo"のimgタグの要素を取得
    image.src = "data:image/jpeg;base64," + imageData;    // 画像データの埋め込み
}
// 撮影に失敗
function cameraError(message) {
    document.getElementById("status").innerHTML = "撮影失敗:" + message;
}

このコード例では、capture()関数が呼び出されると、まずnavigator.camera.getPicture()が実行されるgetPicture()のオプションとして指定しているqualityプロパティは写真の精度を、destinationTypeプロパティは成功時のコールバック関数に渡すデータ形式を表している。destinationTypeにCamera.DestinationType.DATA_URLを指定した場合には画像データをbase64エンコーディングした文字列が、Camera.DestinationType.FILE_URIを指定した場合には画像ファイルを保存したURIが渡されることになる

Androidの場合はこの段階でインテント機能によってカメラアプリが起動して撮影が行えるようになるはず。撮影が完了すると、ふたたびPhoneGapSampleに処理が戻ってきて、コースバック関数のcameraSuccess()が呼び出される。この例の場合にはdestinationTypeがDATA_URLなので、cameraSuccess()にはbase64形式の画像データが渡されているはず。<img>タグのsrc属性にはbase64形式のデータを埋め込む方法が用意されているので、ここではその方法を利用して撮影した画像を表示している。

上記を踏まえて、ボタンを押したら写真を撮影し、その画像をページ内に表示するようなindex.htmlの例を以下に示す。

<!DOCTYPE html>

<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
  <title>PhoneGapのサンプル</title>
  <script type="text/javascript" charset="utf-8" src="cordova-1.9.0.js"></script>
  
  <script type="text/javascript" charset="utf-8">
    var pictureSource;
    var destinationType;

    // デバイスの準備完了
    function onLoad() {
        document.addEventListener("deviceready", onDeviceReady, false);
    }
    function onDeviceReady() {
        pictureSource = navigator.camera.PictureSourceType;
        destinationType = navigator.camera.DestinationType;
    }

    // 写真の撮影
    function capture() {
        navigator.camera.getPicture(cameraSuccess, cameraError, { 
            quality: 50,
            destinationType: destinationType.DATA_URL
        });
    }
    // 撮影に成功
    function cameraSuccess(imageData) {
        document.getElementById("status").innerHTML = "撮影成功";
        var image = document.getElementById("photo");
        image.src = "data:image/jpeg;base64," + imageData;
    }
    // 撮影に失敗
    function cameraError(message) {
        document.getElementById("status").innerHTML = "撮影失敗:" + message;
    }
  </script>
</head>

<body onload="onLoad()">
  <h1>カメラAPIの利用例</h1>
        
  <form>
    <input type="button" value="撮影" onClick="capture()" />
  </form>
        
  <div id="status"></div>
  <img src="" id="photo" width="100%" /> 
</body>

</html>

これをAndroidデバイス上で実行すると、最初は図3.3の画面が表示される。ここで[撮影]ボタンを押すとカメラが起動し、撮影が完了すると図3.4のように撮影した写真が表示される。

図3.3 カメラAPIを利用したアプリの実行例

図3.4 撮影した写真のデータを取得して表示する

このように、デバイスネイティブの機能を使う場合でも、必要となるのはあくまでもHTMLやJavaScriptの知識だけだ。カメラ以外の機能に対するAPIについては、このページを参照していただきたい。

おわりに

PhoneGapは、HTMLやJavaScriptベースのWebアプリ開発の経験があれば、そのノウハウを生かすことができるという強みがあります。また、既存のWebアプリをベースに、モバイル端末向けのネイティブアプリを作るというアプローチを取ることもできます。既存の技術や資産を生かしながらモバイル端末向けの展開を進めたいという開発者やサービス提供者にとって、PhoneGapは大きな武器になるでしょう。