PhoneGap挙動メモ

自メモ)


PhoneGap自体が参考になりそうなサイト)

つい最近数時間ハマったやつ><)

minSdkVersion書かないとActivityが誤動作するという話




2.9.0特記)

これ結局ソースのアタッチしてわかったんだけど

  • libs/cordova-2.9.0.jar.properties
#src=libs_src/cordova-android-master/framework/src
src=../libs_src/cordova-android-master/framework/src
#doc=
  • libs_src/cordova-android-master/framework/src

の認識タイミングがわかんねー。
プロジェクト何回か開き直したり、eclipse再起動したりしてた。

面倒なら手動で

workspace/.metadeta/org.eclipse.jdt.core/.org.eclipse.jdt.core.external.folders/.project

のデータにパスエントリ追加したほうが速いみたい



addJavascriptInterface に関して)

  1. phoneGap
    1. appView.addJavascriptInterface(this,"irof"); でOK
  2. WebView
    1. 4.2以下 webview.addJavascriptInterface(this,"irof"); でOK
    2. 4.2以上 @JavascriptInterface でかけ
      1. Android4.2.xではaddJavascriptInterfaceが動かない+対処法

しかもLintでエラーにしよる‥‥‥死んで欲しいなー ADT22.0.5



  1. Android Application Project
    1. FullScreenActivity で作成
    2. BlankActivity で作成*1
  2. 余計なリソースを消す
    1. suport-v4.jar /values-v11/values-v14
  3. PhoneGap からSDKダウンロード
    1. zip展開
    2. lib/android/exampleから下記をコピー*2
      1. libs/cordova-2.5.0.jar
      2. res/xml
      3. asset/www
  4. 下記の記述をAndrodiManifest.xmlに追加
<uses-sdk
        android:minSdkVersion="4"
        android:targetSdkVersion="18" />

	<supports-screens 
		android:largeScreens="true" 
		android:normalScreens ="true" 
		android:smallScreens="true" 
		android:resizeable="true"
		android:anyDensity="true"/> 
    
	<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

	<activity
		android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
     		android:name="driven.hoge.HogeActivity"

  1. Activity => DroidGap 等に変更
//public class HogeActivity extends Activity {
public class HogeActivity extends DroidGap {


@Override
//protected void onCreate(Bundle savedInstanceState) { //<=なぜかActivityで作るとprotected
public void onCreate(Bundle savedInstanceState) {


    super.loadUrl("file:///android_asset/www/index.html");
}


備考)

  • Android 1.6 では動かない*3
    • ググるとPhoneGap2.0系だと Android1.6とかは動かないよう(1.0系以下なら動く?な記事もちらほら
    • docを見るとサポートは2.1から
  • ローカルのファイルを参照にするにも下記の権限は必要(libraryがネットワーク参照している)
    • (権限なし表記の)オフラインアプリは厳しめ?
	<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

 でもまあ、Android1.6でも一応AU認証等は動くんですが
WebView(組込Webkit)自体が JQuery(Mobile)自体をまともに再生できないらしい ので
まあ動作サポート外にはなるんだろうね(汗


で対応として

  • PhoneGapで起動するActivityを別にしてIntent起動という方針をとるしか無い
    • シェア的に少なくても受け入れ検証的にはチェックされるので
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main); //☆
		
		//PhoneGapのサポートは2.1から
		if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.ECLAIR_MR1){
			Intent i = new Intent(getApplicationContext(), HogeActivityPG.class);
			startActivity(i);
			finish();
			return;
		}

		WebView mWebView = _findViewById(R.id.webView);
		if(mWebView==null) mWebView = new  WebView(this);

		mWebView.loadUrl(url);
		
		//勿論 ☆ をコメントにしてWebViewをsetContentViewするのも可
		//setContentView(mWebView);
	}

	@SuppressWarnings("unchecked")
	protected <T extends View> T _findViewById(final int id){
	    return (T)findViewById(id);
	}
  • res/layout/activity_main.xml
    • TextView => WebViewに変更
<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=".HogeActivity" >

<!--
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/hello_world" />
-->

	<WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
	</WebView>

</RelativeLayout>



ドメインアクセス制限)

WebView版>

  • AndroidManifest.xml
<!--
<data android:scheme="@string/scheme_name"  android:host="@string/host_name" android:path="/" />
 --> 
<data android:scheme="http"  android:host="hogedriven.net" android:path="/" />

Android2.1の対応をするためには下記のベタ書きしかないイメージ

