Search code examples
androidnio

Reading unexpected values from memory-mapped file


I'm mapping a file like this:

try(AssetFileDescriptor fd = mContext.getAssets().openFd("AZ_SMA.shp");
        FileInputStream shp = fd.createInputStream();
        FileChannel ch = shp.getChannel()) {
    ByteBuffer buffer = ch.map(FileChannel.MapMode.READ_ONLY,
                0, ch.size());
    // do stuff with buffer
}

AssetManager#openFd(...) was originally throwing java.io.FileNotFoundException: This file can not be opened as a file descriptor; it is probably compressed until I added this to my build.gradle:

android {
    aaptOptions {
        noCompress "shp"
    }
    ...
}

Now it opens, but I'm reading garbage. As the file name suggests, the file is an Esri shape file. The first four bytes of the file are supposed to be 00 00 27 0A, and I've verified this in a hex editor. But I'm reading them as 50 4B 03 04, or 80 75 03 04 in decimal.

final byte[] bytes = new byte[4];
buffer.get(bytes);
Log.d("DERP", Arrays.toString(bytes));

08-04 12:01:53.951 12051-12267/com.chalcodes.shapefiles D/DERP: [80, 75, 3, 4]

What am I doing wrong?


Solution

  • A friend suggested that I should verify the length of the file. That's when it clicked.

    The AssetFileDescriptor returned by AssetManager#openFd(...) describes a block of the APK that contains the mapped file, not the file itself. My original file was 13,401,744 bytes, but buffer.limit() was returning 15,234,522.

    The solution was to change this:

    ByteBuffer buffer = ch.map(FileChannel.MapMode.READ_ONLY,
        0, ch.size());
    

    ...to this:

    ByteBuffer buffer = ch.map(FileChannel.MapMode.READ_ONLY,
        fd.getStartOffset(), fd.getLength());
    

    I'm not quite clear on the difference between AssetFileDescriptor's getLength() and getDeclaredLength(), but this works. The first four bytes are what I expected, and buffer.limit() returns the expected file size.