Android WebView: обработка изменений ориентации

Проблема заключается в производительности после вращения. WebView должен перезагрузить страницу, которая может быть немного утомительной.

Каков наилучший способ обращения с ориентацией без перезагрузки страницы из источника каждый раз?

Ответ 1

Если вы не хотите, чтобы WebView загружал изменения ориентации, просто переопределите onConfigurationChanged в классе Activity:

@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

Ответ 2

Изменить: этот метод больше не работает, как указано в docs


Оригинальный ответ:

Это может быть выполнено путем перезаписи onSaveInstanceState(Bundle outState) в вашей деятельности и вызова saveState из веб-представления:

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

Затем восстановите это в своем onCreate после того, как веб-просмотр был повторно завышен, конечно:

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

Ответ 3

Лучший ответ на этот вопрос - следуйте документации Android, найденной здесь В основном это предотвратит перезагрузку Webview:

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

При желании вы можете исправить аномалии (если они есть), переопределив 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();
    }
}

Ответ 4

Я попытался использовать onRetainNonConfigurationInstance (вернув WebView), а затем вернул его с getLastNonConfigurationInstance во время onCreate и переустанавливая его.

Кажется, пока не работает. Я не могу не думать, что я действительно близок! Пока что я просто получаю пустой/белый фон 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.
}

ЭТО работает, но это не может считаться лучшей практикой .

Между тем у меня также есть один ImageView, который я хочу автоматически обновлять в зависимости от поворота. Это оказывается очень легко. В моей папке res у меня есть drawable-land и drawable-port для сохранения вариаций пейзажа/портрета, тогда я использую R.drawable.myimagename для источника ImageView и Android "делает правильную вещь" - yay!

... кроме случаев, когда вы отслеживаете изменения конфигурации, тогда это не так.: (

Так что я расходился. Используйте onRetainNonConfigurationInstance, а вращение ImageView работает, но сохранение в WebView не... или использует onConfigurationChanged, а WebView остается стабильным, но ImageView не обновляется. Что делать?

Одна последняя заметка: в моем случае принудительная ориентация не является приемлемым компромиссом. Мы действительно хотим грациозно поддерживать ротацию. Как вам нравится приложение Android Browser!;)

Ответ 5

Один компромисс заключается в том, чтобы избежать вращения. Добавьте это, чтобы зафиксировать активность только для портретной ориентации.

android:screenOrientation="portrait"

Ответ 6

Я понимаю, что это немного поздно, но это тот ответ, который я использовал при разработке своего решения:

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

}

Ответ 7

Лучший способ справиться с изменениями ориентации и предотвратить перезагрузку 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"`

Ответ 8

Вы можете попробовать использовать onSaveInstanceState() и onRestoreInstanceState() в своей деятельности для вызова saveState(...) и restoreState(...) в вашем экземпляре WebView.

Ответ 9

Это 2015 год, и многие люди ищут решение, которое все еще работает на телефонах Jellybean, KK и Lollipop. После долгих попыток я нашел способ сохранить веб-просмотр без изменений после изменения ориентации. Моя стратегия в основном заключается в том, чтобы хранить веб-просмотр в отдельной статической переменной в другом классе. Затем, если происходит поворот, я отвлекаю веб-просмотр от активности, дожидаюсь завершения ориентации и снова привяжу веб-просмотр к активности. Например... сначала положите это на свой МАНИФЕСТ (клавиатура и клавиатура являются необязательными):

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

В поле 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);
    }

Наконец, простой 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 intact (afaik), его текстовые поля, метки, пользовательский интерфейс, переменные javascript и состояния навигации, которые не отражены в URL.

Ответ 10

Обновление: текущая стратегия заключается в перемещении экземпляра WebView в класс Application вместо сохраненного фрагмента при его отсоединении и повторном подключении к возобновлению, как это делает Джош. Чтобы предотвратить закрытие приложения, вы должны использовать службу переднего плана, если вы хотите сохранить состояние, когда пользователь переключается между приложениями.

<ы > Если вы используете фрагменты, вы можете использовать экземпляр сохранения WebView. Веб-просмотр будет сохранен как член экземпляра класса. Тем не менее, вы должны прикрепить веб-представление в OnCreateView и отсоединить его до 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. Кредиты идут в ответ kcoppock С >

Что касается "SaveState()", он больше не работает в соответствии с официальной документацией :

Обратите внимание, что этот метод больше не сохраняет отображаемые данные для это WebView. Предыдущее поведение может потенциально утечка файлов, если restoreState (Bundle) никогда не вызывался.

Ответ 11

@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

Ответ 13

Это единственное, что сработало для меня (я даже использовал состояние экземпляра save в 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;
    }
}

Я сделал держатель Singleton для состояния WebView. Состояние сохраняется до тех пор, пока существует процесс приложения.

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

Хотя я читал, что это не обязательно хорошо работает с кукисами.

Ответ 14

Единственное, что вам нужно сделать, это добавить этот код в файл манифеста:

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

Ответ 15

Просто напишите следующие строки кода в вашем файле манифеста - ничего больше. Это действительно работает:

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

Ответ 16

Эта страница решает мою проблему, но мне нужно внести незначительные изменения в начальный:

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

У этой части есть небольшая проблема для меня. При изменении второй ориентации приложение завершается нулевым указателем

с помощью этого он работал у меня:

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

Ответ 17

Вы должны попробовать следующее:

  • Создайте службу внутри этой службы, создайте свой WebView.
  • Запустите службу из своей деятельности и привяжите ее.
  • В методе onServiceConnected получите WebView и вызовите метод setContentView для визуализации вашего WebView.

Я тестировал его, но он работает, но не с другими WebView, такими как XWalkView или GeckoView.

Ответ 18

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

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

Ответ 19

попробуй это

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

}