阅读 7516

一起来玩Weex

前言

从前几天的面试情况来看,绝大部分的公司提到的更多是Hybrid App,诸如ReactNative、weex和WebView这样的字眼。为啥呢?简单的总结了一下,发布节奏快(节约成本),跨端,前两者还可以拥有Native一样的流畅体验。那么到底是不是这样呢?我这种渣渣说了不算,时间会说明一切,而这篇文章主要讲的三者之一的weex。

在学习一门技术之前必须要有一定的觉悟,而我的觉悟就是:我现在没心情听他们什么狗屁weex现在和未来如何,我现在只想搞weex。

效果图

这是我们这篇文章要撸的最后效果图,下面不仅会介绍如何撸,还会告知撸的时候遇到的问题和一些注意点。之所以选这样的场景是较具代表性。

实战

起因

老是看见大佬用weex开发了什么项目,光羡慕他们有什么用?我们要自己行动起来,比他们更厉害(手动滑稽)。

作为一名地地道道的Android工程师,在面试的时候问我rn或者weex的时候,真不知道咋说,主要我真不会啊,h5还好说,因为我在上家公司主要从事原生与h5的开发,同事也分享了许多前端的框架,但我基本了解下其中的思想,除了大学学的微不足道的js之外,真的是对前端一无所知啊!说了这么多跟主题无关的话,主要想说,不要害怕自己啥都不会,撸起袖子就是干。多一门技术多一份饭碗,提升自己的技术栈,更多的公司需要的是啥都会的人才(嘿嘿)。

Tips:一些坑点和注意点会在讲述的时候掺杂,并不会一次性总结,大家要认真点哦!
复制代码

开战

基本布局

现在我们开始,关于什么环境配置,如何在Android studio上成功跑动demo类似这种我就不说了,你要时刻记住你是一名优秀的程序员,这篇文章要讲的更多的是Android视角,如何在已有项目中集成weex。

我们简单分析一下效果图,如果是我们原生开发,那么TabLayout+ViewPager+Fragment+RecyclerView是一种常见的选择。前三者在weex中并没有现成的组件,但RecyclerView有啊!在weex中有一个叫list的组件可以达到这样的效果。先上最终的代码:

<template>
    <list class="list" @loadmore="loadmore" loadmoreoffset="10">
        <cell class="cell" v-for="(item,index) in lists" @click="itemclick(item,index)">
            <div class="panel1">
                <div class="panel2">
                    <text class="text1" lines="1">{{item.desc}}</text>
                    <text class="text2" lines="1">{{"作者:"+isWhoEmpty(item.who)}}</text>
                    <text class="text3" lines="1">{{"发布时间:"+getLocalTime(item.publishedAt)}}</text>
                </div>
                <image class="icon" src="mipmap://ic_go.png"></image>
            </div>
        </cell>

        <loading class="loading" :display="loadinging ? 'show' : 'hide'">
              <loading-indicator class="indicator"></loading-indicator>
              <text class="indicator-text">正在加载中...</text>
            </loading>
    </list>
</template>
复制代码

关于下面的loading控件主要功能就是提供上拉加载,很简单,与之相配的是refresh组件,聪明的你肯定已经猜到就是我们的下拉刷新。

list组件里面的loadmore是监听我们的上拉加载事件,相当于我们的Listener和下面cell的click都是监听事件。除此之外还有scroll事件,我们这里并没有用到。写法都是一样,前面写个@。

list与我们的RecyclerView一样只不过是容器,更好管理我们的条目,与我们item(ViewHolder)相对应的就是cell组件,这里你要知道一个思想,数据驱动UI,我们的数据源去控制我们UI的渲染。而weex是基于MVVM的,这里的v-for主要对集合的操作,item就是所处索引为index的值。相当于我们在onBindViewHolder根据position拿到数据,这很好理解。而cell组件里面的就是我们的onCreateViewHolder创建的view。

其中textimage我就不过多介绍了,相应链接都给出了。如果没接触过前端的同学肯定很好奇它是如何布局摆放的,像什么宽高啊,padding和margin啊,这其实是我们的css去控制的,但是是阉割版的,如果已经看过v-for链接的同学,发现是vue的官网,是的,weex基于vue,原来编写这样的文件格式为we,而现在要改为vue格式才能编译成正确运行的js文件。

上面说了weex是MVVM模式,其实是vue采用MVVM模式,如果对Android的MVVM模式开发很熟悉的话,越看上面的文件越像我们的xml,我一直想说这个,xml在我们Android Studio中可以预览,如果上面的文件可以预览那该多好?No problem!我们可以利用Playground进行在线编写以及运行调试,甚至可以利用Playground的客户端扫码进行在手机端上调试,简直吊炸天。

Component

Component,也就是我上面说的组件,也就是我们的控件,除了官方内置的几种组件外,我们还可以自定义控件哦,是不是有点小兴奋,想当年也是因为自定义控件入行了Android,最前面所说的TabLayout+ViewPager+Fragment我把集成为一个控件,这样的做法,很明显的一个缺点就是耦合性增强了。我们现在处于学习嘛,怎么简单怎么来,先上我们的代码。

public class WXTabPagerComponent extends WXVContainer<LinearLayout>

@Override
protected LinearLayout initComponentHostView(@NonNull Context context) {
    LinearLayout root = (LinearLayout) View.inflate(context, R.layout.layout_tab_pager, null);
    ...
    return root;
}

@WXComponentProp(name = "titledata")
public void setData(List<String> datas) {
    if (mTabPagerAdapter == null) {
        ...
        mViewPager.setAdapter(mTabPagerAdapter);
        return;
    }
    mTabPagerAdapter.reload(datas);
}

<LinearLayout ...>
    <android.support.design.widget.TabLayout .../>
    <android.support.v4.view.ViewPager .../>
</LinearLayout>

WXSDKEngine.registerComponent("tabPager", WXTabPagerComponent.class);

<template>
    <div>
        <tabPager :titledata="datas" style="flex:1;"></tabPager>
    </div>
</template>
复制代码

大部分无关紧要的代码都省略了,我相信你都可以脑补出来。最上面是我们是我们的自定义组件WXTabPagerComponent继承WXVContainer,还可以继承WXComponent,两者相当于我们的ViewGroup和View。

一般我们会重写initComponentHostView方法,也就是返回我们的自定义控件,我理解为它的泛型就是我们的自定义控件,return之后就是我们的component。

下面的setData方法这里可以理解为数据绑定方法,更准确的理解是属性方法,什么叫属性方法?我们看到最下面的vue代码,titledata就是我们setData的注解name,而datas就是一个字符串集合,上面我已经说过weex是基于MVVM的,如何进行绑定的下面再说。而如何解析成List用的就是very牛皮的fastjson。所以setData这种属性方法你可以简单的理解为TextView的setText方法。

再下面是自定义控件的view层级。再下面是你必须注册该组件,不然weex不认识这玩意。

style="flex:1;"用来指定它的宽高,你可以理解为LinearLayout的weight属性,若不指定宽高,默认是0。

数据绑定

接上面的自定义组件,贴数据绑定代码:

<script>
    export default{
        data () {
            return {
                datas: ["Android","iOS","前端"]
            }
        }
    }
</script>
复制代码

这里的花样也是百花齐放,我学的时候也是一头雾水,比如带return属性写法表示只能在该组件生效,反之全局生效,可能会造成变量污染。这里的datas你可以理解我们的成员变量,就我们WXTabPagerComponent组件来说,我们return的datas直接以:titledata="datas"这样的形式进行绑定,一旦datas变量变化相应绑定的组件随之变化,所以叫属性方法。这里再多嘴一下,:titledata其实是缩写,全写是v-bind:titledata,这样是不是更好理解了?与最上面事件的@写法如出一辙,@写法的全写是v-on:,这些都是vue的东西。

Native和JS通信

自定义事件通知

该用法我还没用过,不做解释,我把官方的贴出。

public void fireEvent(String elementRef,final String type, final Map<String, Object> data,final Map<String, Object> domChanges){  }

public void fireEvent(String elementRef,final String type, final Map<String, Object> data){
  fireEvent(elementRef,type,data,null);
}

public void fireEvent(String elementRef, String type){
  fireEvent(ref,type,new HashMap<String, Object>());
}
复制代码

具体解释可以戳这里。这种方法用于native主动调js。

事件回调

这种方法用于js调native。

一种是带回调的,诸如:

public class WXLocation extends WXModule {
  @JSMethod
  public void getLocation(JSCallback callback){
    //获取定位代码.....
    Map<String,String> data=new HashMap<>();
    data.put("x","x");
    data.put("y","y");
    //通知一次
    callback.invoke(data);
    //持续通知
    callback.invokeAndKeepAlive(data);

    //invoke方法和invokeAndKeepAlive两个方法二选一
  }
}
复制代码

一种是不带回调的,诸如:

public class RouterModule extends WXModule {
    @JSMethod(uiThread = true)
    public void router(String url) {
        BrowserActivity.start(mWXSDKInstance.getContext(), url);
    }
}
复制代码

上面就是一个简单的路由,调用方法要用注解JSMethod,后面是确定是否在主线程调用。

双方使用也很简单:

WXSDKEngine.registerModule("RouterModule", RouterModule.class);//跟自定义组件一样,必须在原生注册

weex.requireModule('RouterModule').router(item.url);
复制代码

Android扩展

确切来说应该是Adapter扩展,Adapter顾名思义就是适配器,就是说js某些地方可能需要原生来实现达到性能最优。

常见有:

  • IWXUserTrackAdapter:相关性能数据 (首屏加载时间、JS-Native 通信时间、dom 更新时间等) 和其他通用信息 (JSLib 文件大小, Weex SDK 版本号等),好像可以用于埋点统计
  • IWXDebugAdapter:从字面上就能确定这玩意拿来调试的
  • IWXStorageAdapter:不多说,用来存储的
  • IWXImgLoaderAdapter:用来图片加载的,默认并没有实现,下面会给出自己实现的代码
  • IWXHttpAdapter:用来支持网络下载的,默认使用HttpURLConnection,下面也会给出实现代码
