Через некоторое время мое приложение зависает во время прокрутки WebView, говоря "не удалось заблокировать поверхность",

Мое Android-приложение состоит из нескольких действий, каждый из которых отвечает за один фрагмент (пока). Мои фрагменты обычно отображаются/прикрепляются примерно так:

mTopicFragment = (TopicFragment)getSupportFragmentManager().findFragmentByTag("topic");
if(mTopicFragment == null)
    mTopicFragment = TopicFragment.newInstance(bid, page, pid);

if (savedInstanceState == null) {
    getSupportFragmentManager().beginTransaction()
            .add(R.id.content, mTopicFragment, "topic")
            .commit();
}

TopicFragment содержит WebView, отображающий некоторые элементы HTML и CSS/JS. После некоторого времени просмотра приложения прокрутка в одном из этих TopicFragment WebViews становится медленной, и в конечном итоге приложение полностью замораживается. В журнале ADB указано следующее исключение:

12-12 22:49:33.931  12582-12582/com.mde.potdroid3 W/Adreno-EGLSUB﹕ <DequeueBuffer:606>: dequeue native buffer fail: Unknown error 2147483646, buffer=0x0, handle=0x0
12-12 22:49:33.941  12582-12582/com.mde.potdroid3 W/Adreno-EGLSUB﹕ <DequeueBuffer:606>: dequeue native buffer fail: Invalid argument, buffer=0x0, handle=0x0
12-12 22:49:33.941  12582-12582/com.mde.potdroid3 W/Adreno-ES20﹕ <gl2_surface_swap:43>: GL_OUT_OF_MEMORY
12-12 22:49:33.941  12582-12582/com.mde.potdroid3 W/Adreno-EGL﹕ <qeglDrvAPI_eglSwapBuffers:3597>: EGL_BAD_SURFACE
12-12 22:49:33.941  12582-12582/com.mde.potdroid3 W/HardwareRenderer﹕ EGL error: EGL_BAD_SURFACE
12-12 22:49:33.951  12582-12582/com.mde.potdroid3 W/HardwareRenderer﹕ Mountain View, we've had a problem here. Switching back to software rendering.

12-12 22:20:04.461  10081-10081/com.mde.potdroid3 E/Surface﹕ dequeueBuffer failed (Unknown error 2147483646)
12-12 22:20:04.461  10081-10081/com.mde.potdroid3 E/ViewRootImpl﹕ Could not lock surface
    java.lang.IllegalArgumentException
            at android.view.Surface.nativeLockCanvas(Native Method)
            at android.view.Surface.lockCanvas(Surface.java:243)
            at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:2435)
            at android.view.ViewRootImpl.draw(ViewRootImpl.java:2409)
            at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2253)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1883)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1000)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5670)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
            at android.view.Choreographer.doCallbacks(Choreographer.java:574)
            at android.view.Choreographer.doFrame(Choreographer.java:544)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5081)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:781)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)

В Интернете я могу найти информацию об этом исключении, где у людей есть свои пользовательские представления. Что здесь происходит? Может ли это быть связано с потреблением памяти моего приложения? Кажется, что каждый раз, когда я вызываю код выше, новый TopicFragment создается, отображается и помещается в задний стек. Как я могу отладить это поведение?


Дополнительная информация: приложение, похоже, использует много CPU, когда я включаю оверлей в настройках разработчика. Может быть, что мои фрагменты не были должным образом отделены, когда я их оставляю и почему-то продолжаю работать в фоновом режиме?


Вот как я пользуюсь WebView:

mWebView = (WebView)getView().findViewById(R.id.topic_webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setDomStorageEnabled(true);
mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
mWebView.getSettings().setAllowFileAccess(true);
mWebView.addJavascriptInterface(mJsInterface, "api");
mWebView.setWebChromeClient(new WebChromeClient());
mWebView.loadData("", "text/html", "utf-8");
mWebView.setBackgroundColor(0x00000000);

Здесь не упоминается утечка памяти .

Ответ 1

Наконец-то я нашел проблему. Отключение аппаратного ускорения для WebView решает его.

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
    //...
    if (Utils.isKitkat()) {
        disableHardwareAcc();
    }
    //...
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
protected void disableHardwareAcc() {
    mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

Ответ 2

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
    View v = super.onCreateView(inflater, container, saved);

    mActivity = (BaseActivity) getSupportActivity();

    // this is the framelayout which will contain our WebView
    mWebContainer = (FrameLayout) v.findViewById(R.id.web_container);

    return v;
}

public void onResume() {
    super.onResume();

    // create new WebView and set all its options.
    mWebView = new WebView(mActivity);
    mWebView....

    // add it to the container
    mWebContainer.addView(mWebView);

    // if data is available, display it immediately
    if(mTopic != null) {
        mWebView.loadDataWithBaseURL("file:///android_asset/", mTopic.getHtmlCache(),
                "text/html", "UTF-8", null);
    }
}

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

    // destroy the webview
    mWebView.destroy();
    mWebView = null;

    // remove the view from the container.
    mWebContainer.removeAllViews();
}

Таким образом, WebView создается и удаляется в onResume и onPause. Это некоторые накладные расходы и не совершенны, но он решает проблему с памятью и едва заметен с точки зрения производительности и т.д.

Ответ 3

Если у вас много WebViews в приложении в разных действиях/фрагментах, вы можете попытаться использовать пул, в котором вы можете получить экземпляр WebView в методе Activies onCreate() и вернуть WebView в пул в Activies onDestory ().

Размер WebViewPool можно настроить с помощью класса памяти устройств пользователей. Это не может быть лучшим решением для каждого приложения, использующего веб-просмотры, но это решение, которое вы можете рассмотреть. Вам может потребоваться некоторое тестирование и т.д.

Но имейте в виду, что объекты в пуле не собираются мусором, поэтому будьте осторожны, если вы решите использовать пул.