Утечка памяти в WebView



у меня есть действие с использованием XML-макета, в который встроен WebView. Я вообще не использую WebView в своем коде активности, все, что он делает, сидит там в моем XML-макете и виден.



Теперь, когда я заканчиваю работу, я обнаруживаю, что моя деятельность не очищается от памяти. (Я проверяю через дамп hprof). Действие полностью очищается, хотя, если я удалю WebView из макета xml.



Я уже пробовал



webView.destroy();
webView = null;


в onDestroy() моей деятельности, но это не очень помогает.



в моем дампе hprof моя активность (названная "браузер") имеет следующие оставшиеся корни GC (после вызова destroy() на это):



com.myapp.android.activity.browser.Browser
- mContext of android.webkit.JWebCoreJavaBridge
- sJavaBridge of android.webkit.BrowserFrame [Class]
- mContext of android.webkit.PluginManager
- mInstance of android.webkit.PluginManager [Class]


я обнаружил, что другой разработчик испытал подобную вещь, см. ответ Филипе Абрантеса на:
http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/




действительно очень интересный пост.
В последнее время мне было очень тяжело
устранение утечки памяти в моем
Приложение для Android. В итоге получилось
что мой XML-макет включал веб-представление
компонент, который, даже если не используется, был
предотвращение память
g-собранный после вращений экрана / app
перезагрузка... это ошибка текущего
реализации, или есть что-то
конкретно, что нужно делать, когда
используя WebView с




Теперь, к сожалению, в блоге или в списке рассылки еще нет ответа на этот вопрос. Поэтому мне интересно, это ошибка в SDK (возможно, похожая на ошибку MapView, как сообщалось http://code.google.com/p/android/issues/detail?id=2181) или как получить активность полностью из памяти с WebView embedded?

529   9  

9 ответов:

из приведенных выше комментариев и дальнейших тестов я делаю вывод, что проблема заключается в ошибке в SDK: при создании WebView через XML-макет действие передается как контекст для WebView, а не контекст приложения. При завершении действия WebView все еще сохраняет ссылки на действие, поэтому действие не удаляется из памяти. Я подала отчет об ошибке для этого , см. ссылку в комментарии выше.

webView = new WebView(getApplicationContext());

обратите внимание, что это только обходной путь работает для определенных случаев использования, т. е. если вам просто нужно отобразить html в webview, без каких-либо href-ссылок или ссылок на диалоги и т. д. См. комментарии ниже.

мне повезло с этим методом:

поместите FrameLayout в свой xml в качестве контейнера, давайте назовем его web_container. Затем программно объявите WebView, как упоминалось выше. onDestroy, удалите его из FrameLayout.

скажем, это где-то в вашем XML-файле макета, например layout/your_layout.xml

<FrameLayout
    android:id="@+id/web_container"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"/>

затем после раздувания представления добавьте экземпляр WebView с контекстом приложения в свой FrameLayout. onDestroy, звоните метод WebView destroy и удалить его из иерархии представлений или вы будете протекать.

public class TestActivity extends Activity {
    private FrameLayout mWebContainer;
    private WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.your_layout);

        mWebContainer = (FrameLayout) findViewById(R.id.web_container);
        mWebView = new WebView(getApplicationContext());
        mWebContainer.addView(mWebView);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mWebContainer.removeAllViews();
        mWebView.destroy();
    }
}

также FrameLayout, а также layout_width и layout_height были произвольно скопированы из существующего проекта, где он работает. Я предполагаю, что другая ViewGroup будет работать, и я уверен, что другие размеры макета будут работать.

это решение также работает с RelativeLayout вместо FrameLayout.

вот подкласс WebView, который использует вышеупомянутый хак, чтобы легко избежать утечек памяти:

package com.mycompany.view;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.webkit.WebViewClient;

/**
 * see http://stackoverflow.com/questions/3130654/memory-leak-in-webview and http://code.google.com/p/android/issues/detail?id=9375
 * Note that the bug does NOT appear to be fixed in android 2.2 as romain claims
 *
 * Also, you must call {@link #destroy()} from your activity's onDestroy method.
 */
public class NonLeakingWebView extends WebView {
    private static Field sConfigCallback;

    static {
        try {
            sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
            sConfigCallback.setAccessible(true);
        } catch (Exception e) {
            // ignored
        }

    }


    public NonLeakingWebView(Context context) {
        super(context.getApplicationContext());
        setWebViewClient( new MyWebViewClient((Activity)context) );
    }

    public NonLeakingWebView(Context context, AttributeSet attrs) {
        super(context.getApplicationContext(), attrs);
        setWebViewClient(new MyWebViewClient((Activity)context));
    }

