Hummer 跨端框架源码解析

作者: 码农一颗颗 | 来源:发表于2021-06-17 16:37 被阅读0次

    首先整体介绍一下Hummer框架,官网地址 https://hummer.didi.cn/home#/
    Hummer 是一套高性能高可用的跨端开发框架,一套代码可以同时支持开发 Android 和 iOS 应用。现已经支持 Vue/TypeScript/JavaScript 三种语法,面向大前端开发人员,总有一款适合你。
    总之一句话,Hummer框架就是为了提高开发效率。

    系列文章
    《码一个简易的跨端框架》
    这篇文章介绍如何将Quickjs在androidstudio中编译,以及实现一个简易的js->js引擎->c->java的回调过程,适用于想要了解跨端框架的人群。

    本篇文章要达到的目的

    1. Hummer框架的项目结构简介
    2. Hummer框架是如何将js代码通过js引擎转换成原生代码的
    Hummer项目结构以及依赖关系
    +--- project :hummer
    |    +--- project :hummer-component
    |    |    \--- project :hummer-sdk
    |    |         +--- com.didi.hummer:hummer-annotation:0.2.2
    |    |         +--- com.didi.hummer:hummer-plugin-interface:0.0.1
    |    |         +--- project :hummer-core
    |    \--- project :hummer-dev-tools
    
    project:hummer

    主要功能:

    1. 初始化SDK:
    public static void init(Context context, HummerConfig config)
    
    1. 提供页面:
    public class HummerActivity
    public class HummerFragment
    
    project :hummer-component

    主要功能:

    1. 定义Hummer组件;例如在js代码中写var button=new Button();对应到原生组件就是
    @Component("Button")
    public class Button extends HMBase<android.widget.Button> 
    @Component("Image")
    public class Image extends HMBase<RoundedImageView>
    @Component("View")
    public class View extends HummerLayoutExtendView
    

    具体这个组件是如何定义的,以及为什么要继承HMBase等我们以后再做分析,这里主要是知道js中写的组件对应的都是原生组件在这个工程下就可以了。

    project :hummer-sdk

    主要功能:

    1. 适配器(Adapter):将网络请求okhttp、图片加载glide、本地存贮SharedPreferences等一些功能进行隔离,对上层框架提供接口封装底层实现方便以后替换实现。
    2. 上下文(Context):为Hummer的上下文环境,返回全局的属性HummerLayout、JSContext等;处理生命周期onStart()、onResume、onPause、onStop、onDestory等;给JS引擎注入JS模板mJsContext.evaluateJavaScript(AssetsUtil.readFile(HUMMER_DEFINITION_FILE), "HummerDefinition.js");;设置JS引擎回调到Java层的invoke类,进行具体的操作registerInvoker(new HummerInvoker()); registerInvoker(new NotifyCenterInvoker());
    3. 模块层:将适配器(Adapter)的功能转换成JS代码可以用的Module
    @Component("Memory")
    public class Memory //存储
    @Component("Request")
    public class Request implements ILifeCycle //网络请求
    @Component("Location")
    public class HMLocation implements ILifeCycle //定位
    
    1. 页面渲染(render):
    2. HummerLayout是对YogaLayout的封装,修改了一些bug,以及支持对View的添加和删除等Hummer中自定义的功能;
    3. BackgroundDrawable这个主要是处理View背景的渲染,设置边框的Hummer中的特殊操作;
    4. HummerStyleUtils主要是用来解析js代码中的Style属性,把他对应到yogalayout或者Hummer自定义的原生属性上
    project :hummer-core

    主要功能:
    这层主要是和JS引擎交互层,

    1. 调用native本地方法注册JS引擎的BridgeHummerBridge.initHummerBridge(long jsContext)
    2. JS引擎解析JS代码后通过JNI接口回调Java层的方法HummerBridge.invoke(String className, long objectID, String methodName, long... params)
    3. 通过native本地方法执行、注入JS代码的方法JavaScriptRuntime.evaluateJavaScriptNative(long jsContext, String script, String scriptId)
    4. JS引擎代码层,我们看jni目录下,有hermes、jsc(JavaScriptCore)、qjs(Quickjs),还有编译他们的CMakeList.txt文件,我们目前只看qjs目录;./quickjs目录为github上下载的源码;./hummer目录为对quickjs代码封装的JNI接口,提供给java层使用;HummerBridge.cpp、HummerRecycler.cpp等目前不做过多代码解析
    Hummer页面渲染(一) Hummer页面渲染时序图(一).jpg
    1. Hummer是如何注册quickjs_bridge:
      时序图步骤4、5、6、7、8、9、10、11、12、13代码非常简单都是顺序调用;
      重点介绍14、15、16、17
    HummerBridge.cpp
    /**
     * 14步
     * 对QuickJs引擎设置属性,也就是设置回调方法,将invoke这个C方法通过JS_SetPropertyStr设置到Quickjs引擎中
     * 这样在js代码中就可以调用invoke这个全局方法了
     */
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_didi_hummer_core_engine_jsc_jni_HummerBridge_initHummerBridge(JNIEnv *env, jobject thiz, jlong js_context) {
        auto jsContext = QJS_CONTEXT(js_context);
    
        //创建HummerBridge类的jni对象
        jobject bridge = env->NewGlobalRef(thiz);
        HUMMER_BRIDGE_MAP[js_context] = bridge;
    
        //找到HummerBridge.java
        //private long invoke(String className, long objectID, String methodName, long... params)
        HUMMER_BRIDGE_INVOKE_ID = env->GetMethodID(
                env->GetObjectClass(thiz),
                "invoke",
                "(Ljava/lang/String;JLjava/lang/String;[J)J");
    
        //将C方法invoke创建成JS引擎中的方法
        auto funcName = "invoke";
        auto invokeFunc = JS_NewCFunction(jsContext, invoke, funcName, strlen(funcName));
    
        //将这个创建之后的js引擎中的invokeFunc方法注入到js引擎中,成为全局方法,这样在写js代码时就可以直接使用invoke这个方法
        JS_SetPropertyStr(jsContext,
                JS_GetGlobalObject(jsContext),
                "invoke",
                invokeFunc);
    }
    /**
     *15步
     * js引擎通过JS_SetPropertyStr将这个方法注入到js引擎中,写js代码时可以直接调用这个方法,
     * 这个方法的声明是参照JSCFunction方法声明的可以查看,在这个方法中通过jni调用java中的代码,
     * 实现js引擎和java代码的通信
     */
    static JSValue invoke(JSContext* ctx, JSValueConst thisObject, int argumentCount, JSValueConst* arguments) {
        long ctxId = QJS_CONTEXT_ID(ctx);
        jobject bridge = HUMMER_BRIDGE_MAP[ctxId];
        if (bridge == nullptr) return JS_NULL;
    
        JNIEnv* env = JNI_GetEnv();
        //取出 long invoke(String className,long objectID,String methodName,long ...param);的最后一个参数long数组
        jlongArray params = nullptr;
        if (argumentCount > 3) {
            int methodParamsCount = argumentCount - 3;
            params = env->NewLongArray(methodParamsCount);
            jlong paramsC[methodParamsCount];
            for (int i = 3; i < argumentCount; i++) {
                paramsC[i - 3] = QJS_VALUE_PTR(arguments[i]);
            }
            env->SetLongArrayRegion(params, 0, methodParamsCount, paramsC);
        }
        //取出objectID
        int64_t objId;
        JS_ToInt64(ctx, &objId, arguments[INDEX_OBJECT_ID]);
        //取出className
        jstring className = TypeConvertor::JSString2JavaString(ctx, arguments[INDEX_CLASS_NAME]);
        //取出methodName
        jstring methodName = TypeConvertor::JSString2JavaString(ctx, arguments[INDEX_METHOD_NAME]);
    
        //调用HummerBridge.java
        // private long invoke(String className, long objectID, String methodName, long... params)
        jlong ret = env->CallLongMethod(
                bridge, HUMMER_BRIDGE_INVOKE_ID,
                className, objId, methodName,
                params);
    
        env->DeleteLocalRef(className);
        env->DeleteLocalRef(methodName);
        env->DeleteLocalRef(params);
    
        JNI_DetachEnv();
    
        return QJS_VALUE(ret);
    }
    
    HummerBridge.java
         /**
         * 16步
         * JNI回调的java代码
         */
        private long invoke(String className, long objectID, String methodName, long... params) {
            if (mCallback == null) {
                return JSCUtils.POINTER_NULL;
            }
            long result = JSCUtils.POINTER_NULL;
            Object[] parameters = null;
            try {
                // 把long型的JS对象指针参数转换为Object或JSValue对象参数
                parameters = JSCUtils.jsValuesToObjects(jsContext, params);
                // 执行具体的invoke方法,并得到Object类型的返回值
                Object ret = mCallback.onInvoke(className, objectID, methodName, parameters);
                // 把Object类型的返回值转换成long型的JS对象指针
                result = JSCUtils.objectToJsValue(jsContext, ret);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    
    JSCHummerContext.java
      /**
         *  17步
         * 从HummerBridge.java通过callback回调到这个方法中
         * 在mRegistry.get(className)中找到注册过的Invoker组件
         * 具体注册:例如
         * registerInvoker(new HummerInvoker());
         * registerInvoker(new NotifyCenterInvoker());
         * registerInvoker(new Button$$Invoker());
         * registerInvoker(new View$$Invoker());
         */
        @Override
        public Object onInvoke(String className, long objectID, String methodName, Object... params) {
            Invoker invoker = mRegistry.get(className);
            if (invoker == null) {
                HMLog.w("HummerNative", String.format("Invoker error: can't find this class [%s]", className));
                return null;
            }
            return invoker.onInvoke(this, objectID, methodName, params);
        }
    
    1. Hummer是如何注册js_base模板代码、js_component组件模板代码
      18步;注册js_base模板代码
    HummerContext.java
    18步
    protected void onCreate() {
            ......
            //注册组件
            registerInvoker(new HummerInvoker());
            registerInvoker(new NotifyCenterInvoker());
            //注册js_base模板代码
            mJsContext.evaluateJavaScript(AssetsUtil.readFile(HUMMER_DEFINITION_FILE), "HummerDefinition.js");
            ......
        }
    /**
    *HummerDefinition.js文件截选
     *预先将这段js代码注入引擎,js组件都继承Base类
     *构造方法,以及每个定义的方法都调用了之前实现在js引擎中
     *创建好的invoke方法,通过这个方法回调到java侧执行相关操作
     */
    class Base {
        constructor(className, ...args) {
            this.className = className;
            this.objID = idGenerator();
            this.recycler = new Recycler(this.objID);
    
            let params = transArgs(...args);
            invoke(this.className, this.objID, "constructor",
                                            this, ...params);
    
            this.initialize(...args);
        }
        initialize(...args) {}
        set style(arg) {
            this._style = arg;
            arg = transSingleArg(arg);
            invoke(this.className, this.objID, "setStyle", arg);
        }
        get style() {
            return this._style;
        }
    }
    

    19步注册js_component组件模板代码

    HummerRegister$$hummer_component.java  
      /**
       * 注册java层组件,注入js_component模板代码
       * js_component继承js_base的Base类,以及通过
       * 预先注册好的invoke方法和java侧建立联系,
       * 回调到registerInvoker注册的Java侧组件进行相关操作
       */
      public static void init(HummerContext hummerContext) {
        hummerContext.registerInvoker(new Image$$Invoker());
        hummerContext.registerInvoker(new Loading$$Invoker());
        hummerContext.registerInvoker(new TextArea$$Invoker());
        hummerContext.registerInvoker(new Input$$Invoker());
        hummerContext.registerInvoker(new Switch$$Invoker());
        hummerContext.registerInvoker(new ViewPager$$Invoker());
        hummerContext.registerInvoker(new Toast$$Invoker());
        hummerContext.registerInvoker(new Dialog$$Invoker());
        hummerContext.registerInvoker(new Button$$Invoker());
        hummerContext.registerInvoker(new List$$Invoker());
        hummerContext.registerInvoker(new View$$Invoker());
        hummerContext.registerInvoker(new Anchor$$Invoker());
        hummerContext.registerInvoker(new Text$$Invoker());
        hummerContext.registerInvoker(new Scroller$$Invoker());
        hummerContext.registerInvoker(new HorizontalScroller$$Invoker());
        hummerContext.evaluateJavaScript(JS_CODE, "hummer_component.js");
      }
    
    hummer_component.js代码片段
    /**
     *继承js_base的Base类,拥有了Base类的基础能力
     *定义自己的特有能力appendChild方法内调用
     *js注入的invoke方法,通知到java侧View组件执行相关操作
     */
    class View extends Base {
          constructor(...args) {
              super('View', ...args);
          }
          appendChild(...args) {
              let stash = args;
              args = transArgs(...args);
              invoke('View', this.objID, 'appendChild', ...args);
          }
          removeChild(...args) {
              let stash = args;
              args = transArgs(...args);
              invoke('View', this.objID, 'removeChild', ...args);
          }
    }
    
    class Button extends Base {
        constructor(...args) {
            super('Button', ...args);
        }
        set text(arg) {
            this._text = arg;
            arg = transSingleArg(arg);
            invoke('Button', this.objID, 'setText', arg);
        }
        get text() {
            return this._text;
        }
    }
    
    1. Hummer是如何注册原生组件
      20步注册原生组件
    HummerRegister$$hummer_component.java
    //在这个aop生成的java文件中注册原生组件,在这里注册之后
    // 17步的Invoker invoker = mRegistry.get(className);才能获取到,
    //才能执行到java组件中的invoke方法执行相关的操作
    hummerContext.registerInvoker(new View$$Invoker());
    /**
    * 17步根据className获取到对应的组件,执行android组件的invoke方法
     * aop根据@Component("View")生成的代码
     * 具体执行回调到invoke方法通过methodName进行判断
     */
    public class View$$Invoker extends BaseInvoker<View> {
      @Override
      public String getName() {
        return "View";
      }
      @Override
      protected View createInstance(JSValue jsValue, Object[] params) {
        String param0 = params.length > 0 && params[0] != null ? String.valueOf(params[0]) : null;
        return new View(mHummerContext, jsValue, param0);
      }
      @Override
      protected Object invoke(View instance, String methodName, Object[] params) {
        Object jsRet = null;
        switch (methodName) {
          case "appendChild": {
            long childId0 = params.length > 0 && params[0] != null ? ((Number) params[0]).longValue() : 0;
            HMBase param0 = mInstanceManager.get(childId0);
            instance.appendChild(param0);
          } break;
          case "removeChild": {
            long childId0 = params.length > 0 && params[0] != null ? ((Number) params[0]).longValue() : 0;
            HMBase param0 = mInstanceManager.get(childId0);
            instance.removeChild(param0);
          } break;
        }
        return jsRet;
      }
    }
    
    Hummer页面渲染(二)
    Hummer页面渲染时序图(二).jpg

    通过上面介绍我们明白了

    1. js引擎和java侧的交互通道
    2. js代码中的组件是通过预先注入js_base、js_component代码实现的
    3. java侧组件的注入,实现js侧和java侧一对一的组件
    4. 我们写的js组件通过1、2应对到3中的java侧组件执行相关操作,

    下面介绍回调到java侧组件之后我们 如何操作
    第一步创建对象

    //js侧创建一个对象回调到BaseInvoker.java文件中,调用如下方法创建一个对象
    //根据objectID保存到mInstanceManager中
    private T getInstance(long objectID, String methodName, Object... params) {
            // objectID <= 0说明是static方法
            if (objectID <= 0) {
                return null;
            }
            T instance = mInstanceManager.get(objectID);
            if (instance == null && "constructor".equals(methodName)) {
                JSValue jsValue = params.length > 0 ? ((JSValue) params[0]) : null;
                if (jsValue != null) {
                    if (params.length > 1) {
                        Object[] parameters = Arrays.copyOfRange(params, 1, params.length);
                        instance = createInstance(jsValue, parameters);
                    } else {
                        instance = createInstance(jsValue);
                    }
                    if (instance instanceof ILifeCycle) {
                        ((ILifeCycle) instance).onCreate();
                    }
                    mInstanceManager.put(objectID, instance);
                }
            }
            return instance;
        }
    

    第二步设置属性

    //创建完成对象我们js侧调用 this.style = {}设置样式
    //回调到BaseInvoker.java文件中
    //invokeHMBase这个方法根据methodName来匹配js侧调用了什么方法,去执行相应的操作
        private Object invokeHMBase(T instance, String methodName, Object... params) {
            HMBase base = (HMBase) instance;
            Object ret = null;
            switch (methodName) {
                case "setStyle":
                    //解析js侧传过来的style样式
                    Map<String, Object> styleMap = HMJsonUtil.toMap(String.valueOf(params[0])); // json耗时1ms
                    base.setStyle(getSortedMap(styleMap)); // sort耗时1ms
                    break;
                    ......
            }
            return ret;
        }
    //因为每个对象的基类都是HMBase.java,所以调用这个类的
    //setStyle方法,这个方法会调用HummerNode.setStyle方法
    //在这个方法中调用HummerStyleUtils.applyStyle(linkView, style);
    //来具体解析Style属性,YogaNode支持的属性直接设置,有很多不支持的属性需要
    //Hummer自己进行处理,自己处理部分查看HMBase.setHummerStyle方法,都是背景的处理
    // 设置控件样式
        static void applyStyle(boolean needIntercept, HMBase view, Map style) {
            if (view == null || style == null) {
                return;
            }
            Map<String, Object> transitionStyle = new HashMap<>();
            for (Object k : style.keySet()) {
                String key = String.valueOf(k);
                Object value = style.get(k);
                if (needIntercept && HummerLayoutExtendUtils.interceptDisplayStyle(view, key, value)) {
                    // 如果需要拦截,并且是Display相关属性,需要先被拦截
                    continue;
                } else if (Hummer.POSITION.equals(key) || Hummer.POSITION_TYPE.equals(key) || Hummer.DISPLAY.equals(key)) {
                    // 如果是Position或者Display相关属性,优先处理
                    if (view.setHummerStyle(key, value)) {
                        continue;
                    }
                } else if (key.startsWith(Hummer.TRANSITION)) {
                    // transition 参数跳过  在最后处理
                    transitionStyle.put(key, value);
                    continue;
                }
                try {
                    // transform 必须走 animator 进行变化
                    if (Hummer.TRANSFORM.equals(key) || (isTransitionStyle(key) && (view.supportTransitionStyle("all") || view.supportTransitionStyle(key)))) {
                        view.handleTransitionStyle(key, value);
                    } else if (isYogaStyle(key)) {
                        // 处理Yoga支持样式
                        applyYogaStyle(view.getYogaNode(), key, value); // 耗时1ms
                    } else {
                        // 处理Hummer自定义样式
                        applyHummerStyle(view, key, value); // 耗时1ms
                    }
                } catch (Exception e) {
                    // 处理异常信息显示不全的问题,补充异常是在哪个key和value下产生
                    ExceptionUtil.addStackTrace(e, new StackTraceElement("<<Style>>", "", String.format("%s: %s", key, value), -1));
                    HummerException.nativeException(view.getJSValue().getJSContext(), e);
                }
            }
            view.getView().requestLayout();
            // 设置transition样式
            for (Object k : transitionStyle.keySet()) {
                String key = String.valueOf(k);
                Object value = style.get(k);
    
                view.setTransitionStyle(key, value);
            }
            view.runAnimator();
        }
    

    第三步渲染

    //js侧调用view.appendCild()会回调到invoke方法,通过methodName判断
    @Override
      protected Object invoke(View instance, String methodName, Object[] params) {
        Object jsRet = null;
        switch (methodName) {
          case "appendChild": {
            long childId0 = params.length > 0 && params[0] != null ? ((Number) params[0]).longValue() : 0;
            HMBase param0 = mInstanceManager.get(childId0);
            instance.appendChild(param0);
          } break;
        }
        return jsRet;
      }
    //调用到java侧View组件
    @Component("View")
    public class View extends HummerLayoutExtendView {
       ......
        @Override
        @JsMethod("appendChild")
        public void appendChild(HMBase child) {
            super.appendChild(child);
            if (child == null) {
                return;
            }
            hummerNode.appendChild(child.getNode());
           ......
        }
        ......
    }
        /**
         * 从如下代码看如果YogaLayout支持的属性直接调用addView添加
         * 如果不支持的属性需要单独进行处理
         */
        public void appendChild(HMBase child) {
            .....
            // 支持 position:fixed 布局样式,添加至窗口视图
            if (child.getPosition() == HummerLayoutExtendUtils.Position.FIXED) {
                hummerContext.getContainer().addView(child);
                FixedNoneBox fixedNoneBox = new FixedNoneBox(hummerContext);
                fixedNoneBoxMap.put(child, fixedNoneBox);
                finalChild = fixedNoneBox;
            }
            // 支持 display:inline 布局样式,外部扩展横向flex布局
            if (getDisplay() == HummerLayoutExtendUtils.Display.BLOCK) {
                HummerLayoutExtendUtils.markExtendCssView(child);
                if ((child.getDisplay() == HummerLayoutExtendUtils.Display.INLINE
                        || child.getDisplay() == HummerLayoutExtendUtils.Display.INLINE_BLOCK)) {
                    HMBase lastChild = getView().getLastChild();
                    if (lastChild instanceof InlineBox) {
                        child.setInlineBox((InlineBox) lastChild);
                        ((InlineBox) lastChild).add(child);
                        finalChild = null;
                    } else {
                        InlineBox box = new InlineBox(hummerContext);
                        child.setInlineBox(box);
                        box.add(child);
                        inlineBoxes.add(box);
                        finalChild = box;
                    }
                }
            }
            // 扩展样式处理
            if (HummerLayoutExtendUtils.isExtendCssView(child)) {
                HummerLayoutExtendUtils.applyChildDisplayStyle(getDisplay().value(), child);
            }
            // 默认处理
            if (finalChild != null) {
                getView().addView(finalChild);
            }
        }
    
     /**
         *HummerLayout中调用YogaLayout.addView添加View,此时subview.getYogaNode()已经设置好Yoga支持的属性
         *添加子视图
         * @param subview 子视图
         * @param index 位置
         */
        public void addView(@NonNull HMBase subview, int index) {
            if (subview == null) {
                return;
            }
            addView(subview.getView(), subview.getYogaNode());
            // 当removeChild再addChild的时候,data会被置为null,需要重新setData
            if (subview.getYogaNode().getData() == null) {
                subview.getYogaNode().setData(subview.getView());
            }
            int childCount = getYogaNode().getChildCount();
            index = index < 0 ? childCount : index;
            getYogaNode().addChildAt(subview.getYogaNode(), index);
            children.put(subview.getViewID(), subview);
            if (index == childCount) {
                lastChild = subview;
            }
        }
    
    

    总结:

    1. Hummer框架通过quickjs和jni实现对js侧和java侧的通信桥梁;
    2. 通过提前注入js组件使得开发者可以使用预先设置的组件和功能;
    3. java侧注册和js一对一的组件,通过桥梁的方式回调到java侧的组件实现相应的操作

    相关文章

      网友评论

        本文标题:Hummer 跨端框架源码解析

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