加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

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

发布时间:2020-12-15 03:33:03 所属栏目:百科 来源:网络整理
导读:无论是计算机领域还是日常生活中,我们所言的通信,其核心都是数据信息的交换,而数据模型的优劣对通信效率有着决定性的作用。 在 React-Native 项目中, Javascript 语言与 Native 两种语言( Java 或 OC 等)间存在着大量的数据交换,也就是所谓的通信。众

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

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的数据类型。

还记得前面React-Native系列Android——Native与Javascript通信原理(一)中分析过,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<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中完全呼应的三个类: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<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中。

template <typename T,typename Base = detail::BaseHybridClass>
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)中。

接下来,看createsetNativePointer两个方法的细节,在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();
}
template <typename T,typename B>
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());
  // 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<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方法,在jnireactjniOnLoad.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中(再次用到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<decltype(integer),int64_t>::value,"folly::dynamic int is not int64_t");
  jint javaint = static_cast<jint>(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::jhybridobject> 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、ReadableNativeArrayWritableNativeArrayJava层和C++层又都有各自的实例,通过Java层实例的HybridDatamNativePointer作为纽带链接,其存储的是C++层实例的指针。
3、无论是Java层还是C++层的ReadableNativeArrayWritableNativeArray都是统一由Java GC进行回收管理。


本博客不定期持续更新,欢迎关注和交流:

http://blog.csdn.net/megatronkings

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读