欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android React-Native通信数据模型分析

程序员文章站 2024-03-05 13:12:06
无论是计算机领域还是日常生活中,我们所言的通信,其核心都是数据信息的交换,而数据模型的优劣对通信效率有着决定性的作用。 在react-native项目中,javascri...

无论是计算机领域还是日常生活中,我们所言的通信,其核心都是数据信息的交换,而数据模型的优劣对通信效率有着决定性的作用。

在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。

package com.facebook.react.bridge;

public class reactbridge extends countable {

 static final string react_native_lib = "reactnativejni";

 static {
  soloader.loadlibrary(react_native_lib);
 }

 ...

 public native void callfunction(int moduleid, int methodid, nativearray arguments);

 ...

}

我们可以看到,传输的数据类型是nativearray,来瞧下具体的代码,位于com.facebook.react.bridge包下:

public abstract class nativearray {
 static {
  soloader.loadlibrary(reactbridge.react_native_lib);
 }

 protected nativearray(hybriddata hybriddata) {
  mhybriddata = hybriddata;
 }

 @override
 public native string tostring();

 @donotstrip
 private hybriddata mhybriddata;
}

nativearray是一个抽象类,其中,只有一个hybriddata类型成员变量,由其构造方法赋值初始化。

nativearray还有一个名为readablenativearray的直接子类,和一个名为writablenativearray的间接子类,后者是继承于前者。顾名思义,一个是用于读数据,一个是用于写数据。

java向bridge传输数据,自然就是写数据了,所以我们先来看writablenativearray。

package com.facebook.react.bridge;

public class writablenativearray extends readablenativearray implements writablearray {

 static {
  soloader.loadlibrary(reactbridge.react_native_lib);
 }

 public writablenativearray() {
  super(inithybrid());
 }

 @override
 public native void pushnull();
 @override
 public native void pushboolean(boolean value);
 @override
 public native void pushdouble(double value);
 @override
 public native void pushint(int value);
 @override
 public native void pushstring(string value);

 @override
 public void pusharray(writablearray array) {
  assertions.assertcondition(
    array == null || array instanceof writablenativearray, "illegal type provided");
  pushnativearray((writablenativearray) array);
 }

 @override
 public void pushmap(writablemap map) {
  assertions.assertcondition(
    map == null || map instanceof writablenativemap, "illegal type provided");
  pushnativemap((writablenativemap) map);
 }

 private native static hybriddata inithybrid();
 private native void pushnativearray(writablenativearray array);
 private native void pushnativemap(writablenativemap map);
}

里面有7个写数据的native方法,涵盖了int、string,array,map等不同的数据类型和结构。

还有一个名为inithybrid()的native方法,用于创建hybriddata类的实例,然后通过构造方法给其超父类nativearray的mhybriddata成员变量赋值,具体作用后面来分析,先略过。

接下来,我们来验证一下writablenativearray是否是真正传输给bridge的数据类型。

java调用javascript组件,都是由名为javascriptmoduleinvocationhandler的动态代理类统一拦截处理的吗?来回顾一下代码,位于com.facebook.react.bridge.javascriptmoduleregistry.java:

 private static class javascriptmoduleinvocationhandler implements invocationhandler {

  private final catalystinstanceimpl mcatalystinstance;
  private final javascriptmoduleregistration mmoduleregistration;

  public javascriptmoduleinvocationhandler(
    catalystinstanceimpl catalystinstance,
    javascriptmoduleregistration moduleregistration) {
   mcatalystinstance = catalystinstance;
   mmoduleregistration = moduleregistration;
  }

  @override
  public @nullable object invoke(object proxy, method method, object[] args) throws throwable {
   string tracingname = mmoduleregistration.gettracingname(method);
   mcatalystinstance.callfunction(
     mmoduleregistration.getmoduleid(),
     mmoduleregistration.getmethodid(method),
     arguments.fromjavaargs(args),
     tracingname);
   return null;
  }
 }

callfunction方法传递的参数类型是arguments.fromjavaargs(args),具体代码又如下:

 public static writablenativearray fromjavaargs(object[] args) {
  writablenativearray arguments = new writablenativearray();
  for (int i = 0; i < args.length; i++) {
   object argument = args[i];
   if (argument == null) {
    arguments.pushnull();
    continue;
   }

   class argumentclass = argument.getclass();
   if (argumentclass == boolean.class) {
    arguments.pushboolean(((boolean) argument).booleanvalue());
   } else if (argumentclass == integer.class) {
    arguments.pushdouble(((integer) argument).doublevalue());
   } else if (argumentclass == double.class) {
    arguments.pushdouble(((double) argument).doublevalue());
   } else if (argumentclass == float.class) {
    arguments.pushdouble(((float) argument).doublevalue());
   } else if (argumentclass == string.class) {
    arguments.pushstring(argument.tostring());
   } else if (argumentclass == writablenativemap.class) {
    arguments.pushmap((writablenativemap) argument);
   } else if (argumentclass == writablenativearray.class) {
    arguments.pusharray((writablenativearray) argument);
   } else {
    throw new runtimeexception("cannot convert argument of type " + argumentclass);
   }
  }
  return arguments;
 }

