fengshi123 / blog

汇总发布的前端博文,大家一起交流学习,如果有帮助到您,欢迎 star ~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Weex 实践总结(vue 技术栈的童鞋跨平台开发首选吗?)

fengshi123 opened this issue · comments

前言

基于 Vue 技术栈的你如果需要选用一种移动端跨平台框架,是 Weex?React-Native?还是Flutter? 无疑,相对于后两者,因为你现在已有比较熟练的 Vue 基础,如果在其他条件一致的情况,Weex 无疑是最佳选择;但是 Weex 真的适合在实际项目中进行移动端跨平台开发吗?Weex 的开发效率、Weex 的质量是否满足需求?

本项目围绕前面提到的两点:基于 Weex 的开发效率如何?Weex 的质量是否满足需求?我们进行了相关的预研和开发,我们将在开发中遇到的问题和经验进行分享,如果你还没有 Weex 开发经验,那么这篇文章将很好的向你展示 Weex 的各方面,官方文档、生态、兼容性等等,希望你在这篇文章中找到你想要的答案。

辛苦整理良久,还望手动点赞鼓励~

博客 github地址为:https://github.com/fengshi123/blog ,汇总了作者的所有博客,也欢迎关注及 star ~

本项目 github 地址为:https://github.com/fengshi123/weex_project

一、开发环境

在这个 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_project3
npm install

(4)启动项目

npm start

项目启动完毕,浏览器窗口会自动打开项目首页,如下图所示:

1.png

(5)添加 Android 平台

weex platform add android

(6)可以运行下面的命令,可以在模拟器或真实设备上启动 Android 应用:

weex run android

2.3、运行我们的项目

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 第三方插件

VSCode 拓展包包含下面的包:

  • weex-new-project - 用于在 VSCode 中创建Weex项目;
  • weex-lang - 用于在 VSCode 中对最新的 Weex 语法进行支持;
  • weex-doctor - 用于检查 iOS 和 Android 本地开发环境;
  • weex-debugger - 用于在 VSCode 中启动Weex调试工具;
  • weex-run - 用于在热更新模式下启动 Android 及 iOS 工程;

我们主要介绍最好用的 weex-run 和 weex-debug,因为 weex-run 其可用于在热更新模式下启动 Android 及 iOS 工程;weex-debug 可用于安卓端的调试。其它的插件使用,可以查看 Weex 官网 VS Code 插件部分 ,下面我们分别介绍 weex-run 和 weex-debug:

3.1、weex-run 的使用

(1)通过截图的步骤来安装 weex-run

1.png

1.png

(2)启动 Android 项目

1.png

104

启动成功控制台输出(启动需要一定时间,如果没有报错,敬请耐心等待):

1.png

我们查看 Android 项目的热更新:

1.png

3.2、weex-debugger 的使用

(1)安装 weex-debugger 插件

1.png

(2)ctrl + shift + p 弹出命令输入框,如下图所示输入:weex debug,然后网页会出现第 2 张截图的二维码:

1.png

1.png

(3)用手机的 Weex Playground App 的二维码进行扫描,出现以下调试页面(一定一定要注意,手机连的 WiFi 和 你开发本地网络在同一局域网)。

1.png

(4)再用手机的 Weex Playground App 的二维码扫描 Weex 应用的二维码,调试页面就会变成对应的 Weex 应用的调试页面,如下图所示:

1.png

四、Weex 项目介绍

4.1、项目目录路径

Weex 项目的目录结构如下:

1.png

4.2、功能模块设计

考虑到更好的体验 Weex 和 H5 在开发效率、功能性能、用户体验等方面的差异性,我们对功能模块进行精心设计,主要基于我们现在实际项目的业务,结合移动端特有的特性。相关的模块功能设计如下图所示,其中红色标注部分表示,受限于开发资源、Weex 生态方面原因,我们暂时还没完成的功能。

1.png

4.3、功能界面展示

我们截取一些功能界面展示如下:

1.png

1.png

4.4、重要功能介绍

我们不介绍这个项目全部功能的实现,其它常规的功能开发,参照 Weex 官网即可,以下介绍的几个功能在 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 ,从而允许其对服务端的数据访问。

1.png

4.4.2、图片选择/上传功能

(1)存在问题

很遗憾,Weex 竟然没有提供文件选择/上传的模块,对于前端开发者来说无疑晴天霹雳,那我不是要手动去写 Android 的 java 代码,经过反复查找,真的没有文件选择/上传模块,于是我们只能自己去写 Java 代码去实现 Android 端图片选择以及上传功能。

(2)实现 Android 原生的图片选择/上传功能

weex_project\platforms\android\app\src\main\java\com\weex\app\extend 目录下新建 图片上传 模块的类 WXAlbumModule ,其继承 WXModule ,其主要两个方法为 choosePhoto 和 onActivityResult ,其中 choosePhoto 用于给 Weex 前端来调用,当 Weex 前端需要选择相册中的图片时,Weex 前端就调用 choosePhoto 方法;onActivityResult 是用户选择好相册中的图片后,会相应触发该事件,并将用户选择的相片以参数形式传入 onActivityResult ,从而我们可以在 onActivityResult 中进行图片的上传逻辑,图片上传完成后,Android 端会在回调事件中通知前端,图片放置在服务端的目录路径,前端可以对应进行图片显示等操作。关键代码逻辑如下,如果如果对 Java 完全一无所知的同学可以先不看,懂 java 代码的建议结合项目代码来看,会更清晰。

