まえおき
WebView使うと、ブラウザっぽいアプリはすぐに作れる!
とおもいきや、実際つくってみると結構ボイラープレートコードが多いんですよね。
( http://arianrod-berry.hatenadiary.com/entry/2018/01/29/233841 あたりにいい感じにまとめられています)
なんかいい感じのWebView-alternativeはないかなーと探していたところ、GeckoViewというものを見つけました。Firefox Focusなどで使われているGeckoベースのWebViewてきなものみたいです。
Qiitaで現時点では「使ってみた」レベルの記事もなかったので、とりあえず使ってみた感じを書いておきます。
セットアップ: build.gradleへの追記
implementationを1行ペロッと書くだけ・・・ではありません。
オフィシャルのWikiに載っていますが、
android {
...
flavorDimensions "abi"
productFlavors {
x86 { dimension "abi" }
x86_64 { dimension "abi" }
arm { dimension "abi" }
aarch64 { dimension "abi" }
}
}
repositories {
maven { url "https://maven.mozilla.org/maven2/" }
}
ext {
geckoviewChannel = "beta"
geckoviewVersion = "64.0.20181105164654"
}
dependencies {
....
x86Implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-x86:${geckoviewVersion}"
x86_64Implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-x86_64:${geckoviewVersion}"
armImplementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-armeabi-v7a:${geckoviewVersion}"
aarch64Implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}-arm64-v8a:${geckoviewVersion}"
}
こんな感じでいろいろ書き足す必要があります。
おためしでやったときはこんな感じ→https://github.com/YusukeIwaki/GeckoViewPlayground/commit/a09dca7f4e87403f2387cfd2b05217361287c407
GeckoViewを使う・・前に
GeckoViewを使うときに出てくるクラスをさらっとだけ書いておきます。
GeckoSession
ブラウザの内部状態をコントロールするもの。
WebViewだと、 loadUrl()
とか goBack()
は直接WebViewに生えているAPIを叩いていたけど、GeckoViewではGeckoSessionを通じて行う。
GeckoRuntime
GeckoSession(内部状態)とGeckoView(ビュー)の橋渡してきなもの?
ContentDelegate, NavigationDelegate, ProgressDelegate, ...
WebViewだとWebViewClientとかWebChromeClientが各種コールバックを使って(割とごちゃまぜな感じで)実装しないといけなかったやつ。GeckoViewだと役割ごとにDelegateを実装するらしい。
GeckoViewを使う。
レイアウトファイルの適当なところにGeckoViewを置くのはWebViewと同じなので省略。
セットアップ処理はWebViewだと、
- setJavascriptEnabledでJS有効化したり
- WebSettingsでクッキーを有効にしてみたり
- WebViewClientを実装したクラスをセットして・・・
- WebChromeClientを実装したクラスをセットして・・・
みたいなのがありましたが、GeckoViewだと、概ねこんな感じになります
class BrowserActivity : AppCompatActivity() {
private lateinit var geckoView: GeckoView
private lateinit var geckoSession: GeckoSession
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
geckoView = findViewById(R.id.geckoview)
...
// セッションの初期化
geckoSession = GeckoSession()
geckoSession.open(GeckoRuntime.getDefault(this))
geckoview.session = geckoSession
// 各種Delegateのセットアップ
geckoSession.contentDelegate = MyContentDelegate(...)
geckoSession.navigationDelegate = MyNavigationDelegate(...)
geckoSession.progressDelegate = MyProgressDelegate(...)
// とりあえずホームページの表示
geckoSession.loadUri("https://www.yahoo.co.jp/")
}
GeckoView こんなときどうする?集
APKがインストールできない?
ビルドバリアントが正しく設定されてない可能性大です。
アプリを終了して2回目の起動時にクラッシュする?
オフィシャルWikiのほうだと
のように GeckoRuntime.create(this)
するように書いてありますが、これだと2回目以降のアプリ起動時にクラッシュします。
11-12 18:01:17.689 E/AndroidRuntime( 4534): Caused by: java.lang.IllegalStateException: Failed to initialize GeckoRuntime
11-12 18:01:17.689 E/AndroidRuntime( 4534): at org.mozilla.geckoview.GeckoRuntime.create(GeckoRuntime.java:270)
11-12 18:01:17.689 E/AndroidRuntime( 4534): at org.mozilla.geckoview.GeckoRuntime.create(GeckoRuntime.java:244)
雑にやるなら GeckoRuntime.getDefault(this)
を使う。真面目にやるなら、サンプルコードにあるようにシングルトンな実装にする必要があるみたいです。
リンクタップしても反応しないものがある?
GeckoViewはマルチセッションが前提らしく、target="_blank"
みたいな属性付きのリンクは自然には辿れなくなっているみたいです。
新しいタブで開くようなリンクを踏んだときは、NavigationDelegateの
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
}
が呼ばれます。
雑にやるなら、
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
session.loadUri(uri)
return null
}
のように、とりあえずloadUriする。真面目にやるならサンプルコードにあるように、新しくセッションを作ってGeckoResultでラップして返す必要があるようです。
「バックキーで戻る」をどうやって実装する?
WebViewと違って、GeckoViewは canGoBack()
canGoForward()
みたいなメソッドが生えていません。
NavigationDelegateの
override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
}
override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
}
を使います。
GeckoSessionの内部状態の考慮は、ライフサイクル依存したくないのでViewModelと併用すると良いでしょう。
class BrowserViewmodel: ViewModel() {
val canGoBack = MutableLiveData<Boolean>()
val canGoForward() = MutableLiveData<Boolean>()
}
class MyNavigationDelegate(private val viewModel: BrowserViewmodel): GeckoSession.NavigationDelegate {
...
override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
viewModel.canGoBack.postValue(canGoBack)
}
override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
viewModel.canGoForward.postValue(canGoForward)
}
...
}
class BrowserActivity: ... {
private lateinit var geckoView: GeckoView
private lateinit var viewModel: BrowserViewModel
private lateinit var geckoSession: GeckoSession
override fun onCreate(...) {
...
viewModel = ViewModelProviders.of(this).get(BrowserActivity::class.java)
// セッションの初期化
...
// 各種Delegateのセットアップ
geckoSession.navigationDelegate = MyNavigationDelegate(viewModel)
...
}
override fun onBackPressed() {
if (viewModel.canGoBack.value == true) {
geckoSession.goBack()
} else {
super.onBackPressed()
}
}
}
プログレス表示はどうやって実装する?
ProgressDelegateの
override fun onPageStart(session: GeckoSession, url: String) {
}
override fun onPageStop(session: GeckoSession, success: Boolean) {
}
override fun onProgressChange(session: GeckoSession, progress: Int) {
}
を使います。
- onPageStart→プログレス表示を開始
- onProgressChange→値を更新
- onPageStop→プログレスを消す
これもライフサイクル依存にならないように、ViewModelを介してやると楽だと思います。
class BrowserViewModel: ViewModel() {
...
val progress = MutableLiveData<Int>()
val loading = MutableLiveData<Boolean>()
}
class MyProgressDelegate(private val viewModel: BrowserViewModel) : GeckoSession.ProgressDelegate {
override fun onPageStart(session: GeckoSession, url: String) {
viewModel.loading.postValue(true)
}
override fun onPageStop(session: GeckoSession, success: Boolean) {
viewModel.loading.postValue(false)
}
override fun onProgressChange(session: GeckoSession, progress: Int) {
viewModel.progress.postValue(progress)
}
...
}
あとは、レイアウトでデータバインディングを使って
<data>
<variable
name="viewModel"
type="com.example.hogehoge.BrowserViewModel" />
<import type="android.view.View" />
</data>
<ProgressBar
android:id="@+id/browser_progress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="@{viewModel.progress}"
android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}" />
こんな感じでOK。
特定のURLを踏んだときの処理を横取りできる?
NavigationDelegateのonLoadRequestあたりをごにょればできそう。私の手元では未検証・・・
エラー画面はカスタマイズできる?
404とかの画面。
サンプルコードを見る限りだと、NavigationDelegateのonLoadErrorでHTML文字列を返せばできそう。私の手元では未検証・・・
GeckoViewのwebページのキャプチャはどうやって撮るの・・・?
まだ調べてない・・・
まとめ
なんとなくalt-WebViewをもとめてGeckoViewを使ってみたら、結構いい感じにコードが書けたので、とりあえず「まずは使ってみた」レベルで共有してみました。
サンプルコードが
https://searchfox.org/mozilla-central/source/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
これ1ファイルでかなり参考になるので、「とりあえずGeckoViewを使ってみたい!」ってからはここから見るとよさそうです。
私が試したサンプルも一応置いておきます↓
https://github.com/YusukeIwaki/GeckoViewPlayground