配色: 字号:
ReactNative 4Android源码分析二JNI智能指针
2016-12-05 | 阅:  转:  |  分享 
  
ReactNative4Android源码分析二:JNI智能指针

《JNI智能指针之介绍篇》:

JNI指针



通常的app中,JNI提供的native函数主要充当Java类的扩展,逻辑层在Java端,JNI端较少使用OOP的设计思想。而对于native端功能较重的模块,例如开源的阅读器FBReader,native端与Java端有较多交互,即native会主动创建Java对象并调用它们的方法以实现功能,这时就需要考虑将native至Java的操作与访问框架化,形成更高层次的封装,以避免直接使用原始的JNI反射API集去操作Java对象。

对于ReactNativeForAndroid而言,这套访问框架尤其重要,其核心就是JNI智能指针这个基本数据类型。它的实现基于C11标准,将先用几篇对这套native至Java的操作框架进行介绍,为后续分析打下良好基础。



Native引用



首先回顾一下JavaObject(jobject)在native端的三种引用类型:



全局引用



类似于C语言中的全局变量。使用NewGlobalRef创建,支持跨线程访问,在调用释放DeleteGlobalRef销毁前,GC无法回收该引用对应的javaobject。



局部引用



概念上与C语言中的局部变量有相似点,但不等同。使用NewLocalRef创建,只能在本线程内安全访问,当创建该引用的native调用链返回至JVM时,未销毁的局部引用会被JVM自动GC回收。但由于局部引用表容量有限,在返回至JVM前,可以调用DeleteLocalRef先行销毁,避免局部引用表超限引起崩溃。



弱全局引用



与全局引用一样具有全局作用域,但不会影响GC回收,GC可以随时回收该引用对应的javaobject。使用NewWeakGlobalRef创建,当需要使用时,需要将其升级为全局引用或者局部引用,若已被回收,会返回null,使用DeleteWeakGlobalRef销毁。该引用类型使用场景较少。



由上可见,JNI智能指针的第一个需求,就是要自动管理jobject的生命周期,当进入与离开对应作用域时,需要自动调用对应生命周期的创建与销毁函数。这在C++中,通常会结合构造与析构函数来进行配对调用。若功能仅限于此,就与普通的智能指针和mutext锁管理机制类似了,更重要的需求是在C++层提供与被管理的Java对象镜像结构的C++对象,形成高层次封装。这样,对jobject的访问与操作就会被封装在对应的镜像C++对象中,相关JNI反射调用的细节被隐藏,对于其他native模块而言,与Java层的交互被转化成了与这些镜像C++对象的交互,整个实现风格OOP化了。这些镜像C++对象被称为wrapper对象,其定义代码位于ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h文件中。



先看一个使用范例:



structMyClass:publicJavaClass{

constexprstaticautokJavaDescriptor="Lcom/example/package/MyClass;";



voidfoo(){

staticautomethod=javaClassStatic()->getMethod("foo");

method(self());

}



staticlocal_refcreate(inti){

returnnewInstance(i);

}

};



autoobj=MyClass::create(10);

obj->foo();

Native的需求是在native端创建com.example.package.MyClass这个自定义的Java类的对象,并访问它的foo方法。



实现步骤



例子中实现的步骤是:



定义java的MyClass的wrapperC++类MyClass,所有wrapper均需要继承于JavaClass的一个模板实例,并将自身类型做为JavaClass的第一个模板类型参数,以供JavaClass获取具体wrapper的类型。



给static成员变量kJavaDescriptor赋值为对应Java类的全类名。

在wrapper类实现镜像方法foo(),其会获取jclass的包装类JClass对象,并获取jmethod的包装类JMethod进行调用。



create工厂方法中使用newInstance构建镜像对象的实例,并将其存至局部智能指针local_ref。这样就可以通过智能指针访问wrapperclass提供的foo方法,实现了native至Java的镜像映射。



除了实现对一个java类的的映射,还需要支持对java继承关系的映射。若java的MyClass有一子类MyChildClass,native层为其建立的wrapperclass可如下:



structMyChildClass:publicJavaClass{

constexprstaticautokJavaDescriptor="Lcom/example/package/MyChildClass;";

};

这里需要用到JavaClass的第二个模板参数,设为MyClass,它是JavaClass



疑问



这就带来几个问题:



javaObject与jobject的关系是什么?



为什么智能指针的模板参数能够接受多种类型?



模板参数起到的作用是什么?



