AndroidバージョンごとのWebView仕様に振り回されないためにCrosswalkのXWalkViewを使ってみたよ


Androidは4.4からWebViewがWebKitベースのものからChromeベースのものに変わりました。
もちろん一部仕様も変更されています。
例えばjavascriptの実行方法が`loadUrl`メソッドから`evaluateJavascript`メソッドに変わってしまっているので、両方対応しようとするとちょいとばかしダサいことになります。

```
if(Build.VERSION.SDK_INT < 19){
	webView.loadUrl("javascript:alert('ダサい')");
}else{
	webView.evaluateJavascript("alert('ダサい')", null);
}
```

また4.3以前の脆弱性サポートは既に打ち切られています。恐いのなんのって。

そこでいろいろ調べて見つけたのが[Crosswalk](https://crosswalk-project.org/)というWebViewエンジン。
Androidアプリに組み込むことでバージョンに依存しないWebviewの実装・動作が可能になる。
素敵だからこいつを使ってみたよ。
そしてバッチリまとまった参考記事が少なかったのでまとめてみたよ。


## 1.XWalkViewをインストール
Mavenリポジトリを使うため`build.gradle`に以下２つを追加する。

```
// 追加①
repositories {
    maven {
        url 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2'
    }
}
```

```
// 追加②
compile 'org.xwalk:xwalk_core_library:20.50.533.12'
```

追加後の`build.gradle`

```build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"

    defaultConfig {
        applicationId "com.sample.hello"
        minSdkVersion 23
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

// 追加①
repositories {
    maven {
        url 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2'
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.0.0'
    // 追加②
    compile 'org.xwalk:xwalk_core_library:20.50.533.12'
}

```

とっても簡単

## 2.XWalkViewを配置
`WebView`の代わりに`org.xwalk.core.XWalkView`を対象のレイアウトに配置する

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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="com.sample.hello.MainActivity">

    <org.xwalk.core.XWalkView
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
    />
</RelativeLayout>

```
とっても簡単

## 5.AndroidManifestにpermissionを追加

```
<uses-permission android:name="android.permission.INTERNET"/>
```

追加後の`AndroidManifest.xml`

```AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.sample.hello">

    <uses-permission android:name="android.permission.INTERNET"/>
    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

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

</manifest>
```

## 4.ActivityからXWalkViewを操作

```MainActivity.java
package com.sample.hello;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import org.xwalk.core.XWalkResourceClient;
import org.xwalk.core.XWalkView;

import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

	// XWalkViewに登録するResourceClient
	private class ResourceClient extends XWalkResourceClient {
		ResourceClient(XWalkView view) {
			super(view);
		}

		// XWalkViewのロード完了通知
		@Override
		public void onLoadFinished(XWalkView view, String url) {
			super.onLoadFinished(view, url);
			// 描画後にグローバルに定義されたjavascript関数をコール
			xWalkView.evaluateJavascript("onLoaded();", null);
		}

		// Location変更通知
		@Override
		public boolean shouldOverrideUrlLoading(XWalkView view, String url) {
			// URLを使って値の受け渡しを行う
			if (url.contains("app-api://")) {
				// 'true'を返した場合Locationの変更がキャンセルされる
				return true;
			}

			// 'false'を返した場合Locationの変更が続行される
			return false;
		}
	}

	private XWalkView xWalkView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// XWalkViewを取得
		xWalkView = (XWalkView) findViewById(R.id.webView);
		
		// XWalkViewから通知を受け取るためのXWalkResourceClient継承クラスをset
		xWalkView.setResourceClient(new ResourceClient(xWalkView));

		// loadメソッドに'Map<String, String>'を渡すことでリクエストヘッダーにフィールドを追加することができる
		Map<String, String> additionalHttpHeaders = new HashMap<>();
		additionalHttpHeaders.put("mobileAppAuth", "3f7d22402f6ac375bf1168c3ef5d8f08");

		//localhostはアクセスできないのでローカルサーバーで開発中は自分のIPアドレス,ポートを指定してね
		xWalkView.load("http://192.168.0.0:8000/gacha", null, additionalHttpHeaders);

		//***********************************************************
		// ここでグローバルに定義されたjavascript関数をコールしても 
		// `is not defined エラーで怒られます`
		//***********************************************************
		// xWalkView.evaluateJavascript("onLoaded();", null);
	}

	@Override
	protected void onPause() {
		super.onPause();
		if (xWalkView != null) {
			xWalkView.pauseTimers();
			xWalkView.onHide();
		}
	}

	@Override
	protected void onResume() {
		super.onResume();
		if (xWalkView != null) {
			xWalkView.resumeTimers();
			xWalkView.onShow();
		}
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (xWalkView != null) {
			xWalkView.onDestroy();
		}
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (xWalkView != null) {
			xWalkView.onActivityResult(requestCode, resultCode, data);
		}
	}

	@Override
	protected void onNewIntent(Intent intent) {
		if (xWalkView != null) {
			xWalkView.onNewIntent(intent);
		}
	}
}
```

`XWalkResourceClient`にはこれだけOverride可能なメソッドがあるのでプログレスバー出したり等いろいろな拡張ができそう。

|return|メソッド名                                         |
|:-----|:------------------------------------------------|
|void	|onLoadFinished(XWalkViewInternal view, String url)|
|void	|onLoadStarted(XWalkViewInternal view, String url)|
|void	|onProgressChanged(XWalkViewInternal view, int progressInPercent)|
|void	|onReceivedLoadError(XWalkViewInternal view, int errorCode, String description, String failingUrl)|
|WebResourceResponse|	shouldInterceptLoadRequest(XWalkView view, String url)|
|WebResourceResponse|	shouldInterceptLoadRequest(XWalkViewInternal view, String url)|

## 5.実行
はい、出ました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/127115/0d8db051-5e51-9c3c-408b-763bec3e6baf.png" width="300px"/>


## 最後に
XWalkView、WebView関連の記事を探していたら`evaluateJavascriptメソッドでグローバルに定義した関数をコールすることができますよ`という旨の記載が多々出てくるが、騙されてはいけない。
実際はWebViewのロード完了後以降でないとコールできないのだが、その点には一切触れていないものが多かったので注意されたし。これだけで3時間はハマる（ハマった）。