programacion - Android WebView: manejo de los cambios de orientación
manual de android en pdf (19)
Actualización: la estrategia actual es mover la instancia de WebView a la clase de Aplicación en lugar de un fragmento retenido cuando se separa y volver a adjuntar en la hoja de vida como lo hace Josh. Para evitar que la Aplicación se cierre, debe usar el servicio de primer plano, si desea conservar el estado cuando el usuario cambie de aplicación.
Si está utilizando fragmentos, puede usar la instancia de retención de WebView. La vista web se conservará como miembro de instancia de la clase. Sin embargo, debe adjuntar la vista web en OnCreateView y desacoplar antes de OnDestroyView para evitar que se destruya con el contenedor principal.
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();
}
}
Créditos de PS van a la respuesta de Kcoppock
En cuanto a ''SaveState ()'' ya no funciona según la documentación oficial :
Tenga en cuenta que este método ya no almacena los datos de visualización para este WebView. El comportamiento anterior podría potencialmente perder archivos si nunca se llamó a restoreState (Bundle).
El problema es el rendimiento después de la rotación. WebView tiene que volver a cargar la página, lo que puede ser un poco tedioso.
¿Cuál es la mejor manera de manejar un cambio de orientación sin volver a cargar la página desde la fuente cada vez?
Aprecio que esto sea un poco tarde, sin embargo, esta es la respuesta que utilicé al desarrollar mi solución:
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);
}
}
Adaptado de here
Espero que esto ayude :)
Deberías probar esto:
- Cree un servicio, dentro de ese servicio, cree su WebView.
- Comience el servicio de su actividad y comprométalo.
- En el método
onServiceConnected
, obtenga WebView y llame al métodosetContentView
para representar su WebView.
Lo probé y funciona, pero no con otras WebView como XWalkView o GeckoView.
Editar: Este método ya no funciona como se indica en los docs
Respuesta original:
Esto se puede manejar sobrescribiendo onSaveInstanceState(Bundle outState)
en su actividad y llamando a saveState
desde la vista web:
protected void onSaveInstanceState(Bundle outState) {
webView.saveState(outState);
}
Luego, recupere esto en su onCreate después de que la vista web se haya vuelto a inflar por supuesto:
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blah);
if (savedInstanceState != null)
((WebView)findViewById(R.id.webview)).restoreState(savedInstanceState);
}
Es 2015, y muchas personas están buscando una solución que todavía funcione en los teléfonos Jellybean, KK y Lollipop. Después de muchas dificultades, encontré una forma de preservar la vista web intacta después de cambiar de orientación. Mi estrategia es básicamente almacenar la vista web en una variable estática separada en otra clase. Luego, si ocurre la rotación, desvinculo la vista web de la actividad, espero a que la orientación termine y vuelvo a adjuntar la vista web a la actividad. Por ejemplo ... primero pon esto en tu MANIFIESTO (tecladoHidden y el teclado son opcionales):
<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>
En una CLASE DE APLICACIÓN SEPARADA, escriba:
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();
}
}
En la ACTIVIDAD poner:
@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);
}
Finalmente, el XML simple:
<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>
Hay varias cosas que podrían mejorarse en mi solución, pero ya pasé mucho tiempo, por ejemplo: una forma más corta de validar si la Actividad se ha lanzado por primera vez en lugar de usar el almacenamiento de Preferencias Compartidas. Este enfoque le preserva la vista web intacta (afaik), sus cuadros de texto, etiquetas, UI, variables de JavaScript y estados de navegación que no se reflejan en la url.
Esta página soluciona mi problema, pero tengo que hacer un pequeño cambio en la inicial:
protected void onSaveInstanceState(Bundle outState) {
webView.saveState(outState);
}
Esta parte tiene un pequeño problema para mí esto. En la segunda orientación, cambie la aplicación terminada con un puntero nulo
usando esto funcionó para mí:
@Override
protected void onSaveInstanceState(Bundle outState ){
((WebView) findViewById(R.id.webview)).saveState(outState);
}
Esto es lo único que funcionó para mí (incluso utilicé el estado de guardar la instancia en onCreateView
pero no fue tan confiable).
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;
}
}
Hice un titular de Singleton para el estado de WebView. El estado se conserva mientras exista el proceso de la aplicación.
EDITAR: loadDataWithBaseURL
no era necesario, funcionó igual de bien con solo
//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());
}
Aunque leo esto, no necesariamente funciona bien con las cookies.
Intenté usar onRetainNonConfigurationInstance (devolver el WebView ), luego recuperarlo con getLastNonConfigurationInstance durante onCreate y reasignarlo.
No parece funcionar todavía. ¡No puedo evitar pensar que estoy muy cerca! Hasta el momento, me acabo de dar un WebView en blanco / fondo blanco. Publicando aquí con la esperanza de que alguien pueda ayudar a que este pase la línea de meta.
Tal vez no debería pasar el WebView . Tal vez un objeto desde el WebView ?
El otro método que probé, no mi favorito, es establecer esto en la actividad:
android:configChanges="keyboardHidden|orientation"
... y luego no haces casi nada aquí:
@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.
}
ESO funciona, pero podría no ser considerado una mejor práctica .
Mientras tanto, también tengo un solo ImageView que quiero actualizar automágicamente según la rotación. Esto resulta ser muy fácil. Debajo de mi carpeta de res
, tengo drawable-land
y drawable-port
para guardar variaciones de paisaje / retrato, luego uso R.drawable.myimagename
para la fuente de ImageView y Android "hace lo correcto" - ¡yay!
... excepto cuando mira los cambios de configuración, entonces no es así. :(
Entonces estoy en desacuerdo Utilice onRetainNonConfigurationInstance y la rotación de ImageView funciona, pero la persistencia de WebView no ... o use onConfigurationChanged y WebView se mantiene estable, pero ImageView no se actualiza. ¿Qué hacer?
Una última nota: en mi caso, forzar la orientación no es un compromiso aceptable. Realmente queremos apoyar con gracia la rotación. ¡Algo así como la aplicación del navegador Android! ;)
La mejor forma de manejar cambios de orientación y evitar la recarga de WebView en Girar.
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}
Con eso en mente, para evitar que se llame a onCreate () cada vez que cambie de orientación, debería agregar android:configChanges="orientation|screenSize" to the AndroidManifest.
o solo ..
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"`
La mejor respuesta a esto es seguir la documentación de Android que se encuentra http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange Básicamente, esto evitará que Webview se vuelva a cargar:
<activity android:name=".MyActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name">
En la actividad:
@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();
}
}
La mejor solución que he encontrado para hacer esto sin tener que filtrar la referencia de Activity
anterior y sin establecer los configChanges
... es usar un MutableContextWrapper .
Lo he implementado aquí: https://github.com/slightfoot/android-web-wrapper/blob/48cb3c48c457d889fc16b4e3eba1c9e925f42cfb/WebWrapper/src/com/example/webwrapper/BrowserActivity.java
Lo único que debe hacer es agregar este código a su archivo de manifiesto:
<activity android:name=".YourActivity"
android:configChanges="orientation|screenSize"
android:label="@string/application_name">
Me gusta esta solución here
Según esto, reutilizamos la misma instancia de WebView. Permite guardar el historial de navegación y la posición de desplazamiento en el cambio de configuración.
Puede intentar usar onSaveInstanceState()
y onRestoreInstanceState()
en su Activity para llamar a saveState(...)
y restoreState(...)
en su instancia de WebView.
Si no desea que WebView vuelva a cargarse en los cambios de orientación, simplemente anule onConfigurationChanged en su clase de actividad:
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}
Y configure el atributo android: configChanges en el manifiesto:
<activity android:name="..."
android:label="@string/appName"
android:configChanges="orientation|screenSize"
para más información ver:
http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange
https://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges
Simplemente escriba las siguientes líneas de código en su archivo Manifiesto, nada más. Realmente funciona:
<activity android:name=".YourActivity"
android:configChanges="orientation|screenSize"
android:label="@string/application_name">
Un compromiso es evitar la rotación. Agregue esto para arreglar la actividad solo para orientación vertical.
android:screenOrientation="portrait"
@Override
protected void onSaveInstanceState(Bundle outState )
{
super.onSaveInstanceState(outState);
webView.saveState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
webView.restoreState(savedInstanceState);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
}
Estos métodos pueden anularse en cualquier actividad, básicamente te permite guardar y restaurar valores cada vez que se crea / destruye una actividad, cuando la orientación de la pantalla cambia, la actividad se destruye y se recrea en el fondo, por lo que podrías utilizar estos métodos para almacenar / restaurar temporalmente estados durante el cambio.
Debería echar un vistazo más profundo a los dos métodos siguientes y ver si se ajusta a su solución.
http://developer.android.com/reference/android/app/Activity.html