Android WebView: 处理方向变化

问题是轮转后的性能。 WebView 必须重新加载页面,这可能有点乏味。

处理方向变化的最佳方法是什么,而不必每次从源代码重新加载页面?

103523 次浏览
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}


@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
}

这些方法可以覆盖任何活动,它只是基本上允许你保存和恢复值每次活动被创建/销毁,当屏幕方向改变活动被销毁和重新创建在后台,所以你可以使用这些方法临时存储/恢复状态在改变。

您应该更深入地研究以下两种方法,看看它是否适合您的解决方案。

Http://developer.android.com/reference/android/app/activity.html

一种折衷方案是避免旋转。 添加此属性以修复仅面向 Portrait 的活动。

android:screenOrientation="portrait"

您可以尝试在您的活动上使用 onSaveInstanceState()onRestoreInstanceState()来调用 WebView 实例上的 saveState(...)restoreState(...)

我试过使用 OnRetainNonConfigurationInstance(返回 WebView) ,然后在 OnCreate期间用 GetLastNonConfigurationInstance得到它并重新分配它。

好像还没起作用。我忍不住想,我真的很接近了!到目前为止,我只得到了一个空白/白色背景的 WebView。发布在这里,希望有人可以帮助推动这一个通过终点线。

也许我不应该传递 WebView。也许一个物体从内部的 WebView

我尝试的另一种方法——不是我最喜欢的——是在活动中设置:

 android:configChanges="keyboardHidden|orientation"

然后什么都不做:

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// We do nothing here. We're only handling this to keep orientation
// or keyboard hiding from causing the WebView activity to restart.
}

这样可以,但可能不被认为是 最佳实践

同时,我也有一个单一的 图片浏览,我想自动更新根据旋转。事实证明这很简单。在我的 res文件夹下,我有 drawable-landdrawable-port来保存风景/肖像的变化,然后我使用 R.drawable.myimagename作为 图片浏览的源和 Android“做正确的事情”-耶!

... 除非你观察配置变化,然后它不会: (

所以我们意见不合。使用 OnRetainNonConfigurationInstance图片浏览旋转工作,但是 WebView持久性不... 或者使用 OnConfigurationChangedWebView保持稳定,但是 图片浏览不更新。怎么办?

最后一点: 在我的情况下,强制定向是不可接受的妥协。我们确实想优雅地支持轮换。有点像 Android 浏览器应用程序的功能!;)

如果您不希望 WebView 重新加载方向更改,只需在 Activity 类中覆盖 onConfigurationChanged:

@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}

并在清单中设置 android: configChanges 属性:

<activity android:name="..."
android:label="@string/appName"
android:configChanges="orientation|screenSize"

更多信息见:
Http://developer.android.com/guide/topics/resources/runtime-changes.html#handlingthechange

Https://developer.android.com/reference/android/app/activity.html#configurationchanges

编辑: 此方法不再按照 医生中的说明工作


原答案:

这可以通过在活动中覆盖 onSaveInstanceState(Bundle outState)并从 webview 调用 saveState来处理:

   protected void onSaveInstanceState(Bundle outState) {
webView.saveState(outState);
}

然后在你的 onCreate 中恢复这个,当然是在 webview 被重新充气之后:

public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blah);
if (savedInstanceState != null)
((WebView)findViewById(R.id.webview)).restoreState(savedInstanceState);
}

这个页面解决了我的问题,但是我必须在第一个页面做一些小小的改动:

    protected void onSaveInstanceState(Bundle outState) {
webView.saveState(outState);
}

这部分对我来说有一个小问题。在第二个方向改变应用程序终止与空指针

使用这个对我很有用:

    @Override
protected void onSaveInstanceState(Bundle outState ){
((WebView) findViewById(R.id.webview)).saveState(outState);
}

我找到的最好的解决方案,这样做不泄漏以前的 Activity参考和没有设置的 configChanges。.就是使用 MutableContextWrapper

我在这里实现了这个: https://github.com/slightfoot/android-web-wrapper/blob/48cb3c48c457d889fc16b4e3eba1c9e925f42cfb/WebWrapper/src/com/example/webwrapper/BrowserActivity.java

这个问题的最佳答案是下面的 Android 文档,可以在这里找到 基本上这将防止 Webview 重新加载:

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

编辑(1/4/2020) : 您不需要这个可选代码,清单属性就是您所需要的全部,在这里留下可选代码以保持答案完整。

可选地 ,您可以通过在活动中重写 onConfigurationChanged来修复异常(如果有的话) :

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);


// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}

我喜欢这个解决方案 http://www.devahead.com/blog/2012/01/preserving-the-state-of-an-android-webview-on-screen-orientation-change/

根据它,我们重用相同的 WebView 实例。它允许在配置更改时保存导航历史记录和滚动位置。

我知道这有点晚了,但这是我在开发解决方案时使用的答案:

AndroidManifest.xml

    <activity
android:name=".WebClient"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize" <--- "screenSize" important
android:label="@string/title_activity_web_client" >
</activity>

WebClient.java

public class WebClient extends Activity {


protected FrameLayout webViewPlaceholder;
protected WebView webView;


private String WEBCLIENT_URL;
private String WEBCLIENT_TITLE;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_client);
initUI();
}


@SuppressLint("SetJavaScriptEnabled")
protected void initUI(){
// Retrieve UI elements
webViewPlaceholder = ((FrameLayout)findViewById(R.id.webViewPlaceholder));


// Initialize the WebView if necessary
if (webView == null)
{
// Create the webview
webView = new WebView(this);
webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
webView.getSettings().setSupportZoom(true);
webView.getSettings().setBuiltInZoomControls(true);
webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
webView.setScrollbarFadingEnabled(true);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setPluginState(android.webkit.WebSettings.PluginState.ON);
webView.getSettings().setLoadsImagesAutomatically(true);


// Load the URLs inside the WebView, not in the external web browser
webView.setWebViewClient(new SetWebClient());
webView.setWebChromeClient(new WebChromeClient());


// Load a page
webView.loadUrl(WEBCLIENT_URL);
}


// Attach the WebView to its placeholder
webViewPlaceholder.addView(webView);
}


private class SetWebClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.web_client, menu);
return true;
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}else if(id == android.R.id.home){
finish();
return true;
}


return super.onOptionsItemSelected(item);
}


@Override
public void onBackPressed() {
if (webView.canGoBack()) {
webView.goBack();
return;
}


// Otherwise defer to system default behavior.
super.onBackPressed();
}


@Override
public void onConfigurationChanged(Configuration newConfig){
if (webView != null){
// Remove the WebView from the old placeholder
webViewPlaceholder.removeView(webView);
}


super.onConfigurationChanged(newConfig);


// Load the layout resource for the new configuration
setContentView(R.layout.activity_web_client);


// Reinitialize the UI
initUI();
}


@Override
protected void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);


// Save the state of the WebView
webView.saveState(outState);
}


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


// Restore the state of the WebView
webView.restoreState(savedInstanceState);
}


}

这是唯一对我有效的方法(我甚至在 onCreateView中使用了保存实例状态,但它不那么可靠)。

public class WebViewFragment extends Fragment
{
private enum WebViewStateHolder
{
INSTANCE;


private Bundle bundle;


public void saveWebViewState(WebView webView)
{
bundle = new Bundle();
webView.saveState(bundle);
}


public Bundle getBundle()
{
return bundle;
}
}


@Override
public void onPause()
{
WebViewStateHolder.INSTANCE.saveWebViewState(myWebView);
super.onPause();
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
ButterKnife.inject(this, rootView);
if(WebViewStateHolder.INSTANCE.getBundle() == null)
{
StringBuilder stringBuilder = new StringBuilder();
BufferedReader br = null;
try
{
br = new BufferedReader(new InputStreamReader(getActivity().getAssets().open("start.html")));


String line = null;
while((line = br.readLine()) != null)
{
stringBuilder.append(line);
}
}
catch(IOException e)
{
Log.d(getClass().getName(), "Failed reading HTML.", e);
}
finally
{
if(br != null)
{
try
{
br.close();
}
catch(IOException e)
{
Log.d(getClass().getName(), "Kappa", e);
}
}
}
myWebView
.loadDataWithBaseURL("file:///android_asset/", stringBuilder.toString(), "text/html", "utf-8", null);
}
else
{
myWebView.restoreState(WebViewStateHolder.INSTANCE.getBundle());
}
return rootView;
}
}

我为 WebView 的状态做了一个 Singleton 持有者。只要应用程序的进程存在,状态就保持不变。

编辑: loadDataWithBaseURL是没有必要的,它工作得很好,只是

    //in onCreate() for Activity, or in onCreateView() for Fragment


if(WebViewStateHolder.INSTANCE.getBundle() == null) {
webView.loadUrl("file:///android_asset/html/merged.html");
} else {
webView.restoreState(WebViewStateHolder.INSTANCE.getBundle());
}

虽然我读到这个不一定适用于 cookie。

现在是2015年,许多人都在寻找一种仍然适用于 Jellybean、 KK 和棒棒糖手机的解决方案。 经过 很多的挣扎,我找到了一种方法,以保持网络视图完好无损后,你改变方向。 我的策略基本上是将 webview 存储在另一个类中的一个单独的静态变量中。然后,如果发生旋转,我将 webview 从活动中分离出来,等待方向完成,并将 webview 重新附加到活动中。 例如... 首先把这个放在你的 MANIFEST (键盘隐藏和键盘是可选的) :

<application
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:name="com.myapp.abc.app">


<activity
android:name=".myRotatingActivity"
android:configChanges="keyboard|keyboardHidden|orientation">
</activity>

在单独的应用程序类中,放置:

     public class app extends Application {
public static WebView webview;
public static FrameLayout webviewPlaceholder;//will hold the webview


@Override
public void onCreate() {
super.onCreate();
//dont forget to put this on the manifest in order for this onCreate method to fire when the app starts: android:name="com.myapp.abc.app"
setFirstLaunch("true");
}


public static String isFirstLaunch(Context appContext, String s) {
try {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
return prefs.getString("booting", "false");
}catch (Exception e) {
return "false";
}
}


public static void setFirstLaunch(Context aContext,String s) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(aContext);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("booting", s);
editor.commit();
}
}

在活动中写道:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(app.isFirstLaunch.equals("true"))) {
app.setFirstLaunch("false");
app.webview = new WebView(thisActivity);
initWebUI("www.mypage.url");
}
}
@Override
public  void onRestoreInstanceState(Bundle savedInstanceState) {
restoreWebview();
}


public void restoreWebview(){
app.webviewPlaceholder = (FrameLayout)thisActivity.findViewById(R.id.webviewplaceholder);
if(app.webviewPlaceholder.getParent()!=null&&((ViewGroup)app.webview.getParent())!=null) {
((ViewGroup) app.webview.getParent()).removeView(app.webview);
}
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.FILL_PARENT);
app.webview.setLayoutParams(params);
app.webviewPlaceholder.addView(app.webview);
app.needToRestoreWebview=false;
}


protected static void initWebUI(String url){
if(app.webviewPlaceholder==null);
app.webviewPlaceholder = (FrameLayout)thisActivity.findViewById(R.id.webviewplaceholder);
app.webview.getSettings().setJavaScriptEnabled(true);       app.webview.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
app.webview.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
app.webview.getSettings().setSupportZoom(false);
app.webview.getSettings().setBuiltInZoomControls(true);
app.webview.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
app.webview.setScrollbarFadingEnabled(true);
app.webview.getSettings().setLoadsImagesAutomatically(true);
app.webview.loadUrl(url);
app.webview.setWebViewClient(new WebViewClient());
if((app.webview.getParent()!=null)){//&&(app.getBooting(thisActivity).equals("true"))) {
((ViewGroup) app.webview.getParent()).removeView(app.webview);
}
app.webviewPlaceholder.addView(app.webview);
}

最后,简单的 XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".myRotatingActivity">
<FrameLayout
android:id="@+id/webviewplaceholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>

在我的解决方案中有几个地方可以改进,但是我已经花费了很多时间,例如: 一个较短的方法来验证活动是否是第一次启动,而不是使用 SharedPreferences 存储。 这种方法保留了 webview 的完整性(afaik)、它的文本框、标签、 UI、 javascript 变量和 URL 未反映的导航状态。

更新: 目前的策略是将 WebView 实例移动到 Application 类,而不是像 Josh 那样在简历中分离并重新附加保留的片段。 若要防止应用程序关闭,如果希望在用户在应用程序之间切换时保持状态,则应使用前台服务。

如果使用的是片段,则可以使用 WebView 的保留实例。 Web 视图将作为类的实例成员保留。然而,您应该在 OnCreateView 中附加 Web 视图,并在 OnDestroyView 之前分离,以防止它与父容器一起被破坏。

class MyFragment extends Fragment{


public MyFragment(){  setRetainInstance(true); }


private WebView webView;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = ....
LinearLayout ll = (LinearLayout)v.findViewById(...);
if (webView == null) {
webView = new WebView(getActivity().getApplicationContext());
}
ll.removeAllViews();
ll.addView(webView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));


return v;
}


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

附注: 信用点去 < a href = “ https://stackoverflow./a/24985760/241986”> kcoppock 的答案

至于‘ SaveState ()’,根据 正式文件,它不再起作用:

请注意,此方法不再存储 以前的行为可能会泄漏文件,如果 从未调用 restoreState (Bundle)。

您唯一应该做的就是将这段代码添加到您的清单文件中:

<activity android:name=".YourActivity"
android:configChanges="orientation|screenSize"
android:label="@string/application_name">

你应该试试这个:

  1. 创建一个服务,在该服务内部,创建您的 WebView。
  2. 从您的活动启动服务并绑定到它。
  3. onServiceConnected方法中,获取 WebView 并调用 setContentView方法来呈现 WebView。

我测试了它,它工作,但不与其他 WebView,如 XWalkView 或 GeckoView。

最好的方法来处理方向变化和防止 WebView 重新加载在旋转。

@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}

考虑到这一点,为了防止每次更改方向时都调用 onCreate () ,必须添加 android:configChanges="orientation|screenSize" to the AndroidManifest.

或者只是。

android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"`
@Override
protected void onSaveInstanceState(Bundle outState )
{
super.onSaveInstanceState(outState);
webView.saveState(outState);
}


@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
webView.restoreState(savedInstanceState);
}

只要在 Manifest 文件中写下下面的代码行就可以了——别的什么都不用做,它真的可以工作:

<activity android:name=".YourActivity"
android:configChanges="orientation|screenSize"
android:label="@string/application_name">

试试这个

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;


public class MainActivity extends AppCompatActivity {


private WebView wv;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


wv = (WebView) findViewById(R.id.webView);
String url = "https://www.google.ps/";


if (savedInstanceState != null)
wv.restoreState(savedInstanceState);
else {
wv.setWebViewClient(new MyBrowser());
wv.getSettings().setLoadsImagesAutomatically(true);
wv.getSettings().setJavaScriptEnabled(true);
wv.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
wv.loadUrl(url);
}
}


@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
wv.saveState(outState);
}


@Override
public void onBackPressed() {
if (wv.canGoBack())
wv.goBack();
else
super.onBackPressed();
}


private class MyBrowser extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}


}