Android React-Native通信数据模型分析
无论是计算机领域还是日常生活中,我们所言的通信,其核心都是数据信息的交换,而数据模型的优劣对通信效率有着决定性的作用。
在React-Native项目中,Javascript语言与Native两种语言(Java或OC等)间存在着大量的数据交换,也就是所谓的通信。众所周知,移动APP对性能的要求无比苛刻,如果通信数据模型设计地不合理,很可能引起多线程下的数据安全问题,以及应用性能问题,比如内存泄漏,UI绘制缓慢等。
前面几篇博客我们详细分析过React-Native的通信机制,主要有两个方向:Java->Bridge->Javascript和Javascript->Bridge->Java。所以,真正的数据交换其实发生在Java与Bridge,Javascript与Bridge两个环节。
Javascript与Bridge间的数据通信是借助于Webkit使用Json完成,简单实用,水到渠成,不多分析。而Java与Bridge间的数据通信相比之下就复杂多了,作为真正运行在设备上的程序语言,这恰恰是决定整个通信过程效率高低最核心的一环,也是本篇博客研究的内容。
Java是Android应用程序的本地开发语言,而Bridge是使用C++开发的动态链接库,由Java语言通过JNI的方式调用。Java与Bridge间的数据通信,实质是Java和C++两种程序语言间的数据传输,而传递的方向又分为两个场景:Java传输数据给C++和C++传输数据给Java。
我们先来看第一种场景。
Java主动向Javascript通信,主要是通过ReactBridge.java类的callFunction方法,将需要调用的组件(moduleId)、功能(methodId)、数据(arguments)三者传递到Bridge。
packagecom.facebook.react.bridge;
publicclassReactBridgeextendsCountable{
staticfinalStringREACT_NATIVE_LIB="reactnativejni";
static{
SoLoader.loadLibrary(REACT_NATIVE_LIB);
}
...
publicnativevoidcallFunction(intmoduleId,intmethodId,NativeArrayarguments);
...
}
我们可以看到,传输的数据类型是NativeArray,来瞧下具体的代码,位于com.facebook.react.bridge包下:
publicabstractclassNativeArray{
static{
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
protectedNativeArray(HybridDatahybridData){
mHybridData=hybridData;
}
@Override
publicnativeStringtoString();
@DoNotStrip
privateHybridDatamHybridData;
}
NativeArray是一个抽象类,其中,只有一个HybridData类型成员变量,由其构造方法赋值初始化。
NativeArray还有一个名为ReadableNativeArray的直接子类,和一个名为WritableNativeArray的间接子类,后者是继承于前者。顾名思义,一个是用于读数据,一个是用于写数据。
Java向Bridge传输数据,自然就是写数据了,所以我们先来看WritableNativeArray。
packagecom.facebook.react.bridge;
publicclassWritableNativeArrayextendsReadableNativeArrayimplementsWritableArray{
static{
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
publicWritableNativeArray(){
super(initHybrid());
}
@Override
publicnativevoidpushNull();
@Override
publicnativevoidpushBoolean(booleanvalue);
@Override
publicnativevoidpushDouble(doublevalue);
@Override
publicnativevoidpushInt(intvalue);
@Override
publicnativevoidpushString(Stringvalue);
@Override
publicvoidpushArray(WritableArrayarray){
Assertions.assertCondition(
array==null||arrayinstanceofWritableNativeArray,"Illegaltypeprovided");
pushNativeArray((WritableNativeArray)array);
}
@Override
publicvoidpushMap(WritableMapmap){
Assertions.assertCondition(
map==null||mapinstanceofWritableNativeMap,"Illegaltypeprovided");
pushNativeMap((WritableNativeMap)map);
}
privatenativestaticHybridDatainitHybrid();
privatenativevoidpushNativeArray(WritableNativeArrayarray);
privatenativevoidpushNativeMap(WritableNativeMapmap);
}
里面有7个写数据的native方法,涵盖了int、string,array,map等不同的数据类型和结构。
还有一个名为initHybrid()的native方法,用于创建HybridData类的实例,然后通过构造方法给其超父类NativeArray的mHybridData成员变量赋值,具体作用后面来分析,先略过。
接下来,我们来验证一下WritableNativeArray是否是真正传输给Bridge的数据类型。
Java调用Javascript组件,都是由名为JavaScriptModuleInvocationHandler的动态代理类统一拦截处理的吗?来回顾一下代码,位于com.facebook.react.bridge.JavaScriptModuleRegistry.java:
privatestaticclassJavaScriptModuleInvocationHandlerimplementsInvocationHandler{
privatefinalCatalystInstanceImplmCatalystInstance;
privatefinalJavaScriptModuleRegistrationmModuleRegistration;
publicJavaScriptModuleInvocationHandler(
CatalystInstanceImplcatalystInstance,
JavaScriptModuleRegistrationmoduleRegistration){
mCatalystInstance=catalystInstance;
mModuleRegistration=moduleRegistration;
}
@Override
public@NullableObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
StringtracingName=mModuleRegistration.getTracingName(method);
mCatalystInstance.callFunction(
mModuleRegistration.getModuleId(),
mModuleRegistration.getMethodId(method),
Arguments.fromJavaArgs(args),
tracingName);
returnnull;
}
}
callFunction方法传递的参数类型是Arguments.fromJavaArgs(args),具体代码又如下:
publicstaticWritableNativeArrayfromJavaArgs(Object[]args){
WritableNativeArrayarguments=newWritableNativeArray();
for(inti=0;i<args.length;i++){
Objectargument=args[i];
if(argument==null){
arguments.pushNull();
continue;
}
ClassargumentClass=argument.getClass();
if(argumentClass==Boolean.class){
arguments.pushBoolean(((Boolean)argument).booleanValue());
}elseif(argumentClass==Integer.class){
arguments.pushDouble(((Integer)argument).doubleValue());
}elseif(argumentClass==Double.class){
arguments.pushDouble(((Double)argument).doubleValue());
}elseif(argumentClass==Float.class){
arguments.pushDouble(((Float)argument).doubleValue());
}elseif(argumentClass==String.class){
arguments.pushString(argument.toString());
}elseif(argumentClass==WritableNativeMap.class){
arguments.pushMap((WritableNativeMap)argument);
}elseif(argumentClass==WritableNativeArray.class){
arguments.pushArray((WritableNativeArray)argument);
}else{
thrownewRuntimeException("Cannotconvertargumentoftype"+argumentClass);
}
}
returnarguments;
}
正如我们猜测的一般,fromJavaArgs静态方法返回的是一个新创建的WritableNativeArray对象实例,然后按照数据类型,调用相应的push方法。有些特殊的是,int型和float型都当成了double型来处理,这样做并不会造成数据的损害。
刚刚说到,WritableNativeArray的所有写入数据的方法都是native方法,即Java层面的通信数据全部是直接写入到Bridge层的,换言之,WritableNativeArray仅仅起到了数据传输管道的作用。这样做,有两个好处:
1、数据只在C++存有一份,这样避免了数据具有多个副本,节省了一部分的内存。
2、减小对WritableNativeArray对象的依赖,使其容易释放,可以由虚拟机GC自动回收内存。
那么,在Bridge层中,C++又是如何处理push过来的数据的呢?
先来看一下WritableNativeArray中native方法在JNI中动态注册的代码,位于react/jni/OnLoad.cpp中:
staticvoidregisterNatives(){
jni::registerNatives("com/facebook/react/bridge/WritableNativeArray",{
makeNativeMethod("initHybrid",WritableNativeArray::initHybrid),
makeNativeMethod("pushNull",WritableNativeArray::pushNull),
makeNativeMethod("pushBoolean",WritableNativeArray::pushBoolean),
makeNativeMethod("pushDouble",WritableNativeArray::pushDouble),
makeNativeMethod("pushInt",WritableNativeArray::pushInt),
makeNativeMethod("pushString",WritableNativeArray::pushString),
makeNativeMethod("pushNativeArray",WritableNativeArray::pushNativeArray),
makeNativeMethod("pushNativeMap","(Lcom/facebook/react/bridge/WritableNativeMap;)V",
WritableNativeArray::pushNativeMap),
});
}
很明显,在C++中也存在着一个名为WritableNativeArray的类,具有与着native方法相对应的方法,巧的是,它也是继承于ReadableNativeArray类(注意HybridClass模板类的第二个泛型表示父类):
structWritableNativeArray
:publicjni::HybridClass<WritableNativeArray,ReadableNativeArray>{
staticconstexprconstchar*kJavaDescriptor="Lcom/facebook/react/bridge/WritableNativeArray;";
WritableNativeArray()
:HybridBase(folly::dynamic({})){}
staticlocal_ref<jhybriddata>initHybrid(alias_ref<jclass>){
returnmakeCxxInstance();
}
voidpushNull(){
...
array.push_back(nullptr);
}
voidpushBoolean(jbooleanvalue){
...
array.push_back(value==JNI_TRUE);
}
voidpushDouble(jdoublevalue){
...
array.push_back(value);
}
voidpushInt(jintvalue){
...
array.push_back(value);
}
voidpushString(jstringvalue){
...
array.push_back(wrap_alias(value)->toStdString());
}
voidpushNativeArray(WritableNativeArray*otherArray){
...
array.push_back(std::move(otherArray->array));
otherArray->isConsumed=true;
}
voidpushNativeMap(jobjectjmap){
...
array.push_back(std::move(map->map));
map->isConsumed=true;
}
...
}
看到这里,我们不禁会猜测,C++中的ReadableNativeArray类很可能也是继承于NativeArray。
当然,事实确实是这样的。在C++中存在着与Java中完全呼应的三个类:NativeArray、ReadableNativeArray、WritableNativeArray,命名和继承关系都是完全一致的!
而且可以看到,所有的数据都被存储到父类NativeArray的array变量中。
不过,问题来了!
C++中的WritableNativeArray对象和Java中的WritableNativeArray两个同名对象间是否存在着某种联系呢,比如一一映射的关系?
答案是肯定的!因为每当一个Java层的WritableNativeArray对象被创建,在C++层都会有一个相应的WritableNativeArray对象被创建,用来接收Java层push过来的数据。
再来回顾下WritableNativeArray.java创建的过程。
publicclassWritableNativeArrayextendsReadableNativeArrayimplementsWritableArray{
...
publicWritableNativeArray(){
super(initHybrid());
}
...
privatenativestaticHybridDatainitHybrid();
...
}
在构造WritableNativeArray的时候,会通过initHybrid方法创建一个HybridData对象,并保存到其超父类NativeArray的成员变量mHybridData中。
而HybridData对象又是什么呢?
publicclassHybridData{
//PrivateC++instance
privatelongmNativePointer=0;
publicHybridData(){
Prerequisites.ensure();
}
publicnativevoidresetNative();
protectedvoidfinalize()throwsThrowable{
resetNative();
super.finalize();
}
}
publicclassPrerequisites{
...
publicstaticvoidensure(){
SoLoader.loadLibrary("fbjni");
}
...
}
构造函数中Prerequisites.ensure(),是用来加载fbjni动态链接库的。
在HybridData类中,有一个long的私有成员变量,根据注释和名字可以猜测与C++指针相关,具体是不是这样呢?我们来看HybridData对象通过initHybrid()初始化的过程。
代码位于react/jni/OnLoad.cpp中:
structWritableNativeArray
:publicjni::HybridClass<WritableNativeArray,ReadableNativeArray>{
...
staticlocal_ref<jhybriddata>initHybrid(alias_ref<jclass>){
returnmakeCxxInstance();
}
...
}
这里的jhybriddata指的就是HybridData(Java)对象,其是通过typedef方式定义在jni/first-party/jni/fbjni/Hybrid.h中的。
...
structHybridData:publicJavaClass<HybridData>{
constexprstaticautokJavaDescriptor="Lcom/facebook/jni/HybridData;";
voidsetNativePointer(std::unique_ptr<BaseHybridClass>new_value);
BaseHybridClass*getNativePointer();
staticlocal_ref<HybridData>create();
};
...
typedefdetail::HybridData::javaobjectjhybriddata;
...
facebook在这里对在JNI中创建Java对象的过程做了非常高效的封装,即JavaClass对象。所有JavaClass的子类都通过一个名为kJavaDescriptor的字符串指针,来描述相对应的Java对象类名。
继续来看makeCxxInstance()是如何创建HybridData(Java)对象的。代码同样在jni/first-party/jni/fbjni/Hybrid.h中。
classHybridClass:publicdetail::HybridTraits<Base>::CxxBase{
...
staticlocal_ref<detail::HybridData>makeHybridData(std::unique_ptr<T>cxxPart){
autohybridData=detail::HybridData::create();
hybridData->setNativePointer(std::move(cxxPart));
returnhybridData;
}
template<typename...Args>
staticlocal_ref<detail::HybridData>makeCxxInstance(Args&&...args){
returnmakeHybridData(std::unique_ptr<T>(newT(std::forward<Args>(args)...)));
}
...
}
结合下前面的WritableNativeArray(C++)来看
structWritableNativeArray
:publicjni::HybridClass<WritableNativeArray,ReadableNativeArray>{
staticconstexprconstchar*kJavaDescriptor="Lcom/facebook/react/bridge/WritableNativeArray;";
...
staticlocal_ref<jhybriddata>initHybrid(alias_ref<jclass>){
returnmakeCxxInstance();
}
...
}
在创建HybridData(Java)的时候,模板类HybridClass的第一个泛型T,表示的是WritableNativeArray(C++)这个结构体。所以,makeHybridData中的newT(std::forward(args)…)新创建的T就是WritableNativeArray(C++)对象。
继续来看makeHybridData方法,参数cxxPart是刚刚创建的WritableNativeArray对象的指针。里面通过detail::HybridData::create()真正创建了HybridData(Java)和HybridData(C++)对象,并将WritableNativeArray(C++)对象的指针通过setNativePointer方法注入到了HybridData(Java)中。
接下来,看create和setNativePointer两个方法的细节,在Hybrid.cpp中:
local_ref<HybridData>HybridData::create(){
returnnewInstance();
}
voidHybridData::setNativePointer(std::unique_ptr<basehybridclass>new_value){
staticautopointerField=getClass()->getField<jlong>("mNativePointer");
auto*old_value=reinterpret_cast<BaseHybridClass*>(getFieldValue(pointerField));
if(new_value){
...
}elseif(old_value==0){
return;
}
deleteold_value;
...
setFieldValue(pointerField,reinterpret_cast<jlong>(new_value.release()));
}
create里面是通过newInstance方式创建了HybridData(Java)和HybridData(C++)对象,具体细节不细说了,读者自行去研究facebook的封装。
HybridData(C++)的setNativePointer方法中的参数new_value,为WritableNativeArray(C++)对象的指针,使用reinterpret_cast关键字将其转换成long型,设置到mNativePointer中。而这里的mNativePointer,就是我们前面谈到的HybridData(Java)类的成员变量了!
有一点需要注意的是,保存WritableNativeArray(C++)对象指针的时候,会先获取原先保存的指针并删除回收(如果存在的话),主要目的是回收WritableNativeArray(C++)对象的内存,调用的时机是HybridData(Java)的finalize,也就是WritableNativeArray(Java)和HybridData(Java)被虚拟机GC回收的时候,这说明了一点,就是WritableNativeArray(C++)对象实例和WritableNativeArray(Java)对象实例的内存释放是完全同步的,都是交由JavaGC来触发!
到这里我们稍稍梳理一下。
当WritableNativeArray(Java)创建的时候,通过JNI调用会先创建WritableNativeArray(C++)对象,其后会创建HybridData(Java)和HybridData(C++),同时将WritableNativeArray(C++)的指针保存到HybridData(Java)的mNativePointer成员变量中,最后把HybridData(Java)保存到WritableNativeArray(Java)对象里面。
这样设计有一个好处。当WritableNativeArray(Java)通过JNI的方式传递到C++层时,可以通过保存在其内部的HybridData(Java)对象的mNativePointer的值,还原WritableNativeArray(C++)对象。
这个还原过程是通过内联函数cthis函数实现的,代码在jni/first-party/jni/fbjni/Hybrid.h中:
//Givena*_refobjectwhichreferstoahybridclass,thiswillreachinside
//ofit,findthemHybridData,extracttheC++instancepointer,castitto
//theappropriatetype,andreturnit.
template<typenameT>
inlineautocthis(Tjthis)->decltype(jthis->cthis()){
returnjthis->cthis();
}
inlineT*HybridClass<T,B>::JavaPart::cthis(){
staticautofield=
HybridClass<T,B>::JavaPart::javaClassStatic()->templategetField<detail::HybridData::javaobject>("mHybridData");
autohybridData=this->getFieldValue(field);
...
//I'dliketousedynamic_casthere,but-fno-rttiisthedefault.
T*value=static_cast<T*>(hybridData->getNativePointer());
...
returnvalue;
};
BaseHybridClass*HybridData::getNativePointer(){
staticautopointerField=getClass()->getField<jlong>("mNativePointer");
auto*value=reinterpret_cast<BaseHybridClass*>(getFieldValue(pointerField));
...
returnvalue;
}
先提取出WritableNativeArray(Java)对象的mHybridData,再提取其mNativePointer,最后使用reinterpret_cast还原出WritableNativeArray(C++)对象。而在WritableNativeArray(C++)对象中存储着所有push的数据(定义在其父类NativeArray中),这样数据的提取工作就完成了。
到此,Java传输数据给C++的场景分析完成,下面我们来研究反向过程。
C++传输数据给Java的场景,主要是在callNativeModules里面,我们直接来看makeJavaCall方法,在jni\react\jni\OnLoad.cpp中
staticvoidmakeJavaCall(JNIEnv*env,ExecutorTokenexecutorToken,jobjectcallback,constMethodCall&call){
if(call.arguments.isNull()){
return;
}
...
autonewArray=ReadableNativeArray::newObjectCxxArgs(std::move(call.arguments));
env->CallVoidMethod(
callback,
gCallbackMethod,
static_cast<JExecutorTokenHolder*>(executorToken.getPlatformExecutorToken().get())->getJobj(),
call.moduleId,
call.methodId,
newArray.get());
}
call.arguments是一个封装好的folly::dynamic对象(详见folly开源库),通过newObjectCxxArgs方法转换成ReadableNativeArray(C++)对象,实现在jni/first-party/jni/fbjni/Hybrid.h中:
template<typename...Args>
staticlocal_ref<JavaPart>newObjectCxxArgs(Args&&...args){
autohybridData=makeCxxInstance(std::forward<Args>(args)...);
returnJavaPart::newInstance(hybridData);
}
template<typename...Args>
staticlocal_ref<detail::HybridData>makeCxxInstance(Args&&...args){
returnmakeHybridData(std::unique_ptr<T>(newT(std::forward<Args>(args)...)));
}
template<typenameJC,typename...Args>
staticlocal_ref<JC>newInstance(Args...args){
staticautocls=JC::javaClassStatic();
staticautoconstructor=cls->templategetConstructor<typenameJC::javaobject(Args...)>();
returncls->newObject(constructor,args...);
}
创建ReadableNativeArray(C++)对象的过程和前面创建WritableNativeArray(C++)对象的过程一模一样。先创建HybridData(Java)和HybridData(C++),同时将ReadableNativeArray(C++)的指针保存到HybridData(Java)的mNativePointer成员变量中。最后ReadableNativeArray(Java)对象被封装在JavaPart中(再次用到facebook用JNI创建Java对象的封装库),通过get方法获取到真正的实例。
继续来看ReadableNativeArray(Java),位于包com.facebook.react.bridge中:
publicclassReadableNativeArrayextendsNativeArrayimplementsReadableArray{
static{
SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
}
protectedReadableNativeArray(HybridDatahybridData){
super(hybridData);
}
@Override
publicnativeintsize();
@Override
publicnativebooleanisNull(intindex);
@Override
publicnativebooleangetBoolean(intindex);
@Override
publicnativedoublegetDouble(intindex);
@Override
publicnativeintgetInt(intindex);
@Override
publicnativeStringgetString(intindex);
@Override
publicnativeReadableNativeArraygetArray(intindex);
@Override
publicnativeReadableNativeMapgetMap(intindex);
@Override
publicnativeReadableTypegetType(intindex);
}
ReadableNativeArray(Java)同样也是一个管道,所有数据仍然是存在C++层,必须全部通过native本地方法来提取,依赖具有了前面说到的两个优点:减少内存和容易回收。
ReadableNativeArray::ReadableNativeArray(folly::dynamicarray)
:HybridBase(std::move(array)){}
...
jintReadableNativeArray::getSize(){
returnarray.size();
}
jbooleanReadableNativeArray::isNull(jintindex){
returnarray.at(index).isNull()?JNI_TRUE:JNI_FALSE;
}
jbooleanReadableNativeArray::getBoolean(jintindex){
returnarray.at(index).getBool()?JNI_TRUE:JNI_FALSE;
}
jdoubleReadableNativeArray::getDouble(jintindex){
constfolly::dynamic&val=array.at(index);
if(val.isInt()){
returnval.getInt();
}
returnval.getDouble();
}
jintReadableNativeArray::getInt(jintindex){
autointeger=array.at(index).getInt();
...
jintjavaint=static_cast<jint>(integer);
...
returnjavaint;
}
constchar*ReadableNativeArray::getString(jintindex){
constfolly::dynamic&dyn=array.at(index);
if(dyn.isNull()){
returnnullptr;
}
returndyn.getString().c_str();
}
ReadableNativeArray::getArray(jintindex){
auto&elem=array.at(index);
if(elem.isNull()){
returnjni::local_ref<ReadableNativeArray::jhybridobject>(nullptr);
}else{
returnReadableNativeArray::newObjectCxxArgs(elem);
}
}
jobjectReadableNativeArray::getMap(jintindex){
returncreateReadableNativeMapWithContents(Environment::current(),array.at(index));
}
jobjectReadableNativeArray::getType(jintindex){
returntype::getType(array.at(index).type());
}
voidReadableNativeArray::registerNatives(){
jni::registerNatives("com/facebook/react/bridge/ReadableNativeArray",{
makeNativeMethod("size",ReadableNativeArray::getSize),
makeNativeMethod("isNull",ReadableNativeArray::isNull),
makeNativeMethod("getBoolean",ReadableNativeArray::getBoolean),
makeNativeMethod("getDouble",ReadableNativeArray::getDouble),
makeNativeMethod("getInt",ReadableNativeArray::getInt),
makeNativeMethod("getString",ReadableNativeArray::getString),
makeNativeMethod("getArray",ReadableNativeArray::getArray),
makeNativeMethod("getMap","(I)Lcom/facebook/react/bridge/ReadableNativeMap;",
ReadableNativeArray::getMap),
makeNativeMethod("getType","(I)Lcom/facebook/react/bridge/ReadableType;",
ReadableNativeArray::getType),
});
}
对数据的提取,最后仍然是对array对象操作,其是定义在父类NativeArray.h中的,不在赘述。
整个数据模型的分析就到此结束了,总结一下有以下几个特点:
1、数据只有一份存储,即在C++中,无论是ReadableNativeArray(Java)还是WritableNativeArray(Java)都只是数据存取的管道。
2、ReadableNativeArray和WritableNativeArray在Java层和C++层又都有各自的实例,通过Java层实例的HybridData的mNativePointer作为纽带链接,其存储的是C++层实例的指针。
3、无论是Java层还是C++层的ReadableNativeArray和WritableNativeArray都是统一由JavaGC进行回收管理。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!