Problemem jest wydajność po rotacji. WebView musi ponownie załadować stronę, co może być trochę uciążliwe.
Jaki jest najlepszy sposób obsługi zmiany orientacji bez ponownego ładowania strony ze źródła za każdym razem?
Problemem jest wydajność po rotacji. WebView musi ponownie załadować stronę, co może być trochę uciążliwe.
Jaki jest najlepszy sposób obsługi zmiany orientacji bez ponownego ładowania strony ze źródła za każdym razem?
Odpowiedzi:
Jeśli nie chcesz, aby WebView ponownie ładował się po zmianach orientacji, po prostu zastąp onConfigurationChanged w swojej klasie Activity:
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}
I ustaw atrybut android: configChanges w manifeście:
<activity android:name="..."
android:label="@string/appName"
android:configChanges="orientation|screenSize"
aby uzyskać więcej informacji, zobacz:
http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange
https://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges
configChanges
atrybut zostanie dodany do podklasy Działanie 2) Jeśli twoja aplikacja zależy od wielu projektów, configChanges
atrybut zostanie dodany do manifestu tego w wierzchołek drzewa zależności (który może nie być projektem zawierającym klasę Activity).
onConfigurationChanged
nadpisanie metody jest bezużyteczne.
Edycja: ta metoda nie działa już zgodnie z opisem w dokumentacji
Oryginalna odpowiedź:
Można to zrobić, nadpisując onSaveInstanceState(Bundle outState)
swoją aktywność i dzwoniąc saveState
z widoku internetowego:
protected void onSaveInstanceState(Bundle outState) {
webView.saveState(outState);
}
Następnie odzyskaj to w swoim onCreate po ponownym zawyżeniu widoku internetowego:
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blah);
if (savedInstanceState != null)
((WebView)findViewById(R.id.webview)).restoreState(savedInstanceState);
}
Najlepszą odpowiedzią na to jest poniższa dokumentacja Androida, którą znajdziesz tutaj. Zasadniczo zapobiegnie to ponownemu załadowaniu Webview:
<activity android:name=".MyActivity"
android:configChanges="keyboardHidden|orientation|screenSize|layoutDirection|uiMode"
android:label="@string/app_name">
Opcjonalnie możesz naprawić anomalie (jeśli występują), nadpisując onConfigurationChanged
działanie:
@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();
}
}
Próbowałem użyć onRetainNonConfigurationInstance (zwracając WebView ), a następnie odzyskać go za pomocą getLastNonConfigurationInstance podczas onCreate i ponownie przypisać.
Wydaje się, że jeszcze nie działa. Nie mogę pomóc, ale myślę, że jestem naprawdę blisko! Jak dotąd, po prostu otrzymuję WebView z pustym / białym tłem zamiast tego . Publikowanie tutaj w nadziei, że ktoś może pomóc przepchnąć to poza linię mety.
Może nie powinienem przekazywać WebView . Być może obiekt z WebView ?
Inną metodą, którą wypróbowałem - nie moją ulubioną - jest ustawienie tego w ćwiczeniu:
android:configChanges="keyboardHidden|orientation"
... a potem prawie nic tutaj nie rób:
@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.
}
TO działa, ale może nie być uważane za najlepszą praktykę .
W międzyczasie mam też pojedynczy ImageView, który chcę automagicznie aktualizować w zależności od rotacji. Okazuje się, że jest to bardzo łatwe. W moim res
folderze mam drawable-land
i drawable-port
przechowuję odmiany w poziomie / pionie, a następnie używam R.drawable.myimagename
do ImageView źródła , a Android „robi to, co należy” - yay!
... z wyjątkiem sytuacji, gdy obserwujesz zmiany konfiguracji, wtedy tak się nie dzieje. :(
Więc jestem w sprzeczności. Użyj onRetainNonConfigurationInstance i rotacji ImageView działa, ale trwałość WebView nie ... lub użyj onConfigurationChanged, a WebView pozostaje stabilny, ale ImageView nie jest aktualizowany. Co robić?
Ostatnia uwaga: w moim przypadku wymuszanie orientacji nie jest akceptowalnym kompromisem. Naprawdę chcemy z wdziękiem wspierać rotację. Trochę jak aplikacja Android Browser! ;)
Najlepszy sposób na obsługę zmian orientacji i zapobieganie ponownemu ładowaniu WebView podczas obracania.
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}
Mając to na uwadze, aby zapobiec wywoływaniu onCreate () za każdym razem, gdy zmieniasz orientację, musisz dodać android:configChanges="orientation|screenSize" to the AndroidManifest.
Lub tylko ..
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"`
Po prostu napisz następujące linie kodu w swoim pliku manifestu - nic więcej. To naprawdę działa:
<activity android:name=".YourActivity"
android:configChanges="orientation|screenSize"
android:label="@string/application_name">
Doceniam, że jest trochę za późno, ale tak brzmi odpowiedź, której użyłem przy tworzeniu mojego rozwiązania:
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);
}
}
Jest rok 2015 i wiele osób szuka rozwiązania, które nadal działa na telefonach Jellybean, KK i Lollipop. Po wielu zmaganiach znalazłem sposób na zachowanie widoku internetowego w nienaruszonym stanie po zmianie orientacji. Moja strategia polega w zasadzie na przechowywaniu widoku internetowego w oddzielnej zmiennej statycznej w innej klasie. Następnie, jeśli nastąpi obrót, odłączam widok internetowy od działania, czekam na zakończenie orientacji i ponownie przyłączam widok internetowy do działania. Na przykład ... najpierw umieść to w swoim MANIFESTIE (klawiaturaHidden i klawiatura są opcjonalne):
<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>
W ODDZIELNEJ KLASIE ZASTOSOWAŃ umieść:
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();
}
}
W ACTIVITY wpisz:
@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);
}
Wreszcie prosty 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>
Jest kilka rzeczy, które można by ulepszyć w moim rozwiązaniu, ale spędziłem już za dużo czasu, na przykład: krótszy sposób sprawdzenia, czy działanie zostało uruchomione po raz pierwszy, zamiast korzystania z magazynu SharedPreferences. Takie podejście zachowuje widok sieci Web w stanie nienaruszonym (afaik), jego pola tekstowe, etykiety, interfejs użytkownika, zmienne javascript i stany nawigacji, które nie są odzwierciedlane w adresie URL.
Aktualizacja: obecna strategia polega na przeniesieniu instancji WebView do klasy Application zamiast zachowywanego fragmentu po odłączeniu i ponownym dołączeniu przy wznowieniu, tak jak robi to Josh. Aby zapobiec zamykaniu aplikacji, należy używać usługi pierwszego planu, jeśli chcesz zachować stan, gdy użytkownik przełącza się między aplikacjami.
Jeśli używasz fragmentów, możesz użyć zachowywania wystąpienia WebView. Widok sieciowy zostanie zachowany jako element członkowski instancji klasy. Należy jednak dołączyć widok sieci Web w OnCreateView i odłączyć przed OnDestroyView, aby zapobiec jego zniszczeniu za pomocą kontenera nadrzędnego.
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();
}
}
Kredyty PS przejdź do odpowiedzi kcoppock
Jeśli chodzi o 'SaveState ()', nie działa już zgodnie z oficjalną dokumentacją :
Należy pamiętać, że ta metoda nie przechowuje już danych wyświetlania dla tego WebView. Poprzednie zachowanie mogło potencjalnie spowodować wyciek plików, jeśli funkcja restoreState (pakiet) nigdy nie została wywołana.
setRetainInstance
zachowuje fragment, widok (a tym samym WebView) nadal jest niszczony.
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
}
Metody te można nadpisać w dowolnym działaniu, w zasadzie pozwala to na zapisywanie i przywracanie wartości za każdym razem, gdy działanie jest tworzone / niszczone, gdy zmienia się orientacja ekranu, działanie zostaje zniszczone i odtworzone w tle, więc możesz użyć tych metod do tymczasowego przechowywania / przywracania stanów podczas zmiany.
Powinieneś dokładniej przyjrzeć się dwóm następującym metodom i sprawdzić, czy pasuje do Twojego rozwiązania.
http://developer.android.com/reference/android/app/Activity.html
Najlepszym rozwiązaniem, jakie znalazłem, aby to zrobić bez wycieku poprzedniego Activity
odniesienia i bez ustawiania configChanges
.., jest użycie MutableContextWrapper .
Zaimplementowałem to tutaj: https://github.com/slightfoot/android-web-wrapper/blob/48cb3c48c457d889fc16b4e3eba1c9e925f42cfb/WebWrapper/src/com/example/webwrapper/BrowserActivity.java
To jedyna rzecz, która działała dla mnie (użyłem nawet stanu zapisywania instancji w, onCreateView
ale nie był on tak niezawodny).
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;
}
}
Zrobiłem uchwyt Singleton dla stanu WebView. Stan jest zachowywany tak długo, jak trwa proces aplikacji.
EDYCJA: Nie loadDataWithBaseURL
było to konieczne, działało równie dobrze z just
//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());
}
Chociaż czytałem, to niekoniecznie działa dobrze z plikami cookie.
Spróbuj tego
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;
}
}
}
Ta strona rozwiązuje mój problem, ale muszę wprowadzić niewielką zmianę w pierwszej:
protected void onSaveInstanceState(Bundle outState) {
webView.saveState(outState);
}
Ta porcja ma dla mnie mały problem z tym. W drugiej orientacji zmień aplikację zakończoną wskaźnikiem zerowym
używając tego, zadziałało dla mnie:
@Override
protected void onSaveInstanceState(Bundle outState ){
((WebView) findViewById(R.id.webview)).saveState(outState);
}
Powinieneś spróbować tego:
onServiceConnected
metodzie pobierz WebView i wywołaj setContentView
metodę, aby renderować swój WebView.Przetestowałem to i działa, ale nie z innymi WebView, takimi jak XWalkView czy GeckoView.
@Override
protected void onSaveInstanceState(Bundle outState )
{
super.onSaveInstanceState(outState);
webView.saveState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
webView.restoreState(savedInstanceState);
}
Podoba mi się to rozwiązanie http://www.devahead.com/blog/2012/01/preserving-the-state-of-an-android-webview-on-screen-orientation-change/
Zgodnie z nią ponownie używamy tej samej instancji WebView. Pozwala na zapisanie historii nawigacji i przewijanie pozycji przy zmianie konfiguracji.