簡體   English   中英

Android WebView:處理方向變化

[英]Android WebView: handling orientation changes

問題是輪換后的表現。 WebView 必須重新加載頁面,這可能有點乏味。

處理方向更改而無需每次都從源代碼重新加載頁面的最佳方法是什么?

如果您不希望 WebView 在方向更改時重新加載,只需在您的 Activity class 中覆蓋 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);
   }

當然,在 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);
}

對此的最佳答案是遵循此處找到的 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();
    }
}

我試過使用onRetainNonConfigurationInstance (返回WebView ),然后在onCreate期間使用getLastNonConfigurationInstance取回它並重新分配它。

似乎還沒有工作。 我忍不住認為我真的很接近,到目前為止,我只是得到一個空白/白色背景WebView 發帖在這里希望有人可以幫助推動這個通過終點線。

也許我不應該通過WebView 也許 WebView 中的object

我嘗試的另一種方法——不是我最喜歡的——是在活動中設置它:

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

可行,但可能不被視為最佳實踐

同時,我還有一個ImageView ,我想根據旋轉自動更新它。 事實證明這很容易。 在我的res文件夾下,我有drawable-landdrawable-port來保存橫向/縱向變化,然后我使用R.drawable.myimagename作為ImageView的來源和 Android “做正確的事” - 耶!

...除非您觀察配置更改,否則不會。 :(

所以我很矛盾。 使用onRetainNonConfigurationInstanceImageView旋轉有效,但WebView持久性不......或使用onConfigurationChangedWebView保持穩定,但ImageView不更新。 該怎么辦?

最后一點:在我的例子中,強制定位不是一個可以接受的妥協。 我們真的很想優雅地支持旋轉。 有點像 Android 瀏覽器應用程序的功能; ;)

我很高興這有點晚了,但這是我在開發解決方案時使用的答案:

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

}

處理方向變化和防止 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"`

一種妥協是避免輪換。 添加此項以僅修復縱向活動。

android:screenOrientation="portrait"

只需在您的 Manifest 文件中寫入以下代碼行 - 僅此而已。 真的行:

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

您可以嘗試在 Activity 上使用onSaveInstanceState()onRestoreInstanceState()來調用 WebView 實例上的saveState(...)restoreState(...)

現在是 2015 年,許多人都在尋找一種仍然適用於 Jellybean、KK 和 Lollipop 手機的解決方案。 經過一番努力,我找到了一種在改變方向后保持 webview 完好無損的方法。 我的策略基本上是將 webview 存儲在另一個 class 的單獨 static 變量中。然后,如果發生旋轉,我將 webview 從活動中分離出來,等待定向完成,然后將 webview 重新附加回活動。 例如...首先將其放在您的 MANIFEST 上(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>

在單獨的應用程序 CLASS 中,輸入:

     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>

我的解決方案中有幾處可以改進,但我已經花了很多時間,例如:驗證 Activity 是否已首次啟動的更短方法,而不是使用 SharedPreferences 存儲。 這種方法使您 webview 保持完整(afaik),它的文本框、標簽、UI、javascript 變量,以及 url 未反映的導航狀態。

更新:當前的策略是將 WebView 實例移動到應用程序 class,而不是像 Josh 那樣在分離時保留片段並在恢復時重新附加。 為防止應用程序關閉,您應該使用前台服務,如果您希望在用戶在應用程序之間切換時保留 state。

如果您正在使用片段,您可以使用 WebView 的保留實例。web 視圖將保留為 class 的實例成員。但是,您應該在 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(); } }

PS 將go 歸功於 kcoppock 的回答

至於“SaveState()”,根據官方文檔,它不再有效:

請注意,此方法不再存儲此 WebView 的顯示數據。如果從未調用 restoreState(Bundle),之前的行為可能會泄漏文件。

您唯一應該做的就是將此代碼添加到您的清單文件中:

<activity android:name=".YourActivity"
      android:configChanges="orientation|screenSize"
      android:label="@string/application_name">
@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

我發現在不泄漏先前的Activity引用且不設置configChanges的情況下執行此操作的最佳解決方案是使用MutableContextWrapper

我在這里實現了這個: https://github.com/slightfoot/android-web-wrapper/blob/48cb3c48c457d889fc16b4e3eba1c9e925f42cfb/WebWrapper/src/com/example/webwrapper/BrowserActivity.java

這是唯一對我有用的東西(我什至在onCreateView中使用了保存實例 state,但它並不可靠)。

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的state做了一個Singleton holder。只要申請的進程存在,state就被保留。

編輯: loadDataWithBaseURL不是必需的,它與 just 一樣有效

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

雖然我讀到這不一定適用於 cookies。

試試這個

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

}

你應該試試這個:

  1. 創建一個服務,在那個服務里面,創建你的 WebView。
  2. 從您的活動啟動服務並綁定到它。
  3. onServiceConnected方法中,獲取 WebView 並調用setContentView方法來呈現您的 WebView。

我測試了它,它可以工作,但不能與 XWalkView 或 GeckoView 等其他 WebView 一起使用。

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

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

此頁面解決了我的問題,但我必須在最初的頁面中稍作更改:

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

這部分對我來說有點問題。 在第二次方向更改時,應用程序以 null 指針終止

使用它對我有用:

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

我喜歡這個解決方案http://www.devahead.com/blog/2012/01/preserving-the-state-of-an-android-webview-on-screen-orientation-change/

根據它,我們重用了 WebView 的相同實例。它允許保存導航歷史記錄並在配置更改時滾動 position。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM