janoliver
janoliver

Reputation: 7824

After a while of usage, my App freezes during scrolling a WebView, saying "could not lock surface"

My Android app consists of several activities, each responsible for a single fragment (for now). My fragments are usually displayed/attached somewhat like this:

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

The TopicFragment contains a WebView displaying some HTML and CSS/JS stuff. After some time of browsing through the app, the scrolling in one of these TopicFragment WebViews becomes slow and eventually, the App freezes completely. The ADB log shows the following exception:

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)

On the internet, I can only find information regarding this Exception, where People have their custom Views. What happens here? Can it be related to memory consumption of my app? It appears as if each time I call the code above, a new TopicFragment is instantiated, displayed and pushed to the back stack. How could I further debug this behaviour?


One more info: The App seems to use a LOT of CPU when I enable the overlay in the developer settings. May it be, that my fragments are not properly detached when I leave them and for some reason keep running in the background?


This is how I use the 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);

It is not the memory leak mentioned here.

Upvotes: 12

Views: 12679

Answers (3)

janoliver
janoliver

Reputation: 7824

I finally found the issue. Switching off hardware acceleration for the WebView solves it.

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

Upvotes: 0

sockeqwe
sockeqwe

Reputation: 15929

If you have many WebViews in your App in different Activities / Fragments than you could try to use a Pool where you can retrieve a WebView instance in the Activies onCreate() method and give the WebView back to the Pool in Activies onDestory().

The WebViewPool size can be setup by users device memory class. This may not be the best solution for every app that uses webviews, but its a solution you can consider. You may need some testing etc.

But keep in mind, Objects in a pool are not garbage collected, so be careful if you decide to use a Pool.

Upvotes: 0

janoliver
janoliver

Reputation: 7824

I guess I fixed it. Apparently, there is a bug which prevents correct memory managment of the WebView. When I start many Activities with WebView in their views, the WebViews live in the memory and the activities are not properly killed when memory runs low. I worked around the issue with the following code in my TopicFragment which displays the 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();
}

This way, the WebView is created and removed in onResume and onPause. This is some overhead and not perfect, but it solves the memory issue and is barely noticable in terms of performance and so on.

Upvotes: 2

Related Questions