适合前端Vue开发童鞋的跨平台Weex - SegmentFault 思否
基于 Vue 技术栈的你如果需要选用一种移动端跨平台框架,是 Weex?React-Native?还是Flutter? 无疑,相对于后两者,因为你现在已有比较熟练的 Vue 基础,如果在其他条件一致的情况,Weex 无疑是最佳选择;但是 Weex 真的适合在实际项目中进行移动端跨平台开发吗?Weex 的开发效率、Weex 的质量是否满足需求?
一、开发环境
在这个 Weex app 开发中,我的开发环境相关配置如下:
工具名称 | 版本号 |
---|---|
Node.js | 8.2.1 |
Npm | 5.3.0 |
Android Studio | 3.2 |
Weex | 2.0.0-beta.17 |
JDK | 1.8 |
Weex-ui | 0.6.14 |
二、Weex 介绍
2.1、Weex 理念
“Write once, run everywhere”, Weex 的定义就像是:写个 vue 前端,顺便帮你编译成性能还不错的 apk 和 ipa(当然,现实有时很骨感)。基于 Vue 设计模式,支持 web、android、ios 三端,原生端同样通过中间层转化,将控件和操作转化为原生逻辑来提高用户体验。 在 weex 中,主要包括三大部分:JS Bridge、Render、Dom,分别对应WXBridgeManager、WXRenderManager、WXDomManager,三部分通过 WXSDKManager 统一管理。其中 JS Bridge 和 Dom 都运行在独立的 HandlerThread 中,而 Render 运行在 UI 线程。 JS Bridge 主要用来和 JS 端实现进行双向通信,比如把 JS 端的 dom 结构传递给 Dom 线程。Dom 主要是用于负责 dom 的解析、映射、添加等等的操作,最后通知 UI 线程更新,而 Render 负责在 UI 线程中对 dom 实现渲染。
Weex 所有的标签也不是真实控件,JS 代码中所生成存的 dom,最后都是由 Native 端解析,再得到对应的 Native控件渲染,如 Android 中标签对应 WXTextView 控件。 Weex 中文件默认为 .vue ,而 vue 文件是被无法直接运行的,所以 vue 会被编译成 .js 格式的文件,Weex SDK会负责加载渲染这个 js 文件。Weex 可以做到跨三端的原理在于:在开发过程中,代码模式、编译过程、模板组件、数据绑定、生命周期等上层语法是一致的。不同的是在 JS Framework 层的最后,web 平台和 Native 平台,对 Virtual DOM 执行的解析方法是有区别的。
2.2 创建 Weex 项目
Weex 提供了一个命令行工具 weex-toolkit 来帮助开发者使用 Weex,它可以用来快速创建一个空项目、初始化 iOS 和 Android 开发环境、调试、安装插件等操作。
我们可以通过以下步骤创建一个基础的 Weex 项目:
(1)安装 weex-toolkit 工具
npm install weex-toolkit -g
(2)创建新项目
weex create weex_project
(3)安装项目依赖
cd weex_project
npm install
(4)启动项目
npm start
项目启动完毕,浏览器窗口会自动打开项目首页,如下图所示:
(5)添加 原生Android 平台
weex platform add android
(6)运行下面的命令,可以在模拟器或真实设备上启动 Android 应用:
weex run android
2.3、运行Weex项目
2.3.1、启动服务端应用
(1)进入目录 weex_project/backend/,安装服务端应用所需要的插件包:
$ npm install
(2)启动服务端应用
$ npm run start
2.3.2、启动 Weex 应用
(1)如果你还没安装 weex 工具,可以运行以下命令进行安装:
$ npm install -g weex-toolkit
(2)安装项目需要的插件包:
$ npm install
(3)启动项目:
$ npm run start
三、Weex 常用的 VSCode 插件
Weex为VSCode提供了一些常用的插件,可以提高开发效率:
- weex-new-project - 用于在 VSCode 中创建Weex项目;
- weex-lang - 用于在 VSCode 中对最新的 Weex 语法进行支持;
- weex-doctor - 用于检查 iOS 和 Android 本地开发环境;
- weex-debugger - 用于在 VSCode 中启动Weex调试工具;
- weex-run - 用于在热更新模式下启动 Android 及 iOS 工程;
3.1、weex-run
可以使用截图的步骤来安装 weex-run插件,可以自行搜索如何安装VSCode插件。
(2)启动 Android 项目
启动成功控制台会输出一堆日志,如下图。
Weex自带热更新功能,接下来,我们查看 Android 项目的热更新。
3.2、weex-debugger
(1)安装 weex-debugger 插件,安装过程和安装weex-run插件类似。
(2)ctrl + shift + p 弹出命令输入框,如下图所示输入:weex debug,然后网页会出现第 2 张截图的二维码:
(3)用手机的 Weex Playground App 的二维码进行扫描,出现以下调试页面(一定一定要注意,手机连的 WiFi 和 你开发本地网络在同一局域网)。
(4)再用手机的 Weex Playground App 的二维码扫描 Weex 应用的二维码,调试页面就会变成对应的 Weex 应用的调试页面,如下图所示。
四、Weex 项目实战
4.1、项目目录路径
下面通过一个Weex 项目来说明Weex的一些基础,项目目录结构如下:
4.2、功能模块设计
考虑到更好的体验 Weex 和 H5 在开发效率、功能性能、用户体验等方面的差异性,我们对功能模块进行精心设计,主要基于我们现有的实际项目的业务进行开发,并结合移动端特有的特性。
相关的模块功能设计如下图所示,其中红色标注部分表示,受限于开发资源、Weex 生态方面原因,我们暂时还没完成全部功能的开发。
4.3、功能界面展示
下面是Weex示例项目截取一些功能界面展示,如下图:
4.4、重要功能介绍
除了一些常规的功能开发外,以下介绍的几个功能在 Weex 官网中并没有详细介绍或者根本没有介绍,我们在开发过程中踩了不少坑,因此将踩坑经验进行汇总,帮助大家避免踩坑:
(1)登录 token 认证
(2)图片选择/上传功能
(3)websocket 功能实现
(4)手机物理键返回上一级功能
(5)Android 如何显示本地图片
4.4.1、token 认证功能
(1)token 认证介绍
在 Web 领域基于 token 的身份验证随处可见。在大多数使用 Web API 的互联网公司中,tokens 是多用户下处理认证的最佳方式。token 具有以下特性:
- 无状态、可扩展
- 支持移动设备
- 跨程序调用
- 安全
基于 token 的身份验证的过程如下:
- 用户通过用户名和密码发送请求。
- 服务端程序验证。
- 程序返回一个签名的 token 给客户端。
- 客户端储存 token,并且每次用于每次发送请求。
- 服务端验证 token 并返回数据。
(2)weex 和 express 之间实现 token 认证
express 服务端主要使用 express-jwt 插件,express-jwt 是 nodejs 的一个中间件,内部对 jsonwebtoken 进行封装使用。express-jwt 会验证指定 http 请求的 jsonwebtoken 的有效性,如果有效就将 jsonwebtoken 的值设置到 req.user 里面,然后跳转到相应的 router。
以下是服务端 express 的代码逻辑,代码如下:
var expressJWT = require('express-jwt');
// token 设置
app.use(expressJWT({
secret: CONSTANT.SECRET_KEY
}).unless({
// 除了以下配置的地址,其他的URL都需要验证
path: ['/getToken', /^\/public\/.*/, /^\/user_disk\/.*/]
}));
// 登录时,需要进行用户密码认证,相应路由跳转到下面一步
app.use('/getToken', tokenRouter);
// 当用户密码正确时,我们进行 token 设置
data: {
token: jsonWebToken.sign({
uid: obj.uid
}, CONSTANT.SECRET_KEY, {
expiresIn: 60 * 60 * 1
}),
}
对应的Weex的代码如下:
// Weex 登录逻辑
login () {
let param = {
uid: this.uid,
password: this.password
};
let options = {
url: '/getToken',
method: 'POST',
body: JSON.stringify(param)
};
let vm = this;
api.fetch(options, function (ret) {
if (ret.ok && ret.data.code === 0) {
// 前端可以获取到服务端返回的 token ,并将其作为全局变量
global.token = 'Bearer ' + ret.data.data.token;
vm.$router.push('/tabIndex');
} else {
modal.toast({
message: '用户认证失败!',
duration: 1
});
}
});
}
// Weex 的每次请求,头部都带上 token
initOptions.headers['Authorization'] = global.token;
经过以上代码逻辑处理后,我们查看 Weex 向服务端发送的请求头部,都携带了 token,如下图所示。这样服务端 express 处理这个请求时,就可以通过解析 token 获取到对应的用户 id ,从而允许其对服务端的数据访问。
4.4.2、图片选择/上传功能
(1)存在问题
很遗憾,Weex 竟然没有提供文件选择/上传的模块,对于前端开发者来说无疑晴天霹雳,那我不是要手动去写 Android 的 java 代码,经过反复查找,真的没有文件选择/上传模块,于是我们只能自己去写 Java 代码去实现 Android 端图片选择以及上传功能。当然,也可以使用一些第三方的插件。
(2)实现 Android 原生的图片选择/上传功能
在 weex_project/platforms/android/app/src/main/java/com/weexapp/extend 目录下新建 图片上传 模块的类 WXAlbumModule ,其继承 WXModule ,其主要两个方法为 choosePhoto 和 onActivityResult ,其中 choosePhoto 用于给 Weex 前端来调用,当 Weex 前端需要选择相册中的图片时,Weex 前端就调用 choosePhoto 方法;onActivityResult 是用户选择好相册中的图片后,会相应触发该事件,并将用户选择的相片以参数形式传入 onActivityResult ,从而我们可以在 onActivityResult 中进行图片的上传逻辑,图片上传完成后,Android 端会在回调事件中通知前端,图片放置在服务端的目录路径,前端可以对应进行图片显示等操作。关键代码逻辑如下,如果如果对 Java 完全一无所知的同学可以先不看,懂 java 代码的建议结合项目代码来看,会更清晰。
例如,下面是Android端封装的
@JSMethod(uiThread = true)
// 给 Weex 前端调用,当用户点击时,调用该函数
public void choosePhoto(String param, JSCallback callback) {
if (ContextCompat.checkSelfPermission(mWXSDKInstance.getContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions((WXPageActivity) mWXSDKInstance.getContext(),
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
CAMERA_REQUEST_CODE);
} else {
choosePhoto();
}
try{
JSONObject jsonObject = new JSONObject(param);
this.type = (String)jsonObject.get("type");
this.path = (String)jsonObject.get("path");
this.url = (String)jsonObject.get("url");
this.token = (String)jsonObject.get("token");
}catch (JSONException e){
e.printStackTrace();
}
this.callback = callback;
}
选择完成后,系统会返回图片的信息,此时就可以进行上传操作,如下所示:
@Override
// 用户选择好相册中的图片后,会相应触发该事件,并将用户选择的相片以参数形式传入
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == WXPageActivity.RESULT_OK) {
switch (requestCode) {
case CAMERA_REQUEST_CODE: {
try {
Uri selectedImage = data.getData();
String[] filePathColumns = {MediaStore.Images.Media.DATA};
Cursor c = mWXSDKInstance.getContext().getContentResolver().query(selectedImage, filePathColumns, null, null, null);
c.moveToFirst();
int columnIndex = c.getColumnIndex(filePathColumns[0]);
String picturePath = c.getString(columnIndex);
c.close();
//上传的文件
File file = new File(picturePath);
// 普通参数
HashMap<String , String> params = new HashMap<>();
params.put("path", this.path);
uploadForm(params, "file", file, "", this.url);
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
实现好以上选择图片和上传图片的代码逻辑后,我们需要在 weex_project/platforms/android/app/src/main/java/WXApplication.java 中进行模块的注册,代码逻辑如下:
WXSDKEngine.registerModule("wxalbum", WXAlbumModule.class);
然后,Weex 前端调用注册的原生模块即可,如下所示:
const WXAlbum = weex.requireModule('wxalbum');
upload () {
let path = 'public/upload/';
let vm = this;
storage.getItem('token', event => {
let param = {
type: 'image/jpeg', // 选择的数据类型
path: path,
url: CONSTANT.SERVER_URL + '/users/upload',
token: event.data
};
WXAlbum.choosePhoto(JSON.stringify(param), ret => {
let obj = JSON.parse(ret);
vm.imgPath = '/' + path + obj.file[0].originalFilename;
modal.alert({
message: vm.imgPath,
okTitle: '确认'
}, function () {
console.log('alert callback')
})
});
})
},
4.4.3、WebSocket 功能实现
(1)存在问题
Weex 官网的 webSocket 章节特意标注以下警告字眼:
h5 提供 WebSockets 的 protocol 默认实现,iOS 和 Android 需要自定义实现,Android 可参考:
- DefaultWebSocketAdapter.java
- DefaultWebSocketAdapterFactory.java
好吧,根本没有封装 WebSocket 功能,那我就按官网给的参考来实现吧,于是,我点击前面两个参考链接,链接打开的页面根本不存在,报 404(官网出现这种问题,实在不应该啊)。网上谷歌搜索一圈,没有发现类似的问题,还是主要查看了这个给的 url 以及结合阿里将 weex 贡献给 Apache 维护这个事情,猜测是不是 Weex 捐给 Apache 维护,github 的库目录更改,但是官网对应的 url 地址没有做修改。经过查找,确实是这个问题,在旧库中以下目录找到官网提的:DefaultWebSocketAdapter.java 和 DefaultWebSocketAdapterFactor.java :
github.com/alibaba/wee…
(2)手动实现 WebSocket 功能
我们 在 weex_project/platforms/android/app/src/main/java/com/weex/appadapter 目录底下创建 Websocket 的实现类 DefaultWebSocketAdapter.java 和工厂创建类 DefaultWebSocketAdapterFactory.java ,关键逻辑代码如下:
// 该类主要实现 Websocket 的连接、发送消息、接收消息、关闭等函数或事件
public class DefaultWebSocketAdapter implements IWebSocketAdapter {
@Override
public void connect(){...}
@Override
public void send(String data) {...}
@Override
public void close(int code, String reason) {...}
@Override
public void destroy() {...}
...
}
然后,为该类主要为创建 Websocket 对象的工厂类:
// 该类主要为创建 Websocket 对象的工厂类
public class DefaultWebSocketAdapterFactory implements IWebSocketAdapterFactory {
@Override
public IWebSocketAdapter createWebSocketAdapter() {
return new DefaultWebSocketAdapter();
}
}
接下来,在 weex_project/platforms/android/app/src/main/java/com/weexapp/WXApplication.java 中初始化 Websocket ,如下所示:
WXSDKEngine.initialize(this,
new InitConfig.Builder().setImgAdapter(new ImageAdapter()). setWebSocketAdapterFactory(new DefaultWebSocketAdapterFactory()).build()
);
然后,在 Weex 的前端中导入Websocket模块,就可以使用 Websocket,相关代码如下:
const ws = weex.requireModule('webSocket');
ws.WebSocket(CONSTANT.SOCKET_WS, '');
// 需要注意 web 端的写法和 android 端的写法不一样
// android 的 onxx 事件是一个方法,需要传入一个JSCallback的值,
if (weex.config.env.platform === 'Web') {
ws.onmessage = this.socketMessage;
} else {
ws.onmessage(this.socketMessage);
}
4.4.4、点击手机物理键返回上一级功能
(1)存在问题
我们开发的 Weex app,如果在 app 的哪个界面,点击手机的返回上一级物理键,都会导致 app 退出,好吧,Weex 也没有提供对应的事件处理,我们不得不自己再去写安卓的 java 代码去向 Weex 的 Web 端抛出这个事件。
(2)重写手机物理键返回上一级的处理逻辑
正常交互逻辑:当处于主界面时,返回上一级物理键会进行提示“再点击一次退出”,如果不是处于主界面时,会返回上一级页面。
首先,我们在 weex_project/platforms/android/app/src/main/java/com/weexapp/WXPageActivity.java 中添加监听点击手机物理键的事件,如下所示:
public void onBackPressed(){
Map<String,Object> params=new HashMap<>();
params.put("name","msg");
mInstance.fireGlobalEventCallback("androidback",params);
}
在 Weex 的 vue 入口文件中,监听 androidback 事件,当接收到该事件时,进行相应的逻辑处理,代码如下所示:
listenAndroidBack () {
let vm = this;
globalEvent.addEventListener('androidback', function (e) {
if (vm.$route.name === 'tabIndex' || vm.$route.name === 'loginPage') {
if (vm.exitFlag) {
weex.requireModule('wxclose').closeApp();
} else {
modal.toast({
message: '再点一次退出',
duration: 1
});
vm.exitFlag = true;
vm.clearExitFlag();
}
} else {
vm.$router.go(-1);
}
});
},
4.4.5、Android 显示本地图片
(1)存在问题
Weex 官网中 image 图片组件显示项目目录下图片,src 地址直接写成相对路径,如下所示;但是这种写法存在问题,它只支持 web 端的显示,在 Android 端是无法显示的,找不到对应图片。
<image ref="poster" src="path/to/image.png"></image>
(2)Android/IOS 端显示本地图片
Weex 没有在将 vue 编译成 Android 组件时,对应将图片放置到 Android 对应的目录下,所以我们只好自己将图片手动再放置一份,其中 Android 端需要额外将图片放在 /platforms/android/app/src/main/res/drawable-xxhdpi ,IOS 放入xcode 底下的 /Source/images/下 ,然后我们在代码逻辑中,根据环境判断现在是 Web 环境、Android 环境或者 IOS 环境,再对应的获取对应目录下的图片(暂时只能做到这种程度了...),如下代码所示:
const ICON_URL = {
Web: `${WEB_IMAGE_URL}`,
android: `local:///${pureName}`,
iOS: `local:///filePng/${pureName}${suffixName}`
}
return ICON_URL[CUR_RUN_PLATFORM];
五、编译 Android apk
Android apk 打包分 debug 版和 release 版,通常所说的打包指生成 release 版的 apk,release 版的 apk 会比debug 版的小,release 版的还会进行混淆和用自己的 keystore 签名,以防止别人反编译后重新打包替换你的应用。 下面我们主要介绍如何在 Android Studio 中对 weex 项目进行打包。
5.1、Android 平台目录
Android Studio 打开 Android 工程,目录为:weex 项目 /platforms/android 。
5.2、常规的 AS 打包分为两种
一种是没有 “.jks” 文件的打包
一种是有 “.jks” 文件的打包
注:.jks” 文件 类似 apk 身份证;
5.3、没有 “ .jks ” 文件的打包
(1)打包步骤如下截图:
(2)我们点击选择 【Create new】创建jks
(3)填写 key 的相关信息
(4)点击 OK 之后,可以看到如下信息已被自动填充,并点击打包即可。
(5)等待打包完成后,就可以查看打包好的 apk 文件
六、Weex 开发总结
6.1、官网经常无法访问
Weex 官网经常出现无法访问的情况,频率大概一周至少一次;这就很影响开发效率了,因为在开发过程中需要经常查看官网的写法、说明等,如果访问不了,则会造成一定程度的开发 block。
6.2、官网文档粗糙
Weex 官网的文档比较粗糙,如果没有比较好的前端和移动端原生开发知识储备的话,看官网的文档就很吃力了,官网很多讲解写的非常简单,都默认你同时熟练前端和移动端原生开发,而且同时有较好前端和移动端原生开发人员应该在业界还比较少吧。
6.3、生态贫瘠
Weex 生态是真的贫瘠,除了阿里自己出产的组件库 weex-ui 外,其它的相关插件几乎找不到,有也是少于100个 star 的,例如我在项目开始前设计的一些功能:拍照、图片选择上传、语音录入、通讯、定位、文件预览等等移动端的特有功能,都没有插件,都需要自己去写 Android 的原生代码,那这时就失去了利用框架提高开发效率的意义;生态跟 react-native 差的真不是一丁半点,而是根本不是一个量级。
6.4、是否两个 Weex 版本
结合上一点,坊间传闻:Weex 存在两个版本,一个版本是阿里内部使用的,一个是非阿里内部使用;这个传言无从验证,但是结合第2点说的 Weex 生态贫瘠,我却无意在浏览器搜索中,发现了一系列常见功能的插件封装:weex.apache.org/zh/biz-comp… ,截图如下,但是这些插件并没有提供出来使用,存在 Weex 官网中,但是却没有访问入口。如果这些插件功能能提供使用,无疑将很大程度丰富 Weex 的生态。
6.5、三端兼容性不好
Weex 号称 “一次撰写,多端运行”,但是存在很多兼容性问题,比如我们在 Web 端调试开完后一个功能模块,但是在 Android 端一运行,就各种跑不通,各种兼容性问题;这种问题导致,我们后期根本不敢在 Web 环境开发,例如:我们这个项目是想开发个 Android 的 app,我们最终都直接在 Android 环境下开发,这种效率肯定就没有在 Web 环境开发效率高。
6.6、Vue 支持度不够
Weex 默认集成 Vue 框架,而且主打 Vue 受众,但是 Weex 对 Vue 的支持度还不够,除了官网上提到的那些 vue 特性不支持外,还有很多特性没有被列出,例如:vuex 等。
参考:
1,Weex实战项目链接
2,《WEEX跨平台开发实战》出版啦
3, Weex开发之地图篇
4, Weex Eros快速入门
5, WEEX环境搭建与入门
6, Weex开发之WEEX-EROS开发踩坑
7, 移动跨平台技术方案总结