Android Java: Reading file from zip file into webview/string, Why ZipInputStream limits read performance?

StackOverflow https://stackoverflow.com/questions/22259773

Question

Let's explain it first

I'm using a webView to load a HTML app. To protect the source a bit (script-kiddie protection) I want to load the html code from a (protected) zip file. The html code is already packed, minified, combined etc so is only 700kb in size (unpacked). This works pretty well but there is one problem, it is a bit slow.

In the example below I read the html file from the zip and put the result in a string. This string will be used to load the html code into the webview by using the following code:

this.webView.loadDataWithBaseURL("file:///android_asset/", this.unzipStream(sFileName), "text/html", "UTF-8", "");  

I have played with different solutions to get it faster and figure out that the bottleneck is at reading the contents from the zip. I increased the read buffer's blocksize but doesn't help, it never reads the full blocksize. For example, when I used 4096 bytes (4kb) as blocksize it reads only 700 to 1100 bytes at once.

Question(s):

  • How can I force the read function to use the full blocksize specified?
  • Otherwise, is there another better way to do it (for example put it directly into webview)?

Here is the code I have made:

   public String unzipStream( String sFileName ) 
    { 
        final int BLOCKSIZE = 4096;
        //String sResult = ""; 
        long iSize   = 0;
        int iReaded = 0;
        ByteArrayOutputStream sb = new ByteArrayOutputStream(); 

        try  { 
          InputStream is = this.activity.getAssets().open( sFileName );
          BufferedInputStream fin = new BufferedInputStream( is ); 
          ZipInputStream zin = new ZipInputStream(fin); 
          ZipEntry ze;

          while( (iSize == 0) && ((ze = zin.getNextEntry()) != null) && !ze.isDirectory() ) 
          {
               byte data[]  = new byte[BLOCKSIZE];
               long iTotal  = ze.getSize();

               while ((iReaded = zin.read(data,0,BLOCKSIZE)) > 0 && ((iSize+=iReaded) <= iTotal) ) 
               {   
                   sb.write(data,0,iReaded);
               }
               zin.closeEntry(); 
          } 

          zin.close(); 
        } 
        catch(Exception e) 
        { 
             System.out.println("Error unzip: "+e.getMessage());
             //sResult = "";
             iSize = 0;
        } 

        if( iSize > 0 )
        {
            //Base64.
            try {
                return sb.toString("UTF-8");
                //sResult = new String( Base64.decode(sb.toString("UTF-8"), Base64.DEFAULT), Charset.forName("UTF-8") );
            }
            catch(Exception ee)
            {
               //sResult = "";
            }
        }

        return "";
      } 

Maybe another way to do it:

Also found this java zipfile class http://www.lingala.net/zip4j/. It makes it easier to handle (password protected) zip files but does not include a function (at least I think so) to unzip it to string. Found nothing when searching on it. Is this possible anyway with this class?

Was it helpful?

Solution

Afterwards, maybe the title could be "ZipInputStream limits read performance" or similar like that because other kind of streams don't limit the read size. When you want to get 4096 bytes, you will get 4096 bytes. Tested this with a text-file for example. I still do not know why the ZipInputStream limits the read performance.

I'm not really sure there is a real performance boost (sometimes it is and sometimes not), but using now IOUtils from apache's 'Commons IO' package - http://commons.apache.org/proper/commons-io/. This also simplifies the whole 'operation'.

I have also seen some solutions with channels but seem to only work with filestreams so cannot use it (or cannot figure out how to apply it to this situation). See also: Faster way of copying data in Java? (see accepted answer).

This is the new version I have made (change also object names to meaningful names):

public String unzipStream( String sFileName ) 
{ 
    ByteArrayOutputStream oBaosBuffer = new ByteArrayOutputStream(); 
    try  
    { 
      ZipInputStream oZipStream = new ZipInputStream( this.activity.getAssets().open( sFileName ) ); 
      ZipEntry oZipEntry;
      long iSize = 0;

      while( (iSize == 0) && ((oZipEntry = oZipStream.getNextEntry()) != null) && !oZipEntry.isDirectory() ) 
      {
           iSize = IOUtils.copyLarge(oZipStream, oBaosBuffer);
           oZipStream.closeEntry(); 
      } 

      oZipStream.close(); 

      if( iSize > 0 )
      {
        return oBaosBuffer.toString("UTF-8");
        //sResult = new String( Base64.decode(sb.toString("UTF-8"), Base64.DEFAULT), Charset.forName("UTF-8") );
      }
    } 
    catch(Exception e) 
    { 
         System.out.println("Error unzip: "+e.getMessage());
    } 

    return null;
} 

OTHER TIPS

You might get a small performance boost from using shouldInterceptRequest instead of loadDataWithBaseUrl. That won't solve your block size problem but it will allow WebKit to start parsing your content before you've finished decompressing the whole thing.

The way you'd use it is:

class MyWebViewClient extends WebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest (WebView view, String url) {
    // this method is *not* called on the UI thread, be careful to not touch UI classes.
    if (Uri.parse(url).getHost().equals(Uri.parse("file:///android_asset/..."))) {
        // This assumes you pass in the AssetManager to the MyWebViewClient constructor.
        InputStream is = assetManager.open(sFileName);
        ZipInputStream zipInputStream = new ZipInputStream(is); 
        return new WebResourceResponse("text/html", "UTF-8", zipInputStream);
    }
    return super.shouldInterceptRequest(view, url);
}

Try setting the internal buffer sizes of your streams:

    BufferedInputStream fin = new BufferedInputStream(is,  BLOCKSIZE); 
    ZipInputStream zin = new ZipInputStream(fin){ { buf = new byte[BLOCKSIZE]; }  }; 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top