Android WebView: обработка изменений ориентации
Проблема заключается в производительности после ротации. WebView должен перезагрузить страницу, что может быть немного утомительно.
Каков наилучший способ обработки изменения ориентации без перезагрузки страницы из источника каждый раз?
20 ответов
Если вы не хотите, чтобы 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
https://developer.android.com/reference/android/app/Activity.html
Изменить: этот метод больше не работает, как указано в документации
Оригинальный ответ:
Это может быть обработано перезаписью 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);
}
Лучшим ответом на это будет следующая документация Android, найденная здесь. В основном это предотвратит перезагрузку Webview:
<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 "делает правильные вещи" - ура!
... кроме случаев, когда вы смотрите изменения конфигурации, тогда это не так.:(
Так что я в ссоре. Используйте onRetainNonConfigurationInstance, и ротация ImageView работает, но постоянство WebView не... или используйте onConfigurationChanged, и WebView остается стабильным, но ImageView не обновляется. Что делать?
Последнее замечание: в моем случае форсирование ориентации не является приемлемым компромиссом. Мы действительно хотим изящно поддержать ротацию. Вроде как, как работает приложение Android Browser!;)
Я понимаю, что это немного поздно, но это тот ответ, который я использовал при разработке своего решения:
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"
Просто напишите следующие строки кода в вашем файле манифеста - больше ничего. Это действительно работает:
<activity android:name=".YourActivity"
android:configChanges="orientation|screenSize"
android:label="@string/application_name">
Обновление: текущая стратегия состоит в том, чтобы переместить экземпляр WebView в класс приложения вместо сохраненного фрагмента, когда он отсоединен, и снова присоединить его к резюме, как это делает Джош. Чтобы предотвратить закрытие приложения, вы должны использовать службу переднего плана, если вы хотите сохранить состояние, когда пользователь переключается между приложениями.
Если вы используете фрагменты, вы можете использовать сохранить экземпляр 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).
Вы можете попробовать использовать 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>
В моем решении есть несколько вещей, которые можно улучшить, но я уже потратил много времени, например: более короткий способ проверки, был ли Activity запущен в первый раз вместо использования хранилища SharedPreferences. Этот подход сохраняет веб-просмотр без изменений (afaik), его текстовые поля, метки, пользовательский интерфейс, переменные javascript и состояния навигации, которые не отражаются в URL.
Единственное, что вам нужно сделать, это добавить этот код в файл манифеста:
<activity android:name=".YourActivity"
android:configChanges="orientation|screenSize"
android:label="@string/application_name">
Лучшее решение, которое я нашел, чтобы сделать это без утечки предыдущего Activity
ссылка и без настройки configChanges
..это использовать MutableContextWrapper.
Я реализовал это здесь: https://github.com/slightfoot/android-web-wrapper/blob/48cb3c48c457d889fc16b4e3eba1c9e925f42cfb/WebWrapper/src/com/example/webwrapper/BrowserActivity.java
Попробуй это
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;
}
}
}
Это единственное, что сработало для меня (я даже использовал состояние сохранения экземпляра в 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. Состояние сохраняется до тех пор, пока существует процесс приложения.
РЕДАКТИРОВАТЬ: 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());
}
Хотя я читаю, это не обязательно хорошо работает с куки.
@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
Вы должны попробовать это:
- Создайте сервис, внутри этого сервиса создайте свой WebView.
- Запустите сервис из своей деятельности и привяжите к нему.
- в
onServiceConnected
метод, получить WebView и вызватьsetContentView
метод для визуализации вашего WebView.
Я протестировал его, и он работает, но не с другими WebViews, такими как XWalkView или GeckoView.
Эта страница решает мою проблему, но я должен сделать небольшое изменение в начальной:
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. Это позволяет сохранить историю навигации и положение прокрутки при изменении конфигурации.
@Override
protected void onSaveInstanceState(Bundle outState )
{
super.onSaveInstanceState(outState);
webView.saveState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
webView.restoreState(savedInstanceState);
}