web-dev-qa-db-fra.com

Android WebView: gestion des changements d'orientation

Le problème est la performance après la rotation. La WebView doit recharger la page, ce qui peut être un peu fastidieux.

Quelle est la meilleure façon de gérer un changement d'orientation sans recharger la page à partir du source à chaque fois?

134
glennanthonyb

Si vous ne souhaitez pas que WebView recharge les modifications d'orientation, remplacez simplement onConfigurationChanged dans votre classe d'activité: 

@Override
public void onConfigurationChanged(Configuration newConfig){        
    super.onConfigurationChanged(newConfig);
}

Et définissez l'attribut Android: configChanges dans le manifeste:

<activity Android:name="..."
          Android:label="@string/appName"
          Android:configChanges="orientation|screenSize"

pour plus d'informations, voir:
http://developer.Android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange

https://developer.Android.com/reference/Android/app/Activity.html#ConfigurationChanges

83
Totach

Edit: cette méthode ne fonctionne plus comme indiqué dans docs


Réponse originale:

Ceci peut être géré en écrasant onSaveInstanceState(Bundle outState) dans votre activité et en appelant saveState à partir de la vue Web:

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

Puis récupérez ceci dans votre onCreate après que la vue Web ait été regonflée bien sûr:

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

La meilleure réponse à cette question est de suivre la documentation Android trouvée ici Cela empêchera Webview de recharger:

<activity Android:name=".MyActivity"
      Android:configChanges="orientation|screenSize"
      Android:label="@string/app_name">

Dans l'activité:

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

J'ai essayé d'utiliser onRetainNonConfigurationInstance (en renvoyant WebView), puis de le récupérer avec getLastNonConfigurationInstance pendant onCreate et de le réaffecter.

Ne semble pas fonctionner pour l'instant. Je ne peux pas m'empêcher de penser que je suis vraiment proche cependant! Jusqu'à présent, je viens d'obtenir un WebView vide/blanc. Publier ici dans l'espoir que quelqu'un puisse aider Poussez celui-ci au-delà de la ligne d'arrivée.

Peut-être que je ne devrais pas passer le WebView. Peut-être un objet à l'intérieur de la WebView?

L’autre méthode que j’ai essayée - et non ma préférée - consiste à définir ceci dans l’activité:

 Android:configChanges="keyboardHidden|orientation"

... et ne fais pratiquement rien ici:

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

CELA fonctionne, mais cela pourrait ne pas être considéré comme une meilleure pratique .

En attendant, j'ai également un seul ImageView que je souhaite mettre à jour automatiquement en fonction de la rotation. Cela s'avère être très facile. Sous mon dossier res, j'ai drawable-land et drawable-port pour contenir les variations paysage/portrait, puis j'utilise R.drawable.myimagename pour la source de ImageView et Android "fait ce qu'il faut" - yay!

... sauf lorsque vous surveillez les changements de configuration, alors ce n'est pas le cas. :(

Donc, je suis en désaccord. Utilisez onRetainNonConfigurationInstance et la rotation ImageView, mais WebView la persistance ne fonctionne pas ... ou utilisez onConfigurationChanged et la WebView reste stable, mais le ImageView ne met pas à jour. Que faire?

Une dernière remarque: dans mon cas, forcer l'orientation n'est pas un compromis acceptable. Nous voulons vraiment soutenir gracieusement la rotation. Un peu comme comment le navigateur Android app fait! ;)

5
Joe D'Andrea

Un compromis consiste à éviter la rotation . Ajoutez ceci pour corriger l'activité uniquement pour l'orientation Portrait.

Android:screenOrientation="portrait"
3

J'apprécie que ce soit un peu tard, mais voici la réponse que j'ai utilisée lors du développement de ma solution:

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

}
3
David Passmore

Vous pouvez essayer d'utiliser onSaveInstanceState() et onRestoreInstanceState() sur votre activité pour appeler saveState(...) et restoreState(...) sur votre instance WebView. 