结尾



这些问题将在下一篇智能指针的具体实现篇中解答。

总结一下,在ReactNativeforAndroid中,为了简化native层对Java层的调用,提供了镜像结构的wrapperclass,结合智能指针,将jobject的生命周期管理、javamethod的反射调用等“样板”代码封装起来,是比较优雅的JNI调用框架。



《JNI智能指针之实现篇》

global_ref



全局指针与jobject全局引用相对应,使用场景包括全局变量、成员变量等。这些场景中的jobject,不应该从native返回至JVM时释放,故使用global_ref进行包裹。



local_ref



局部指针与jobject局部引用相对应,使用场景包括局部变量、函数返回值等。当local_ref离开所在作用域时,会释放自身对jobject的引用,即在析构函数中调用DeleteLocalRef。



weak_ref



弱指针与jobject弱全局引用相对应,在目前版本的RN代码中未实际使用。



alias_ref



别名指针,不对持有的jobject进行生命周期管理。即在构造与析构别名智能指针对象时,不会对持有的jobject进行创建与销毁的JNI操作。该指针的目的只是为了提供调用wrapper对象方法的能力,jobject的生命周期由另外的智能指针或直接由JVM进行管理和保证有效性,指针自身不对其额外进行管理。



以上智能指针均未提供引用计数功能,而是通过在智能指针间交换被管理的对象来进行指针转换。智能指针的类图如下,其代码位于ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/References.h:

智能指针类图





智能指针



从上图可以看出,由于功能区别,alias_ref别名指针是独立的一个类,其余的智能指针有共同的父类base_owned_ref。最需要关注智能指针的存储,base_owned_ref与alias_ref均有同样的成员变量:



detail::ReprStoragestorage_;

storage_用来存储创建出来的wrapper对象。这边的设计比较巧妙,使用C++中的类型萃取技术(typetraits)把wrapper对象和jobject关联,并将jobject(JNI层),javaobject(RN层),wrapper对象(RN层)三者在内存空间上统一了。先看ReprStorage的实现:



template

structReprStorage{

explicitReprStorage(JniTypeobj)noexcept;



voidset(JniTypeobj)noexcept;



Repr&get()noexcept;

constRepr&get()constnoexcept;

JniTypejobj()constnoexcept;



private:



usingStorage=typenamestd::aligned_storage::type;

Storagestorage_;

};



template

voidReprStorage::set(JniTypeobj)noexcept{

new(&storage_)Repr;

ReprAccess::set(get(),obj);

}



template

Repr&ReprStorage::get()noexcept{

returnreinterpret_cast(&storage_);

}

无关的代码已被略去。ReprStorage使用私有变量storage_做为存储空间,尺寸为JObjectBase类的size。从set和get函数可以看出,storage_内存空间的分配是delay到设值的时候,并将storage_内存空间的指针通过reinterpret_cast类型转换为Repr类型。ReprStorage的模板参数Repr是存储的wrapperclass的类型,在上章的使用范例中,也就是MyClass:



structMyClass:publicJavaClass

wrapperclass之间的继承关系如下:



wrapperclass继承关系



JObjectBase



JObjectBase是wrapperclass的根父类,这里显然存在一个问题:为何能用父类size的内存空间去存放任意子类对象?完成继承关系的讨论后,再回顾这个问题。图中的CustomJavaClass是一个自定义wrapperclass,它继承于JavaClass的一个模板实例。所有Java类(除去Object类)的native镜像wrapperclass,均需要继承于JavaClass的某个模板实例。JavaClass起到两个桥梁作用:当前定义的wrapperclass与对应Java类父类的wrapperclass之间继承关系的桥梁;当前定义的wrapperclass与对应Java对象的JNIjobject的桥梁。它有三个模板参数,下面是它的类声明,其代码位于ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/CoreClasses.h:



template

classFBEXPORTJavaClass:publicBase{

public:

staticalias_refjavaClassStatic();

staticlocal_refjavaClassLocal();

protected:

///Allocatesanewobjectandinvokesthespecifiedconstructor

///LikeJClass''sgetConstructor,thisfunctioncanonlycheckatruntimeif

///theclassactuallyhasaconstructorthatacceptsthecorrespondingtypes.

///WhileaJavaClass-typecanexposethisfunctiondirectly,itisrecommended

///toinsteadtousethistoexplicitlyonlyexposethoseconstructorsthat

///theJavaclassactuallyhas(i.e.withstaticcreate()functions).

template

staticlocal_refnewInstance(Args...args){

returndetail::newInstance(args...);

}



javaobjectself()constnoexcept;

}

