0

PhoneGap/CordovaのWebViewの拡張方法(調査)

Android版PhoneGap(Cordova)で新しいwebブラウザのプロトタイプ的なものを作る方法を調べた。
既存のwebサイトを表示して、その上に自作のUIを表示したりしてページを操作できるようなのを作る。
例えばbubble cursorを常時使用し続けるAndroid用webブラウザ等が作れる。

っぽい方法をわりと真面目に調べた。まだ試していない。


手順

PhoneGapプラグインを作ると、Java側でonNativeReadyイベントを受け取れる。
そのタイミングでスクリプトタグをHTMLの末尾に挿入すれば、既存のwebサイト上にcordova.jsを読み込ませたり、自分のスクリプトを実行したりできる。
(試していないが多分できると思う)


既存サイトにスクリプトタグを挿入する


スクリプト挿入するbookmarkletを作る。

これを
(function(){
var load_script = function(url){
var script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
};
load_script('//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js');
})();


圧縮してbookmarklet形式にする
javascript:(function(){var e=function(e){var t=document.createElement("script");t.src=e,document.body.appendChild(t)};e("//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js")})();
nodeのuglify-jsを使った


試しにjQueryが読み込まれていないサイトで実行してみると、jQueryが使えるようになる


webView上でスクリプト実行


MainActivity.java のsuper.loadUrl();でindex.htmlを読み込んでいる。
その後で
super.loadurl("javascript:alert('hello!!!')");
を書くと、ページを読んだ時に実行できる。


前述のbookmarkletをloadUrlすればいい。


しかし、alertと違って前述のscriptタグ挿入bookmarkletは、HTMLがロード完了してDOMが構築し終わってからでないと動かなかった(当たり前だが)
また、MainActivity.javaに書いても最初の1ページ目でしかスクリプトが挿入できないので、ページ遷移する都度scriptタグ挿入してやる必要がある。


とりあえず後輩のうすきくんと一緒に検証してみた。

MainActivityの中で
super.loadUrl("http://www.yahoo.co.jp");
final MainActivity self = this;
new Thread(){
public void run(){
try{
Thread.sleep(10000);
self.loadUrl("javascript:(function(){var e=function(e){var t=document.createElement('script');t.src=e,document.body.appendChild(t)};e('http://shokai.org/alert.js')})();");
}
catch(Exception ex){
Log.e(ex);
}
}
}.start();
(このコード、思い出して書いたので多少間違ってるかも)


なんと動いた。
ページ遷移完了後にloadUrlでbookmarklet実行したいんだけど、イベントが取得できないので10秒待ってからスクリプトタグを挿入した。
まずはPhoneGapのwebview上に後からスクリプトタグ挿入できるかの検証なので、alert.jsの中身はalert(“script insert success!!!”);しか書かないようにした。


あとは、10秒待つのではなくページ遷移毎にスクリプトタグ挿入できれば目標達成できる。


JavaScriptファイルはfile://の場所で指定してもロードしてくれなかったが、HTTPでアクセスできる場所に置いたらロードしてくれた。gistやgithubとかに置いてもいいと思う。


ページ遷移イベント


PhoneGapプラグインを作れば、外部サイトをロードした場合でもページ遷移完了イベントを受け取れる。


WebViewの画面遷移やロード完了イベントは、WebViewClientを設定すればイベントを受けられるのだが、PhoneGap(Cordova)本体が先にWebViewClientを設定してしまっている。
つまり先客がいるわけで、自作のWebViewClientをPhoneGapに食わせる事はできない。


とりあえずCordovaのソースコードを読んでみる
https://github.com/apache/incubator-cordova-android


framework/src/org/apache/cordova/CordovaWebViewClient.java がWebViewClientを継承したクラスで、コイツがPhoneGapのWebViewのイベントを全て受信している。
これを書きなおして自分でcordova.jarをビルドしてもいいのだが、本体を修正すると更新についていけなくなるので避けたい。


さらに辿ってみると、 framework/src/org/apache/cordova/CordovaWebViewClient.java からpostMessage関数で”onNativeReady”というイベントが送られている。
onNativeReadyは framework/assets/js/cordova.android.js の中から呼ばれている。
PhoneGapでよく使われるJS側のdevicereadyイベントの直後に呼び出される。

んで framework/src/org/apache/cordova/api/PluginManager.java から全プラグインにpostMessageの内容が中継されているので、

phonegap pluginを作って、onMessageで”onNativeReady”イベントを受け取れば、ページが読み込み終わった事がJava側でわかるはず。


そのタイミングでloadUrlにbookmarklet形式でスクリプトタグ挿入させればいいのではないか?

という所まで調べたのでうすきくん頑張ってください・・


PhoneGap pluginの作成


このへんが参考になると思われる。

Apache Cordova API Documentation Plugin Development Guide
橋本商会 » PhoneGap NFC Plugin作った