    public NonLeakingWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context.getApplicationContext(), attrs, defStyle);
        setWebViewClient(new MyWebViewClient((Activity)context));
    }

    @Override
    public void destroy() {
        super.destroy();

        try {
            if( sConfigCallback!=null )
                sConfigCallback.set(null, null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    protected static class MyWebViewClient extends WebViewClient {
        protected WeakReference<Activity> activityRef;

        public MyWebViewClient( Activity activity ) {
            this.activityRef = new WeakReference<Activity>(activity);
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            try {
                final Activity activity = activityRef.get();
                if( activity!=null )
                    activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
            }catch( RuntimeException ignored ) {
                // ignore any url parsing exceptions
            }
            return true;
        }
    }
}

чтобы использовать его, просто замените WebView на NonLeakingWebView в ваших макетах

                    <com.mycompany.view.NonLeakingWebView
                            android:layout_width="fill_parent"
                            android:layout_height="wrap_content"
                            ...
                            />

тогда обязательно позвоните NonLeakingWebView.destroy() из метода onDestroy вашей деятельности.

обратите внимание, что этот webclient должен обрабатывать общие случаи, но он может быть не таким полнофункциональным, как обычный webclient. Я не тестировал его для таких вещей, как flash, например.

на основе ответа пользователя 1668939 на этот пост (https://stackoverflow.com/a/12408703/1369016), Вот как я исправил утечку WebView внутри фрагмента:

@Override
public void onDetach(){

    super.onDetach();

    webView.removeAllViews();
    webView.destroy();
}

отличие от ответа user1668939 заключается в том, что я не использовал никаких заполнителей. Просто вызов removeAllViews () в самой ссылке WebvView сделал трюк.

обновление####

Если вы похожи на меня и есть WebViews внутри нескольких фрагментов (и вы делаете не хотите повторять приведенный выше код во всех ваших фрагментах), вы можете использовать отражение для его решения. Просто сделайте ваши фрагменты расширить этот:

public class FragmentWebViewLeakFree extends Fragment{

    @Override
    public void onDetach(){

        super.onDetach();

        try {
            Field fieldWebView = this.getClass().getDeclaredField("webView");
            fieldWebView.setAccessible(true);
            WebView webView = (WebView) fieldWebView.get(this);
            webView.removeAllViews();
            webView.destroy();

        }catch (NoSuchFieldException e) {
            e.printStackTrace();

        }catch (IllegalArgumentException e) {
            e.printStackTrace();

        }catch (IllegalAccessException e) {
            e.printStackTrace();

        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

Я предполагаю, что вы называете свое поле WebView "webView" (и да, ваша ссылка на WebView должна быть полем, к сожалению). Я не нашел другого способа сделать это, который был бы независим от имени Поля (если я не пройду через все поля и не проверю, является ли каждый из них классом WebView, который я не хочу делать для проблем с производительностью).

после прочтения http://code.google.com/p/android/issues/detail?id=9375, может быть, мы могли бы использовать отражение, чтобы установить ConfigCallback.mWindowManager для null на активности.onDestroy и восстановить его на активность.onCreate. Я не уверен, хотя, если это требует некоторых разрешений или нарушает какую-либо политику. Это зависит от android.реализация webkit и она может потерпеть неудачу на более поздних версиях Android.

public void setConfigCallback(WindowManager windowManager) {
    try {
        Field field = WebView.class.getDeclaredField("mWebViewCore");
        field = field.getType().getDeclaredField("mBrowserFrame");
        field = field.getType().getDeclaredField("sConfigCallback");
        field.setAccessible(true);
        Object configCallback = field.get(null);

        if (null == configCallback) {
            return;
        }

        field = field.getType().getDeclaredField("mWindowManager");
        field.setAccessible(true);
        field.set(configCallback, windowManager);
    } catch(Exception e) {
    }
}

вызов вышеуказанного метода в Activity

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
}

public void onDestroy() {
    setConfigCallback(null);
    super.onDestroy();
}

я исправил проблему утечки памяти разочарования Webview следующим образом:

(Я надеюсь, что это может помочь многим)

основы:

  • для создания веб-представления необходима ссылка (скажем, действие).
  • убить процесс:

android.os.Process.killProcess(android.os.Process.myPid()); можно назвать.

перелом:

по умолчанию все действия выполняются в одном процессе в одном приложении. (процесс определяется имя пакета.) Но:

различные процессы могут быть созданы в одном приложении.

устранение: Если для действия создается другой процесс, его контекст можно использовать для создания веб-представления. И когда этот процесс убит, все компоненты, имеющие ссылки на эту деятельность (webview в данном случае), убиты, и основная желаемая часть:

GC называется принудительно собирать этот мусор (webview).

код для справки: (один простой случай)

всего два вида деятельности: скажем, A & B

файл манифеста:

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:process="com.processkill.p1" // can be given any name 
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.processkill.A"
            android:process="com.processkill.p2"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name="com.processkill.B"
            android:process="com.processkill.p3"
            android:label="@string/app_name" >
        </activity>
    </application>

начните A, затем B

A > B

B создается с WebView embedded.

когда backKey нажата на activity B, onDestroy называется:

@Override
    public void onDestroy() {
        android.os.Process.killProcess(android.os.Process.myPid());
        super.onDestroy();
    }

и это убивает текущий процесс, т. е. com.процесс убивает.p3

и забирает webview, на который ссылается на него

Примечание: будьте особенно осторожны при использовании этой команды убить. (не рекомендуется по понятным причинам). Не реализуйте статический метод в activity (activity B в этом случае). Не используйте никаких ссылок на эту деятельность из любой другой (так как она будет убита и больше не доступна).

вы можете попробовать поместить веб-активность в отдельный процесс и выйти, когда активность будет уничтожена, если обработка нескольких процессов не является для вас большим усилием.

вам нужно удалить WebView из родительского представления перед вызовом WebView.destroy().

WebView destroy() комментарий - "Этот метод должен быть вызван после того, как этот WebView был удален из системы представления."

существует проблема с "контекст приложения" обходной путь: сбой, когда WebView пытается показать любой диалог. Например, диалог" запомнить пароль " при подаче форм входа/пропуска (любые другие случаи?).

Это может быть исправлено с WebView настройки' setSavePassword(false) для случая "запомнить пароль".

Comments

    Ничего не найдено.