@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 可参考:

好吧,根本没有封装 WebSocket 功能,那我就按官网给的参考来实现吧,于是,我点击前面两个参考链接,链接打开的页面根本不存在,报 404(官网出现这种问题,实在不应该啊)。网上谷歌搜索一圈,没有发现类似的问题,还是主要查看了这个给的 url 以及结合阿里将 weex 贡献给 Apache 维护这个事情,猜测是不是 Weex 捐给 Apache 维护,github 的库目录更改,但是官网对应的 url 地址没有做修改。经过查找,确实是这个问题,在旧库中以下目录找到官网提的:DefaultWebSocketAdapter.java 和 DefaultWebSocketAdapterFactor.java :

https://github.com/alibaba/weex/tree/master/android/commons/src/main/java/com/alibaba/weex/commons/adapter

(2)手动实现 WebSocket 功能

我们 在 weex_project\platforms\android\app\src\main\java\com\weex\app\adapter 目录底下创建 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 对象的工厂类
public class DefaultWebSocketAdapterFactory implements IWebSocketAdapterFactory {
    @Override
    public IWebSocketAdapter createWebSocketAdapter() {
        return new DefaultWebSocketAdapter();
    }
}

weex_project\platforms\android\app\src\main\java\com\weex\app\WXApplication.java 中初始化 Websocket :

WXSDKEngine.initialize(this,
        new InitConfig.Builder().setImgAdapter(new ImageAdapter()).                        setWebSocketAdapterFactory(new DefaultWebSocketAdapterFactory()).build()
);

在 Weex 的前端中使用 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\weex\app\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)打包步骤如下截图:

1.png

1.png

(2)我们点击选择 Create new

1.png

(3)生成 jks

1.png

(4)填写 key 的相关信息

1.png

(5)点击 OK 之后,可以看到如下信息已被自动填充

1.png

(6)点击 Next

1.png

(7)点击 Finish 后,会看到 Android Studio 底部显示正在打包

1.png

(8)打包完成,会看到 Android Studio 右下角会显示打包成功的提示

1.png

(9)查看打包好的 apk 文件

1.png

5.4、有 “.jks” 文件的打包

有 “.jks” 文件的打包 比 没有 ".jks" 文件的打包简单很多,直接点击 Choose existing... ,进行选择 .jks 文件,其它步骤跟没有 ".jks" 文件的打包一样,这里不再赘述。

1.png

六、Weex 开发存在的坑

6.1、官网经常无法访问

Weex 官网经常出现无法访问的情况,频率大概一周至少一次;这就很影响开发效率了,因为在开发过程中需要经常查看官网的写法、说明等,如果访问不了,则会造成一定程度的开发 block;

6.2、官网文档粗糙

Weex 官网的文档比较粗糙,如果没有比较好的前端和移动端原生开发知识储备的话,看官网的文档就很吃力了,官网很多讲解写的非常简单,都默认你同时熟练前端和移动端原生开发,而且同时有较好前端和移动端原生开发人员应该在业界还比较少吧;

6.3、生态贫瘠

Weex 生态是真的贫瘠,除了阿里自己出产的组件库 weex-ui 外,其它的相关插件几乎找不到,有也是少于100个 star 的,例如我在项目开始前设计的一些功能:拍照、图片选择上传、语音录入、通讯、定位、文件预览等等移动端的特有功能,都没有插件,都需要自己去写 Android 的原生代码,那这时就失去了利用框架提高开发效率的意义;生态跟 react-native 差的真不是一丁半点,而是根本不是一个量级;

6.4、是否两个 Weex 版本

结合上一点,坊间传闻:Weex 存在两个版本,一个版本是阿里内部使用的,一个是非阿里内部使用;这个传言无从验证,但是结合第2点说的 Weex 生态贫瘠,我却无意在浏览器搜索中,发现了一系列常见功能的插件封装:https://weex.apache.org/zh/biz-component/ ,截图如下,但是这些插件并没有提供出来使用,存在 Weex 官网中,但是却没有访问入口。如果这些插件功能能提供使用,无疑将很大程度丰富 Weex 的生态。

1.png

6.5、三端兼容性不好

Weex 号称 “一次撰写,多端运行”,但是存在很多兼容性问题,比如我们在 Web 端调试开完后一个功能模块,但是在 Android 端一运行,就各种跑不通,各种兼容性问题;这种问题导致,我们后期根本不敢在 Web 环境开发,例如:我们这个项目是想开发个 Android 的 app,我们最终都直接在 Android 环境下开发,这种效率肯定就没有在 Web 环境开发效率高。

6.6、Vue 支持度不够

Weex 默认集成 Vue 框架,而且主打 Vue 受众,但是 Weex 对 Vue 的支持度还不够,除了官网上提到的那些 vue 特性不支持外,还有很多特性没有被列出,例如:vuex 等。

七、总结

本文主要基于 Weex 框架的实践进行总结,分享了 Weex 理念、Weex 的 VSCode 的第三发插件、Weex 项目的功能介绍、Weex 项目编译以及 Weex 存在的一些问题,希望对完全阅读完的你有启发和帮助,如果有不足,欢迎批评、指正、交流!

预期 十一月份 会推出姊妹篇《react-native 实践总结》,敬请关注!

辛苦整理良久,还望手动点赞鼓励~

博客 github地址为:https://github.com/fengshi123/blog ,汇总了作者的所有博客,也欢迎关注及 star ~

本项目 github 地址为:https://github.com/fengshi123/weex_project