プラグインの解説はJavaのコードをJavaScriptから利用するためのインタフェースを作る部分が大半なのだが、
今回はPluginManagerが中継しているonNativeReadyイベントを受け取ってブックマークレット実行するだけなので、ほとんどの部分を読み飛ばして良いと思うしJSも書く必要ないと思う。

0

PhoneGap NFC Plugin作った

PhoneGapでAndroidのNFCを使えるpluginを作った。



JavaScriptから簡単にNFCタグのIDが読める。
<script src='./phonegap.nfc.js' type='text/javascript' />
document.addEventListener('deviceready', function(){
window.plugins.nfc.id(
function(tag){
var id = tag.id; // get Tag ID;
},
function(e){
log(e);
});
}, true);


インストール方法はgithubに書いた。サンプルプロジェクトもある。
普通のPhoneGap pluginと同じく、phonegap.nfc.jsとNfcPlugin.javaを所定の場所に置くのだけど、NFCを使うためにguava librariesとAndroidManifest.xmlの修正も必要。

とりあえず、NFCでタグのIDを読むだけしか機能は無い。もちろん他にも色々データは取れるんだけど、どこまでwrapしたらいいのかよくわからないのと俺がIDしか使う予定が無いので、とりあえずIDだけにしておいた。(要望あれば追加する)




PhoneGap pluginの作り方は、phonegap / PhoneGap Pluginsが参考になった。JavaScriptからJavaのコードを呼びだす方法が書かれている。


既に公開されているプラグインがいくつかあって、それも参考になった。


1

PhoneGapでAndroidアプリを作る

増井さんがPhoneGapを使って5分で作るAndroidアプリケーションというビデオを2時間ぐらいかけて撮影していたので、俺も触発されてPhoneGapやってみた。
PhoneGapはHTML/CSS/JavaScriptでAndroidやiPhoneアプリを作れる環境。アプリ起動時にブラウザコンポーネントを読み込んで、そこにHTMLで画面を作るしくみになってる。


特にコンテンツとして面白いところもないサンプルアプリケーションっぽいのができた。バスの中で。
せっかくAndroidのネイティブアプリ相当が作れるのだから、加速度センサーを秒間30回取得して画面を描画しつつ、本体のバイブレーションとビープを鳴らしたりした。



ソースはgithubに全部置いておいた
https://github.com/shokai/droidgap-test-app


さて、PhoneGapでAndroidアプリを作る方法をメモしておく。


■Androidの開発環境を作る
まず、普通にEclipseでJavaとXMLで書かれたAndroidプロジェクトをコンパイルできる環境を整える。
android sdkインストール実機で実行するに書いた。
Javaは書かなくていいので、最初に新規プロジェクト作成したままをコンパイルして、自分のAndroid端末(もしくはエミュレータ)に送って実行できる事を確かめておく。



■PhoneGap/DroidGapでの開発環境を作る
droidgapでandroidアプリ開発にくわしく書いた。
PhoneGap本家サイトからPhoneGap一式をダウンロードすると、iPhoneとかBlackberryとかpalm用の環境も手に入るけど、DroidGapが入ってない。
DroidGapはAndroid Development Toolと連携してプロジェクトを自動生成してPhoneGapでの開発に必要なファイルも全て配置してくれる神ツールなので、DroidGapを使うべき。DroidGap自体もrubyで書かれているので不具合があってもなんとかしやすい。

https://github.com/phonegap/phonegapではなくhttps://github.com/phonegap/phonegap-androidの方を使う。



■アプリを作る

droidgap gen アプリ名
してテンプレートを作って、
ant install
でビルドしてインストールできる。アプリの実行は端末から手動でする。


最近はhamlが気に入っているので、index.hamlを書く
!!! XML
%html
%head
%script{:type => 'text/javascript', :src => "./phonegap.js"}
%script{:type => 'text/javascript', :src => "./jquery.js"}
%script{:type => 'text/javascript', :src => "./jquery.color.ver2.js"}
%script{:type => 'text/javascript', :src => "./main.js"}
%body
%h1 droidgap test app
%h2 accelerometer:
%div#acc_vars
%div#x
%div#y
%div#z
%div#color
%h2 logs
%div#log

で、hamlからhtmlに変換する
gem install haml
haml index.haml index.html


中のコンテンツはjQueryで動的に作るので、idの振られたdivがあるだけでhtmlはシンプル。
index.html
<?xml version='1.0' encoding='utf-8' ?>
<html>
<head>
<script src='./phonegap.js' type='text/javascript'></script>
<script src='./jquery.js' type='text/javascript'></script>
<script src='./jquery.color.ver2.js' type='text/javascript'></script>
<script src='./main.js' type='text/javascript'></script>
</head>
<body>
<h1>droidgap test app</h1>
<h2>accelerometer:</h2>
<div id='acc_vars'>
<div id='x'></div>
<div id='y'></div>
<div id='z'></div>
</div>
<div id='color'></div>
<h2>logs</h2>
<div id='log'></div>
</body>
</html>


