I'm using this method to load assets in NDK:
jclass localRefCls = myEnv->FindClass("(...)/AssetLoaderHelper");
helperClass = reinterpret_cast<jclass>(myEnv->NewGlobalRef(localRefCls));
myEnv->DeleteLocalRef(localRefCls);
helperMethod1ID = myEnv->GetStaticMethodID(helperClass, "getFileData", "(Ljava/lang/String;)[B");
...
myEnv->PushLocalFrame(10);
jstring pathString = myEnv->NewStringUTF(path);
jbyteArray data = (jbyteArray) myEnv->CallStaticObjectMethod(helperClass, helperMethod1ID, pathString);
char* buffer = new char[len];
myEnv->GetByteArrayRegion(data, 0, len, (jbyte*)buffer);
myEnv->DeleteLocalRef(pathString);
myEnv->DeleteLocalRef(data);
jobject result;
myEnv->PopLocalFrame(result);
myEnv->DeleteLocalRef(result);
return buffer;
in java:
public static byte[] getFileData(String path)
{
InputStream asset = getAsset(path); //my method using InputStream.open
byte[] b = null;
try
{
int size = asset.available();
b = new byte[size];
asset.read(b, 0, size);
asset.close();
}
catch (IOException e1)
{
Log.e("getFileData", e1.getMessage());
}
return b;
}
It works but when i load many assets there is crash or system locks. Am I making any mistake or someone knows better method to load assets to NDK? Perhaps it is only problem with low memory in my device?
I'm not sure on your exact problem, but I may offer a alternative solution to opening assets JNI side:
Java side create a AssetFileDescriptor for each file in question (call this fd for now on
Pass the value of fd.getFileDescriptor(), fd.getStartOffset(), and fd.getLength() to a JNI function
JNI side you can now use fdopen(), fseek(), fread(), etc. using the information from #2
Don't forget to call fd.close() after your JNI work
Hope that helps
Related
I am going to load an image by provided file_path and then downsample it then save it. The whole thing should be done on android device.
I am in trouble to load an image on device and convert to halide::buffer.
Halide::Tools::load_image didn't work on android if I use it like
JNIEXPORT bool JNICALL Java_com_example_boxdownsample_MainActivity_downsample(
JNIEnv *env, jobject obj, jstring file_path) { ...
const char *path = env->GetStringUTFChars(file_path, NULL);
std::string work_path = path;
LOGD("the path is:%s", path);
std::string input_file = work_path + "input.png";
std::string output_file = work_path + "output.png";
Halide::Buffer<uint16_t> input = Halide::Tools::load_image(input_file);//load_image didn't work ....
int ret = box_downsample_halide(input, downsample_factor, output_u16); //box_downsample_halide is a static lib generated by halide generator
... } }
So, am I use it wrong? or
I should load it use java(that would be bitmap format) then encode it to halide::buffer, but this seems a bit hard and indirect.
Is there any easier way to do it?
Thanks!
UPDATE:
I found a way, in case who also need this, please go to my github
I am using the latest version of Android Studio (2.2.3) and I have loaded up the HelloGL2 sample project.
I now want to add a file (any type of file) to my app, and then be able to open it and read it in the c++ code using something like c's fopen etc (any direct file access api is fine)
How do I do this?
There are two options, it will depend on your target.
If your file is a basic text configuration file, you can use both cases, but if your file is a 3D object such as (.obj, .max, .dae) you should use AssetManager class.
First option: (store your files in res raw (You can use fopen())).
Create a folder called raw inside res directory (res->raw).
Write your files in the apk private directory.
In Java:
public void writeFileToPrivateStorage(int fromFile, String toFile)
{
InputStream is = mContext.getResources().openRawResource(fromFile);
int bytes_read;
byte[] buffer = new byte[4096];
try
{
FileOutputStream fos = mContext.openFileOutput(toFile, Context.MODE_PRIVATE);
while ((bytes_read = is.read(buffer)) != -1)
fos.write(buffer, 0, bytes_read); // write
fos.close();
is.close();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
Then, call to your function:
writeFileToPrivateStorage(R.raw.your_file,"your_output_file.txt");
Get your private path
path=mContext.getApplicationContext().getFilesDir().toString();
Define your JNI funcion in Java:
public static native void setconfiguration(String yourpath);
Implement it in C/C++:
JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_setconfiguration(JNIEnv * env, jobject obj, jstring path)
{
//convert your string into std::string.
const char *nativeString = env->GetStringUTFChars(config_path, 0);
//make here your fopen.
fopen(nativeString,"r");
}
Second option (use assetManager, usually for opengl resources).
The parameter, in this case, is not the path of the directory is the asset manager.
Store your files in the asset directory.
Define your native function in C/C++
public static native void yourfunction(AssetManager assetManager);
Call in java to this function:
loadYourFile(m_context.getAssets());
Create your jni function in C/C++
JNIEXPORT void Java_com_android_gl2jni_GL2JNILib_(JNIEnv * env, jobject obj,jobject java_asset_manager)
{
AAssetManager* mgr = AAssetManager_fromJava(env,java_asset_manager);
AAsset* asset = AAssetManager_open(mgr, (const char *) js, AASSET_MODE_UNKNOWN);
if (NULL == asset) {
__android_log_print(ANDROID_LOG_ERROR, NF_LOG_TAG, "_ASSET_NOT_FOUND_");
return JNI_FALSE;
}
long size = AAsset_getLength(asset);
char* buffer = (char*) malloc (sizeof(char)*size);
AAsset_read (asset,buffer,size);
__android_log_print(ANDROID_LOG_ERROR, NF_LOG_TAG, buffer);
AAsset_close(asset);
}
Note: Do not forget to add the permissions in your AndroidManifest.xml.
Note II: Do not forget to add:
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
I hope this answer helps you.
I'm making a simple app in Android. I'm using NDK to make JNI calls. I have a file in a resource subfolder (raw), which I need to access from native c++ code. I want to read it from native using for example "ifstream" function but I don't get to do that.
That's my Java code:
Algorithm algorithm = new Algorithm();
InputStream isModel = getResources().openRawResource(R.raw.model);
String model = algorithm.ReadResourceFile(isModel);
if(imgInput != null && txtResults != null)
{
Bitmap bmp = ((BitmapDrawable)imgInput.getDrawable()).getBitmap();
//Convert Bitmap to Mat
Mat image = new Mat(bmp.getHeight(), bmp.getWidth(), CvType.CV_8U);
//Print results on txtResults
String results = algorithm.DetectEmotionByImage(image.nativeObj, model);
txtResults.setText(results);
}
That's my C++ code:
JNIEXPORT jstring JNICALL
Java_org_ctic_emoplay_1android_algorithm_Algorithm_DetectEmotionByImage(JNIEnv *env,
jobject instance,
jlong image,
jstring fileModel_,
jstring fileRange_,
jstring fileModelFlandmarks_,
jstring fileHaarCascade_)
{
const char *fileModel = env->GetStringUTFChars(fileModel_, NULL);
SVM_testing testing;
Mat* imageInput= (Mat*)image;
Mat& inImageInput = *(Mat*) imageInput;
string results = testing.TestModel(inImageInput, fileModel);
const char* final_results = results.c_str();
env->ReleaseStringUTFChars(fileModel_, fileModel);
return env->NewStringUTF(final_results);
}
Anyone can help me? I'm desperated. Thanks!
The file will be stored inside the APK, but if you rename the file extension to something like .PNG then it will not be compressed. Put the file in the assets folder, not res/raw.
You can get the APK file path like this:
public static String getAPKFilepath(Context context) {
// Get the path
String apkFilePath = null;
ApplicationInfo appInfo = null;
PackageManager packMgmr = context.getPackageManager();
String packageName = context.getApplicationContext().getPackageName();
try {
appInfo = packMgmr.getApplicationInfo(packageName, 0);
apkFilePath = appInfo.sourceDir;
} catch (NameNotFoundException e) {
}
return apkFilePath;
}
Then find the offset of your resource inside the APK:
public static void findAPKFile(String filepath, Context context) {
String apkFilepath = getAPKFilepath(context);
// Get the offset and length for the file: theUrl, that is in your
// assets folder
AssetManager assetManager = context.getAssets();
try {
AssetFileDescriptor assFD = assetManager.openFd(filepath);
if (assFD != null) {
long offset = assFD.getStartOffset();
long fileSize = assFD.getLength();
assFD.close();
// **** offset and fileSize are the offset and size
// **** in bytes of the asset inside the APK
}
} catch (IOException e) {
e.printStackTrace();
}
}
Call like this:
findAPKFile("model.png", MyActivity.this);
You can call your C++ and pass offset, fileSize and apkFilepath via JNI. Open the file, seek past offset bytes and then read out fileSize bytes of data.
The accepted answer to this question shows an alternative method but I haven't tried doing it that way so I can't vouch for it.
I Am trying to load a class as a byte array so I could send it over the network and execute it remotely via Reflection. This Class (Bubble in this case) is in the same package. The thing is that I can't get the resource using the getResourceAsStream(classpath) method.
The .getResourceAsStream(classpath) is always returning null. I've tested this code in a Java project and worked properly. I think the problem is the resource path, does Android load a .class file?
private void doSomething() {
Bubble b = new Bubble();
try {
//Try to retrieve the class byte array
byte[] classBytes = getBytes(b.getClass());
} catch (IOException e) {
e.printStackTrace(System.err);
}
...
}
private byte[] getBytes(Class c) throws IOException{
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte b[] = new byte[1024];
String classpath = c.getCanonicalName().replace('.', File.pathSeparatorChar) + ".class";
//classpath is now, for example, com:myproject:Bubble.class
InputStream in = c.getClassLoader().getResourceAsStream(classpath);
int load;
while((load=in.read(b))>0){
out.write(b,0,load);
}
byte[] _r = out.toByteArray();
out.close();
in.close();
return _r;
}
Android uses dex file format for classes, and the best way would be to not send zipped (jarred) classes.dex, which contains all classes you need.
I have only been able to find solutions dated 2010 and earlier. So I wanted to see if there was a more up-to-date stance on this.
I'd like to avoid using Java and purely use C++, to access files (some less-or-more than 1MB) stored away in the APK. Using AssetManager means I can't access files like every other file on every other operating system (including iOS).
If not, is there a method in C++ where I could somehow map fopen/fread to the AssetManager APIs?
I actually found pretty elegant answer to the problem and blogged about it here.
The summary is:
The AAssetManager API has NDK bindings. This lets you load assets from the APK.
It is possible to combine a set of functions that know how to read/write/seek against anything and disguise them as a file pointer (FILE*).
If we create a function that takes an asset name, uses AssetManager to open it, and then disguises the result as a FILE* then we have something that's very similar to fopen.
If we define a macro named fopen we can replace all uses of that function with ours instead.
My blog has a full write up and all the code you need to implement in pure C. I use this to build lua and libogg for Android.
Short answer
No. AFAIK mapping fread/fopen in C++ to AAssetManager is not possible. And if were it would probably limit you to files in the assets folder. There is however a workaround, but it's not straightforward.
Long Answer
It IS possible to access any file anywhere in the APK using zlib and libzip in C++.
Requirements : some java, zlib and/or libzip (for ease of use, so that's what I settled for). You can get libzip here: http://www.nih.at/libzip/
libzip may need some tinkering to get it to work on android, but nothing serious.
Step 1 : retrieve APK location in Java and pass to JNI/C++
String PathToAPK;
ApplicationInfo appInfo = null;
PackageManager packMgmr = parent.getPackageManager();
try {
appInfo = packMgmr.getApplicationInfo("com.your.application", 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("Unable to locate APK...");
}
PathToAPK = appInfo.sourceDir;
Passing PathToAPK to C++/JNI
JNIEXPORT jlong JNICALL Java_com_your_app(JNIEnv *env, jobject obj, jstring PathToAPK)
{
// convert strings
const char *apk_location = env->GetStringUTFChars(PathToAPK, 0);
// Do some assigning, data init, whatever...
// insert code here
//release strings
env->ReleaseStringUTFChars(PathToAPK, apk_location);
return 0;
}
Assuming that you now have a std::string with your APK location and you have zlib on libzip working you can do something like this:
if(apk_open == false)
{
apk_file = zip_open(apk_location.c_str(), 0, NULL);
if(apk_file == NULL)
{
LOGE("Error opening APK!");
result = ASSET_APK_NOT_FOUND_ERROR;
}else
{
apk_open = true;
result = ASSET_NO_ERROR;
}
}
And to read a file from the APK:
if(apk_file != NULL){
// file you wish to read; **any** file from the APK, you're not limited to regular assets
const char *file_name = "path/to/file.png";
int file_index;
zip_file *file;
struct zip_stat file_stat;
file_index = zip_name_locate(apk_file, file_name, 0);
if(file_index == -1)
{
zip_close(apk_file);
apk_open = false;
return;
}
file = zip_fopen_index(apk_file, file_index, 0);
if(file == NULL)
{
zip_close(apk_file);
apk_open = false;
return;
}
// get the file stats
zip_stat_init(&file_stat);
zip_stat(apk_file, file_name, 0, &file_stat);
char *buffer = new char[file_stat.size];
// read the file
int result = zip_fread(file, buffer, file_stat.size);
if(result == -1)
{
delete[] buffer;
zip_fclose(file);
zip_close(apk_file);
apk_open = false;
return;
}
// do something with the file
// code goes here
// delete the buffer, close the file and apk
delete[] buffer;
zip_fclose(file);
zip_close(apk_file);
apk_open = false;
Not exactly fopen/fread but it gets the job done. It should be pretty easy to wrap this to your own file reading function to abstract the zip layer.