在 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变量,你可以模仿saveState
和restoreState
所做的。这些方法的理想做法是分别使用 activity 的 onSaveInstanceState()
和 onRestoreInstanceState()
保存和恢复 WebView
中的内容。由于潜在的内存泄漏,这些方法不会执行它们所做的事情。
因此,您需要做的就是创建自己的网络视图 (MyWebView extends WebView
)。这里面有两个方法:saveVariableState()
和restoreVariableState()
。在你的saveVariableState()
中,只需将你想要的每个变量保存在一个包中,然后return它(public Bundle saveVariableState(){}
)。现在在 Activity
的 onSaveInstanceState()
中,调用 MyWebView.saveVariableState
并保存包 returns。一旦方向发生变化,您就可以从 onRestoreInstanceState
或 onCreate
中获取包,并通过构造函数或 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);
}
注意: 没试过刚刚写的这段代码,但认为它应该可以工作。
我的 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
and onRetainNonConfigurationInstance
已弃用 自 API 级别 13.getLastNonConfigurationInstance
强制屏幕方向
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变量,你可以模仿saveState
和restoreState
所做的。这些方法的理想做法是分别使用 activity 的 onSaveInstanceState()
和 onRestoreInstanceState()
保存和恢复 WebView
中的内容。由于潜在的内存泄漏,这些方法不会执行它们所做的事情。
因此,您需要做的就是创建自己的网络视图 (MyWebView extends WebView
)。这里面有两个方法:saveVariableState()
和restoreVariableState()
。在你的saveVariableState()
中,只需将你想要的每个变量保存在一个包中,然后return它(public Bundle saveVariableState(){}
)。现在在 Activity
的 onSaveInstanceState()
中,调用 MyWebView.saveVariableState
并保存包 returns。一旦方向发生变化,您就可以从 onRestoreInstanceState
或 onCreate
中获取包,并通过构造函数或 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);
}
注意: 没试过刚刚写的这段代码,但认为它应该可以工作。