android Bindingx sdk源码分析
android Bindingx sdk源码分析
- Bindingx 是什么?
BindingX
是解决weex
和React Native
上富交互问题的一种解决方案。
如果按照传统的weex
和 native
的交互方式,比如要实现一个清屏的动画,需要在这个视图上绑定touch
事件后,监听到每一次手势的变化,native
与weex
都要进行一次通信,频繁的通信交互必然会消耗性能,导致无法在16ms内完成一次渲染,使得动画效果卡顿。
而bindingx
则是利用了Expression Binding
方案,通过对手势控制行为的表达式传递给native
,当手势变化时,不再与weex交互,而是直接解析表达式的运算符和目标view来更新视图,达到流畅的实现weex
动画。
- 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,expressionArgs
。expressionArgs
对应的就是属性名为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()
方法是定义在IEventHandler
, AbstractEventHandler
做了基础的解析,然后对于每种动画类型都做了各自特有的实现。
先看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<>(),
这个mScope
的key
是weex
识别的名称,如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()
找到具体改变的属性值的更新方法。