2
deltaearl

Mise à jour: la stratégie actuelle consiste à déplacer l'instance WebView vers la classe Application au lieu du fragment conservé lorsqu'elle est détachée et de la reconnecter lors de la reprise, comme le fait Josh ... Pour éviter que Application ne se ferme, vous devez utiliser le service de premier plan si vous souhaitez conserver l'état lorsque l'utilisateur bascule entre les applications.

Si vous utilisez des fragments, vous pouvez utiliser une instance de WebView. La vue Web sera conservée en tant que membre d'instance de la classe. Vous devez toutefois attacher la vue Web dans OnCreateView et la dissocier avant OnDestroyView pour éviter sa destruction avec le conteneur parent. 

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. Les crédits vont à kcoppock answer </ S>

Quant à 'SaveState ()', il ne fonctionne plus conformément à documentation officielle :

Veuillez noter que cette méthode ne stocke plus les données d'affichage pour cette WebView. Le comportement précédent pourrait potentiellement entraîner la fuite de fichiers si restoreState (Bundle) n'a jamais été appelé.

2
Boris Treukhov

C'est la seule chose qui a fonctionné pour moi (j'ai même utilisé l'état de l'instance de sauvegarde dans onCreateView mais ce n'était pas aussi fiable).

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

J'ai créé un détenteur Singleton pour l'état de la WebView. L'état est préservé tant que le processus de l'application existe.

EDIT: La loadDataWithBaseURL n'était pas nécessaire, cela fonctionnait aussi bien avec juste

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

Bien que j'ai lu cela ne fonctionne pas nécessairement bien avec les cookies.

1
EpicPandaForce

Écrivez simplement les lignes de code suivantes dans votre fichier manifeste - rien d’autre. Ça marche vraiment:

<activity Android:name=".YourActivity"
  Android:configChanges="orientation|screenSize"
  Android:label="@string/application_name">
1
Eddie Valmont
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);    
}

@Override
protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);    
}

Ces méthodes peuvent être écrasées sur n'importe quelle activité. Elles vous permettent simplement de sauvegarder et de restaurer des valeurs chaque fois qu'une activité est créée/détruite. Lorsque l'orientation de l'écran change, l'activité est détruite et recréée en arrière-plan. Vous pouvez donc utiliser ces méthodes. stocker/restaurer temporairement les états pendant le changement.

Vous devriez examiner de plus près les deux méthodes suivantes et voir si cela convient à votre solution.

http://developer.Android.com/reference/Android/app/Activity.html

1
Chiwai Chan

La seule chose à faire est d’ajouter ce code à votre fichier manifeste:

<activity Android:name=".YourActivity"
      Android:configChanges="orientation|screenSize"
      Android:label="@string/application_name">
1
Les Paul

Meilleure façon de gérer les changements d'orientation et d'empêcher le rechargement de WebView lors de la rotation.

@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}

Dans cet esprit, pour éviter d'appeler onCreate () à chaque fois que vous changez d'orientation, vous devez ajouter Android:configChanges="orientation|screenSize" to the AndroidManifest.

ou juste ..

Android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"`
1
Amit raj

Vous devriez essayer ceci:

  1. Créez un service, à l'intérieur de ce service, créez votre WebView.
  2. Lancez le service à partir de votre activité et liez-vous à celle-ci.
  3. Dans la méthode onServiceConnected, récupérez WebView et appelez la méthode setContentView pour restituer votre WebView.

Je l'ai testé et cela fonctionne, mais pas avec d'autres WebViews comme XWalkView ou GeckoView.

0
Ramdane Oualitsen

essaye ça

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

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

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

Cette page résout mon problème mais je dois apporter une légère modification à la première:

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

Cette partie a un léger problème pour moi cela. Sur la deuxième orientation, modifier l'application terminée par un pointeur nul

en utilisant cela cela a fonctionné pour moi:

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