在 WebView 中处理屏幕旋转

Handling screen rotation in WebView

我的 Web 应用程序在 Chrome 中运行良好,它可以很好地处理配置更改(例如屏幕旋转)。一切都保存完好

将我的网络应用程序加载到我的 Android 应用程序中的 WebView 时,网络应用程序会在屏幕方向更改时丢失状态。它 部分 保留了状态,即它保留了 <input> 表单元素的数据,但是所有 JavaScript 变量和 DOM 操作都丢失了。

我希望我的 WebView 以 Chrome 的方式运行,即完全保留状态,包括任何 JavaScript 变量。需要注意的是,虽然 Chrome 和 WebView 派生自相同的代码库 Chrome 并没有在内部使用 WebView。

屏幕方向改变时发生的事情是 Activity(以及任何最终的片段)被破坏然后随后重新创建。 WebView 继承自 View and overrides the methods onSaveInstanceState and onRestoreInstanceState 用于处理配置更改,因此它会自动保存和恢复任何 HTML 表单元素的内容以及 back/forward 导航历史状态。但是,JavaScript 变量和 DOM 的状态未保存和恢复。

建议的解决方案

已经提出了一些解决方案。所有这些都不起作用,仅保留部分状态或以其他方式次优。

正在为 WebView 分配一个 id

WebView 继承自 View,后者在 <WebView> 元素的声明中具有方法 setId which can also be declared in the layout XML file using the android:id 属性。这对于保存和恢复状态是必要的,但是状态只是部分恢复。这会恢复表单输入元素,但不会恢复 JavaScript 变量和 DOM 的状态。

onRetainNonConfigurationInstance 和 getLastNonConfigurationInstance

onRetainNonConfigurationInstance and getLastNonConfigurationInstance 已弃用 自 API 级别 13.

强制屏幕方向

Activity 可以通过设置 screenOrientation attribute for the <Activity> element in the AndroidManifest.xml file or via the setRequestedOrientation 方法强制其屏幕方向。这是不受欢迎的,因为它打破了屏幕旋转的预期。这也只处理屏幕方向的变化,而不处理其他配置变化。

保留片段实例

无效。在片段上调用方法 setRetainInstance 确实会保留片段(它不会被销毁),因此会保留片段的所有实例变量,但是它 破坏片段的因此 WebView 确实被销毁了。

手动处理配置更改

设置 configChanges 属性时调用 configChanges attribute can be declared for an Activity in the AndroidManifest.xml file as android:configChanges="orientation|screenSize" to handle configuration changes by preventing them. This works, it prevents the activity from getting destroyed hence the WebView and its contents is fully preserved. However this has been discouraged and is said to be used only as a last resort solution as it may cause the app to break in subtle ways and get buggy. The method onConfigurationChanged

MutableContextWrapper

听说可以用MutableContextWrapper,但没评估过这种方法

saveState() 和 restoreState()

WebView 有方法 saveState and restoreState。请注意,根据文档,无论这意味着什么,saveState 方法都不再存储 WebView 的显示数据。无论哪种方式,这些方法似乎都不能完全保留 WebView 的状态。

WebViewFragment

WebViewFragment 只是一个方便的片段,它为您包装了 WebView,因此可以轻松地使用较少的样板代码,就像 ListFragment 一样。它不做任何额外的状态保留以完全保留状态。

此 class 在 API 级别 28 中已弃用。

问题

对于 WebView 在配置更改时被破坏并丢失其状态的问题,是否有真正的解决方案? (比如屏幕旋转)

完全 保留所有状态的解决方案,包括 JavaScript 变量和 DOM 操作。一个干净的解决方案,而不是建立在 hack 或已弃用的方法之上。

我建议你重新渲染整个东西。我搜索了一段时间,但找不到干净的现成解决方案。那里的一切都表明您让网页在方向更改时重新呈现。

但是如果你真的需要持久化你的JS变量,你可以模仿saveStaterestoreState所做的。这些方法的理想做法是分别使用 activity 的 onSaveInstanceState()onRestoreInstanceState() 保存和恢复 WebView 中的内容。由于潜在的内存泄漏,这些方法不会执行它们所做的事情。

因此,您需要做的就是创建自己的网络视图 (MyWebView extends WebView)。这里面有两个方法:saveVariableState()restoreVariableState()。在你的saveVariableState()中,只需将你想要的每个变量保存在一个包中,然后return它(public Bundle saveVariableState(){})。现在在 ActivityonSaveInstanceState() 中,调用 MyWebView.saveVariableState 并保存包 returns。一旦方向发生变化,您就可以从 onRestoreInstanceStateonCreate 中获取包,并通过构造函数或 restoreVariableState 将其传递给 MyWebView

这不是 hack,而是保存其他视图数据内容的正常方法。在 WebView 的情况下,您将保存 JS 变量,而不是保存视图的数据。

在研究和尝试不同的方法后,我发现了我所相信的最佳解决方案。

它使用 setRetainInstance to retain the fragment instance along with addView and removeView in the onCreateView and onDestroyView 方法来防止 WebView 被破坏。

MainActivity.java

public class MainActivity extends Activity {
    private static final String TAG_FRAGMENT = "webView";

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        WebViewFragment fragment = (WebViewFragment) getFragmentManager().findFragmentByTag(TAG_FRAGMENT);
        if (fragment == null) {
            fragment = new WebViewFragment();
        }

        getFragmentManager().beginTransaction().replace(android.R.id.content, fragment, TAG_FRAGMENT).commit();
    }
}

WebViewFragment.java

public class WebViewFragment extends Fragment {
    private WebView mWebView;

    public WebViewFragment() {
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_webview, container, false);
        LinearLayout layout = (LinearLayout)v.findViewById(R.id.linearLayout);
        if (mWebView == null) {
            mWebView = new WebView(getActivity());
            setupWebView();
        }
        layout.removeAllViews();
        layout.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        return v;
    }

    @Override
    public void onDestroyView() {
        if (getRetainInstance() && mWebView.getParent() instanceof ViewGroup) {
            ((ViewGroup) mWebView.getParent()).removeView(mWebView);
        }
        super.onDestroyView();
    }

    private void setupWebView() {
        mWebView.loadUrl("https:///www.example.com/");
    }
}

以下清单代码声明了一个 activity 来处理屏幕方向更改和键盘可用性更改: 此代码:

<activity android:name=".MyActivity">

变成:

<activity android:name=".MyActivity"
          android:configChanges="orientation|screenSize|keyboardHidden"
          android:label="@string/app_name">

这里只要加上:screenSize就可以了

参考:https://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject

您可以做的简单事情如下:

WebView webView;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_webview, container, false);

    webView = v.findViewById(R.id.webView);

    if(savedInstanceState != null){
        webView.restoreState(savedInstanceState);
    } else {
        loadUrl();
    }
    return v;
}

private void loadUrl(){
    webView.loadUrl("someUrlYouWant");
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    webView.saveState(outState);
}

注意: 没试过刚刚写的这段代码,但认为它应该可以工作。