Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android WebView Asset Reference Memory Leak

My Android 4.0.4 application consists of a WebView through which the user can view multiple pages stored locally in the asset directory. When cycling through the pages, the following error eventually gets triggered and the application crashes:

  • JNI ERROR (app bug): local reference table overflow (max=512)
  • Failed adding to JNI local ref table (has 512 entries)
  • VM aborting
  • Fatal signal 11 (SIGSEGV) at 0xdeadd00dd (code=1)

The problem seems to be related to the ones reported here:

  • WebView memory leak in android app
  • Android WebView Memory Leak when using Assets
  • Viewpager Webview memory issue

I've used the Memory Analyzer Tool plugin instructions given at the following link to examine the details:

http://therockncoder.blogspot.ca/2012/09/fixing-android-memory-leak.html

Results are shown below (can't post screen captures yet, so text will have to do):

MAT Excerpt

Class Name                                                                                                                                                                                                                                                                                      | Shallow Heap | Retained Heap | Percentage
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
                                                                                                                                                                                                                                                                                                |              |               |           
java.lang.Thread @ 0x40daa320  Thread-39775 Thread                                                                                                                                                                                                                                              |           80 |    15,310,552 |     76.74%
|- byte[32768] @ 0x40d5a8d0  <!DOCTYPE html>.<html xml:lang="">.<head>.   <title>Android Test-HTML5-480PX-Page 0</title>.   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />.   <meta name="viewport" content="width=360, height=480">. <!--.   <meta name="viewport" co...|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x40e25bb8  on-play-state: running;.      -webkit-animation-timing-function: step-start;.   }.   @-webkit-keyframes ag16780-anim45051.   {.        0.000% { background-position:0px 0px,77px 0px,77px 0px,77px 0px,77px 0px,77px 0px; }.       20.000% { background-position...|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x40e60520  //<![CDATA[.var pageParams = new Object();.pageParams['readMode'] = 1;...function applyReadMode().{.    var audioNodes = document.getElementsByClassName('BGAudio');.    for (var i=0; i<audioNodes.length; i++).    {.    .if (pageParams['readMode'] == 0).   ...|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x40e86f48  .PNG........IHDR....... ......U......gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<....bKGD..............pHYs...#...#.x.?v...ZIDATH..U]l\W..f.9.......8..G.....Z$ZUi..*EQQ...RE.Hi.D".0BTj..J.x..O.J..C.)..IU.R......HB.8..&..\.x..w..{fx...|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x40f09f00  html.{.   padding           : 0px 0px 0px 0px;.   margin            : 0px 0px 0px 0px;.   height            : 100%;.   width             : 100%;.   background        : #ffffff;.}.body.{.   padding           : 0px 0px 0px 0px;.   margin            : 0px 0px...|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x40f11f18  .=Xt.....H...hE:t.;.......=.s....f.(.....v.5'<.8}=.=kXF..&&...K...j.........<...A..........}.......c.c..7.e{b.....O.p..h....e.1....8.zd{..........}3.Z.W..v.|}y.u...3M.....h2IDAT...........Z.;u..M.....'!.(.S.....|j.]..h.l7.... .....I...u.&J5.";9.d04.S.........|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x41009af0  //<![CDATA[.var pageParams = new Object();.pageParams['readMode'] = 1;...function applyReadMode().{.    var audioNodes = document.getElementsByClassName('BGAudio');.    for (var i=0; i<audioNodes.length; i++).    {.    .if (pageParams['readMode'] == 0).   ...|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x41011b08  html.{.   padding           : 0px 0px 0px 0px;.   margin            : 0px 0px 0px 0px;.   height            : 100%;.   width             : 100%;.   background        : #ffffff;.}.body.{.   padding           : 0px 0px 0px 0px;.   margin            : 0px 0px...|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x41019b20  ...t.%#2#b......0..0.6.....A.M....%.F,.*...(.>.q$_.0... a..sF...."Ypn"....#[email protected]).F....4.....Q.1.Wd..3.|.Y%........:.w.F~ ]..0i>a......4n.E7.O..+.7S...D...|.IDAT..'...<.....E.n...!.1.....Tx211..E....4 .*f....>..)..)...gS.j.. WX....|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x41021b38  .PNG........IHDR...|.................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<....PLTE.................................................................................................................................................|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x41029b50  .PNG........IHDR.......Q.......0.....gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<....bKGD..............pHYs...#...#.x.?v..:.IDATx...gx...........-.....4...p...S....%$!$.$$....z....8T.:[email protected]....|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x41031b68  .PNG........IHDR...V.........0..X....gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<....bKGD..............pHYs...#...#.x.?v....IDATh..Zyp.U....f.;3....j0..p...E]...M...*%..,.J..Pb..,....Z.AE.E.....n.."[email protected]...>...........W......|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410889d0  .PNG........IHDR.............a.~e....gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<....PLTE...Q_HRgL......=I93?/f~Zg~[DV=@P8)0$'2&(3&/<('1$......""!:;:"#"......)3&...............&&&'''.......... ...........$.677888.$....................|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410909e8  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...wPLTE........~..............v...........~.........{yn........................................................F.. ..4..............!...........+..l....|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x41098a00  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...wPLTE...uuj....................i.......................................................................S..'..$..............*..............[..........|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410a0a18  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...tPLTE...llh...........................|.u.....................................................]..*..+..............-..............T..............M....|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410a8a30  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...wPLTE...ooi.......................}....................................................................[..)..%..............+..............Y..........|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410b0a48  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...zPLTE...wvk.......................{....................................................................\..(..$..............,..............\..........|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410b8a60  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...zPLTE...rqg.....d...................................z........................................................R..%.. ..............(..............]....|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410c0a78  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...wPLTE...rrh........r............................................................................................Q..%.. ..............(................|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410c8a90  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...wPLTE........}..............O...........|.........xwl........................................................G..!.....8.............."...........'....|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410d0aa8  .PNG........IHDR.....................gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...zPLTE........~..............m...........~.........{yn........................................................F.. ..4..............!...........+..l....|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410d8ac0  .PNG........IHDR.............e.. ....gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...yPLTE...mmmhfZ........h.................)........c..kppp..h..u.......................................................................x..o..a..........|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410e0ad8  .PNG........IHDR.............e.. ....gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<....PLTE...hhh.......................-...........e..~........|.............................................................................s.............|       32,784 |        32,784 |      0.16%
|- byte[32768] @ 0x410e8af0  .PNG........IHDR.............e.. ....gAMA......a.....sRGB........ cHRM..z&..............u0...`..:....p..Q<...|PLTE.........,,+........c..~...........]..............y....................n.........................................................................|       32,784 |        32,784 |      0.16%
'- Total: 25 of 471 entries; 446 more                                                                                                                                                                                                                                                           |              |               |           
android.widget.HorizontalScrollView @ 0x40e1d8b8                                                                                                                                                                                                                                                |          576 |       978,456 |      4.90%
class android.content.res.Resources @ 0x40ab1570 System Class                                                                                                                                                                                                                                   |           48 |       266,432 |      1.34%
...

Log Excerpt

...
10-08 22:10:28.970: D/MediaPlayer(9823): pause() out
10-08 22:10:29.090: E/dalvikvm(9823): JNI ERROR (app bug): local reference table overflow (max=512)
10-08 22:10:29.090: W/dalvikvm(9823): JNI local reference table (0x2498270) dump:
10-08 22:10:29.090: W/dalvikvm(9823):   Last 10 entries (of 512):
10-08 22:10:29.090: W/dalvikvm(9823):       511: 0x40d696d0 android.content.res.AssetManager
10-08 22:10:29.090: W/dalvikvm(9823):       510: 0x4201eab0 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):       509: 0x42016a98 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):       508: 0x4200ea80 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):       507: 0x42006a68 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):       506: 0x41ffea50 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):       505: 0x41ff6a38 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):       504: 0x41feea20 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):       503: 0x41fe6a08 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):       502: 0x41fde9f0 byte[] (32768 elements)
10-08 22:10:29.090: W/dalvikvm(9823):   Summary:
10-08 22:10:29.090: W/dalvikvm(9823):         1 of java.lang.Class
10-08 22:10:29.090: W/dalvikvm(9823):       510 of byte[] (32768 elements) (510 unique instances)
10-08 22:10:29.090: W/dalvikvm(9823):         1 of android.content.res.AssetManager
10-08 22:10:29.090: E/dalvikvm(9823): Failed adding to JNI local ref table (has 512 entries)
10-08 22:10:29.090: I/dalvikvm(9823): "Thread-39898" prio=5 tid=12 RUNNABLE
10-08 22:10:29.090: I/dalvikvm(9823):   | group="main" sCount=0 dsCount=0 obj=0x40d6b9d8 self=0x222a030
10-08 22:10:29.090: I/dalvikvm(9823):   | sysTid=9871 nice=0 sched=0/0 cgrp=default handle=29456640
10-08 22:10:29.090: I/dalvikvm(9823):   | schedstat=( 520906000 142824000 757 ) utm=22 stm=30 core=2
10-08 22:10:29.090: I/dalvikvm(9823):   at android.content.res.AssetManager.readAsset(Native Method)
10-08 22:10:29.090: I/dalvikvm(9823):   at android.content.res.AssetManager.access$700(AssetManager.java:35)
10-08 22:10:29.090: I/dalvikvm(9823):   at android.content.res.AssetManager$AssetInputStream.read(AssetManager.java:648)
10-08 22:10:29.090: I/dalvikvm(9823):   at dalvik.system.NativeStart.run(Native Method)
10-08 22:10:29.090: E/dalvikvm(9823): VM aborting
10-08 22:10:29.090: A/libc(9823): Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1)
10-08 22:10:29.140: I/MediaPlayer(9823): setLPAflag() in
...

By looking at the MAT entries, it appears that the main Thread is holding references to all HTML pages, CSS and Javascript files, images and audio files and does not release them. Eventually when a new URL is called, the AssetManager tries to write entries beyond the bounds (512 entries) of the JNI local ref table, exhausts the available space and causes a memory leak.

I've tried all sorts of things to try to prevent retaining the references, with no luck. This includes:

  • initializing the WebView so that it does not cache the pages:

    mWebView.getSettings().setAppCacheEnabled(false);
    mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
    mWebView.setDrawingCacheEnabled(false);
    
  • trying to remove all existing WebView data before loading the next page:

    mWebView.stopLoading();
    mWebView.clearCache(true);
    mWebView.destroyDrawingCache();
    mWebView.clearHistory();
    mWebView.freeMemory();
    mWebView.getInstance().deleteAllData();
    
  • creating the WebView dynamically rather than through the res/layout XML, and assigning it the application Context rather than the Activity context:

    mWebView = new WebView(getApplicationContext());
    
  • destroying the current WebView before changing pages and dynamically creating a new one for the next page, but this has no effect other than slowing down the application; the JNI local table still retains the old references:

    mWebViewContainer.removeViewAt(0);
    mWebView.destroyDrawingCache();
    mWebView.destroy();
    mWebView = null;
    System.gc();
    

The ideal thing would be to remove the old entries in the JNI table prior to loading the next page, but I've found no way to do that -- once a URL is called, the asset directory references are held despite what I've tried. Maybe I've missed something or a fix is pending in a later release of Android? Failing that, even though destroying and creating new WebViews for each page negatively affects performance and it would complicate the design, if the pages could be created within their own Thread and hold their own asset references rather than the main Thread, maybe that would work (provided those Threads can be stopped during a page change).

Update

I tried creating my WebViews in separate threads and got the error "All WebView methods must be called on the UI thread. Future versions of WebView may not support use on other threads.". I'm assuming 'UI thread' refers to the main thread. This posting seems to support that:

A WebView in a thread can't be created

like image 386
VicTorn Avatar asked Oct 06 '22 09:10

VicTorn


2 Answers

I have come up with a workaround that avoids the memory leak but at a cost of doubling the amount of data stored. Basically all content of the asset directory must be copied to either internal or external storage and the HTML pages must be loaded into WebView from those storage directories instead.

Internal Storage

  • target directory: mContext.getFilesDir();
  • eg. /data/data/com.package.name/files
  • HTML/CSS/Javascript code is hidden from user
  • code will be deleted if application is uninstalled
  • media files must be placed at root directory and not in subdirectory if <audio> or <video> tags are to work normally with MediaPlayer; if placed in subdirectory, files can only be triggered through Javascript by using a URL redirect [ eg. location.href=audioNodes.src; ] rather than play [ audioNodes.play(); ] and trapping that URL in WebViewClient.shouldOverrideUrlLoading and assigning it to a MediaPlayer variable (subdirectories do not appear to behave in conventional manner).

External Storage

  • target directory: new File(mContext.getExternalFilesDir(STORAGE_SERVICE).getAbsolutePath());
  • eg. /mnt/sdcard/Android/data/com.package.name/files/storage
  • HTML/CSS/Javascript code can be seen by user, revealing the inner workings of your code
  • requires SD card to be present (if phone uses external card) and not write-protected
  • code will be deleted if application is uninstalled
  • no special treatment needed for media files in subdirectories, will work directly with MediaPlayer (behaves conventionally)

Below is a sample method for transferring the contents of a specified directory/subdirectory from the assets folder to storage (internal in this case). If encounters a subdirectory, will recursively call to drill down. Seed call must pass "" as sourceDirName.

NOTE: As written, will only work for a subdirectory depth of 1, and files are distinguished from directories by containing a "." character between the name and extension -- you may need to use a different test if this one doesn't work for you. It is not possible to use a test like if (new File(sourceFileNames[i]).isDirectory()) on an asset member because they are not true files; from AssetManager documentation: 'files ... have been bundled with the application as a simple stream of bytes.'

private void copyAssetsToStorage(String sourceDirName)
{
    // /data/data/com.package.name/files
    try
    {
        String[] sourceFileNames = mContext.getAssets().list(sourceDirName);
        File     targetDir       = mContext.getFilesDir();

        if (sourceDirName != "")
        {
            sourceDirName += "/";
            targetDir      = new File(targetDir, sourceDirName);
            targetDir.mkdir();
        }

        targetDir.setReadable(true, false);
        if (sourceFileNames != null)
        {
            byte[]           buffer;
            int              length;
            InputStream      inStream;
            File             outFile;
            FileOutputStream outStream;
            for (int i = 0; i < sourceFileNames.length; i++)
            {
                if (sourceFileNames[i].contains(".") == false)
                {
                    // Recursive call to drill down
                    copyAssetsToStorage(sourceFileNames[i]);
                }
                else
                {
                    inStream  = mContext.getAssets().open(
                                sourceDirName + sourceFileNames[i]);
                    outFile   = new File(targetDir, sourceFileNames[i]);
                    outStream = new FileOutputStream(outFile, false);
                    buffer    = new byte[8192];
                    while ((length = inStream.read(buffer)) > 0)
                    {
                        outStream.write(buffer, 0, length);
                    }
                    inStream.close();
                    inStream = null;
                    outStream.flush();
                    outStream.close();
                    outStream = null;
                    outFile.setReadable(true, false);
                }
            }
        }
    }
    catch (Exception ex)
    {
        ex.printStackTrace();
    }

}   // copyAssetsToStorage
like image 196
VicTorn Avatar answered Oct 10 '22 01:10

VicTorn


just try this

WebSettings settings = webvew.getSettings();
settings.setAppCacheEnabled(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setDatabaseEnabled(false);
settings.setDomStorageEnabled(false);
settings.setGeolocationEnabled(false);
settings.setPluginsEnabled(false);
settings.setSaveFormData(false);
settings.setSavePassword(false);

before loading html in webview and i used loadDataWithBaseURL instead of loadUrl.

loadUrl can be used only when you load a web server in webview, other wise its lead to memory leak.

its worked for me

like image 28
Rohith Avatar answered Oct 10 '22 02:10

Rohith