Android JNI 高级编程
Posted ayanwan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android JNI 高级编程相关的知识,希望对你有一定的参考价值。
JNI作为java空间与C空间的沟通桥梁,在android中起到至关重要的作用。本文讲述jni的高级编程,需要有一定的jni基础,先分析了android源码中的jni编程模式,然后阐述一些实用的jni编程技巧。基础文章可以参考以下相关文章:
在android源码中,有两种JNI的注册方式,一种是:在android系统启动之初,使用startReg方法进行注册;另一种是使用static静态代码块中System.loadLibrary方法来加载动态库。
1、startReg
/*
* Register android native functions with the VM.
*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
/*
* This hook causes all future threads created in this process to be
* attached to the JavaVM. (This needs to go away in favor of JNI
* Attach calls.)
*/
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
ALOGV("--- registering native functions ---\\n");
/*
* Every "register" function calls one or more things that return
* a local reference (e.g. FindClass). Because we haven't really
* started the VM yet, they're all getting stored in the base frame
* and never released. Use Push/Pop to manage the storage.
*/
env->PushLocalFrame(200);
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) //gRegJNI包含了几百个原生函数
env->PopLocalFrame(NULL);
return -1;
env->PopLocalFrame(NULL);
//createJavaThread("fubar", quickTest, (void*) "hello");
return 0;
static const RegJNIRec gRegJNI[] =
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_android_os_Process),
REG_JNI(register_android_os_Binder),
...
下面以register_android_os_Binder为例,分析android 原生jni编程
1.1 register_android_os_Binder
int register_android_os_Binder(JNIEnv* env)
if (int_register_android_os_Binder(env) < 0)
return -1;
if (int_register_android_os_BinderInternal(env) < 0)
return -1;
if (int_register_android_os_BinderProxy(env) < 0)
return -1;
jclass clazz;
clazz = env->FindClass("android/util/Log");
LOG_FATAL_IF(clazz == NULL, "Unable to find class android.util.Log");
gLogOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
gLogOffsets.mLogE = env->GetStaticMethodID(
clazz, "e", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I");
assert(gLogOffsets.mLogE);
clazz = env->FindClass("android/os/ParcelFileDescriptor");
LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
gParcelFileDescriptorOffsets.mConstructor
= env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
clazz = env->FindClass("android/os/StrictMode");
LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.StrictMode");
gStrictModeCallbackOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
gStrictModeCallbackOffsets.mCallback = env->GetStaticMethodID(
clazz, "onBinderStrictModePolicyChange", "(I)V");
LOG_FATAL_IF(gStrictModeCallbackOffsets.mCallback == NULL,
"Unable to find strict mode callback.");
return 0;
先看int_register_android_os_Binder方法。
static struct bindernative_offsets_t
// Class state.
jclass mClass;//用于保存java空间中的binder的实例
jmethodID mExecTransact;//用于保存binder类中的execTransact方法
// Object state.
jfieldID mObject;//用于保存JavaBBinderHolder对象的指针
gBinderOffsets;
const char* const kBinderPathName = "android/os/Binder";
static int int_register_android_os_Binder(JNIEnv* env)
jclass clazz;
clazz = env->FindClass(kBinderPathName);
LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Binder");
gBinderOffsets.mClass = (jclass) env->NewGlobalRef(clazz);// mClass是在jni空间获取了一个binder实例
gBinderOffsets.mExecTransact
= env->GetMethodID(clazz, "execTransact", "(IJJI)Z");//获取并保存binder的成员方法execTranscat
assert(gBinderOffsets.mExecTransact);
gBinderOffsets.mObject
= env->GetFieldID(clazz, "mObject", "J");//获取并保存了binder的成员变量mObject
assert(gBinderOffsets.mObject);
return AndroidRuntime::registerNativeMethods(
env, kBinderPathName,
gBinderMethods, NELEM(gBinderMethods));//注册native方法
(1)gBinderOffsets结构体是关键。
首先,该结构体里面保存了jni中需要变量或方法的ID;
其次,这个是static属性,这保证了该结构体只能在本文件中使用,更为重要的是,这保证了该变量的值在程序整个运行期间都不释放。这样就可以减少获取java类中成员变量和成员函数的时间,进而提高运行效率。这种编程思路是应该提倡使用的。
(2)AndroidRuntime::registerNativeMethods该方法就是简单的注册native方法,这里就不分析了。
同样的,int_register_android_os_BinderInternal和int_register_android_os_BinderProxy也是都是一样的编程手法:使用静态结构体在注册native方法之前,获取并保存变量或方法的ID。
1.2 android_os_Binder_init的jni分析
该方法在binder的构造函数的调用:
/**
* Default constructor initializes the object.
*/
public Binder()
init();
if (FIND_POTENTIAL_LEAKS)
final Class<? extends Binder> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0)
Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
klass.getCanonicalName());
其源码如下:
static void android_os_Binder_init(JNIEnv* env, jobject obj)
JavaBBinderHolder* jbh = new JavaBBinderHolder();
if (jbh == NULL)
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return;
ALOGV("Java Binder %p: acquiring first ref on holder %p", obj, jbh);
jbh->incStrong((void*)android_os_Binder_init);
env->SetLongField(obj, gBinderOffsets.mObject, (jlong)jbh);//将JavaBBinderHolder赋值给mObject。
由此可见,在实例化Binder的时候,就JavaBBinderHolder对象指针保存在了gBinderOffsets.mObject里。
(1)JavaBBinderHolder分析
class JavaBBinderHolder : public RefBase
public:
sp<JavaBBinder> get(JNIEnv* env, jobject obj)
AutoMutex _l(mLock);
sp<JavaBBinder> b = mBinder.promote();
if (b == NULL)
b = new JavaBBinder(env, obj);
mBinder = b;
ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\\n",
b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount());
return b;
sp<JavaBBinder> getExisting()
AutoMutex _l(mLock);
return mBinder.promote();
private:
Mutex mLock;
wp<JavaBBinder> mBinder;
;
该类的get(JNIEnv* env, jobject obj)方法实例化了JavaBBinder方法,并返回该对象。
(2)JavaBBinder的jni分析
static JavaVM* jnienv_to_javavm(JNIEnv* env)
JavaVM* vm;
return env->GetJavaVM(&vm) >= 0 ? vm : NULL;
static JNIEnv* javavm_to_jnienv(JavaVM* vm)
JNIEnv* env;
return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL;
class JavaBBinder : public BBinder
public:
JavaBBinder(JNIEnv* env, jobject object)
: mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))//使用初始化列表对成员变量进行初始化
ALOGV("Creating JavaBBinder %p\\n", this);
android_atomic_inc(&gNumLocalRefs);
incRefsCreated(env);
bool checkSubclass(const void* subclassID) const
return subclassID == &gBinderOffsets;
jobject object() const
return mObject;
protected:
virtual ~JavaBBinder()//释放mObject成员变量。
ALOGV("Destroying JavaBBinder %p\\n", this);
android_atomic_dec(&gNumLocalRefs);
JNIEnv* env = javavm_to_jnienv(mVM);
env->DeleteGlobalRef(mObject);
virtual status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
JNIEnv* env = javavm_to_jnienv(mVM);
ALOGV("onTransact() on %p calling object %p in env %p vm %p\\n", this, mObject, env, mVM);
IPCThreadState* thread_state = IPCThreadState::self();
const int32_t strict_policy_before = thread_state->getStrictModePolicy();
//printf("Transact from %p to Java code sending: ", this);
//data.print();
//printf("\\n");
jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);//调用execTransact()方法,是mObject,可以想到是stub。
jthrowable excep = env->ExceptionOccurred();
if (excep)
report_exception(env, excep,
"*** Uncaught remote exception! "
"(Exceptions are not yet supported across processes.)");
res = JNI_FALSE;
/* clean up JNI local ref -- we don't return to Java code */
env->DeleteLocalRef(excep);
// Check if the strict mode state changed while processing the
// call. The Binder state will be restored by the underlying
// Binder system in IPCThreadState, however we need to take care
// of the parallel Java state as well.
if (thread_state->getStrictModePolicy() != strict_policy_before)
set_dalvik_blockguard_policy(env, strict_policy_before);
jthrowable excep2 = env->ExceptionOccurred();
if (excep2)
report_exception(env, excep2,
"*** Uncaught exception in onBinderStrictModePolicyChange");
/* clean up JNI local ref -- we don't return to Java code */
env->DeleteLocalRef(excep2);
// Need to always call through the native implementation of
// SYSPROPS_TRANSACTION.
if (code == SYSPROPS_TRANSACTION)
BBinder::onTransact(code, data, reply, flags);
//aout << "onTransact to Java code; result=" << res << endl
// << "Transact from " << this << " to Java code returning "
// << reply << ": " << *reply << endl;
return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;
virtual status_t dump(int fd, const Vector<String16>& args)
return 0;
private:
JavaVM* const mVM;
jobject const mObject;
;
(1)成员标量:mVM:就是JavaVM;mObject:是关键,传入的就是Binder的stub对象。
(2)构造和析构函数完成了对mObject的实例化与释放。此外构造函数中还考虑了jnienv的多线程问题。
(3)onTransact方法调用的是mObject的execTransact方法,由此可以联想到这里的mObject就是stub。
(4)NewGlobalRef与DeleteGlobalRef成对出现。申请了就要释放,防止内存泄漏。
那么mObject究竟是在什么地方传入的呢?答案是:ibinderForJavaObject
sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj)
if (obj == NULL) return NULL;
if (env->IsInstanceOf(obj, gBinderOffsets.mClass))
JavaBBinderHolder* jbh = (JavaBBinderHolder*)
env->GetLongField(obj, gBinderOffsets.mObject);
return jbh != NULL ? jbh->get(env, obj) : NULL;
//stub
if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass))
return (IBinder*)
env->GetLongField(obj, gBinderProxyOffsets.mObject);
//proxy
ALOGW("ibinderForJavaObject: %p is not a Binder object", obj);
return NULL;
在stub的流程中,先获通过gBinderOffsets的mObject变量获得JavaBBinderHolder的指针,然后调用其get方法,将stub保存在JavaBBinder中的mObject成员变量中。而ibinderForJavaObject则是有android_os_Parcel_writeStrongBinder传入参数
static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL)
const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
if (err != NO_ERROR)
signalExceptionForError(env, clazz, err);
简单的说就是如下关系:
gBinderOffsets.mObject--->JavaBBinderHolder.get()--->JavaBBinder.mObject--->stub
分析了很多,binder的jni还是挺复杂的,建议先了解一下binder机制,这里将总结一下:每个线程都只有一个JNIEnv,在反调用java的方法是注意JNIEnv需要通过JavaVM进行转换。在native method中,JNIEnv作为第一个参数传入。那么在JNIEnv不作为参数传入的时候,该如何获得它?JNI提供了两个函数:(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)和(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)。两个函数都利用JavaVM接口获得JNIEnv接口。GetEnv是1.2版本以后的新增接口。
2、System.loadLibrary
这是应用编程使用native方法的惯用方法。下面以MediaPlayer.java为例。
public class MediaPlayer
static
System.loadLibrary("media_jni");
native_init();//关键
private static native final void native_init();
...
简单的说,System.loadLibrary()的作用就是调用相应库中的JNI_OnLoad()方法。
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
ALOGE("ERROR: GetEnv failed\\n");
goto bail;
assert(env != NULL);
if (register_android_media_MediaPlayer(env) < 0) //注册native_init()方法。
ALOGE("ERROR: MediaPlayer native registration failed\\n");
goto bail;
if (register_android_media_MediaScanner(env) < 0)
ALOGE("ERROR: MediaScanner native registration failed\\n");
goto bail;
...
/* success -- return valid version number */
result = JNI_VERSION_1_4;
bail:
return result;
显然,这些也仅仅是注册native方法而已。而值得关注的是MediaPlayer.java中的一个native方法:native_init()。
struct fields_t
jfieldID context;
jfieldID surface_texture;
jmethodID post_event;
jmethodID proxyConfigGetHost;
jmethodID proxyConfigGetPort;
jmethodID proxyConfigGetExclusionList;
;
static fields_t fields;
static void
android_media_MediaPlayer_native_init(JNIEnv *env)
jclass clazz;
clazz = env->FindClass("android/media/MediaPlayer");
if (clazz == NULL)
return;
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL)
return;
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL)
return;
fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
if (fields.surface_texture == NULL)
return;
clazz = env->FindClass("android/net/ProxyInfo");
if (clazz == NULL)
return;
fields.proxyConfigGetHost =
env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;");
fields.proxyConfigGetPort =
env->GetMethodID(clazz, "getPort", "()I");
fields.proxyConfigGetExclusionList =
env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
也是使用静态结构体在注册native方法之前,获取并保存变量或方法的ID。同样的本质,不同的手法而已。这种编程手法是值得学习的:注册一个native_init()方法,然后在该方法中获取并保存变量或方法的ID,以提高效率。
3、测试
本文写了一个测试程序,用于验证本文的观点。
(1)试验说明:java调用native函数getInfo(Infor *pInfor),native将获取到的数据传给java空间,以便java空间使用。Infor结构体如下:
typedef struct
unsigned char A[2];
unsigned char B[12];
unsigned char C[4];
unsigned char D[9];
unsigned char E;
unsigned char F[4];
unsigned char G[4];
unsigned char H[4];
unsigned char E[16];
unsigned char I[64];
unsigned char T[7];
Infor;
jni 代码就不贴了。
(2)实验数据如下(单位:s)。其中,old是在getInfo函数中获取ID,执行完函数耗时情况;new是在native_init()中获取ID的情况。显然,速度提升了0.15s左右。
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | average | |
old | 0.365 | 0.312 | 0.306 | 0.299 | 0.327 | 0.472 | 0.299 | 0.301 | 0.335125 |
new | 0.182 | 0.184 | 0.178 | 0.204 | 0.181 | 0.182 | 0.194 | 0.18 | 0.185625 |
4、总结
(1)获取并保存变量或方法的ID,以提高效率。
(2)在native method中,JNIEnv作为第一个参数传入。那么在JNIEnv不作为参数传入的时候,JNI提供了两个函数:(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)和(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)。两个函数都利用JavaVM接口获得JNIEnv接口。GetEnv是1.2版本以后的新增接口。
【如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!】
以上是关于Android JNI 高级编程的主要内容,如果未能解决你的问题,请参考以下文章