6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

もうWebViewに飽きた?そんなときはGeckoViewを使ってみよう。

Last updated at Posted at 2018-11-13

まえおき

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がインストールできない?

image.png

ビルドバリアントが正しく設定されてない可能性大です。

image.png

アプリを終了して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

6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?