美文网首页
ReactNative源码篇:代码调用

ReactNative源码篇:代码调用

作者: 江湖再见2024 | 来源:发表于2017-04-26 18:11 被阅读81次

    ReactNative源码篇:代码调用

    关于作者

    郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。

    文章目录:https://github.com/guoxiaoxing/react-native/blob/master/README.md

    本篇系列文章主要分析ReactNative源码,分析ReactNative的启动流程、渲染原理、通信机制与线程模型等方面内容。

    Java与C++的交互

    我们都知道如果需要用Java调用C/C++,需要用到Java中的JNI,但是用过JNI的同学都知道这是个繁琐且低效的调用方式,在大型工程体现的更加明显,因为我们需要将Java与C/C++的
    相互访问与通信框架化,形成更高层次的封装,避免直接使用原始的JNI反射API去做调用。

    RN框架便有着优秀的Java与Native访问框架,这套框架的核心在于JNI智能指针,我们来详细的看一看它的实现原理。

    Java对象(jobject)在Native层有3种引用类型:

    全局引用

    全局引用:使用NewGlobalRef创建,使用DeleteGlobalRef销毁。支持跨线程访问,在调用DeleteGlobalRef销毁前,GC无法回收该引用对应的Java Object。
    

    局部引用

    局部引用:使用NewLocalRef创建,使用DeleteLocalRef销毁。只能在本线程内安全访问,当创建该引用的Native调用链返回至JVM时,未被销毁的局部引用可以被GC回收,但是局部引用表容量有限,应该
    在返回JVM前,调用DeleteLocalRef先行销毁,避免局部引用表超限引用崩溃。
    

    弱全局引用

    局部引用:使用NewWeekGlobalRef创建,使用DeleteWeekGlobalRef销毁。与全局引用一样具有全局作用域,但是不会影响GC回收,GC可以随时回收该引用。
    

    JNI动态注册

    JNI在注册Native函数时,可以利用javah命令生成函数签名,而静态注册就是利用这些函数名在JNI层中寻找着写函数,如果没有找到就会报错,如果找到了就会建立一个关联关系,以后
    在调用时就会直接调用这个函数,该操作有虚拟机来完成。但是每次调用都要进行查找的做法效率比较低,因而便衍生了动态注册的方法。

    动态注册

    动态注册允许你提供一个函数映射表,提供给虚拟机,这样虚拟机就可以根据函数映射表来调用相应的函数。

    函数映射表中函数的结构如下所示:

    typedef struct { 
    const char* name; //Java中Native方法的名字
    const char* signature; //Java中Native方法的参数和返回值。
    void* fnPtr; //函数指针,指向C函数
    } JNINativeMethod; 
    

    JNI_OnLoad()函数在System.loadLibrary加载完JNI动态库后会自动调用,我们在这里完成动态注册工作,该函数有2个作用:

    1 指定JNI版本,告诉虚拟机应该使用哪一个JNI版本。
    2 注册Native函数,调用方法jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)来实现。
    

    我们来看一个简单的例子。

    1 在Java层编写本地方法

    public class HelloJni { 
    
        static {
            System.loadLibrary("hello-jni");
        }
    
        public static final main(String[] args){
            System.out.println(stringFromJNI());
        }
    
        public native String stringFromJNI();
    }
    

    2 在JNI中进行动态注册

    #include <stdlib.h>  
    #include <string.h>  
    #include <stdio.h>  
    #include <jni.h>  
    #include <assert.h>  
      
    /* This is a trivial JNI example where we use a native method 
     * to return a new VM String. See the corresponding Java source 
     * file located at: 
     * 
     *   apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java 
     */  
    jstring native_hello(JNIEnv* env, jobject thiz)  
    {  
        return (*env)->NewStringUTF(env, "动态注册JNI");  
    }  
      
    /** 
    * 方法对应表 
    */  
    static JNINativeMethod gMethods[] = {  
        {"stringFromJNI", "()Ljava/lang/String;", (void*)native_hello},
    };  
      
    /* 
    * 为某一个类注册本地方法 
    */  
    static int registerNativeMethods(JNIEnv* env  
            , const char* className  
            , JNINativeMethod* gMethods, int numMethods) {  
        jclass clazz;  
        clazz = (*env)->FindClass(env, className);  
        if (clazz == NULL) {  
            return JNI_FALSE;  
        }  
        if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {  
            return JNI_FALSE;  
        }  
      
        return JNI_TRUE;  
    }  
      
      
    /* 
    * 为所有类注册本地方法 
    */  
    static int registerNatives(JNIEnv* env) {
        //指定要注册的类
        const char* kClassName = "com/example/hellojni/HelloJni";
        return registerNativeMethods(env, kClassName, gMethods,  
                sizeof(gMethods) / sizeof(gMethods[0]));  
    }  
      
    /* 
    * System.loadLibrary("lib")时调用自动调用JNI_OnLoad
    * 如果成功返回JNI版本, 失败返回-1 
    */  
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {  
        JNIEnv* env = NULL;  
        jint result = -1;  
      
        if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
            return -1;  
        }  
        assert(env != NULL);  
      
        if (!registerNatives(env)) {//注册  
            return -1;  
        }  
        //成功  
        result = JNI_VERSION_1_4;  
      
        return result;  
    } 
    

    JavaScript与C++的交互

    RN解析JS用的是Webkit的脚本引擎JavaScriptCore,JavaScriptCore负责JS的解释与执行。

    Webkit是一个开源的浏览器引擎,Safari、Chrome等浏览器都使用了该引擎,它包括一个网页排版渲染引擎WebCore与一个脚本引擎JavaScriptCore。

    JavaScriptCore API数据结构:

    JSGlobalContextRef:JavaScript全局上下文。也就是JavaScript的执行环境。
    JSValueRef:JavaScript的一个值,可以是变量、object、函数。
    JSObjectRef:JavaScript的一个object或函数。
    SStringRef:JavaScript的一个字符串。
    JSClassRef:JavaScript的类。
    JSClassDefinition:JavaScript的类定义,使用这个结构,C、C++可以定义和注入JavaScript的类。
    

    JavaScriptCore API主要函数:

    JSGlobalContextCreateJSGlobalContextRelease:创建和销毁JavaScript全局上下文。
    JSContextGetGlobalObject:获取JavaScript的Global对象。
    JSObjectSetPropertyJSObjectGetProperty:JavaScript对象的属性操作。
    JSEvaluateScript:执行一段JS脚本。
    JSClassCreate:创建一个JavaScript类。
    JSObjectMake:创建一个JavaScript对象。
    JSObjectCallAsFunction:调用一个JavaScript函数。
    JSStringCreateWithUTF8CstringJSStringRelease:创建、销毁一个JavaScript字符串
    JSValueToBooleanJSValueToNumber JSValueToStringCopy:JSValueRef转为C++类型
    JSValueMakeBooleanJSValueMakeNumber JSValueMakeString:C++类型转为JSValueRef
    

    C++调用JavaScript

    1 获取Global全局对象

    JSGlobalContextRef context = JSGlobalContextCreate(NULL);
    JSObjectRef global = JSContextGetGlobalObject(ctx); 
    

    2 获取JavaScript的全局变量、全局函数或者全局复杂对象,并完成调用。

    //获取全局变量
    JSStringRef varName = JSStringCreateWithUTF8CString("JavaScript变量名");
    JSValueRef var = JSObjectGetProperty(ctx, globalObj, varName,NULL); JSStringRelease(varName);
    //转化为C++类型
    int n = JSValueToNumber(ctx, var, NULL);
    
    //获取全局函数
    JSStringRef funcName = JSStringCreateWithUTF8CString("JavaScript函数名");
    JSValueRef func = JSObjectGetProperty(ctx, globalObj, funcName,NULL); JSStringRelease(funcName);
    //装换为函数对象
    JSObjectRef funcObject = JSValueToObject(ctx,func, NULL);
    //组织参数,将两个数值1和2作为两个参数
    JSValueRef args[2];
    args[0] = JSValueMakeNumber(ctx, 1);
    args[1] = JSValueMakeNumber(ctx, 2);
    //调用函数
    JSValueRef returnValue = JSObjectCallAsFunction(ctx, funcObject,NULL, 2, args, NULL);
    //处理返回值
    int ret = JSValueToNumber(ctx, returnValue, NULL);
    
    //获取复杂的对象
    JSStringRef objName=JSStringCreateWithUTF8CString("JavaScript复杂对象名");
    JSValueRef obj = JSObjectGetProperty(ctx, globalObj, objName,NULL); JSStringRelease(objName);
    //装换为对象
    JSObjectRef object = JSValueToObject(ctx,obj, NULL);
    //获取对象的方法
    JSStringRef funcObjName =JSStringCreateWithUTF8CString("JavaScript复杂对象的方法");
    JSValueRef objFunc = JSObjectGetProperty(ctx, object, funcObjName,NULL); JSStringRelease(funcObjName);
    //调用复杂对象的方法,这里省略了参数和返回值
    JSObjectCallAsFunction(ctx, objFunc, NULL, 0, 0, NULL);
    
    

    JavaScript调用C++

    JavaScript想要调用C++,就必须先要将C++里的变量、函数与类注入到JavaScript中。

    1 定义一个C++类,在类中定义一组全局函数,并封装JavaScriptCore对C++类的调用,提供给JavaScriptCore进行CallBack回调。

    class test{         
    
    public:
        test(){
            number=0;
        };
    
        void func(){
            number++;
        }
        int number;
    };
    
    test g_test;//变量定义
    
    //全局函数,封装test类的func方法调用
    JSValueRef testFunc(JSContextRef ctx, JSObjectRef ,JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[],JSValueRef*){
    
        test* t =static_cast<test*>(JSObjectGetPrivate(thisObject));
        t->func();
        returnJSValueMakeUndefined(ctx);
    }
    
    //全局函数,封装test类的成员变量number的get操作
    
    JSValueRef getTestNumber(JSContextRef ctx, JSObjectRefthisObject, JSStringRef, JSValueRef*){
    
        test* t =static_cast<test*>(JSObjectGetPrivate(thisObject));
        returnJSValueMakeNumber(ctx, t->number);
    }
    
    //使用一个函数, 创建JavaScript类
    JSClassRef createTestClass(){
    
        //类成员变量定义,可以有多个,最后一个必须是{ 0, 0, 0 },也可以指定set操作
        static JSStaticValuetestValues[] = {
            {"number", getTestNumber, 0, kJSPropertyAttributeNone },
            { 0, 0, 0, 0}
        };
    
        //类的方法定义,可以有多个,最后一个必须是{ 0, 0, 0 }
        staticJSStaticFunction testFunctions[] = {
            {"func", testFunc, kJSPropertyAttributeNone },
            { 0, 0, 0 }
        };
    
        //定义一个类
        staticJSClassDefinition classDefinition = {
    
            0,kJSClassAttributeNone, "test", 0, testValues, testFunctions,
            0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0
    
        };
    
        //JSClassCreate执行后,就创建一个了JavaScript test类
        staticJSClassRef t = JSClassCreate(&classDefinition);
        return t;
    }
    

    2 创建JavaScript类

    createTestClass ();
    JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
    JSObjectRef globalObj = JSContextGetGlobalObject(ctx); 
    

    3 新建一个JavaScript类对象,并使之绑定g_test变量

    JSObjectRef classObj= JSObjectMake(ctx,testClass(), &g_test);
    

    4 将新建的对象注入JavaScript中

    JSStringRef objName= JSStringCreateWithUTF8CString("g_test");
    JSObjectSetProperty(ctx,globalObj,objName,classObj,kJSPropertyAttributeNone,NULL);
    

    将C++类和类指针注入到JavaScript后,在JavaScript中就可以这样使用了:

    g_test.func();
    var n = g_test.number;
    var t = new test;
    

    相关文章

      网友评论

          本文标题:ReactNative源码篇:代码调用

          本文链接:https://www.haomeiwen.com/subject/kvkwzttx.html