Bug 30293 - java.lang.reflect.InvocationTargetException is thrown when instantiating a ZipArchive from a Stream
Summary: java.lang.reflect.InvocationTargetException is thrown when instantiating a Zi...
Status: RESOLVED UPSTREAM
Alias: None
Product: Android
Classification: Xamarin
Component: General ()
Version: 5.2
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2015-05-21 03:09 UTC by Adam Hartley [MSFT]
Modified: 2015-05-27 12:48 UTC (History)
4 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Sample project (182.15 KB, application/zip)
2015-05-21 03:09 UTC, Adam Hartley [MSFT]
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED UPSTREAM

Description Adam Hartley [MSFT] 2015-05-21 03:09:53 UTC
Created attachment 11273 [details]
Sample project

## Overview

A Java.Lang.RuntimeException: java.lang.reflect.InvocationTargetException is thrown when instantiating a ZipArchive from a Stream.

## Steps to reproduce

1. Download the sample project (attached).
2. Run the Android project
See line 24 of MainActivity.cs for the relevant code.

## Expected result

Should not crash.

## Actual result

Java.Lang.RuntimeException: java.lang.reflect.InvocationTargetException
at --- End of managed exception stack trace ---
at java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
at Caused by: java.lang.reflect.InvocationTargetException
at at java.lang.reflect.Method.invoke(Native Method)
at at java.lang.reflect.Method.invoke(Method.java:372)
at at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at ... 1 more
at Caused by: md52ce486a14f4bcd95899665e9d932190b.JavaProxyThrowable: System.IO.InvalidDataException: The contents of the stream are not in the zip archive format.
at at System.IO.Compression.ZipArchive.CreateZip (System.IO.Stream,System.IO.Compression.ZipArchiveMode) <IL 0x000b9, 0x0038b>
at at System.IO.Compression.ZipArchive..ctor (System.IO.Stream,System.IO.Compression.ZipArchiveMode) <IL 0x00028, 0x000d7>
at ZipCrash.Droid.MainActivity.OnCreate (Android.OS.Bundle) [0x00023] in /Users/Adam/Downloads/ZipCrash/Droid/MainActivity.cs:29
at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) [0x00011] in /Users/builder/data/lanes/1780/3518c4ce/source/monodroid/src/Mono.Android/platforms/android-21/src/generated/Android.App.Activity.cs:2707
at at (wrapper dynamic-method) object.1739740d-6728-43c2-8c45-8ef00a08024a (intptr,intptr,intptr) <IL 0x00017, 0x0002b>
at at md50e099cb01f0a899b59c1a399b2b37919.MainActivity.n_onCreate(Native Method)
at at md50e099cb01f0a899b59c1a399b2b37919.MainActivity.onCreate(MainActivity.java:28)
at at android.app.Activity.performCreate(Activity.java:5990)
at at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
at at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
at at android.app.ActivityThread.access$800(ActivityThread.java:151)
at at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
at at android.os.Handler.dispatchMessage(Handler.java:102)
at at android.os.Looper.loop(Looper.java:135)
at at android.app.ActivityThread.main(ActivityThread.java:5254)
at ... 4 more 

## Notes

It seems to work if copied into a MemoryStream. See line 25-28 of MainActivity.cs for workaround.
Comment 1 Jonathan Pryor 2015-05-27 12:48:37 UTC
The exception is thrown from here:

https://github.com/mono/mono/blob/mono-4.0.0-branch/mcs/class/System.IO.Compression/ZipArchive.cs#L110

Which unfortunately doesn't provide the inner exception. Some investigation later, and we see that the originally thrown exception is an ArgumentException, thrown from here:

https://github.com/mono/mono/blob/mono-4.0.0-branch/mcs/class/System.IO.Compression/SharpCompress/Archive/AbstractArchive.cs#L79

i.e. the Stream *must* return true from Stream.CanSeek and Stream.CanRead, which isn't the case for the Android.Runtime.InputStreamInvoker that Assets.Open() returns -- InputStreamInvoker.CanSeek is false, because java.io.InputStream isn't seekable:

http://developer.android.com/reference/java/io/InputStream.html

Some additional investigation shows that there is a way to seek files in Java via FileChannel.position(long):

http://stackoverflow.com/a/21136552
http://developer.android.com/reference/java/nio/channels/FileChannel.html#position(long)

There's just one problem here, at least as it relates to Assets: AssetManager.open() at runtime is returning an android.content.res.AssetManager.AssetInputStream:

http://developer.android.com/reference/android/content/res/AssetManager.html#open(java.lang.String)
http://developer.android.com/reference/android/content/res/AssetManager.AssetInputStream.html

AssetManager.AssetInputStream, meanwhile, doesn't provide a FileChannel, and thus (afaict) provides no way to explicitly set the file position. Consequently, it's not seekable. Consequently, this can't work.

Unfortunately, this is by design: ZipArchive requires a seekable Stream, and Assets.Open() can't provide one.

WORKAROUND: Don't use @(AndroidAsset). Use @(EmbeddedResource).

https://support.microsoft.com/en-us/kb/319292

Assembly.GetManifestResourceStream() *does* return a Stream that is seekable and readable, and thus can be used with ZipArchive without error.

*Furthermore*, @(EmbeddedResource)  is compatible with all .NET environments -- .NET, Mono, Xamarin.iOS, Xamarin.Mac, etc. -- and thus promotes greater code and data sharing by reducing use of Android-specific concepts such as @(AndroidAsset). @(EmbeddedResource) is also faster, as there's no cross-VM overhead involved in reading the data.