Android: Access APK files using AssetFileDescriptor
This commit is contained in:
parent
b2b90c9f49
commit
678523ea7c
2 changed files with 217 additions and 117 deletions
|
@ -94,8 +94,11 @@ typedef struct SDL_RWops
|
|||
void *inputStreamRef;
|
||||
void *readableByteChannelRef;
|
||||
void *readMethod;
|
||||
void *assetFileDescriptorRef;
|
||||
long position;
|
||||
int size;
|
||||
long size;
|
||||
long offset;
|
||||
int fd;
|
||||
} androidio;
|
||||
#elif defined(__WIN32__)
|
||||
struct
|
||||
|
|
|
@ -37,6 +37,8 @@ extern "C" {
|
|||
|
||||
#include <android/log.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#define LOG_TAG "SDL_android"
|
||||
//#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
|
||||
//#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
||||
|
@ -559,6 +561,9 @@ static int Android_JNI_FileOpen(SDL_RWops* ctx)
|
|||
jclass channels;
|
||||
jobject readableByteChannel;
|
||||
jstring fileNameJString;
|
||||
jobject fd;
|
||||
jclass fdCls;
|
||||
jfieldID descriptor;
|
||||
|
||||
JNIEnv *mEnv = Android_JNI_GetEnv();
|
||||
if (!refs.init(mEnv)) {
|
||||
|
@ -566,61 +571,97 @@ static int Android_JNI_FileOpen(SDL_RWops* ctx)
|
|||
}
|
||||
|
||||
fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
|
||||
ctx->hidden.androidio.position = 0;
|
||||
|
||||
// context = SDLActivity.getContext();
|
||||
mid = mEnv->GetStaticMethodID(mActivityClass,
|
||||
"getContext","()Landroid/content/Context;");
|
||||
context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
|
||||
|
||||
|
||||
// assetManager = context.getAssets();
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
|
||||
"getAssets", "()Landroid/content/res/AssetManager;");
|
||||
assetManager = mEnv->CallObjectMethod(context, mid);
|
||||
|
||||
// inputStream = assetManager.open(<filename>);
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
|
||||
"open", "(Ljava/lang/String;)Ljava/io/InputStream;");
|
||||
/* First let's try opening the file to obtain an AssetFileDescriptor.
|
||||
* This method reads the files directly from the APKs using standard *nix calls
|
||||
*/
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
|
||||
inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
goto failure;
|
||||
goto fallback;
|
||||
}
|
||||
|
||||
ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
|
||||
|
||||
// Despite all the visible documentation on [Asset]InputStream claiming
|
||||
// that the .available() method is not guaranteed to return the entire file
|
||||
// size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
|
||||
// android/apis/content/ReadAsset.java imply that Android's
|
||||
// AssetInputStream.available() /will/ always return the total file size
|
||||
|
||||
// size = inputStream.available();
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
|
||||
"available", "()I");
|
||||
ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
|
||||
ctx->hidden.androidio.assetFileDescriptorRef = mEnv->NewGlobalRef(inputStream);
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getStartOffset", "()J");
|
||||
ctx->hidden.androidio.offset = mEnv->CallLongMethod(inputStream, mid);
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
goto failure;
|
||||
goto fallback;
|
||||
}
|
||||
|
||||
// readableByteChannel = Channels.newChannel(inputStream);
|
||||
channels = mEnv->FindClass("java/nio/channels/Channels");
|
||||
mid = mEnv->GetStaticMethodID(channels,
|
||||
"newChannel",
|
||||
"(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
|
||||
readableByteChannel = mEnv->CallStaticObjectMethod(
|
||||
channels, mid, inputStream);
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getDeclaredLength", "()J");
|
||||
ctx->hidden.androidio.size = mEnv->CallLongMethod(inputStream, mid);
|
||||
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
goto failure;
|
||||
goto fallback;
|
||||
}
|
||||
|
||||
ctx->hidden.androidio.readableByteChannelRef =
|
||||
mEnv->NewGlobalRef(readableByteChannel);
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
|
||||
fd = mEnv->CallObjectMethod(inputStream, mid);
|
||||
fdCls = mEnv->GetObjectClass(fd);
|
||||
descriptor = mEnv->GetFieldID(fdCls, "descriptor", "I");
|
||||
ctx->hidden.androidio.fd = mEnv->GetIntField(fd, descriptor);
|
||||
|
||||
// Store .read id for reading purposes
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
|
||||
"read", "(Ljava/nio/ByteBuffer;)I");
|
||||
ctx->hidden.androidio.readMethod = mid;
|
||||
if (false) {
|
||||
fallback:
|
||||
__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
|
||||
/* Try the old method using InputStream */
|
||||
ctx->hidden.androidio.assetFileDescriptorRef = NULL;
|
||||
|
||||
ctx->hidden.androidio.position = 0;
|
||||
// inputStream = assetManager.open(<filename>);
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
|
||||
"open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
|
||||
inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
|
||||
|
||||
// Despite all the visible documentation on [Asset]InputStream claiming
|
||||
// that the .available() method is not guaranteed to return the entire file
|
||||
// size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
|
||||
// android/apis/content/ReadAsset.java imply that Android's
|
||||
// AssetInputStream.available() /will/ always return the total file size
|
||||
|
||||
// size = inputStream.available();
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
|
||||
"available", "()I");
|
||||
ctx->hidden.androidio.size = (long)mEnv->CallIntMethod(inputStream, mid);
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
// readableByteChannel = Channels.newChannel(inputStream);
|
||||
channels = mEnv->FindClass("java/nio/channels/Channels");
|
||||
mid = mEnv->GetStaticMethodID(channels,
|
||||
"newChannel",
|
||||
"(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
|
||||
readableByteChannel = mEnv->CallStaticObjectMethod(
|
||||
channels, mid, inputStream);
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
ctx->hidden.androidio.readableByteChannelRef =
|
||||
mEnv->NewGlobalRef(readableByteChannel);
|
||||
|
||||
// Store .read id for reading purposes
|
||||
mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
|
||||
"read", "(Ljava/nio/ByteBuffer;)I");
|
||||
ctx->hidden.androidio.readMethod = mid;
|
||||
}
|
||||
|
||||
if (false) {
|
||||
failure:
|
||||
|
@ -636,6 +677,10 @@ failure:
|
|||
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
|
||||
}
|
||||
|
||||
if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
|
||||
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -660,6 +705,7 @@ extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
|
|||
ctx->hidden.androidio.inputStreamRef = NULL;
|
||||
ctx->hidden.androidio.readableByteChannelRef = NULL;
|
||||
ctx->hidden.androidio.readMethod = NULL;
|
||||
ctx->hidden.androidio.assetFileDescriptorRef = NULL;
|
||||
|
||||
return Android_JNI_FileOpen(ctx);
|
||||
}
|
||||
|
@ -668,40 +714,53 @@ extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
|
|||
size_t size, size_t maxnum)
|
||||
{
|
||||
LocalReferenceHolder refs(__FUNCTION__);
|
||||
jlong bytesRemaining = (jlong) (size * maxnum);
|
||||
jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
|
||||
int bytesRead = 0;
|
||||
|
||||
/* Don't read more bytes than those that remain in the file, otherwise we get an exception */
|
||||
if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
|
||||
if (ctx->hidden.androidio.assetFileDescriptorRef) {
|
||||
size_t bytesMax = size * maxnum;
|
||||
if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
|
||||
bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
|
||||
}
|
||||
size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
|
||||
if (result > 0) {
|
||||
ctx->hidden.androidio.position += result;
|
||||
return result / size;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
jlong bytesRemaining = (jlong) (size * maxnum);
|
||||
jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
|
||||
int bytesRead = 0;
|
||||
|
||||
JNIEnv *mEnv = Android_JNI_GetEnv();
|
||||
if (!refs.init(mEnv)) {
|
||||
return -1;
|
||||
}
|
||||
/* Don't read more bytes than those that remain in the file, otherwise we get an exception */
|
||||
if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
|
||||
|
||||
jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
|
||||
jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
|
||||
jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
// result = readableByteChannel.read(...);
|
||||
int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
|
||||
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
return 0;
|
||||
JNIEnv *mEnv = Android_JNI_GetEnv();
|
||||
if (!refs.init(mEnv)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (result < 0) {
|
||||
break;
|
||||
jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
|
||||
jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
|
||||
jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
// result = readableByteChannel.read(...);
|
||||
int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
|
||||
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (result < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytesRemaining -= result;
|
||||
bytesRead += result;
|
||||
ctx->hidden.androidio.position += result;
|
||||
}
|
||||
|
||||
bytesRemaining -= result;
|
||||
bytesRead += result;
|
||||
ctx->hidden.androidio.position += result;
|
||||
}
|
||||
|
||||
return bytesRead / size;
|
||||
return bytesRead / size;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
|
||||
|
@ -727,16 +786,28 @@ static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
|
|||
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
|
||||
}
|
||||
|
||||
jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
|
||||
if (ctx->hidden.androidio.assetFileDescriptorRef) {
|
||||
jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
|
||||
jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
|
||||
"close", "()V");
|
||||
mEnv->CallVoidMethod(inputStream, mid);
|
||||
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
|
||||
|
||||
// inputStream.close();
|
||||
jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
|
||||
"close", "()V");
|
||||
mEnv->CallVoidMethod(inputStream, mid);
|
||||
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
|
||||
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
result = -1;
|
||||
// inputStream.close();
|
||||
jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
|
||||
"close", "()V");
|
||||
mEnv->CallVoidMethod(inputStream, mid);
|
||||
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
|
||||
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
|
||||
if (Android_JNI_ExceptionOccurred()) {
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (release) {
|
||||
|
@ -755,60 +826,86 @@ extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
|
|||
|
||||
extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
|
||||
{
|
||||
Sint64 newPosition;
|
||||
|
||||
switch (whence) {
|
||||
case RW_SEEK_SET:
|
||||
newPosition = offset;
|
||||
break;
|
||||
case RW_SEEK_CUR:
|
||||
newPosition = ctx->hidden.androidio.position + offset;
|
||||
break;
|
||||
case RW_SEEK_END:
|
||||
newPosition = ctx->hidden.androidio.size + offset;
|
||||
break;
|
||||
default:
|
||||
SDL_SetError("Unknown value for 'whence'");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Validate the new position */
|
||||
if (newPosition < 0) {
|
||||
SDL_Error(SDL_EFSEEK);
|
||||
return -1;
|
||||
}
|
||||
if (newPosition > ctx->hidden.androidio.size) {
|
||||
newPosition = ctx->hidden.androidio.size;
|
||||
}
|
||||
|
||||
Sint64 movement = newPosition - ctx->hidden.androidio.position;
|
||||
if (movement > 0) {
|
||||
unsigned char buffer[4096];
|
||||
|
||||
// The easy case where we're seeking forwards
|
||||
while (movement > 0) {
|
||||
Sint64 amount = sizeof (buffer);
|
||||
if (amount > movement) {
|
||||
amount = movement;
|
||||
}
|
||||
size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
|
||||
if (result <= 0) {
|
||||
// Failed to read/skip the required amount, so fail
|
||||
if (ctx->hidden.androidio.assetFileDescriptorRef) {
|
||||
switch (whence) {
|
||||
case RW_SEEK_SET:
|
||||
if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
|
||||
offset += ctx->hidden.androidio.offset;
|
||||
break;
|
||||
case RW_SEEK_CUR:
|
||||
offset += ctx->hidden.androidio.position;
|
||||
if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
|
||||
offset += ctx->hidden.androidio.offset;
|
||||
break;
|
||||
case RW_SEEK_END:
|
||||
offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
|
||||
break;
|
||||
default:
|
||||
SDL_SetError("Unknown value for 'whence'");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
whence = SEEK_SET;
|
||||
|
||||
movement -= result;
|
||||
off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
|
||||
if (ret == -1) return -1;
|
||||
ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
|
||||
} else {
|
||||
Sint64 newPosition;
|
||||
|
||||
switch (whence) {
|
||||
case RW_SEEK_SET:
|
||||
newPosition = offset;
|
||||
break;
|
||||
case RW_SEEK_CUR:
|
||||
newPosition = ctx->hidden.androidio.position + offset;
|
||||
break;
|
||||
case RW_SEEK_END:
|
||||
newPosition = ctx->hidden.androidio.size + offset;
|
||||
break;
|
||||
default:
|
||||
SDL_SetError("Unknown value for 'whence'");
|
||||
return -1;
|
||||
}
|
||||
|
||||
} else if (movement < 0) {
|
||||
// We can't seek backwards so we have to reopen the file and seek
|
||||
// forwards which obviously isn't very efficient
|
||||
Android_JNI_FileClose(ctx, false);
|
||||
Android_JNI_FileOpen(ctx);
|
||||
Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
|
||||
/* Validate the new position */
|
||||
if (newPosition < 0) {
|
||||
SDL_Error(SDL_EFSEEK);
|
||||
return -1;
|
||||
}
|
||||
if (newPosition > ctx->hidden.androidio.size) {
|
||||
newPosition = ctx->hidden.androidio.size;
|
||||
}
|
||||
|
||||
Sint64 movement = newPosition - ctx->hidden.androidio.position;
|
||||
if (movement > 0) {
|
||||
unsigned char buffer[4096];
|
||||
|
||||
// The easy case where we're seeking forwards
|
||||
while (movement > 0) {
|
||||
Sint64 amount = sizeof (buffer);
|
||||
if (amount > movement) {
|
||||
amount = movement;
|
||||
}
|
||||
size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
|
||||
if (result <= 0) {
|
||||
// Failed to read/skip the required amount, so fail
|
||||
return -1;
|
||||
}
|
||||
|
||||
movement -= result;
|
||||
}
|
||||
|
||||
} else if (movement < 0) {
|
||||
// We can't seek backwards so we have to reopen the file and seek
|
||||
// forwards which obviously isn't very efficient
|
||||
Android_JNI_FileClose(ctx, false);
|
||||
Android_JNI_FileOpen(ctx);
|
||||
Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
return ctx->hidden.androidio.position;
|
||||
|
||||
}
|
||||
|
||||
extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue