Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> React-Native系列Android——通信數據模型分析

React-Native系列Android——通信數據模型分析

編輯:關於Android編程

無論是計算機領域還是日常生活中,我們所言的通信,其核心都是數據信息的交換,而數據模型的優劣對通信效率有著決定性的作用。

React-Native項目中,Javascript語言與Native兩種語言(JavaOC等)間存在著大量的數據交換,也就是所謂的通信。眾所周知,移動APP對性能的要求無比苛刻,如果通信數據模型設計地不合理,很可能引起多線程下的數據安全問題,以及應用性能問題,比如內存洩漏,UI繪制緩慢等。

前面幾篇博客我們詳細分析過React-Native的通信機制,主要有兩個方向: Java->Bridge->JavascriptJavascript->Bridge->Java。所以,真正的數據交換其實發生在JavaBridgeJavascriptBridge兩個環節。

JavascriptBridge間的數據通信是借助於Webkit使用Json完成,簡單實用,水到渠成,不多分析。而JavaBridge間的數據通信相比之下就復雜多了,作為真正運行在設備上的程序語言,這恰恰是決定整個通信過程效率高低最核心的一環,也是本篇博客研究的內容。


JavaAndroid應用程序的本地開發語言,而Bridge是使用C++開發的動態鏈接庫,由Java語言通過JNI的方式調用。JavaBridge間的數據通信,實質是JavaC++兩種程序語言間的數據傳輸,而傳遞的方向又分為兩個場景: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的間接子類,後者是繼承於前者。顧名思義,一個是用於讀數據,一個是用於寫數據。

JavaBridge傳輸數據,自然就是寫數據了,所以我們先來看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方法,涵蓋了intstringarraymap等不同的數據類型和結構。

還有一個名為initHybrid()native方法,用於創建HybridData類的實例,然後通過構造方法給其超父類NativeArraymHybridData成員變量賦值,具體作用後面來分析,先略過。

接下來,我們來驗證一下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過來的數據的呢?

先來看一下WritableNativeArraynative方法在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 {
  static constexpr const char* kJavaDescriptor = "Lcom/facebook/react/bridge/WritableNativeArray;";

  WritableNativeArray()
      : HybridBase(folly::dynamic({})) {}

  static local_ref initHybrid(alias_ref) {
    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中完全呼應的三個類:NativeArrayReadableNativeArrayWritableNativeArray,命名和繼承關系都是完全一致的!

而且可以看到,所有的數據都被存儲到父類NativeArrayarray變量中。

不過,問題來了!

C++中的WritableNativeArray對象和Java中的WritableNativeArray兩個同名對象間是否存在著某種聯系呢,比如一一映射的關系?

答案是肯定的! 因為每當一個Java層的WritableNativeArray對象被創建,在C++層都會有一個相應的WritableNativeArray對象被創建,用來接收Javapush過來的數據。

再來回顧下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 {
   ...

  static local_ref initHybrid(alias_ref) {
      return makeCxxInstance();
  }

   ... 
}

這裡的jhybriddata指的就是HybridData(Java)對象,其是通過typedef方式定義在jni/first-party/jni/fbjni/Hybrid.h中的。

...

struct HybridData : public JavaClass {
   constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridData;";
   void setNativePointer(std::unique_ptr new_value);
   BaseHybridClass* getNativePointer();
   static local_ref create();
};

...

typedef detail::HybridData::javaobject jhybriddata;

...

facebook在這裡對在JNI中創建Java對象的過程做了非常高效的封裝,即JavaClass對象。所有JavaClass的子類都通過一個名為kJavaDescriptor的字符串指針,來描述相對應的Java對象類名。

繼續來看makeCxxInstance()是如何創建HybridData(Java) 對象的。代碼同樣在jni/first-party/jni/fbjni/Hybrid.h中。

template 
class HybridClass : public detail::HybridTraits::CxxBase {
   ...

   static local_ref makeHybridData(std::unique_ptr cxxPart) {
      auto hybridData = detail::HybridData::create();
      hybridData->setNativePointer(std::move(cxxPart));
      return hybridData;
    }

    template 
    static local_ref makeCxxInstance(Args&&... args) {
       return makeHybridData(std::unique_ptr(new T(std::forward(args)...)));
    }

   ...
}

結合下前面的WritableNativeArray(C++)來看

struct WritableNativeArray
    : public jni::HybridClass {
  static constexpr const char* kJavaDescriptor = "Lcom/facebook/react/bridge/WritableNativeArray;";

   ...

   static local_ref initHybrid(alias_ref) {
      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)中。

接下來,看createsetNativePointer兩個方法的細節,在Hybrid.cpp中:

local_ref HybridData::create() {
  return newInstance();
}
void HybridData::setNativePointer(std::unique_ptr new_value) {
  static auto pointerField = getClass()->getField("mNativePointer");
  auto* old_value = reinterpret_cast(getFieldValue(pointerField));
  if (new_value) {
    ...
  } else if (old_value == 0) {
    return;
  }
  delete old_value;
  ...
  setFieldValue(pointerField, reinterpret_cast(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 
inline auto cthis(T jthis) -> decltype(jthis->cthis()) {
   return jthis->cthis();
}
template 
inline T* HybridClass::JavaPart::cthis() {
  static auto field = HybridClass::JavaPart::javaClassStatic()->template getField("mHybridData");
  auto hybridData = this->getFieldValue(field);
  ...
  // I'd like to use dynamic_cast here, but -fno-rtti is the default.
  T* value = static_cast(hybridData->getNativePointer());
  // This would require some serious programmer error.
  FBASSERTMSGF(value != 0, "Incorrect C++ type in hybrid field");
  return value;
};
BaseHybridClass* HybridData::getNativePointer() {
  static auto pointerField = getClass()->getField("mNativePointer");
  auto* value = reinterpret_cast(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(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 
  static local_ref newObjectCxxArgs(Args&&... args) {
    auto hybridData = makeCxxInstance(std::forward(args)...);
    return JavaPart::newInstance(hybridData);
  }
  template 
  static local_ref makeCxxInstance(Args&&... args) {
    return makeHybridData(std::unique_ptr(new T(std::forward(args)...)));
  }
template
static local_ref newInstance(Args... args) {
  static auto cls = JC::javaClassStatic();
  static auto constructor = cls->template getConstructor();
  return cls->newObject(constructor, args...);
}

創建ReadableNativeArray(C++)對象的過程和前面創建WritableNativeArray(C++)對象的過程一模一樣。先創建HybridData(Java)HybridData(C++),同時將ReadableNativeArray(C++)的指針保存到HybridData(Java)mNativePointer成員變量中。最後ReadableNativeArray(Java)對象被封裝在JavaPart中(再次用到facebookJNI創建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();
  static_assert(std::is_same::value,
                "folly::dynamic int is not int64_t");
  jint javaint = static_cast(integer);
  if (integer != javaint) {
    throwNewJavaException(
      exceptions::gUnexpectedNativeTypeExceptionClass,
      "Value '%lld' doesn't fit into a 32 bit signed int", 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();
}

jni::local_ref ReadableNativeArray::getArray(jint index) {
  auto& elem = array.at(index);
  if (elem.isNull()) {
    return jni::local_ref(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、ReadableNativeArrayWritableNativeArrayJava層和C++層又都有各自的實例,通過Java層實例的HybridDatamNativePointer作為紐帶鏈接,其存儲的是C++層實例的指針。
3、無論是Java層還是C++層的ReadableNativeArrayWritableNativeArray都是統一由Java GC進行回收管理。


 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved