Android WebView:处理方向变化

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1002085/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:34:58  来源:igfitidea点击:

Android WebView: handling orientation changes

androidwebvieworientation

提问by glennanthonyb

The issue is the performance following rotation. The WebView has to reload the page, which can be a bit tedious.

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

What's the best way of handling an orientation change without reloading the page from source each time?

处理方向更改而无需每次从源重新加载页面的最佳方法是什么?

回答by Totach

If you do not want the WebView to reload on orientation changes simply override onConfigurationChanged in your Activity class:

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

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

And set the android:configChanges attribute in the manifest:

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

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

for more info see:
http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange

有关更多信息,请参阅:http:
//developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange

https://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges

https://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges

回答by KWright

Edit: This method no longer works as stated in the docs

编辑:此方法不再如文档中所述



Original answer:

原答案:

This can be handled by overrwriting onSaveInstanceState(Bundle outState)in your activity and calling saveStatefrom the webview:

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

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

Then recover this in your onCreate after the webview has been re-inflated of course:

然后在 webview 重新充气后在你的 onCreate 中恢复它:

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

回答by Tinashe

The best answer to this is following Android documentation found hereBasically this will prevent Webview from reloading:

对此的最佳答案是遵循此处找到的 Android 文档 基本上这将阻止 Webview 重新加载:

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

Optionally, you can fix anomalies (if any) by overriding onConfigurationChangedin the activity:

或者,您可以通过覆盖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();
    }
}

回答by Joe D'Andrea

I've tried using onRetainNonConfigurationInstance(returning the WebView), then getting it back with getLastNonConfigurationInstanceduring onCreateand re-assigning it.

我试过使用onRetainNonConfigurationInstance(返回WebView),然后在onCreate期间使用getLastNonConfigurationInstance取回它并重新分配它。

Doesn't seem to work just yet. I can't help but think I'm really close though! So far, I just get a blank/white-background WebViewinstead. Posting here in the hopes that someone can help push this one past the finish line.

似乎还没有工作。我不禁觉得我真的很亲近!到目前为止,我只是得到一个空白/白色背景的WebView。在这里发帖,希望有人可以帮助推动这个越过终点线。

Maybe I shouldn't be passing the WebView. Perhaps an object from within the WebView?

也许我不应该通过WebView。也许来自WebView 中的对象?

The other method I tried - not my favorite - is to set this in the activity:

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

 android:configChanges="keyboardHidden|orientation"

... and then do pretty much nothing here:

...然后在这里几乎什么都不做:

@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.
}

THAT works, but it might not be considered a best practice.

这有效,但它可能不被视为最佳实践

Meanwhile, I also have a single ImageViewthat I want to automagically update depending on the rotation. This turns out to be very easy. Under my resfolder, I have drawable-landand drawable-portto hold landscape/portrait variations, then I use R.drawable.myimagenamefor the ImageView's source and Android "does the right thing" - yay!

同时,我还有一个ImageView,我想根据旋转自动更新它。这很容易。在我的res文件夹下,我拥有drawable-landdrawable-port保存横向/纵向变化,然后我将其R.drawable.myimagename用于ImageView的源代码和 Android“做正确的事情” - 是的!

... except when you watch for config changes, then it doesn't. :(

...除非您注意配置更改,否则不会。:(

So I'm at odds. Use onRetainNonConfigurationInstanceand the ImageViewrotation works, but WebViewpersistence doesn't ... or use onConfigurationChangedand the WebViewstays stable, but the ImageViewdoesn't update. What to do?

所以我很矛盾。使用onRetainNonConfigurationInstance并且ImageView旋转有效,但WebView持久性不......或使用onConfigurationChanged并且WebView保持稳定,但ImageView不更新。该怎么办?

One last note: In my case, forcing orientation isn't an acceptable compromise. We really do want to gracefully support rotation. Kinda like how the Android Browser app does! ;)

最后一点:就我而言,强制定向不是可接受的折衷方案。我们确实希望优雅地支持轮换。有点像 Android 浏览器应用程序的功能!;)

回答by Jacques René Mesrine

One compromise is to avoid rotation. Add this to fix the activity for Portrait orientation only.

一种妥协是避免轮换。添加此项以仅修复纵向方向的活动。

android:screenOrientation="portrait"

回答by David Passmore

I appreciate this is a little late, however this is the answer that I used when developing my solution:

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

AndroidManifest.xml

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

}

回答by Amit raj

Best way to handle orientation changes and Preventing WebView reload on Rotate.

处理方向更改和防止 WebView 在旋转时重新加载的最佳方法。

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

With that in mind, to prevent onCreate() from being called every time you change orientation, you would have to add android:configChanges="orientation|screenSize" to the AndroidManifest.

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

or just ..

要不就 ..

android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"`

回答by Eddie Valmont

Just write the following code lines in your Manifest file - nothing else. It really works:

只需在您的清单文件中编写以下代码行 - 没有别的。真的行:

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

回答by Josh

It is 2015, and many people are looking for a solution that still workds on Jellybean, KK and Lollipop phones. After muchstruggling I found a way to preserve the webview intact after you change orientation. My strategy is basically to store the webview in a separate static variable in another class. Then, if rotation occurs, I dettach the webview from the activity, wait for the orientation to finish, and reattach the webview back to the activity. For example... first put this on your MANIFEST (keyboardHidden and keyboard are optional):

现在是 2015 年,许多人正在寻找一种仍然适用于 Jellybean、KK 和 Lollipop 手机的解决方案。经过多少挣扎我找到了一种方法来保存完好的WebView你改变方向后。我的策略基本上是将 webview 存储在另一个类中的单独静态变量中。然后,如果发生旋转,我会从活动中分离 webview,等待方向完成,然后将 webview 重新附加回活动。例如...首先把它放在你的清单上(keyboardHidden 和 keyboard 是可选的):

<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>

In a SEPARATE APPLICATION CLASS, put:

在单独的应用程序类中,输入:

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

In the ACTIVITY put:

在活动中输入:

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

Finally, the simple XML:

最后,简单的 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>

There are several things that could be improved in my solution, but I already spent to much time, for example: a shorter way to validate if the Activity has been launched for the very first time instead of using SharedPreferences storage. This approach preserves you webview intact (afaik),its textboxes, labels, UI, javascript variables, and navigation states that are not reflected by the url.

在我的解决方案中有几件事情可以改进,但我已经花了很多时间,例如:一种更短的方法来验证 Activity 是否已被首次启动,而不是使用 SharedPreferences 存储。这种方法使您的 webview 保持完整 (afaik),其文本框、标签、UI、javascript 变量和 url 未反映的导航状态。

回答by Boris Treukhov

Update: current strategy is to move WebView instance to Application class instead of retained fragment when it's detached and reattach on resume as Josh does. To prevent Application from closing, you should use foreground service, if you want to retain state when user switches between applications.

更新:当前的策略是将 WebView 实例移动到 Application 类,而不是像 Josh 那样分离并在恢复时重新附加时保留的片段。为了防止应用程序关闭,如果您想在用户在应用程序之间切换时保持状态,您应该使用前台服务。

If you are using fragments, you can use retain instance of the WebView. The web view will be retained as instance member of the class. You should however attach web view in OnCreateView and detach before OnDestroyView to prevent it from destruction with the parent container.

如果您正在使用片段,则可以使用 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();
    } 
}
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();
    } 
}

P.S. Credits go to kcoppock answer

PS 积分转到kcoppock 答案

As for 'SaveState()' it no longer works according to official documentation:

至于 'SaveState()' 根据官方文档,它不再起作用:

Please note that this method no longer stores the display data for this WebView. The previous behavior could potentially leak files if restoreState(Bundle) was never called.

请注意,此方法不再存储此 WebView 的显示数据。如果从不调用 restoreState(Bundle) ,则先前的行为可能会泄漏文件。