首页 > 解决方案 > 设备旋转/拆分视图后使用 ViewModel 恢复 Android WebView 页面

问题描述

我使用 WebView 来显示数据,数据通过 JS 函数得到增强。在设备旋转之前它工作正常。使用 ViewModel 保留页面数据并在配置更改后恢复它似乎是正确的想法,但我遇到了问题,页面没有得到恢复。我将代码缩减为一个极简示例,包括在此处(没有 XML 布局)


DataStore 类来存储数据,覆盖 ViewModel,简单的将一些字符串存储在一个列表中

package com.automationce.labyrinth.webview;

import android.arch.lifecycle.ViewModel;
import java.util.ArrayList;
import java.util.List;

/* The idea is to use ViewModel to store page data
* so it can be restored on device rotate/split view */
public final class DataStore extends ViewModel {
   public List<String> records = new ArrayList<>();

   public DataStore() {
      super();
   }
}

简单的 Web 视图类,覆盖 WebView

package com.automationce.labyrinth.webview;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.webkit.WebView;

public final class SimpleWebView extends WebView {
   // Use to keep and restore data between config changes
   // (not related to browser history by any means)
   private DataStore dataStore;

   public SimpleWebView(Context context, AttributeSet attrs) {
      super(context, attrs);
      setBackgroundColor(Color.LTGRAY);
      getSettings().setJavaScriptEnabled(true);
      setWebContentsDebuggingEnabled(true);

      // Define style, write JS function(s)
      String content = "<!DOCTYPE html><html><head>" +
            "<style>" +
            "span { font-size: " +
            0.875 +
            "em; } " +
            "</style>" +
            "</head><body id=\"body\">" +
            jsFunctions() + "</body></html>";

      loadDataWithBaseURL(null, content, "text/html", "utf-8", null);
   }

   // JS function(s) as written to the page
   private String jsFunctions() {
      return "<script>" +
            "function append (string) { " +
            "var body = document.getElementById('body');" +
            "var block = document.createElement('span');" +
            "var text = document.createTextNode(string);" +
            "var br = document.createElement('br'); " +
            "block.appendChild(text);" +
            "block.appendChild(br); " +
            "body.appendChild(block);" +
            "block.scrollIntoView();" +
            " }" +
            "</script>";
   }

   public void setHistory (final DataStore storedData) {
      dataStore = storedData;
   }

   // Restore page from DataStore data
   public void restore() {
      for(String record : dataStore.records) {
         evaluateJavascript("append ('" + record + "');", null);
      }
   }

   // Append new data to existing page
   public void append(final String string) {
      dataStore.records.add(string);
      evaluateJavascript("append ('" + string + "');", null);
   }
}

最后是 MainActivity 本身

package com.automationce.labyrinth.webview;

import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

   private SimpleWebView pageView;
   private Button appendButton;
   private int counter;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      DataStore storedData = ViewModelProviders.of(this).get(DataStore.class);

      appendButton = findViewById(R.id.button);
      pageView = findViewById(R.id.webView);
      pageView.setHistory(storedData);
      pageView.restore();

      appendButton.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
            append();
         }
      });
   }

   private void append() {
      counter++;
      pageView.append("Some kind of new text " + counter);
   }
}

想法是保留 ViewModel 中的数据并在创建过程中恢复它。我花了一段时间才弄清楚为什么它不起作用。DataStore 确实在旋转期间得到了保留(我可以看到 DataStore 对象中的所有历史数据),但是虽然我看到正在执行恢复代码,但它永远不会在 SimpleWebView 中呈现。


原因是,虽然 JavaScript 函数确实正确写入页面,但我在页面实际呈现之前从 MainActivity.OnCreate 调用 restore()。JS 函数被调用并很快被忽略,因为实际上,就 WebView 而言,该页面并不存在(我认为)。令人惊讶的是,这实际上适用于我使用的大多数模拟器 - 现在我认为它不应该而且我不知道为什么会这样 - 直到我在物理设备(Galaxy S7)上运行它,其中恢复被忽略


我的解决方案是给我的 SimpleWebView 一个新的 WebViewClient 并覆盖其 OnPageFinished 方法,并在页面完成加载后在其中恢复页面的内容。SimpleView 构造函数(以下代码中的“firstView”布尔标志在设备旋转后第一次重新加载视图后失效):

   public SimpleWebView(Context context, AttributeSet attrs) {
      super(context, attrs);
      setBackgroundColor(Color.LTGRAY);
      getSettings().setJavaScriptEnabled(true);
      setWebContentsDebuggingEnabled(true);

      setWebViewClient(new WebViewClient() {
         @Override
         public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            Log.v("PAGE", "finished");
            if (firstView) {
               ((SimpleWebView)view).restore();
            }
         }
      });
     .
     .

现在,网页在设备旋转/拆分窗口后从持久数据中重建。它似乎运行良好,没有任何明显的加载延迟(我现在正在处理相对较小的数据集)。

我一直在寻找解决方案,我真的不想在 OnSaveInstanceState / onRestoreInstanceState 中保存和恢复整个页面的字符串内容,因为在这种情况下,它对我没有任何好处 - 我将拥有数据但我仍然需要在页面渲染 后恢复它

我从集体中寻求明智的话:

谢谢, 吉里

标签: javascriptandroidwebviewrotationviewmodel

解决方案


推荐阅读