美文网首页
android Bindingx sdk源码分析

android Bindingx sdk源码分析

作者: 雨打空城 | 来源:发表于2019-01-08 14:05 被阅读72次

    android Bindingx sdk源码分析

    1. Bindingx 是什么?

    BindingX 是解决weexReact Native上富交互问题的一种解决方案。

    如果按照传统的weexnative 的交互方式,比如要实现一个清屏的动画,需要在这个视图上绑定touch事件后,监听到每一次手势的变化,nativeweex都要进行一次通信,频繁的通信交互必然会消耗性能,导致无法在16ms内完成一次渲染,使得动画效果卡顿。

    bindingx则是利用了Expression Binding方案,通过对手势控制行为的表达式传递给native,当手势变化时,不再与weex交互,而是直接解析表达式的运算符和目标view来更新视图,达到流畅的实现weex动画。

    1. Bindingx 流程

    Bindingx 是一个扩展的module, 所以集成到android项目中时,要注册这个module.

    public class BindingX {
        private BindingX(){}
    
        /**
         * register binding module
         * */
        public static void register() throws WXException{
            WXSDKEngine.registerModule("expressionBinding", WXExpressionBindingModule.class);
            WXSDKEngine.registerModule("binding", WXBindingXModule.class);
            WXSDKEngine.registerModule("bindingx", WXBindingXModule.class);
        }
    }
    

    这里以timing动画为例来分析。

    timing动画就是随着时间流逝,对view进行相应的更新。所以变量为时间t,而因变量则为view的基础属性,如translateX,translateY,alpha,opacity, scaleX,scaleY,rotateX,rotateY,background-color,color,scroll.contentOffsetX, scroll.contentOffsetY等。

    具体可以参看源码WXViewUpdateSerive.java定义的sTransformPropertyUpdaterMap

    #WXViewUpdateService.java 
    static {
            sTransformPropertyUpdaterMap = new HashMap<>();
            sTransformPropertyUpdaterMap.put("opacity",new OpacityUpdater());
            sTransformPropertyUpdaterMap.put("transform.translate",new TranslateUpdater());
            sTransformPropertyUpdaterMap.put("transform.translateX",new TranslateXUpdater());
            sTransformPropertyUpdaterMap.put("transform.translateY",new TranslateYUpdater());
    
            sTransformPropertyUpdaterMap.put("transform.scale",new ScaleUpdater());
            sTransformPropertyUpdaterMap.put("transform.scaleX",new ScaleXUpdater());
            sTransformPropertyUpdaterMap.put("transform.scaleY",new ScaleYUpdater());
    
            sTransformPropertyUpdaterMap.put("transform.rotate",new RotateUpdater());
            sTransformPropertyUpdaterMap.put("transform.rotateZ",new RotateUpdater());
            sTransformPropertyUpdaterMap.put("transform.rotateX",new RotateXUpdater());
            sTransformPropertyUpdaterMap.put("transform.rotateY",new RotateYUpdater());
    
            sTransformPropertyUpdaterMap.put("background-color",new BackgroundUpdater());
            sTransformPropertyUpdaterMap.put("color", new ColorUpdater());
    
            sTransformPropertyUpdaterMap.put("scroll.contentOffset", new ContentOffsetUpdater());
            sTransformPropertyUpdaterMap.put("scroll.contentOffsetX", new ContentOffsetXUpdater());
            sTransformPropertyUpdaterMap.put("scroll.contentOffsetY", new ContentOffsetYUpdater());
    
            sTransformPropertyUpdaterMap.put("border-top-left-radius", new BorderRadiusTopLeftUpdater());
            sTransformPropertyUpdaterMap.put("border-top-right-radius", new BorderRadiusTopRightUpdater());
            sTransformPropertyUpdaterMap.put("border-bottom-left-radius", new BorderRadiusBottomLeftUpdater());
            sTransformPropertyUpdaterMap.put("border-bottom-right-radius", new BorderRadiusBottomRightUpdater());
    
            sTransformPropertyUpdaterMap.put("border-radius", new BorderRadiusUpdater());
        }
    

    weex定义一个timing动画,

    Binding.bind({
            eventType: 'timing',
            exitExpression: 't>1000',
            props: [{
                element: my,
                property: 'transform.translateX',
                expression: `easeOutExpo(t,${self.x},${changed_x},1000)`
              },
              {
                element: my,
                property: 'opacity',
                expression: `linear(t,${self.opacity},${changed_opacity},1000)`
              }
            ]
          });
    

    这个方法会先走到类BindingXCore.java的方法doBind()

     public String doBind(@Nullable Context context,
                             @Nullable String instanceId,
                             @NonNull Map<String, Object> params,
                             @NonNull JavaScriptCallback callback, Object... extension) {
            String eventType = Utils.getStringValue(params, BindingXConstants.KEY_EVENT_TYPE);
           ...
            ExpressionPair exitExpressionPair = Utils.getExpressionPair(params, BindingXConstants.KEY_EXIT_EXPRESSION);
    
            String anchor = Utils.getStringValue(params, BindingXConstants.KEY_ANCHOR); // maybe nullable
            List<Map<String, Object>> expressionArgs = Utils.getRuntimeProps(params);
            Map<String,ExpressionPair> interceptors = Utils.getCustomInterceptors(params);
            return doBind(anchor, anchorInstanceId, eventType, configMap, exitExpressionPair, expressionArgs, interceptors, callback, context, instanceId, extension);
        }
    

    这个方法主要用来解析一些参数,weex方代码的参数会映射成parmas, 那么在timing动画中,就会解析到eventType,exitExpressionPair,expressionArgsexpressionArgs对应的就是属性名为props。 另外anchor参数表示为动作触发的目标view,因为动画类型为timing, 所以也就不存在anchor,如果存在anchor, 会把anchor赋值给定义的动画的返回结果即token, 便于后面的解绑,如果没有anchor, 那么sdk就会自动生成一个token,来赋值给动画绑定完成后的结果。interceptors指的是动画拦截器,可以自定义特定需要监听的事件。定义的格式如下:

    interceptors: {
      interceptor_name: {
          expression: {
            origin:'',
            transformed:''
         }
      },
         ...
    }
    

    如想要收到用户横向滑动到100px这个事件,可以定义如下:

     interceptors: {
       user_horizontal_scroll_100: {
            expression: 'x>100'
       }
     }
    

    当解析完成之后,就到了关键执行动画表达式的代码了。也就是继续调用另一个doBind()方法。

     public String doBind(@Nullable String anchor,
                             @Nullable String anchorInstanceId,
                             @Nullable String eventType,
                             @Nullable Map<String, Object> globalConfig,
                             @Nullable ExpressionPair exitExpressionPair,
                             @Nullable List<Map<String, Object>> expressionArgs,
                             @Nullable Map<String,ExpressionPair> interceptors,
                             @Nullable JavaScriptCallback callback,
                             @Nullable Context context,
                             @Nullable String instanceId,
                             @Nullable Object... extension)  {
     ...
     token = doPrepare(context, instanceId, anchor, anchorInstanceId, eventType, globalConfig);
     ...                 
     handler.onBindExpression(eventType, globalConfig, exitExpressionPair, expressionArgs, callback);
     ...
    }
    

    在这个方法中,重点是这两个方法的调用doPrepare()onBindExpression, 在doPrepare()方法中,主要目的是为了生成一个token,用于后续的unbind以及设置一些相关的变量,即相关生命周期的调用,如onCreate,onStart

    onBindExpression()方法是定义在IEventHandlerAbstractEventHandler做了基础的解析,然后对于每种动画类型都做了各自特有的实现。

    先看AbstractEventHandler类中的方法:

    @Override
        public void onBindExpression(@NonNull String eventType,
                                     @Nullable Map<String,Object> globalConfig,
                                     @Nullable ExpressionPair exitExpressionPair,
                                     @NonNull List<Map<String, Object>> expressionArgs,
                                     @Nullable BindingXCore.JavaScriptCallback callback) {
            clearExpressions();
            transformArgs(eventType, expressionArgs);
            this.mCallback = callback;
            this.mExitExpressionPair = exitExpressionPair;
    
            if(!mScope.isEmpty()) {
                mScope.clear();
            }
            applyFunctionsToScope();
        }
    

    大神的代码很严谨啊,绑定表达式的时候,都做了清空处理。 重点只需要看两个方法:transformArgs(eventType, expressionArgs);applyFunctionsToScope();

    transformArgs(eventType, expressionArgs)这个方法是将props里面的json转成相对应的map存储在mExpressionHoldersMap中。

     private void transformArgs(@NonNull String eventType, @NonNull List<Map<String, Object>> originalArgs) {
            if (mExpressionHoldersMap == null) {
                mExpressionHoldersMap = new HashMap<>();
            }
            for (Map<String, Object> arg : originalArgs) {
                String targetRef = Utils.getStringValue(arg, BindingXConstants.KEY_ELEMENT);
                String targetInstanceId = Utils.getStringValue(arg, BindingXConstants.KEY_INSTANCE_ID);
                String property = Utils.getStringValue(arg, BindingXConstants.KEY_PROPERTY);
    
                ExpressionPair expressionPair = Utils.getExpressionPair(arg, BindingXConstants.KEY_EXPRESSION);
    
                Object configObj = arg.get(BindingXConstants.KEY_CONFIG);
                Map<String,Object> configMap = null;
                if(configObj != null && configObj instanceof Map) {
                    try {
                        configMap = Utils.toMap(new JSONObject((Map) configObj));
                    }catch (Exception e) {
                        LogProxy.e("parse config failed", e);
                    }
                }
    
                if (TextUtils.isEmpty(targetRef) || TextUtils.isEmpty(property) || expressionPair == null) {
                    LogProxy.e("skip illegal binding args[" + targetRef + "," + property + "," + expressionPair + "]");
                    continue;
                }
                ExpressionHolder holder = new ExpressionHolder(targetRef,targetInstanceId, expressionPair, property, eventType, configMap);
    
                List<ExpressionHolder> holders = mExpressionHoldersMap.get(targetRef);
                if (holders == null) {
                    holders = new ArrayList<>(4);
                    mExpressionHoldersMap.put(targetRef, holders);
                    holders.add(holder);
                } else if (!holders.contains(holder)) {
                    holders.add(holder);
                }
            }
        }
    

    而方法applyFunctionsToScope() 中最重要的理解就是Map<String, Object> mScope = new HashMap<>(),这个mScopekeyweex识别的名称,如JSMath类下的sin,cos,TimingFunctions下的easeInQuad,easeOutQuad, 以及自定义方法的名称。 对应的value则是对这些名称的具体实现。

    private void applyFunctionsToScope() {
            JSMath.applyToScope(mScope);
            TimingFunctions.applyToScope(mScope);
            // register custom js functions
            Map<String,JSFunctionInterface> customFunctions = BindingXJSFunctionRegister.getInstance().getJSFunctions();
            if(customFunctions != null && !customFunctions.isEmpty()) {
                mScope.putAll(customFunctions);
            }
        }
    

    回到类BindingXTimingHandler.java看到,利用这句话来开启时间动画

    mAnimationFrame.requestAnimationFrame(this);

    这里在抽象类AnimationFrame.java中有两个实现类,根据API的版本,分成了两个实现类ChoreographerAnimationFrameImpl, HandlerAnimationFrameImpl。具体执行表达式的方法,我们看到

     @Override
            public void doFrame(long frameTimeNanos) {
                if(callback != null) {
                    callback.doFrame();
                }
    

    在BindingXTimingHandler.java中

     @Override
        public void doFrame() {
            handleTimingCallback();
        }
    
    @WorkerThread
        private void handleTimingCallback() {
            long deltaT;
            if(mStartTime == 0) {
                mStartTime = AnimationUtils.currentAnimationTimeMillis();
                deltaT = 0;
                isFinish = false;
            } else {
                deltaT = AnimationUtils.currentAnimationTimeMillis() - mStartTime;
            }
    
            try {
                if(LogProxy.sEnableLog) {
                    LogProxy.d(String.format(Locale.getDefault(), "[TimingHandler] timing elapsed. (t:%d)", deltaT));
                }
                JSMath.applyTimingValuesToScope(mScope, deltaT);
                if(!isFinish) {
                    consumeExpression(mExpressionHoldersMap, mScope, BindingXEventType.TYPE_TIMING);
                }
                isFinish = evaluateExitExpression(mExitExpressionPair,mScope);
            } catch (Exception e) {
                LogProxy.e("runtime error", e);
            }
        }
    

    handeTimingCallback()方法中,

    (1) 将当前时间更新到mScope

    (2) 根据表达式,更新UI

    (3) 判断是否到退出条件。

    这里呢,我们分析一下,如何根据表达式更新UI的。我们进入方法consumeExpression()

     protected void consumeExpression(@Nullable Map<String, List<ExpressionHolder>> args, @NonNull Map<String,Object> scope,
                               @NonNull String currentType) throws IllegalArgumentException, JSONException {
            tryInterceptAllIfNeeded(scope);
            ...
            List<Object> extension = new LinkedList<>();
            for (List<ExpressionHolder> holderList : args.values()) {
                for (ExpressionHolder holder : holderList) {
                    Object obj = expression.execute(scope);
                    //apply transformation/layout change ... to target view.
                    View targetView = mPlatformManager.getViewFinder().findViewBy(holder.targetRef, extension.toArray());
                    BindingXPropertyInterceptor.getInstance().performIntercept(
                            targetView,
                            holder.prop,
                            obj,
                            mPlatformManager.getResolutionTranslator(),
                            holder.config,
                            holder.targetRef,
                            instanceId
                    );
    
                    // default behavior                mPlatformManager.getViewUpdater().synchronouslyUpdateViewOnUIThread(
                            targetView,
                            holder.prop,
                            obj,
                            mPlatformManager.getResolutionTranslator(),
                            holder.config,
                            holder.targetRef,/*additional params for weex*/
                            instanceId       /*additional params for weex*/
                    );
                }
            }
        }
    

    省略掉了一些判断语句。

    方法首先是触发相应的拦截器,其次是对args进行遍历,参数args就是之前提到的mExpressionHoldersMap, 这里存储了对应weex代码中props对象的element, 和 expression. 然后对每个expression, 都执行expression.execute(scope).

    执行结束后,就会调用intercept下更新UI。

    synchronouslyUpdateViewOnUIThread()这个方法会在bind()方法中的prepare()下实现的,最终走到了

    WXViewUpdateService.findUpdater(propertyName).update(
                                    targetComponent,
                                    targetView,
                                    propertyValue,
                                    translator,
                                    config);
    

    通过findUpdater()找到具体改变的属性值的更新方法。

    相关文章

      网友评论

          本文标题:android Bindingx sdk源码分析

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