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

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

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

Если вы не хотите, чтобы 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

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


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

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

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

Лучший ответ на этот вопрос – следующая документация от Google, найденная здесь. В основном это предотвратит перезагрузку Webview:

 <activity android:name=".MyActivity" android:configChanges="orientation|screenSize" android:label="@string/app_name"> Размер <activity android:name=".MyActivity" android:configChanges="orientation|screenSize" android:label="@string/app_name"> 

В деятельности:

 @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 ), а затем возвращаю его с помощью 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! 😉

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

 android:screenOrientation="portrait" 

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

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

В ДЕЯТЕЛЬНОСТИ:

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

Обновление: текущая стратегия заключается в перемещении экземпляра 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(); } } 

PS Кредиты идут в ответ kcoppock

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

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

 @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

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

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

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

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

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

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

Используя это, это сработало для меня:

  @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. Это позволяет сохранить историю навигации и положение прокрутки при изменении конфигурации.

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

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

Адаптировано отсюда

Надеюсь это поможет 🙂

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

 <activity android:name=".YourActivity" android:configChanges="orientation|screenSize" android:label="@string/application_name"> Размер <activity android:name=".YourActivity" android:configChanges="orientation|screenSize" android:label="@string/application_name"> 

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

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

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