基本介绍:
体验了一下weex,发现weex语法还挺简单,上手容易,发现自己没什么前端知识,也能极易上手,出于强烈好奇和业务预研的需要,分析了其Android端的Weex Sdk一些源码.
先从WXSDKManager入手后,画出其结构图如图:
IWXUserTrackAdapter
:用来处理日志信息接口,常常拿来做一些用户埋点统计.
IWXImgLoaderAdapter
:用来处理View加载图片接口,可以实现其控制如何加载远程和本地图片.
IWXHttpAdapter
:用来处理网络请求的接口,常常处理请求一系列过程,默认实现DefaultWXHttpAdapter
.
IActivityNavBarSetter
:用来处理页面跳转接口,可以实现其接口来控制页面的跳转.
IWXStorageAdapter
:用来处理存储接口,例如SQLite存储,默认实现DefaultWXStorage
.
IWXDebugAdapter
:用来处理调试接口,通常实现其接口来在Chrom上做一些页面的调试.
WXDomManager
:专门用来管理Dom节点一些操作,如创建节点对应对象,但真正操作是委托给其他的对象,其关联如图:
WXBridgeManager
:用来处理Js和Android端的通信,例如Js端调用Android端Native层的方法.其关联如图:
WXRenderManager
:用来处理一些渲染操作,例如通过WXRenderStatement将Js层标签转到native层的View组件,其关联如图:
从上面看知道,一个weex页面在Android端渲染,分了三大模块,Dom节点操作管理模块,跨端通信模块,渲染模块,其三个端具体关联分别如下.
节点操作模块:
跨端通信模块:
渲染模块:
weex的绘制流程:
在分析weex如何在android端绘制流程之前,首先先弄清楚一个weex页面在native层的生命周期是如何?
那么在没有WebView的情况下,Native层又如何去解析Js代码呢?梳理了一下其源码,发现weex主要通过下图方式,建起js和java之间的通信桥梁:
从图可知,Js如果要与java通信,那么可以通过google v8引擎先与c++通信,然后在通过jni机制来实现与java的通信,从解决了Js页面与Native的通信了.同理,java与Js通信也一样.
接下来就分析了其weex之android端的绘制流程了,但限于前端和v8引擎知识有限,所以还不能很好的深入到里面,只能肤浅概况其绘制流程:
weex的组件:
weex能很灵活的支持组件扩展,在weex android sdk里,定义一系列weex组件,并且映射到native对应View组件.这里大概概况一下组件注册流程:
那weex组件设置属性又如何映射到native层,weex组件转换native组件步骤如图(非根节点,js调用过程类似上面时序图):
weex的module扩展:
在这里weex的module自扩展注册和weex的组件注册流程差不多,也是通过@WXModuleAnno注解标记native层方法供js调用,其调用流程如下:
weex的实践
说了那么多,还是来实践一个浮窗的weex控件吧,(浮窗控件还有很多没完成,完成会尝试同步到weex开源项目上)这里我直接贴核心代码了.
原生部分代码:
//浮窗接口
public interface FloatWindowInterface {
void init(WindowManager windowManager,View windowView);
void show();
void hide();
}
//浮窗native的View容器
public class WXFloatFrameLayout extends WXFrameLayout {
private FloatWindowInterface mFloatWindow;
public WXFloatFrameLayout(Context context, FloatWindowInterface floatWindow) {
super(context);
this.mFloatWindow = floatWindow;
}
public FloatWindowInterface getFloatWindow() {
return mFloatWindow;
}
public void setFloatWindow(FloatWindowInterface floatWindow) {
mFloatWindow = floatWindow;
}
public boolean isIntercept() {
return isIntercept;
}
public void setIntercept(boolean intercept) {
isIntercept = intercept;
}
private boolean isIntercept=true;
public WXFloatFrameLayout(Context context) {
super(context);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(isIntercept){
return true;
}
return super.onInterceptTouchEvent(ev);
}
}
//浮窗的weex组件
@Component(lazyload = false)
public class WXWindowComponent extends WXDiv implements WXSDKInstance.OnInstanceVisibleListener,View.OnTouchListener,FloatWindowInterface {
private WXSDKInstance mViewInstance;
private String src;
private boolean mIsVisible=true;
private String originUrl;
private FloatViewRenderListener mListener;
private WindowManager mWm;
private View mWindowView;
private WindowManager.LayoutParams mLayoutParams;
private int mGravity=Gravity.CENTER;
private int mFlag=WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
private float mTouchX;
private float mTouchY;
private int mLeft=0;
private int mTop=0;
private int mDeviceWidth;
private boolean mDisableFloat=false;
public WXWindowComponent(WXSDKInstance instance, WXDomObject node, WXVContainer parent, boolean lazy) {
super(instance, node, parent, lazy);
mListener=new FloatViewRenderListener(this);
}
private void updateViewPosition(){
this.mLayoutParams.x=(int) (mTouchX-mLeft);
this.mLayoutParams.y=(int) (mTouchY-mTop);
mWm.updateViewLayout(mWindowView,this.mLayoutParams);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if(mDisableFloat){
return false;
}
mTouchX = event.getRawX();
mTouchY = event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
updateViewPosition();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if(mTouchX>=this.mDeviceWidth>>1){
mTouchX=this.mDeviceWidth;
}else {
mTouchX=0;
}
updateViewPosition();
break;
}
return true;
}
public void init(WindowManager windowManager,View windowView) {
this.mDeviceWidth= WXViewUtils.getScreenWidth(getContext());
this.mLayoutParams=new WindowManager.LayoutParams();
this.mLayoutParams.height=WindowManager.LayoutParams.WRAP_CONTENT;
this.mLayoutParams.width=WindowManager.LayoutParams.WRAP_CONTENT;
this.mLayoutParams.format= PixelFormat.TRANSLUCENT;
this.mLayoutParams.type=WindowManager.LayoutParams.TYPE_APPLICATION;
this.mLayoutParams.gravity=this.mGravity;
this.mLayoutParams.flags =this.mFlag;
this.mWm= windowManager;
this.mWindowView=windowView;
((WXFloatFrameLayout)getHostView()).setIntercept(true);
this.mWindowView.setOnTouchListener(this);
}
void loadInstance(){
mViewInstance=createInstance();
}
@Override
public void onAppear() {
if(mIsVisible&&mViewInstance!=null){
WXComponent comp=mViewInstance.getRootCom();
show();
if(comp!=null){
mViewInstance.fireEvent(comp.getRef(), Constants.Event.VIEWAPPEAR,null, null);
}
}
}
@Override
public void onDisappear() {
if(mIsVisible && mViewInstance != null){
WXComponent comp = mViewInstance.getRootCom();
hide();
if(comp != null)
mViewInstance.fireEvent(comp.getRef(), Constants.Event.VIEWDISAPPEAR,null, null);
}
}
public void renderNewURL(String url){
this.src=url;
loadInstance();
}
public ViewGroup getViewContainer(){
return getHostView();
}
private WXSDKInstance createInstance() {
WXSDKInstance sdkInstance =new WXSDKInstance(getContext());
getInstance().addOnInstanceVisibleListener(this);
sdkInstance.registerRenderListener(mListener);
final String url=src;
if(TextUtils.isEmpty(url)){
return sdkInstance;
}
ViewGroup.LayoutParams layoutParams = getHostView().getLayoutParams();
sdkInstance.renderByUrl(WXPerformance.DEFAULT,
url,
null, null, layoutParams.width,
layoutParams.height,
WXRenderStrategy.APPEND_ASYNC);
return sdkInstance;
}
static class FloatViewRenderListener implements IWXRenderListener{
WXWindowComponent mComponent;
public FloatViewRenderListener(WXWindowComponent wxWindowComponent){
this.mComponent=wxWindowComponent;
}
@Override
public void onViewCreated(WXSDKInstance instance, View view) {
FrameLayout hostView=this.mComponent.getHostView();
view.invalidate();
hostView.removeAllViews();
hostView.addView(view);
}
@Override
public void onRenderSuccess(WXSDKInstance instance, int width, int height) {
}
@Override
public void onRefreshSuccess(WXSDKInstance instance, int width, int height) {
}
@Override
public void onException(WXSDKInstance instance, String errCode, String msg) {
}
}
@Override
protected boolean setProperty(String key, Object param) {
switch (key) {
case Constants.Name.SRC:
String src = WXUtils.getString(param,null);
if (src != null)
setSrc(src);
return true;
}
return super.setProperty(key, param);
}
@WXComponentProp(name = Constants.Name.SRC)
public void setSrc(String src) {
originUrl=this.src;
this.src = src;
if (mViewInstance != null) {
mViewInstance.destroy();
mViewInstance = null;
}
loadInstance();
}
@WXComponentProp(name = com.scau.beyondboy.weexdemo.weex.common.Constants.Name.GRAVITY)
public void setGravity(int gravity){
this.mGravity=gravity;
this.mLayoutParams.gravity=this.mGravity;
show();
}
@WXComponentProp(name = com.scau.beyondboy.weexdemo.weex.common.Constants.Name.DISPlAY_WINDOW)
public void displayWindow(boolean displayWindow){
if(displayWindow){
show();
}else {
hide();
}
}
@WXComponentProp(name = com.scau.beyondboy.weexdemo.weex.common.Constants.Name.DISABLE_FLOAT)
public void disableFloat(boolean disableFloat){
this.mDisableFloat=disableFloat;
if(this.mDisableFloat){
((WXFloatFrameLayout)getHostView()).setIntercept(false);
}else{
((WXFloatFrameLayout)getHostView()).setIntercept(true);
}
}
@WXComponentProp(name = com.scau.beyondboy.weexdemo.weex.common.Constants.Name.FLAG)
public void setFlag(int flag){
this.mFlag=flag;
}
public void show(){
if(this.mWm==null){
return;
}
if(this.mWindowView.getParent()!=null){
if(this.mWindowView.getParent()!=null){
this.mWm.removeView(mWindowView);
}
}
this.mWm.addView(this.mWindowView,this.mLayoutParams);
this.mWindowView.post(new Runnable() {
@Override
public void run() {
int[] location =new int[2];
mWindowView.getLocationOnScreen(location);
mLeft=location[0]+(mWindowView.getWidth()>>1);
mTop=location[1]+(mWindowView.getHeight()>>1);
}
});
}
public void hide(){
if(this.mWm==null){
return;
}
if(this.mWindowView!=null&&this.mWindowView.getParent()!=null){
this.mWm.removeView(this.mWindowView);
this.mLayoutParams.x=0;
this.mLayoutParams.y=0;
}
}
public String getSrc() {
return src;
}
public String getOriginUrl() {
return originUrl;
}
public void setOriginUrl(String originUrl) {
this.originUrl = originUrl;
}
@Override
public void destroy() {
super.destroy();
hide();
if(mViewInstance!=null){
mViewInstance.destroy();
mViewInstance=null;
}
src=null;
}
@Override
protected WXFloatFrameLayout initComponentHostView(@NonNull Context context) {
return new WXFloatFrameLayout(context,this);
}
}
这里别忘了注册一下组件,这里我用这行代码注册WXSDKEngine.registerComponent("float", WXWindowComponent.class,true);
.
we文件代码:
<!--weex的浮窗文件代码-->
<template>
<!-- <div> -->
<float src="file://assets/floatImage.js" style="width: 200px;height: 200;"></float>
<!-- </div> -->
</template>
<!--weex的floatImage文件代码-->
<template>
<div >
<img src="//gw.alicdn.com/tps/i2/TB1DpsmMpXXXXabaXXX20ySQVXX-512-512.png_400x400.jpg"
style="width:200px; height:200px; background-color:gray;
box-sizing:border-box;
border-radius: 150px"/>
</div>
</template>
通过写bash脚本去编译一下,这些we文件会通过weex工具去转换js文件存到我的android项目assets目录下,运行的结果如图(红点是受录制影响):
Weex的总结
weex相对于native的原生加载页面还是存一些性能瓶颈,如内存消耗,动画时间消耗,通常内存消耗和时间消耗是互相关联,同时也关联了CPU的性能.文件尺寸.对于优化内存消耗部分,可以采用一些复用对象方式或对象池方式等手段来减少内存开销,如触屏事件的Target.对于时间消耗,可以采用缓存策略来去管理一些weex实例,如缓存常用对象等手段,对于文件尺寸来说,可以采用js代码压缩,甚至通过技巧去共享依赖模块,而不是每次转换js文件,就要导入依赖模块等方式来减少文件尺寸.
由于对于前端知识缺少了解,有不足之处望多多指正.不过后面时间还是会继续写一些Weex之Android端的细节地方.