正如我们猜测的一般,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中:

static void registernatives() {
  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模板类的第二个泛型表示父类):

struct writablenativearray
  : public jni::hybridclass<writablenativearray, readablenativearray> {
 static constexpr const char* kjavadescriptor = "lcom/facebook/react/bridge/writablenativearray;";

 writablenativearray()
   : hybridbase(folly::dynamic({})) {}

 static local_ref<jhybriddata> inithybrid(alias_ref<jclass>) {
  return makecxxinstance();
 }

 void pushnull() {
  ...
  array.push_back(nullptr);
 }

 void pushboolean(jboolean value) {
  ...
  array.push_back(value == jni_true);
 }

 void pushdouble(jdouble value) {
  ...
  array.push_back(value);
 }

 void pushint(jint value) {
  ...
  array.push_back(value);
 }

 void pushstring(jstring value) {
  ...
  array.push_back(wrap_alias(value)->tostdstring());
 }

 void pushnativearray(writablenativearray* otherarray) {
  ...
  array.push_back(std::move(otherarray->array));
  otherarray->isconsumed = true;
 }

 void pushnativemap(jobject jmap) {
  ...
  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创建的过程。

public class writablenativearray extends readablenativearray implements writablearray {
  ...
  public writablenativearray() {
   super(inithybrid());
  }
  ...
  private native static hybriddata inithybrid();
  ...
}

在构造writablenativearray的时候,会通过inithybrid方法创建一个hybriddata对象,并保存到其超父类nativearray的成员变量mhybriddata中。

而hybriddata对象又是什么呢?

public class hybriddata {
  // private c++ instance
  private long mnativepointer = 0;
  public hybriddata() {
    prerequisites.ensure();
  }
  public native void resetnative();

  protected void finalize() throws throwable {
   resetnative();
   super.finalize();
  }
}

public class prerequisites {
  ...
  public static void ensure() {
    soloader.loadlibrary("fbjni");
  }
  ...
}

构造函数中prerequisites.ensure(),是用来加载fbjni动态链接库的。

在hybriddata 类中,有一个long的私有成员变量,根据注释和名字可以猜测与c++指针相关,具体是不是这样呢?我们来看hybriddata对象通过inithybrid()初始化的过程。

代码位于react/jni/onload.cpp中:

struct writablenativearray
  : public jni::hybridclass<writablenativearray, readablenativearray> {
  ...

 static local_ref<jhybriddata> inithybrid(alias_ref<jclass>) {
   return makecxxinstance();
 }

  ... 
}

这里的jhybriddata指的就是hybriddata(java)对象,其是通过typedef方式定义在jni/first-party/jni/fbjni/hybrid.h中的。

...

struct hybriddata : public javaclass<hybriddata> {
 constexpr static auto kjavadescriptor = "lcom/facebook/jni/hybriddata;";
 void setnativepointer(std::unique_ptr<basehybridclass> new_value);
 basehybridclass* getnativepointer();
 static local_ref<hybriddata> create();
};

...

typedef detail::hybriddata::javaobject jhybriddata;

...

facebook在这里对在jni中创建java对象的过程做了非常高效的封装,即javaclass对象。所有javaclass的子类都通过一个名为kjavadescriptor的字符串指针,来描述相对应的java对象类名。

继续来看makecxxinstance()是如何创建hybriddata(java) 对象的。代码同样在jni/first-party/jni/fbjni/hybrid.h中。

class hybridclass : public detail::hybridtraits<base>::cxxbase {
  ...

 static local_ref<detail::hybriddata> makehybriddata(std::unique_ptr<t> cxxpart) {
  auto hybriddata = detail::hybriddata::create();
  hybriddata->setnativepointer(std::move(cxxpart));
  return hybriddata;
 }

 template <typename... args>
 static local_ref<detail::hybriddata> makecxxinstance(args&&... args) {
  return makehybriddata(std::unique_ptr<t>(new t(std::forward<args>(args)...)));
 }

  ...
}

结合下前面的writablenativearray(c++)来看

struct writablenativearray
  : public jni::hybridclass<writablenativearray, readablenativearray> {
 static constexpr const char* kjavadescriptor = "lcom/facebook/react/bridge/writablenativearray;";

  ...

  static local_ref<jhybriddata> inithybrid(alias_ref<jclass>) {
   return makecxxinstance();
  }

  ...
}


在创建hybriddata(java)的时候,模板类hybridclass的第一个泛型t,表示的是writablenativearray(c++)这个结构体。所以,makehybriddata中的new t(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() {
 return newinstance();
}
void hybriddata::setnativepointer(std::unique_ptr<basehybridclass> new_value) {
 static auto pointerfield = getclass()->getfield<jlong>("mnativepointer");
 auto* old_value = reinterpret_cast<basehybridclass*>(getfieldvalue(pointerfield));

 if (new_value) {
  ...
 } else if (old_value == 0) {
  return;
 }
 delete old_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)对象实例的内存释放是完全同步的,都是交由java gc来触发!

到这里我们稍稍梳理一下。

当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中:

// given a *_ref object which refers to a hybrid class, this will reach inside
// of it, find the mhybriddata, extract the c++ instance pointer, cast it to
// the appropriate type, and return it.
template <typename t>
inline auto cthis(t jthis) -> decltype(jthis->cthis()) {
 return jthis->cthis();
}
inline t* hybridclass<t, b>::javapart::cthis() {
 static auto field =
  hybridclass<t, b>::javapart::javaclassstatic()->template getfield<detail::hybriddata::javaobject>("mhybriddata");
 auto hybriddata = this->getfieldvalue(field);
 ...
 // i'd like to use dynamic_cast here, but -fno-rtti is the default.
 t* value = static_cast<t*>(hybriddata->getnativepointer());
 ...
 return value;
};
basehybridclass* hybriddata::getnativepointer() {
 static auto pointerfield = getclass()->getfield<jlong>("mnativepointer");
 auto* value = reinterpret_cast<basehybridclass*>(getfieldvalue(pointerfield));
 ...
 return value;
}

先提取出writablenativearray(java)对象的mhybriddata,再提取其mnativepointer,最后使用reinterpret_cast还原出writablenativearray(c++)对象。而在writablenativearray(c++)对象中存储着所有push的数据(定义在其父类nativearray中),这样数据的提取工作就完成了。

到此,java传输数据给c++的场景分析完成,下面我们来研究反向过程。

c++传输数据给java的场景,主要是在callnativemodules里面,我们直接来看makejavacall方法,在jni\react\jni\onload.cpp中

static void makejavacall(jnienv* env, executortoken executortoken, jobject callback, const methodcall& call) {
 if (call.arguments.isnull()) {
  return;
 }

 ...

 auto newarray = 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>
static local_ref<javapart> newobjectcxxargs(args&&... args) {
  auto hybriddata = makecxxinstance(std::forward<args>(args)...);
  return javapart::newinstance(hybriddata);
}
template <typename... args>
static local_ref<detail::hybriddata> makecxxinstance(args&&... args) {
 return makehybriddata(std::unique_ptr<t>(new t(std::forward<args>(args)...)));
}
template<typename jc, typename... args>
static local_ref<jc> newinstance(args... args) {
 static auto cls = jc::javaclassstatic();
 static auto constructor = cls->template getconstructor<typename jc::javaobject(args...)>();
 return cls->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中:

public class readablenativearray extends nativearray implements readablearray {
  static {
   soloader.loadlibrary(reactbridge.react_native_lib);
  }

  protected readablenativearray(hybriddata hybriddata) {
   super(hybriddata);
  }

  @override
  public native int size();
  @override
  public native boolean isnull(int index);
  @override
  public native boolean getboolean(int index);
  @override
  public native double getdouble(int index);
  @override
  public native int getint(int index);
  @override
  public native string getstring(int index);
  @override
  public native readablenativearray getarray(int index);
  @override
  public native readablenativemap getmap(int index);
  @override
  public native readabletype gettype(int index);
}

readablenativearray(java)同样也是一个管道,所有数据仍然是存在c++层,必须全部通过native本地方法来提取,依赖具有了前面说到的两个优点:减少内存和容易回收。

readablenativearray::readablenativearray(folly::dynamic array)
  : hybridbase(std::move(array)) {}

...

jint readablenativearray::getsize() {
 return array.size();
}

jboolean readablenativearray::isnull(jint index) {
 return array.at(index).isnull() ? jni_true : jni_false;
}

jboolean readablenativearray::getboolean(jint index) {
 return array.at(index).getbool() ? jni_true : jni_false;
}

jdouble readablenativearray::getdouble(jint index) {
 const folly::dynamic& val = array.at(index);
 if (val.isint()) {
  return val.getint();
 }
 return val.getdouble();
}

jint readablenativearray::getint(jint index) {
 auto integer = array.at(index).getint();
 ...
 jint javaint = static_cast<jint>(integer);
 ...
 return javaint;
}

const char* readablenativearray::getstring(jint index) {
 const folly::dynamic& dyn = array.at(index);
 if (dyn.isnull()) {
  return nullptr;
 }
 return dyn.getstring().c_str();
}

readablenativearray::getarray(jint index) {
 auto& elem = array.at(index);
 if (elem.isnull()) {
  return jni::local_ref<readablenativearray::jhybridobject>(nullptr);
 } else {
  return readablenativearray::newobjectcxxargs(elem);
 }
}

jobject readablenativearray::getmap(jint index) {
 return createreadablenativemapwithcontents(environment::current(), array.at(index));
}

jobject readablenativearray::gettype(jint index) {
 return type::gettype(array.at(index).type());
}

void readablenativearray::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都是统一由java gc进行回收管理。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!