public class WXImageAdapter implements IWXImgLoaderAdapter {
    @Override
    public void setImage(String url, ImageView view, WXImageQuality quality, WXImageStrategy strategy) {

        if (TextUtils.isEmpty(url)) {
            return;
        }
        final Context context = view.getContext();

        if (url.startsWith("mipmap://")) {//加载本地资源
            ...
            view.setImageResource(imgId);
            return;
        }

        Glide.with(context)
                .load(url)
                .crossFade()
                .into(view);
    }

    private String getResIdStr(String url) {
        ...
        return url.substring(start, end);
    }
}
复制代码
public class WXHttpAdapter implements IWXHttpAdapter {

    private OkHttpClient mOkHttpClient;

    public WXHttpAdapter(OkHttpClient okHttpClient) {
        mOkHttpClient = okHttpClient;
    }

    @Override
    public void sendRequest(WXRequest request, OnHttpListener listener) {

        if (listener != null) {
            listener.onHttpStart();
        }

        if (request == null) {
            if (listener != null) {
                WXResponse wxResponse = new WXResponse();
                wxResponse.errorMsg = "WXRequest为空";
                listener.onHttpFinish(wxResponse);
            }
            return;
        }

        Request okHttpRequest;
        if (Constant.POST.equalsIgnoreCase(request.method)) {
            okHttpRequest = (new Request.Builder())
                    .headers(getHeaders(request))
                    .url(request.url)
                    .post(RequestBody.create(MediaType.parse(request.body), request.body))
                    .build();
        } else {
            okHttpRequest = (new okhttp3.Request.Builder()).url(request.url).build();
        }

        mOkHttpClient.newCall(okHttpRequest)
                .enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        if (listener != null) {
                            WXResponse wxResponse = new WXResponse();
                            wxResponse.errorCode = String.valueOf(-100);
                            wxResponse.statusCode = String.valueOf(-100);
                            wxResponse.errorMsg = e.getMessage();
                            try {
                                listener.onHttpFinish(wxResponse);
                            } catch (Exception e1) {
                                LoggerUtil.d(e1.getMessage());
                            }
                        }
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        if (listener != null) {
                            WXResponse wxResponse = new WXResponse();
                            wxResponse.statusCode = String.valueOf(response.code());
                            ResponseBody body = response.body();
                            if (body == null) {
                                wxResponse.errorMsg = "body为空";
                            } else {
                                wxResponse.originalData = body.bytes();
                            }
                            try {
                                listener.onHttpFinish(wxResponse);
                            } catch (Exception e) {
                                LoggerUtil.d(e.getMessage());
                            }
                        }
                    }
                });
    }

    @TargetApi(24)
    private Headers getHeaders(WXRequest request) {
        okhttp3.Headers.Builder builder = new okhttp3.Headers.Builder();
        if (request.paramMap != null) {
            Set<String> keySet = request.paramMap.keySet();
            keySet.forEach(key -> builder.add(key, request.paramMap.get(key)));
        }
        return builder.build();
    }
}
复制代码

代码篇幅比较长,但逻辑很简单,根据提供的参数WXRequest实现自己的网络请求,然后通过OnHttpListener回调给JS,其中注意点是尽量把回调给JS的变量WXResponse参数传齐,不然可能导致网络请求失败。

其它

我猜很多哥们内心现在最想问的是Android原生到底如何执行js文件?Weex的原理到底是什么?你那个效果图有bug,滑动的时候TabLayout透明了,你行不行啊?

听我一一道来,关于Weex的工作原理的示意图如下:

对于这种原理我也是半知半解,无能为力了。

关于Android原生执行js文件一般有两种,一种是从网络上获取,一种直接执行本地的js文件。

mWXSDKInstance.render("GankioList", WXFileUtils.loadAsset("gankiolist.js", context), options, null, WXRenderStrategy.APPEND_ASYNC);
mWXSDKInstance.renderByUrl();
复制代码

前者是执行本地,后者是网络获取,我们常常会操作options以此给js传递一些native的参数。

关于最后一个问题,我也很无奈啊,原来这个项目就是卡片阴影背景,一开始发现weex也有这样的样式,欣喜若狂,后来发现这个问题,我一度以为我使用姿势错了,急忙回去看文档,天空飘来仅仅支持ios几个字。

为何ios这么叼?为何ios这么叼?为何ios这么叼?

杂谈

这算是对自己这几天学习Weex的一点小总结,小弟不才,已经尽力了,希望对想入门或者刚入门Weex的同学有所帮助,同时希望Weex的前辈能够指出其中错误。文中的代码已经集成到我开源项目CrazyDaily。学习Weex主要还是看重它基于Vue,传说Vue是一位长得很帅的国人创立的,请给我一个理由不支持?大家学习的时候最好还是以官方文档为主,刚开始看不懂也正常,先通读一遍,然后自己敲Demo碰到问题再回去精读一遍。

最后,感谢一直支持我的人!

传送门

Github:github.com/crazysunj/

博客:crazysunj.com/

Weex常用网站:github.com/joggerplus/…

关注下面的标签,发现更多相似文章
评论
说说你的看法