【问题标题】:Handling screen rotation in WebView在 WebView 中处理屏幕旋转
【发布时间】:2015-10-30 23:49:39
【问题描述】:

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

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

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

屏幕方向更改时会发生 Activity(以及任何最终的 Fragment)被销毁然后重新创建。 WebView 继承自 View 并覆盖方法 onSaveInstanceStateonRestoreInstanceState 用于处理配置更改,因此它会自动保存和恢复任何 HTML 表单元素的内容以及后退/前进导航历史状态。但是 JavaScript 变量和 DOM 的状态不会被保存和恢复。

建议的解决方案

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

为 WebView 分配一个 id

WebView 继承自 View,它具有 setId 方法,也可以在布局 XML 文件中使用 android:id 属性在 <WebView> 元素的声明中声明。这是保存和恢复状态所必需的,但是状态只是部分恢复。这会恢复表单输入元素,但不会恢复 JavaScript 变量和 DOM 的状态。

onRetainNonConfigurationInstance 和 getLastNonConfigurationInstance

onRetainNonConfigurationInstancegetLastNonConfigurationInstance 自 API 级别 13 起已弃用

强制屏幕方向

可以通过在AndroidManifest.xml 文件中为<Activity> 元素设置screenOrientation 属性或通过setRequestedOrientation 方法来强制一个Activity 的屏幕方向。这是不希望的,因为它打破了屏幕旋转的预期。这也只涉及屏幕方向的变化,而不涉及其他配置变化。

保留片段实例

不起作用。在片段上调用方法setRetainInstance 确实保留了片段(它不会被破坏),因此片段的所有实例变量都被保留,但是它确实破坏了片段的视图,因此@987654357 @ 确实被破坏了。

手动处理配置更改

configChanges 属性可以在 AndroidManifest.xml 文件中为 Activity 声明为 android:configChanges="orientation|screenSize",以通过阻止 configuration changes 来处理它们。这有效,它可以防止活动被破坏,因此WebView 及其内容被完全保留。然而,这已被劝阻,据说仅用作最后的解决方案,因为它可能会导致应用程序以微妙的方式中断并出现错误。设置configChanges 属性时调用方法onConfigurationChanged

MutableContextWrapper

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

saveState() 和 restoreState()

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

WebViewFragment

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

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

问题

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

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

【问题讨论】:

    标签: android webview android-webview android-view android-configchanges


    【解决方案1】:

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

    它使用setRetainInstance 来保留片段实例以及onCreateViewonDestroyView 方法中的addViewremoveView,以防止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/");
        }
    }
    

    【讨论】:

    • 很好地总结了这个问题。当我正在开发的应用程序返回嵌入式 Web 视图时,再次遇到它。
    • 保留new WebView(getActivity()) 将在配置更改时泄漏 Activity 上下文(因为 WebView 仍然持有对初始 Activity 的引用)。此外,在配置更改后打开某些表单元素(如下拉列表)时,WebView 可能会崩溃。
    【解决方案2】:

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

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

    因此,您需要做的就是创建自己的 web 视图 (MyWebView extends WebView)。在这有两种方法:saveVariableState()restoreVariableState()。在您的saveVariableState() 中,只需将您想要的每个变量保存在一个包中并返回它(public Bundle saveVariableState(){})。现在在ActivityonSaveInstanceState() 中,调用MyWebView.saveVariableState 并保存它返回的包。一旦方向改变,您从onRestoreInstanceStateonCreate 获取包并通过构造函数或restoreVariableState 将其传递给MyWebView

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

    【讨论】:

    • 这适用于全局命名空间中的一两个变量,但不适用于具有修改后的私有变量且您无法从外部访问和 DOM 操作的已创建对象。
    【解决方案3】:

    以下清单代码声明了一个处理屏幕方向更改和键盘可用性更改的活动: 这段代码:

    <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

    【讨论】:

      【解决方案4】:

      你可以做的简单的事情是这样的:

      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);
      }
      

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

      【讨论】:

        猜你喜欢
        • 2021-11-16
        • 2012-10-04
        • 1970-01-01
        • 1970-01-01
        • 2011-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多