无关的代码已被略去。



第一个模板参数是子wrapperclass的类型。

JavaClass的这个模板实例作为这个wrapperclass的父类,提供了创建wrapperclass对象的工厂方法和与对应jobject关联的能力,故需要获得子类的类型。



第二个模板参数是该JavaClass模板实例的父类。

它的默认类型是JObject,代表java.lang.Object类的wrapperclass,是唯一不需要继承于JavaClass的wrapper对象。JObject提供了对java对象的Class、Field、Method等的访问封装方法,wrapperclass通过对它的继承关系,获得了去调用Javamethod的能力。若wrapperclass无需提供Java类父类方法的调用能力,则第二个模板参数保持默认值JObject即可,否则,第二个模板参数就为Java类父类的wrapperclass,例子在上章中已提供。由于JavaClass帮助构建了继承链,wrapperclass具备了提供父java类的native镜像方法的能力。



第三个模板参数是定义的wrapperclass对应Java对象的JNIjobject的类型



JavaClass会将wrapperclass与jobject建立起绑定关系。根据jobject的具体类型,会分两种情况,如果为JNI预定义的jobject类型,例如jclass、jthrowable、jarray、jstring等,第三个模板参数就是它们,RN中已经预定义了它们的wrapperclass。例如:



classFBEXPORTJString:publicJavaClass{

}

另外一种情况就是非预定义类型,也就是jobject这个通用类型(jclass、jstring等预定义类型也是它的子类),这时第三个参数就应为默认值void。即不由模板参数指定jobject的具体子类,而是使用wrapperclass内部嵌套定义的扩展子类。



浏览jobject内部定义前,先回顾刚才的存储问题。既然sizeof(JObjectBase)的内存空间能够正确放置任意wrapperclass子类的实例,就说明子类所占的内存空间与根类JObjectBase一样。而对于一个C++类而言,对象的size就是所有非static成员变量的size之和(需考虑内存对齐),这就约束了wrapperclass作为子类不能额外声明任何非static成员变量,才能与根父类JObjectBase保持size的一致。从wrapperclass的设计目的考虑,它只是Java类在native空间的镜像类和接口包装类,业务逻辑应由调用者实现,wrapperclass自身应该是无状态的,所以不允许wrapperclass定义非static成员变量是合理的。



智能指针存储的是wrapperclass的实例,wrapperclass中存储的是jobject,从以上分析可以知道,存储的jobject成员变量只能由根父类JObjectBase去承载。下面是JObjectBase的类定义,代码位于ReactAndroid/src/main/jni/first-party/fb/include/fb/fbjni/References-forward.h:



structJObjectBase{

jobjectget()constnoexcept;

voidset(jobjectreference)noexcept;

jobjectthis_;

};

JObjectBase是一个简单的bean类,唯一的成员变量就是jobject,这也是所有wrapperclass唯一的成员变量。在JavaClass模板类中,为了实现jobject与wrapperclass两者的关联,对jobject做了内部扩展定义。为了理解它,先回顾下jobject在jni.h中的原始定义:



class_jobject{};

typedef_jobjectjobject;

class_jstring:public_jobject{};

所以,jobject就是个指针,指向javaobject在JVM中的内存对象,对于像dexposed这样的热修复框架,就是利用这些指针去修改java对象模型来改变javamethod的属性以实现hook。这里的定义_jobject是空类,只是为了定义指针语法以在JNI中去引用内存对象,并不意味Java内存对象真的是个空对象,真正定义是JVM内部的、平台相关的,而不需要将实现细节暴露给JNI。在JavaClass中,对jobject的扩展定义javaobject类型如下:



template

classFBEXPORTJavaClass:publicBase{

usingJObjType=typenamedetail::JTypeFor;

public:

using_javaobject=typenameJObjType::_javaobject;

usingjavaobject=typenameJObjType::javaobject;

};



namespacedetail{

template

structJTypeFor{

static_assert(

std::is_base_of<

std::remove_pointer::type,

typenamestd::remove_pointer::type

>::value,"");

using_javaobject=typenamestd::remove_pointer::type;

usingjavaobject=JType;

};



template

structJTypeFor{

//JNIpatternforjobjectassignablepointer

struct_javaobject:Base::_javaobject{

//Thisallowsustomapbacktothedefiningtype(inReprType,for

//examwww.shanxiwang.netple).

typedefTJniRefRepr;

};

usingjavaobject=_javaobject;

};

}

在JavaClass中,将jobject拓展定义为javaobject。javaobject是typenameJObjType::javaobject,也就是JTypeFor的一个模板实例类型的成员指针类型。以例子代码中的MyClass为例,父类JavaClass接收的三个模板参数分别为MyClass,JObject,void,JTypeFor的三个模板参数也依次是它们,由于第三个参数是void,故会使用上面代码中的JTypeFor



structJTypeFor{

struct_javaobject:JObject::_javaobject{

typedefMyClassJniRefRepr;

};

usingjavaobject=_javaobject;

};

}

可看到_javaobject继承于JObject::_javaobject,它的定义如下:



typedef_jobject_javaobject;

typedef_javaobjectjavaobject;

JObject::_javaobject就是jni.h中的_jobject类型,故MyClass中的_javaobject对_jobject的继承扩展,只是添加了一个嵌套类成员类型JniRefRepr,来指向当前_javaobject所对应的wrapperclass类型,这就是所谓的C++类型萃取技术。因为_jobject是用来指向Java内存对象,所以不能用继承后添加成员变量的方式来扩展,否则会破坏内存对象,而成员类型是属于类定义,不会占用对象的空间。另,javaobject是指向_javaobject的指针,jobject是指向_jobject的指针。



问题解答



现在来解答上章的三个问题:



javaobject与jobject的关系是什么?



两者本质是一样的,都是指向java内存对象的JNI引用;区别是javaobject是jobject继承扩展,继承后的javaobject拥有一个类成员类型变量,指向对应的wrapperclass,使得两者彼此关联。从内存上看,sizeof(JObjectBase)==sizeof(任意wrapperclass)==sizeof(jobject)==sizeof(javaobject),达到了有机的统一。



为什么智能指针的模板参数能够接受多种类型?



在上章例子中,local_ref\与local_ref\传递了不同模板参数,从语法上看区别很大,但在内部实现时,都会进行类型萃取。即无论传递的何种类型,都会萃取出对应的ReprType(wrapperclass类型)、JniType(javaobject类型)。javaobject类是wrapperclass的成员类型,故从wrapperclass可以获得对应javaobject引用的类型;javaobject类的成员类型是wrapperclass,故从javaobject业可以获得对应wrapperclass的类型。在框架中提供了两个工具来进行类型萃取和转换:



//GivenT,eitherajobject-liketypeoraJavaClass-derivedtype,ReprType

//isthecorrespondingJavaClass-derivedtypeandJniTypeisthe

//jobject-liketype.

template

usingReprType=typenamedetail::RefReprType::type;



template

usingJniType=typenamedetail::JavaObjectType::type;

ReprType用来从模板参数中获得wrapperclass类型,JniType用来从模板参数中获得javaobject类型。通过这样的机制,两个类型彼此打通,故无论传递何种模板参数,智能指针都能正确存储对应类型的wrapperclass。



模板参数起到的作用是什么?



从上可以了解到,智能指针的模板参数用来获取存储的wrapperclass的类型。对于local_ref和global_ref,它们由于都是强引用,可以用来直接调用存储的wrapperclass提供的方法,所以它们的实现模板类basic_strong_ref在base_owned_ref提供的存储功能的基础上,继承扩展提供了指针操作符的重载,以将对智能指针的访问转发到wrapper对象上,代码如下:



template

inlineautobasic_strong_ref::operator->()noexcept->Repr{

return&storage_.get();

}



template

inlineautobasic_strong_ref::operator->()constnoexcept->constRepr{

return&storage_.get();

}

重载实现中,从storage_中获得存储的wrapperclass实例返回即可。对于模板参数Alloc,则是分配器的类型,封装了创建、销毁jobject的操作,由智能指针在构造和析构时调用,实现jobject生命周期的管理。



总结



总结一下,本篇简述了wrapperclass与智能指针的主干实现。wrapperclass扩展jobject至javaobject,使类型彼此关联;构建了继承链,将对javamethod的反射调用和java类自身的继承关系分解在链路的不同节点。智能指针通过类型萃取负责将jobject存储至正确的wrapper实例,以对外提供镜像方法;结合构造与析构函数,自动进行jobject的生命周期管理。

献花(0)
+1
(本文系网络学习天...首藏)