種別 元URL 抽出箇所
scheme http://hogedriven.net http
host http://hogedriven.net hogedriven.net
path http://hogedriven.net /


参考)

Phonegap2.5.0>

設定ファイル)
res/xml/config.xml

 <access origin="http://hogedriven.net" subdomains="false" />

ここにCROSSーDOMAINの登録



オレオレSSL証明書対応)

SSLErrorが出るので対応が必要(出るとみっともないという話があるので)

普通のView版>

ソース自体は
ANDROID_SDK/source/android-15 あたりから

コピって使うと。
(ただしADTがSDKの方を強く参照しようとするので デバック実行時に毎回cleanをしないとうまく認識しないかも)

	public class MyWebViewClient extends WebViewClient {

	    @Override
	    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
	        // testing against getPrimaryError() or hasErrors() will fail on Honeycomb or older.
	        // You might check for something different, such as specific info in the certificate,
	        //if (error.getPrimaryError() == SslError.SSL_IDMISMATCH) {
	            handler.proceed();
	        //} else {
	        //    super.onReceivedSslError(view, handler, error);
	        //}
	    }
	}

 ちなみに
WebViewClient は android-16からかなり変わっているようなので
こっちから持ってくると芋蔓が大変なことに(汗


PhoneGap2.5.0版>


若干コードは変更した

	@Override
	public void init(CordovaWebView webView,
			CordovaWebViewClient webViewClient,
			CordovaChromeClient webChromeClient) {
		
		webViewClient = new MyWebViewClient(this);
		super.init(webView, webViewClient, webChromeClient);
	}
	
	@SuppressLint("NewApi")
	public class MyWebViewClient extends CordovaWebViewClient {

	    public MyWebViewClient(DroidGap ctx) {
	        super(ctx);
	    }

	    @Override
	    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
	        // testing against getPrimaryError() or hasErrors() will fail on Honeycomb or older.
	        // You might check for something different, such as specific info in the certificate,
	        //if (error.getPrimaryError() == SslError.SSL_IDMISMATCH) {
	            handler.proceed();
	        //} else {
	        //    super.onReceivedSslError(view, handler, error);
	        //}
	    }
	}



Cookie制御)

ログイン情報(cookie)が無ければ
ログイン画面に遷移させたい場合

WebView版>

CookieSyncManager.createInstance(this);//*
CookieSyncManager.getInstance().startSync();//*
CookieManager.getInstance().setAcceptCookie(true);
CookieManager.getInstance().removeExpiredCookie();//期限切れ削除

CookieManager cMgr = CookieManager.getInstance();
String loginCookie = cMgr.getCookie(call_uri);
if(loginCookie==null || "".equals(loginCookie.trim())){
	mWebView.loadUrl(call_uri_login);
}
else{
	mWebView.loadUrl(call_uri);
}
mWebView.requestFocus();

*)GalaxyTab等一部の端末で落ちるようなので追記

ちなみに

String[] cookies = loginCookie.split(";");
for (String keyValue : cookies) {
    keyValue = keyValue.trim();
    String[] cookieSet = keyValue.split("=");
}

でクッキー情報は取得可能

参考)


PhoneGap版>

CookieSyncManager.createInstance(this);//☆
CookieSyncManager.getInstance().startSync();//☆
CookieManager.getInstance().setAcceptCookie(true);
CookieManager.getInstance().removeExpiredCookie();//期限切れ削除

CookieManager cMgr = CookieManager.getInstance();
String loginCookie = cMgr.getCookie(call_uri);
if(loginCookie==null || "".equals(loginCookie.trim())){
	super.loadUrl(call_uri_login);
}
else{
	super.loadUrl(call_uri);
}
super.setIntegerProperty("loadUrlTimeoutValue", 60000); //タイムアウト対策

この☆の2つが重要!!

追記)
上記で動いてたわけなんだけど、最終的に全部httpsに仕様変更された

  1. =>cookieが上手く保存できない
  2. IOS(Safari)やPC(Mac)やAndroid標準ブラウザでは動いてるよ
    1. Android固有の問題でしょ?どうにかしてよ!
    2. と言われて緊急対応*4

 あとから見てもやっつけ感満載で
いやだな〜とか思うんだけど、時間期限が何か異常に短いので仕方ないよな。。
*5
スケジュール感が全然ないよな。
まあAndroidは儲からない から片手間感満載なのかも。。
企業的に儲かるところに力を入れるのは間違いではないのですけれど。。。

 Andに関してはIOSとちょっとでもリリースずれるとリリースされないとか
平気であるし*6

>>備忘録として書いておく暫定対応*7

  • クッキーが取得できない時の復元側
String BASE_URL="https://hogedriven.net"; //[注]これは仮のものです
String BASE_DOMAIN="hogedriven.net";       //[注]これは仮のものです

String ROOT_HOME="hogedriven";
String ROOT_KEY="irof";


CookieManager cMgr = CookieManager.getInstance();
String hogeCookie = cMgr.getCookie(BASE_URL);

if(hogeCookie==null){
	String value = getSharedPreferences(ROOT_HOME, 0).getString(ROOT_KEY,null);
	if(value!=null){
		String[] arr = value.split(";");
		for(String s:arr){
			s += "; domain=" + BASE_DOMAIN;
			cMgr.setCookie(BASE_DOMAIN, s);
			CookieSyncManager.getInstance().sync();
		}
	}
}

super.loadUrl(url);
  • クッキーが取得できない時の保存側
public boolean urlCheck(String url){
	if(url==null || "".equals(url.trim()))return false;
	int idx = url.lastIndexOf(".");
	int idxSep = url.lastIndexOf("/");
	if(idx > idxSep)return false;
	return true;
}

@SuppressLint("NewApi")
public class HogeWebViewClient extends CordovaWebViewClient {

	public void onPageFinished(WebView wv, String url){
		if(urlCheck(url)){
			CookieManager cMgr = CookieManager.getInstance();
			String hogeCookie = cMgr.getCookie(BASE_URL);
			if(hogeCookie!=null && url.indexOf(BASE_DOMAIN)!=-1){
				SharedPreferences.Editor editor = getSharedPreferences(ROOT_HOME, MODE_PRIVATE).edit();
				editor.putString(ROOT_KEY, hogeCookie);
				editor.commit();
			}
			hogeCookie = cMgr.getCookie(BASE_URL);
		}



Alertダイアログ等)
ゲーム作る場合は、ここらへんの標準ダイアログは出るとかっこ悪いので上書きしておく

WebView版>

//alert dialog対策
mWebView.setWebChromeClient(new WebChromeClient(){
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result){
	Log.v(TAG,"[onJsAlert](url,msg)"+ url +"," + message);
	return false;
    }
    
     @Override
     public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
	Log.v(TAG,"[onJsConfirm](url,msg)"+ url +"," + message);
	return true;
     }

     @Override
     public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
       	 if(debug_f) Log.v(TAG,"[onJsPrompt](url,msg)"+ url +"," + message);
       	 return true;
     }
});


PhoeGap2.5.0版>

//[TODO]super.lodarURLの後でないと appViewは初期化されていないです。
//alert dialog対策
appView.setWebChromeClient(new WebChromeClient(){
   @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result){
	    if(debug_f)Log.v(TAG,"[onJsAlert](url,msg)"+ url +"," + message);
	    return false;
    }
    
     @Override
     public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
	     if(debug_f) Log.v(TAG,"[onJsConfirm](url,msg)"+ url +"," + message);
	     return true;
     }

     @Override
     public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
       	 if(debug_f) Log.v(TAG,"[onJsPrompt](url,msg)"+ url +"," + message);
       	 return true;
     }
});

 ココらへんも結局目視デバックできない

Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();

で 表示することに・・。正直わけわからんわ。。><

使用可能な関数のまとめ)

WebViewでTwitterが見れない&横に変な隙間が。 - 素人のアンドロイドアプリ開発日記
WebViewClientで使える関数のまとめ - 素人のアンドロイドアプリ開発日記
WebChromeClientで使える関数のまとめ - 素人のアンドロイドアプリ開発日記
来世から本気出す: AndroidのWebViewのリファレンスを流し読み

中に表示するコンテンツのデバック用メモ)


Activity宣言関連メモ)
挙動が変で凄く悩んだのでメモ

  • 普通のActivity&WebView
<activity
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
	android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
     	android:name="driven.hoge.HogeActivity"
  • DroidGap
<activity
	android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
     	android:name="driven.hoge.HogeActivity"

android:theme 宣言があると頭50dipぐらいがちょんぎれるので注意




WebView設定関連>

  • 普通のActivity&WebView
    • getSetting() => loadUrl
  • DroidGap
  • super.loadUrl(uri); したあとに getSetting() 等を設定する


Backkey関連>
DroidGapは

  • BackkeyがJSからいじる形でメインは殺されている

花水木のエトセトラ: 【JQueryMobileとPhoneGap】 Phone Gapを実装したActivityを終了させる編
見てると他のキー使え な記述がorz


 でも結局のところIOSと同じく?やっぱりBackkey要らないなお話があり急遽封じることに*8
IOS至上主義死ね!とか何時も思ってたりしますが。。。
Tizenとか他のプラットホームが出ても同じだろうな。。
HTML5スマホが作れるのが幸せって思えないorz

 PhoneGap的には、JSだけで全部やれ な路線なんだけど
今回のオーダは Andはビューワのみでござる って話なので。
そりゃIOSは内部的にはSafari(標準ブラウザ)で動くから楽だろうさ。。。*9


 これもアホな暫定対応。一応また有りそうなのでメモっておく
ついでに一応Menuも対応した


PhoeGap2.5.0版>

@SuppressLint("NewApi")
public class HogeActivityPG extends DroidGap {

	@Override
	public void init(CordovaWebView webView,
			CordovaWebViewClient webViewClient,
			CordovaChromeClient webChromeClient) {

		webView = new HogeCordovaWebView(this); //☆ 上書き
		webViewClient = new IrofWebViewClient(this);
		super.init(webView, webViewClient, webChromeClient);
	}
public class HogeCordovaWebView extends CordovaWebView {

	private boolean debug_f = false;
	private Resources m_r;
	
	
	public HogeCordovaWebView(Context context) {
		super(context);
		
		m_r = getResources();
		debug_f = m_r.getBoolean(R.bool.debug_f);
	}
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event){
		switch(keyCode){
			case KeyEvent.KEYCODE_MENU:
			case KeyEvent.KEYCODE_BACK:
				return false;
			case KeyEvent.KEYCODE_HOME:
			default:
				break;
		}
		return super.onKeyUp(keyCode, event);
    }

	
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
		case KeyEvent.KEYCODE_MENU:
			//対応処理
			return false;
		case KeyEvent.KEYCODE_BACK:
			//対応処理
			return false;
		case KeyEvent.KEYCODE_HOME:
			break;
		default:
			break;
		}
		return super.onKeyDown(keyCode, event);
    }
}


Menu関連>
DroidGapは

  • Menu表示が効かない

PhoneGap2.5.0の DroidGap が標準状態でMenuサポートしていないようなので別の方法を考える*10

を参照するとPluginを作って有効にしてね? と記載があるようですが初心者にはそこまではきつい。。><

追記>

これで対応できそうだけど、

  • 鯖の仕様コロコロ変更=>情報共有なし って感じだから確認できず。。*11


今回は
throw Life - Androidアプリにサイドメニューを簡単に追加できるライブラリ
を使う方針で

  • demoから 下記のファイルをコピー
    • libs/SimpleSideDrawer0.0.1.jar
    • res/layout/activity_behind_simple.xml
    • value/style.xmlの中身をコピー
mSlidingMenu = new SimpleSideDrawer(this);
mSlidingMenu.setBehindContentView(R.layout.activity_behind_simple);
mSlidingMenu.findViewById(R.id.behind_btn).setOnClickListener(new OnClickListener() {
	public void onClick(View header) {
		behindAction();
	}
});


//Android2系用
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
	switch(keyCode){
		case KeyEvent.KEYCODE_MENU:
			if(mSlidingMenu==null)break;
			mSlidingMenu.toggleDrawer();
			return true;
		case KeyEvent.KEYCODE_HOME:
			break;
//		case KeyEvent.KEYCODE_BACK:
//			onDestroy();
//			android.os.Process.killProcess(android.os.Process.myPid());
//			return true;
//			break;
		default:
			break;
	}
	return super.onKeyDown(keyCode, event);
}
	
//Android3系以降だとこちらを有効にしないとトリガー無い(Homeキー)
@Override
public void onUserLeaveHint(){
	behindAction();
}

public void behindAction(){
	if(mSlidingMenu==null)return;
	mSlidingMenu.toggleDrawer();
}
  • res/layout/activity_behind_simple.xml を適宜拡張する
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    style="@style/behindMenuScroll" >

    <LinearLayout style="@style/behindMenuScrollContent"
        android:layout_marginTop="50dip" >

        <Button
	     android:layout_marginTop="8dip"
            android:id="@+id/behind_btn"
            style="@style/behindMenuItemLabel"
            android:text="@string/menu_bhind" 
            />
        <TextView
	     android:layout_marginTop="8dip"
            android:id="@+id/menu_reload"
            style="@style/behindMenuItemLabel"
            android:text="@string/menu_reload" 
            android:clickable="true"
            />
        <TextView
	     android:layout_marginTop="8dip"
            android:id="@+id/menu_clear"
            style="@style/behindMenuItemLabel"
            android:text="@string/menu_clear" 
            android:clickable="true"
            />

    </LinearLayout>
</ScrollView>

ラベルをクリックする場合は


 android:clickable="true"
を追加。infrate扱いなので、勿論 android:onClick 等でAction関連付けは不可
横幅は微妙に調整できない(勝手に文字が折り返す)


追記2)
SimpleSideDrawer v1=>v2 移行

  • libs
    • SimpleSideDrawer0.0.1.jar => simple-side-drawer2.jar
  • src
    • setBehindContentView => setLeftBehindContentView
    • toggleDrawer =>toggleLeftDrawer
  • res/layout/activity_behind.xml => activity_behind_left.xml
    • @style/behindMenuScroll => @style/leftBehindMenuScroll
  • res/values/style.xml
    • BehindMenuScroll => leftBehindMenuScroll / rightBehindMenuScroll




DroidGap の他にもおかしい挙動メモ)

  • OnKeyDownの KeyEvent.KEYCODE_BACK 等を上書きすると終了しなくなるとかも有
  • WebViewClient等をオーバライドした時
    • onLoadResource/onPageFinished で
    @Override
    public void onLoadResource(WebView wv,String url){
	    if(debug_f)Log.v(TAG,"onLoadResource:" + url);
	    super.onLoadResource(wv,url);//☆
    }
    
    @Override
    public void onPageFinished(WebView wv, String url){
	    if(debug_f)Log.v(TAG,"onPageFinished:" + url);
	    super.onPageFinished(wv,url);//☆
    }

のsuperの記述を追加しないとページローディングもできなくなる(必ずタイムアウト
普通のWebView等ではおこらない(汗




Proguard問題)

これを試してみたけど駄目(初期ページのローディングの時点で失敗)
proguard.configを無効にすると動くイメージ。

  • project.properties
# Project target.
target=android-17
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
  • proguard-project.txt
    • commons-codec-1.6.jar はmavenから別途落としてきてlibsに入れる*12
#-dontwarn org.apache.commons.codec.binary.Base64

# PhoneGap proguard
-keep public class * extends com.phonegap.api.Plugin 
-keep public class org.apache.cordova.** 
-dontwarn android.webkit.*
-libraryjars ./libs/commons-codec-1.6.jar

# 今回手動で追加したものをkeep classする
-keep class android.webkit.WebViewClient
-keep class android.net.http.SslError


参考資料)

Android/iPhone/Windows Phone対応 jQuery Mobileスマートフォンアプリ開発

Android/iPhone/Windows Phone対応 jQuery Mobileスマートフォンアプリ開発

プロになるためのJavaScript入門 ~node.js、Backbone.js、HTML5、jQuery-Mobile (Software Design plus)

プロになるためのJavaScript入門 ~node.js、Backbone.js、HTML5、jQuery-Mobile (Software Design plus)

HTML5/JavaScriptとPhoneGapで作るiPhoneアプリ開発入門

HTML5/JavaScriptとPhoneGapで作るiPhoneアプリ開発入門

Web技術者のためのHTML5+JavaScriptで作るAndroidアプリ

Web技術者のためのHTML5+JavaScriptで作るAndroidアプリ

HTML5/JavaScriptで作るAndroidアプリ開発ガイドブック

HTML5/JavaScriptで作るAndroidアプリ開発ガイドブック


参考リンク系)



追記)
WebView系は結構鬼門ぽいな。。

*1:変なコードが沢山追加されているので作りなおしたorz

*2:アップグレードする場合は個々らへんを差し替えるイメージ

*3:VerificationError

*4:つうか知らない内に勝手に鯖仕様変えて動かないどうにかしろとか多い気がする

*5:とか思いつつも前回急いでapk作っても結局他のこと忙しくて or 気分がのらなくて 上がリリースしてない とか平気であるし

*6:バグ修正トリガーではリリースされないorz

*7:多分ベストではない

*8:つうか保留でよかった話が急遽リリースとかふざけんな!

*9:AndroidIOSに比べて糞って話を 上とIOS担当にdisられるなう。つうかそんなに嫌なら対応やめればいいのにとか思いつつ、立場弱いから無理だね。。<汗

*10:ActionBarが推奨されているからいらない路線?

*11:管理するなら茶庵として欲しいんだけどね。上の人

*12:SDK標準は 1.4、PhoneGapは1.6つかつているらしい