jQueryは自分でダウンロードしてきて同じディレクトリに置いておく。
phonegap.jsはdroidgap gemした時に自動配置してくれてる。
で、main.jsを書く。
var acc_stat = false;
var acc_watch;
var acc_max = 0;

document.addEventListener("deviceready", function(){
navigator.notification.beep(1);
navigator.notification.vibrate(0);
accel(true);
}, true);

$(function(){
log('start');
$('div#color').css('width','100%').css('height',400).css('background-color', '#000000');
$('div#color').click(function(){
navigator.notification.beep(1);
navigator.notification.vibrate(0);
});
});

function log(message){
$('div#log').prepend($('<p>').html(message));
};

function accel(start_stop){
log("accel "+start_stop);
if(start_stop == acc_stat) return;
acc_stat = start_stop;
if(start_stop){
var opts = new Object();
opts.frequency = 30; // 30ミリ秒
acc_watch = navigator.accelerometer.watchAcceleration(
display_acc, function(e){
acc_stat = false;
navigator.accelerometer.clearWatch(acc_watch);
log("accelerometer error "+e.name+":"+e.message);
}, opts);
}
else{
navigation.accelerometer.clearWatch(acc_watch);
display_acc(new Object());
}
};

function display_acc(acc){
$('div#acc_vars div#x').html('x : '+acc.x);
$('div#acc_vars div#y').html('y : '+acc.y);
$('div#acc_vars div#z').html('z : '+acc.z);
var r = Math.floor(acc.x*60);
if(r < 0) r *= -1;
if(r > 255) r = 255;
var g = Math.floor(acc.y*60);
if(g < 0) g *= -1;
if(g > 255) g = 255;
var b = Math.floor(acc.z*60);
if(b < 0) b *= -1;
if(b > 255) b = 255;
$('div#color').css('background-color',$.parseColorCode([r,g,b]));
};
30ミリ秒毎に加速度センサーの値を取得して、毎回HTMLを書きなおしているけど特に問題ない。
あと、ハードウェアの機能が使えるようになるタイミングはdocument.onloadではなく、devicereadyイベントの後になる。
ユーザが画面に触れて動かす系のイベントは、ふつうに$(‘dom’).click()とかで登録して良いだろうけど、起動時に加速度センサーをonにする等の処理はdevicereadyイベントに登録する。

色を書き換えるのに、jQuery Color プラグインを拡張:humming birdを使わせてもらった。0〜255の値から16進数のカラーコードを作れるのでとても便利。



■JavaScriptのデバッグ
Androidのwebkitはchromeやfirebugと同じくconsole.log(message)関数が使えて、logcatで見れる。JavaScriptの実行時エラーも見れる。
Debugging Web Apps | Android Developers

PhoneGapの場合は行頭にI/Web Consoleが付いてconsole.logが、E/Web Consoleでエラーが来るのでgrepしておくと良い
adb logcat | grep "^./Web Console" --color=auto

Androidのデフォルトのwebブラウザも、console.logとエラーをadbに投げてくれる。こちらは^./browserでgrep。
adb logcat | grep "^./browser" --color=auto

このようにどのファイルの何行目でどんなエラーが起こったかまでちゃんと追えるので便利


■PhoneGapができなそうな事をいくつか
アプリ組み込み版のGoogleMapが表示できなそう。PhoneGapではJavaの方でActivityの代わりにDroidGapを継承しているのだが、AndroidのGoogleMapはGoogleが提供しているMapActivityを継承して使うことで色々と隠蔽されるようになっているので。

クロスプラットフォームになるのかというと、そうでもなさそうな気がする。特にハードウェア依存の機能があるとどのOSも同じコードで動かせる、とはいかなくなるし、同じ種類のセンサーを持っていてもセンサーが返す値はOS/機種毎に違うんじゃないか?とかいう気がしている。
画面の解像度も機種毎に違うので、特にOSが違う場合にフォントサイズがどうなってしまうのかも気になる。

Androidのサービスを作る、暗黙的インテントを受信する、ホームスクリーンウィジェットを作るなど、PhoneGapのサポートしていない機能もある。が、これは無い部分をJavaで書けばなんとかなりそう。


■JavaからJavaScriptを呼び出す
最初に起動するactivityで、
super.loadUrl("file:///android_asset/www/index.html");
とやってhtmlを読み込んでいるんだけど、同じように
super.loadUrl("javascript://alert('kzsk')");
と、ブックマークレットを渡すと、JavaScriptの関数を呼び出して値を渡せる。phonegap.jsの中で使ってた。URLの文字数の制限がどれぐらいあるか調べてないけど、これでPhoneGapで機能的に足りない部分は自分でJavaのコード書いて補えるはず。

逆に、JavaScriptからJavaを呼び出すのはどうやるかはまだわからない。今調べてる。