Skip to content

Instantly share code, notes, and snippets.

@mcxiaoke
Created January 14, 2016 15:58
Show Gist options
  • Save mcxiaoke/ee4908e17bf6dcf53aec to your computer and use it in GitHub Desktop.
Save mcxiaoke/ee4908e17bf6dcf53aec to your computer and use it in GitHub Desktop.
JNIHelper from Android NDK
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <fstream>
#include <iostream>
#include "JNIHelper.h"
namespace ndk_helper
{
#define CLASS_NAME "android/app/NativeActivity"
//---------------------------------------------------------------------------
//JNI Helper functions
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//Singleton
//---------------------------------------------------------------------------
JNIHelper* JNIHelper::GetInstance()
{
static JNIHelper helper;
return &helper;
}
//---------------------------------------------------------------------------
//Ctor
//---------------------------------------------------------------------------
JNIHelper::JNIHelper()
{
pthread_mutex_init( &mutex_, NULL );
}
//---------------------------------------------------------------------------
//Dtor
//---------------------------------------------------------------------------
JNIHelper::~JNIHelper()
{
pthread_mutex_lock( &mutex_ );
JNIEnv *env;
activity_->vm->AttachCurrentThread( &env, NULL );
env->DeleteGlobalRef( jni_helper_java_ref_ );
env->DeleteGlobalRef( jni_helper_java_class_ );
activity_->vm->DetachCurrentThread();
pthread_mutex_destroy( &mutex_ );
}
//---------------------------------------------------------------------------
//Init
//---------------------------------------------------------------------------
void JNIHelper::Init( ANativeActivity* activity,
const char* helper_class_name )
{
JNIHelper& helper = *GetInstance();
pthread_mutex_lock( &helper.mutex_ );
helper.activity_ = activity;
JNIEnv *env;
helper.activity_->vm->AttachCurrentThread( &env, NULL );
//Retrieve app name
jclass android_content_Context = env->GetObjectClass( helper.activity_->clazz );
jmethodID midGetPackageName = env->GetMethodID( android_content_Context, "getPackageName",
"()Ljava/lang/String;" );
jstring packageName = (jstring) env->CallObjectMethod( helper.activity_->clazz,
midGetPackageName );
const char* appname = env->GetStringUTFChars( packageName, NULL );
helper.app_name_ = std::string( appname );
jclass cls = helper.RetrieveClass( env, helper_class_name );
helper.jni_helper_java_class_ = (jclass) env->NewGlobalRef( cls );
jmethodID constructor = env->GetMethodID( helper.jni_helper_java_class_, "<init>", "()V" );
helper.jni_helper_java_ref_ = env->NewObject( helper.jni_helper_java_class_, constructor );
helper.jni_helper_java_ref_ = env->NewGlobalRef( helper.jni_helper_java_ref_ );
env->ReleaseStringUTFChars( packageName, appname );
helper.activity_->vm->DetachCurrentThread();
pthread_mutex_unlock( &helper.mutex_ );
}
//---------------------------------------------------------------------------
//readFile
//---------------------------------------------------------------------------
bool JNIHelper::ReadFile( const char* fileName,
std::vector<uint8_t>* buffer_ref )
{
if( activity_ == NULL )
{
LOGI( "JNIHelper has not been initialized.Call init() to initialize the helper" );
return false;
}
//First, try reading from externalFileDir;
JNIEnv *env;
jmethodID mid;
pthread_mutex_lock( &mutex_ );
activity_->vm->AttachCurrentThread( &env, NULL );
jstring str_path = GetExternalFilesDirJString( env );
const char* path = env->GetStringUTFChars( str_path, NULL );
std::string s( path );
if( fileName[0] != '/' )
{
s.append( "/" );
}
s.append( fileName );
std::ifstream f( s.c_str(), std::ios::binary );
env->ReleaseStringUTFChars( str_path, path );
env->DeleteLocalRef( str_path );
activity_->vm->DetachCurrentThread();
if( f )
{
LOGI( "reading:%s", s.c_str() );
f.seekg( 0, std::ifstream::end );
int32_t fileSize = f.tellg();
f.seekg( 0, std::ifstream::beg );
buffer_ref->reserve( fileSize );
buffer_ref->assign( std::istreambuf_iterator<char>( f ), std::istreambuf_iterator<char>() );
f.close();
pthread_mutex_unlock( &mutex_ );
return true;
}
else
{
//Fallback to assetManager
AAssetManager* assetManager = activity_->assetManager;
AAsset* assetFile = AAssetManager_open( assetManager, fileName, AASSET_MODE_BUFFER );
if( !assetFile )
{
pthread_mutex_unlock( &mutex_ );
return false;
}
uint8_t* data = (uint8_t*) AAsset_getBuffer( assetFile );
int32_t size = AAsset_getLength( assetFile );
if( data == NULL )
{
AAsset_close( assetFile );
LOGI( "Failed to load:%s", fileName );
pthread_mutex_unlock( &mutex_ );
return false;
}
buffer_ref->reserve( size );
buffer_ref->assign( data, data + size );
AAsset_close( assetFile );
pthread_mutex_unlock( &mutex_ );
return true;
}
}
std::string JNIHelper::GetExternalFilesDir()
{
if( activity_ == NULL )
{
LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
return std::string( "" );
}
pthread_mutex_lock( &mutex_ );
//First, try reading from externalFileDir;
JNIEnv *env;
jmethodID mid;
activity_->vm->AttachCurrentThread( &env, NULL );
jstring strPath = GetExternalFilesDirJString( env );
const char* path = env->GetStringUTFChars( strPath, NULL );
std::string s( path );
env->ReleaseStringUTFChars( strPath, path );
env->DeleteLocalRef( strPath );
activity_->vm->DetachCurrentThread();
pthread_mutex_unlock( &mutex_ );
return s;
}
uint32_t JNIHelper::LoadTexture( const char* file_name )
{
if( activity_ == NULL )
{
LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
return 0;
}
JNIEnv *env;
jmethodID mid;
pthread_mutex_lock( &mutex_ );
activity_->vm->AttachCurrentThread( &env, NULL );
jstring name = env->NewStringUTF( file_name );
GLuint tex;
glGenTextures( 1, &tex );
glBindTexture( GL_TEXTURE_2D, tex );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
mid = env->GetMethodID( jni_helper_java_class_, "loadTexture", "(Ljava/lang/String;)Z" );
jboolean ret = env->CallBooleanMethod( jni_helper_java_ref_, mid, name );
if( !ret )
{
glDeleteTextures( 1, &tex );
tex = -1;
LOGI( "Texture load failed %s", file_name );
}
//Generate mipmap
glGenerateMipmap( GL_TEXTURE_2D );
env->DeleteLocalRef( name );
activity_->vm->DetachCurrentThread();
pthread_mutex_unlock( &mutex_ );
return tex;
}
std::string JNIHelper::ConvertString( const char* str,
const char* encode )
{
if( activity_ == NULL )
{
LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
return std::string( "" );
}
JNIEnv *env;
pthread_mutex_lock( &mutex_ );
activity_->vm->AttachCurrentThread( &env, NULL );
int32_t iLength = strlen( (const char*) str );
jbyteArray array = env->NewByteArray( iLength );
env->SetByteArrayRegion( array, 0, iLength, (const signed char*) str );
jstring strEncode = env->NewStringUTF( encode );
jclass cls = env->FindClass( "java/lang/String" );
jmethodID ctor = env->GetMethodID( cls, "<init>", "([BLjava/lang/String;)V" );
jstring object = (jstring) env->NewObject( cls, ctor, array, strEncode );
const char *cparam = env->GetStringUTFChars( object, NULL );
std::string s = std::string( cparam );
env->ReleaseStringUTFChars( object, cparam );
env->DeleteLocalRef( strEncode );
env->DeleteLocalRef( object );
activity_->vm->DetachCurrentThread();
pthread_mutex_unlock( &mutex_ );
return s;
}
//---------------------------------------------------------------------------
//Audio helpers
//---------------------------------------------------------------------------
int32_t JNIHelper::GetNativeAudioBufferSize()
{
if( activity_ == NULL )
{
LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
return 0;
}
JNIEnv *env;
jmethodID mid;
pthread_mutex_lock( &mutex_ );
activity_->vm->AttachCurrentThread( &env, NULL );
mid = env->GetMethodID( jni_helper_java_class_, "getNativeAudioBufferSize", "()I" );
int32_t i = env->CallIntMethod( jni_helper_java_ref_, mid );
activity_->vm->DetachCurrentThread();
pthread_mutex_unlock( &mutex_ );
return i;
}
int32_t JNIHelper::GetNativeAudioSampleRate()
{
if( activity_ == NULL )
{
LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
return 0;
}
JNIEnv *env;
jmethodID mid;
pthread_mutex_lock( &mutex_ );
activity_->vm->AttachCurrentThread( &env, NULL );
mid = env->GetMethodID( jni_helper_java_class_, "getNativeAudioSampleRate", "()I" );
int32_t i = env->CallIntMethod( jni_helper_java_ref_, mid );
activity_->vm->DetachCurrentThread();
pthread_mutex_unlock( &mutex_ );
return i;
}
//---------------------------------------------------------------------------
//Misc implementations
//---------------------------------------------------------------------------
jclass JNIHelper::RetrieveClass( JNIEnv *jni,
const char* class_name )
{
jclass activity_class = jni->FindClass( CLASS_NAME );
jmethodID get_class_loader = jni->GetMethodID( activity_class, "getClassLoader",
"()Ljava/lang/ClassLoader;" );
jobject cls = jni->CallObjectMethod( activity_->clazz, get_class_loader );
jclass class_loader = jni->FindClass( "java/lang/ClassLoader" );
jmethodID find_class = jni->GetMethodID( class_loader, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;" );
jstring str_class_name = jni->NewStringUTF( class_name );
jclass class_retrieved = (jclass) jni->CallObjectMethod( cls, find_class, str_class_name );
jni->DeleteLocalRef( str_class_name );
return class_retrieved;
}
jstring JNIHelper::GetExternalFilesDirJString( JNIEnv *env )
{
if( activity_ == NULL )
{
LOGI( "JNIHelper has not been initialized. Call init() to initialize the helper" );
return NULL;
}
// Invoking getExternalFilesDir() java API
jclass cls_Env = env->FindClass( CLASS_NAME );
jmethodID mid = env->GetMethodID( cls_Env, "getExternalFilesDir",
"(Ljava/lang/String;)Ljava/io/File;" );
jobject obj_File = env->CallObjectMethod( activity_->clazz, mid, NULL );
jclass cls_File = env->FindClass( "java/io/File" );
jmethodID mid_getPath = env->GetMethodID( cls_File, "getPath", "()Ljava/lang/String;" );
jstring obj_Path = (jstring) env->CallObjectMethod( obj_File, mid_getPath );
return obj_Path;
}
} //namespace ndkHelper
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <jni.h>
#include <vector>
#include <string>
#include <android/log.h>
#include <android_native_app_glue.h>
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, ndk_helper::JNIHelper::GetInstance()->GetAppName(), __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, ndk_helper::JNIHelper::GetInstance()->GetAppName(), __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, ndk_helper::JNIHelper::GetInstance()->GetAppName(), __VA_ARGS__))
namespace ndk_helper
{
/******************************************************************
* Helper functions for JNI calls
* This class wraps JNI calls and provides handy interface calling commonly used features
* in Java SDK.
* Such as
* - loading graphics files (e.g. PNG, JPG)
* - character code conversion
* - retrieving system properties which only supported in Java SDK
*
* NOTE: To use this class, add NDKHelper.java as a corresponding helpers in Java code
*/
class JNIHelper
{
private:
std::string app_name_;
ANativeActivity* activity_;
jobject jni_helper_java_ref_;
jclass jni_helper_java_class_;
//mutex for synchronization
//This class uses singleton pattern and can be invoked from multiple threads,
//each methods locks the mutex for a thread safety
mutable pthread_mutex_t mutex_;
jstring GetExternalFilesDirJString( JNIEnv *env );
jclass RetrieveClass( JNIEnv *jni,
const char* class_name );
JNIHelper();
~JNIHelper();
JNIHelper( const JNIHelper& rhs );
JNIHelper& operator=( const JNIHelper& rhs );
public:
/*
* To load your own Java classes, JNIHelper requires to be initialized with a ANativeActivity handle.
* This methods need to be called before any call to the helper class.
* Static member of the class
*
* arguments:
* in: activity, pointer to ANativeActivity. Used internally to set up JNI environment
* in: helper_class_name, pointer to Java side helper class name. (e.g. "com/sample/helper/NDKHelper" in samples )
*/
static void Init( ANativeActivity* activity,
const char* helper_class_name );
/*
* Retrieve the singleton object of the helper.
* Static member of the class
* Methods in the class are designed as thread safe.
*/
static JNIHelper* GetInstance();
/*
* Read a file from a strorage.
* First, the method tries to read the file from an external storage.
* If it fails to read, it falls back to use assset manager and try to read the file from APK asset.
*
* arguments:
* in: file_name, file name to read
* out: buffer_ref, pointer to a vector buffer to read a file.
* when the call succeeded, the buffer includes contents of specified file
* when the call failed, contents of the buffer remains same
* return:
* true when file read succeeded
* false when it failed to read the file
*/
bool ReadFile( const char* file_name,
std::vector<uint8_t>* buffer_ref );
/*
* Load and create OpenGL texture from given file name.
* The method invokes BitmapFactory in Java so it can read jpeg/png formatted files
*
* The methods creates mip-map and set texture parameters like this,
* glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
* glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
* glGenerateMipmap( GL_TEXTURE_2D );
*
* arguments:
* in: file_name, file name to read, PNG&JPG is supported
* return:
* OpenGL texture name when the call succeeded
* When it failed to load the texture, it returns -1
*/
uint32_t LoadTexture( const char* file_name );
/*
* Convert string from character code other than UTF-8
*
* arguments:
* in: str, pointer to a string which is encoded other than UTF-8
* in: encoding, pointer to a character encoding string.
* The encoding string can be any valid java.nio.charset.Charset name
* e.g. "UTF-16", "Shift_JIS"
* return: converted input string as an UTF-8 std::string
*/
std::string ConvertString( const char* str,
const char* encode );
/*
* Retrieve external file directory through JNI call
*
* return: std::string containing external file diretory
*/
std::string GetExternalFilesDir();
/*
* Audio helper
* Retrieves native audio buffer size which is required to achieve low latency audio
*
* return: Native audio buffer size which is a hint to achieve low latency audio
* If the API is not supported (API level < 17), it returns 0
*/
int32_t GetNativeAudioBufferSize();
/*
* Audio helper
* Retrieves native audio sample rate which is required to achieve low latency audio
*
* return: Native audio sample rate which is a hint to achieve low latency audio
*/
int32_t GetNativeAudioSampleRate();
/*
* Retrieves application bundle name
*
* return: pointer to an app name string
*
*/
const char* GetAppName()
{
return app_name_.c_str();
}
};
} //namespace ndkHelper
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment