1.10.3 • Published 5 years ago

hybrid-cli v1.10.3

Weekly downloads
-
License
MIT
Repository
-
Last release
5 years ago

一、Hybrid-Cli简介

1、概述

Hybrid-Cli是一套前端使用的打包脚本,可以用来将node项目打包成Android apk。本脚本必须配合指定的混合版Android项目一起使用。

2、引入

使用npm下载即可,暂时不支持yarn。

npm install -g hybrid-cli

3、命令

Hybrid-Cli包含一套命令,帮助前端项目打包使用。

命令含义
hybrid run build打包Android apk(release版和debug版都会打出)
hybrid run device打包Android apk并在打包之后,运行到手机上
hybrid run release打包Android apk(release版和debug版都会打出)
hybrid platform add重新下载Android项目
hybrid platform rm删除Android项目
hybrid gen resource生成资源文件夹
hybrid gen config生成配置文件
hybrid gen mtijs生成mti.js插件脚本

4、配置

前端项目需要引入hybrid.json配置文件,并进行适当的配置。

{
    "AndroidGitUrl": "", // Android项目的Git地址
    "WebUrl": "backup/index.html", // 前端首页访问地址
    "WebTitle": true, // Android项目是否显示ActionBar
    "WebTitleText": "MTI", // Android项目的ActionBar的文字内容
    "SDK": "", // SDK路径
    "Gradle": "",  // Gradle路径
    "WebProjectDist": "dist", // 前端项目打包后,前端包相对于前端项目根目录的相对路径
    "KeyStore": "", // KeyStore文件
    "KeyAlias": "", // KeyStore别名
    "KeyPassword": "", // Key密码
    "StorePassword": "", // Store密码
    "AppId": "", // 打包apk的应用ID
    "AppName": "" // 打包apk的应用名称
}

二、插件

1、插件使用指南

1.1、拍照

调用示例

mti.takePhoto().then(data => {
    console.log(data);
}).catch(e => {
    console.log(e);
})

调用参数

返回内容

// 成功示例:
{
    compressUrl:"/storage/emulated/0/mti/compress/20190618-080235.jpg", // 压缩图地址
    isUpload:false, // 是否已经上传
    localUrl:"/storage/emulated/0/mti/picture/20190618-080224.jpg" // 原图地址
}
// 失败示例:
"canceled"

1.2、录音

调用示例

mti.takeAudio().then(data => {
    console.log(data);
}).catch(e => {
    console.log(e);
})

调用参数

返回内容

// 成功示例:
{
    duration:1, // 音频时长
    isUpload:false, // 是否已经上传
    localUrl:"/storage/emulated/0/mti/audio/20190618-081044.amr" // 音频地址
}
// 失败示例:
"canceled"

1.3、视频

调用示例

mti.takeVideo().then(data => {
    console.log(data);
}).catch(e => {
    console.log(e);
})

调用参数

返回内容

// 成功示例:
{
    compressUrl:"/storage/emulated/0/mti/compress/20190618-081932.jpg", // 视频首帧压缩图
    isUpload:false, // 是否已经上传
    localUrl:"/storage/emulated/0/mti/video/20190618-081926.mp4" // 视频地址
}
// 失败示例:
"canceled"

1.4、语音转文字

调用示例

mti.speechToWord().then(data => {
    console.log(data);
}).catch(e => {
    console.log(e);
})

调用参数

返回内容

// 成功示例:
"今晚去哪吃饭?"
// 失败示例:
"canceled"

1.5、定位

调用示例

mti.lastLocation().then(data => {
    console.log(data);
}).catch(e => {
    console.log(e);
})

调用参数

返回内容

// 成功示例:
"121.03965,29.29768" // 格式:经度在前、纬度在后、中间用英文逗号分隔
// 失败示例:
"no location"

1.6、文件上传

调用示例

mti.fileUpload({
    uploadFile: '/storage/emulated/0/mti/download/node-v10.15.3-x64.msi',
    uploadPath: 'http://10.168.6.246:8080/Web/rest/file',
    uploadData: '1'
}).then(data => {
    console.log(data);
}).catch(e => {
    console.log(e);
})

调用参数

// 参数示例:
{
    uploadFile: "/storage/emulated/0/mti/download/node-v10.15.3-x64.msi", // 本地路径
    uploadPath: "http://10.168.6.246:8080/Web/rest/file", // 上传路径
    uploadData: "1" // 额外携带数据,字符串类型
}

注意:上述参数会转换为Multipart的格式上传到指定后台。文件Key值为:file,数据Key值为:data。

返回内容

// 成功示例:
Object // 服务端的响应体对象
// 失败示例:
"404" // http请求响应的错误消息

1.7、文件下载

调用示例

mti.fileDownload({
    downloadPath: 'http://cdn.npm.taobao.org/dist/node/v10.15.3/node-v10.15.3-x64.msi'
}).then(data => {
    console.log(data);
}).catch(e => {
    console.log(e);
})

调用参数

// 参数示例:
{
    downloadPath: 'http://cdn.npm.taobao.org/node-v10.15.3-x64.msi' // 请求地址
}

返回内容

// 成功示例:
"/storage/emulated/0/mti/download/node-v10.15.3-x64.msi" // 下载完成后,文件的本地路径
// 失败示例:
"404" // http请求响应的错误消息

1.8、文件选择功能

html5支持通过input标签来选择文件,在移动端也对该功能增加了支持。

调用示例

<!-- 选择本地图片并在img标签中显示 -->
<input type="file" accept="image/*" capture="camera" onchange="image(this.files)">
<img id="file-image" style="width: 100px; height: 100px;">
<script type="text/javascript">
function image(files) {
    var fileImage = document.getElementById('file-image')
    fileImage.src = URL.createObjectURL(files[0]);
}
</script>
<!-- 选择本地视频并在video标签中显示 -->
<input type="file" accept="video/*" capture="camcorder" onchange="video(this.files)">
<video id="file-video" style="width: 100px; height: 100px;"></video>
<script type="text/javascript">
function video(files) {
    var fileVideo = document.getElementById('file-video')
    fileVideo.src = URL.createObjectURL(files[0]);
}
</script>
<!-- 选择本地音频并在audio标签中显示 -->
<input type="file" accept="audio/*" capture="microphone" onchange="audio(this.files)">
<audio id="file-audio" controls>/audio>
<script type="text/javascript">
function audio(files) {
    var fileAudio = document.getElementById('file-audio')
    fileAudio.src = URL.createObjectURL(files[0]);
}
</script>

1.9、注册Websocket监听

通过本插件可以向Android项目中的长连接注册消息。

调用示例

mti.registerWebsocket('a', 'handler', (data) => {
	console.log(data);
})

调用参数

// 参数示例:
messageType // 注册的消息类型名称
callbackName // Js回调函数的名称
callback // 接收消息的Js回调函数

返回内容

// 成功示例:
{ "messageType": "a", "messageData": "" }

2、资源调用指南

在上述插件中,涉及到拍照、录音、视频的插件,返回到前端的内容是本地路径,类似如下:

{
    compressUrl:"/storage/emulated/0/mti/compress/20190618-081932.jpg", // 视频首帧压缩图
    localUrl:"/storage/emulated/0/mti/video/20190618-081926.mp4" // 视频地址
}

有时,前端可能有这样的需求:拍照之后,将本地图片在标签中展示。这个需求有两种实现可能:

  • 源站点是file://头协议的站点(使用hybrid-cli打包属于file://头协议的源站点)。在绝对路径前增加file://协议头即可访问。
<img src="file:///storage/emulated/0/mti/compress/20190618-081932.jpg">
<img src="http://mti-media/emulated/0/mti/compress/20190618-081932.jpg">

其他文件类型(视频、音频)也是上述的实现方式。但是需要注意,如果使用等标签显示文件,需要确定这些标签是否支持相应的文件类型。

3、插件开发指南

本节前端开发者不需要关心,Android端开发者需要注意,开发新的插件的步骤如下:

  • 第一步,自定义插件继承BridgePlugin类。重写构造方法,为插件的BridgeContext上下文赋值。重写handler方法,实现插件功能。
public class TakePhotoPlugin extends BridgePlugin {
    public TakePhotoPlugin(BridgeContext context) {
        this.context = context;
    }
    @Override
    public void handler(String data, CallBackFunction function) {
        // 在此处实现插件的功能
    }
}
  • 第二步,在BridgePluginMap类中,配置插件。
public class BridgePluginMap {
    // 插件名,js端调用时使用的就是这个插件名
    public final static String TAKE_PHOTO = "takePhoto";
    private Map<String, Class> pluginMap;
    // 存储所有的插件的类对象
    private BridgePluginMap() {
        pluginMap = new ArrayMap<>();
        pluginMap.put(BridgePluginMap.TAKE_PHOTO, TakePhotoPlugin.class);
    }
}
  • 第三部,修改mti.js文件中调用插件的方法。
// 调用android中的拍照插件
Plugins.prototype.takePhoto = function () {
    return new Promise(function (resolve, reject) {
        WebViewJavascriptBridge.callHandler('takePhoto', '',
            function (responseData) {
                console.log(responseData);
                let result = JSON.parse(responseData);
                if (result.ret == 200) {
                    resolve(result.data);
                } else {
                    reject(result.message);
                }
            }
        );
    })
}
  • 第四步,如果插件中需要处理Android权限问题,可以使用@RequestPermission注解请求权限,然后实现PermissionInterface接口,获取请求权限的结果。注意,在没有获得权限之前,注解方法之后的逻辑都不会执行。
public class TakePhotoPlugin extends BridgePlugin implements PermissionInterface {
    public CallBackFunction mCallBackFunction;
    public TakePhotoPlugin(BridgeContext context) {
        this.context = context;
    }
    @Override
    public void handler(String data, CallBackFunction function) {
        this.mCallBackFunction = function;
        takePhoto();
    }
    // 调用系统相机拍照
    @RequestPermission(permission = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE})
    public void takePhoto() {}
    // 插件权限请求结果
    @Override
    public void requestResult(boolean result) {
        if (!result) {
            Log.e("tag", "拒绝存储权限,拍照功能不可用!");
            return;
        }
        takePhoto();
    }
}
  • 第五步,如果插件中使用了startActivityForResult方法,所使用的请求码建议统一到AppRequestCode类中定义,并遵循如下规则。
/**
 * 项目中用到的所有RequestCode都在此处定义
 * 这样统一定义的好处是,方便对来自不同上下文环境的请求进行分流
 */
public class AppRequestCode {
    /**
     * Activity请求使用到的请求码(规则是:100<code<1000)
     */
    public final static int BOUND_OF_ACTIVITY_REQUEST = 100;
    //文件选择
    public final static int ACTIVITY_REQUEST_FILE_CHOOSER = 101;
    /**
     * 插件请求使用到的请求码(规则是:1000<code<10000)
     */
    public final static int BOUND_OF_PLUGIN_REQUEST = 1000;
    //插件的拍照请求
    public final static int PLUGIN_REQUEST_TAKE_PHOTO = 1001;
    //插件的录音请求
    public final static int PLUGIN_REQUEST_TAKE_AUDIO = 1002;
    //插件的视频请求
    public final static int PLUGIN_REQUEST_TAKE_VIDEO = 1003;
    /**
     * 权限请求使用到的请求码(规则是:code>10000)
     */
    public final static int BOUND_OF_PERMISSION_REQUEST = 10000;
    //Activity中请求权限
    public final static int PERMISSION_REQUEST_FOR_ACTIVITY = 10001;
    //Plugin中请求权限
    public final static int PERMISSION_REQUEST_FOR_PLUGIN = 10002;
}

4、注意事项

在使用Vue等类似框架时,请不要在created、mounted等生命周期方法中,调用插件方法。原因是:在执行created、mounted等生命周期方法时,可能插件依赖的JsBridge还没有初始化完成,导致插件调用不成功。所以,现在提供window上的全局事件,通过监听这个事件,并在这个事件触发时,执行调用插件的逻辑。

window.addEventListener('nativeReady', event => {
  mti.registerWebsocket('a', 'handler', (data) => {
	console.log(data);
  });
})

三、热更新

1、概述

本项目的Android部分提供了三种更新方式:

  • Apk更新
  • Tinker补丁热更新
  • 前端资源包单独更新

有关热更新的配置同样位于hybrid.json的配置文件中,所有配置如下:

配置含义
AppVersionCode应用版本号
AppVersionName应用版本名称
TinkerVersionCode补丁版本号
WebVersionCode前端版本号
VersionControlUrl更新接口地址
TinkerEnable是否开启Tinker编译
TinkerBaseApkTinker编译补丁的基准包绝对路径

热更新依赖VersionControlUrl配置更新接口的地址,这是在Android项目中已经写好的HTTP请求,请求类型是GET,接收的返回值类型是application/json。这个地址代表的可以是某个服务端的json文件或者接口,这个可以根据各自项目来决定,但是返回的参数类型是已经被定义好的,也就是说这个接口必须返回如下类型的参数:

{
    "patchVersion": 3,
    "patchPath": "http://localhost:8080/test/patch_signed_7zip.apk",
    "webVersion": 3,
    "webPath": "http://localhost:8080/test/dist.zip",
    "apkVersion": 2,
    "apkPath": "http://localhost:8080/test/update-apk.apk"
}

2、APK更新

当客户端请求到上述的更新响应值时,App会判断apkVersion,如果大于App自身的VersionCode,则会提醒用户进行版本更新。当用户同意版本更新,则会通过apkPath下载最新的apk并安装。

3、补丁更新

当客户端请求到上述的更新响应值时,App同样会判断patchVersion,如果大于App当前的补丁版本号,那么App会自动通过patchPath下载并自动热更新。

打包方式

补丁的生成和编译依赖tinker的命令。首先,需要将TinkerEnable配置为true(在平时开发过程中,最好不要开启这个配置,因为会生成大量的编译缓存apk)。然后,配置TinkerBaseApk为基准包的绝对路径(基准包是指当前的补丁是针对哪个版本的APK,比如:基准包为A.apk,Android项目修改之后,基于A.apk生成补丁,这个补丁会运行在A.apk对应版本的App上)。接下来,在命令行中输入如下命令:

hybrid run tinker

补丁生成完成后,生成结果路径为platform/baseframework/release/tinkerPatch/hybrid/release/patch_signed_7zip.apk,将这个apk补丁放到服务器相应的目录等待用户下载即可。该文件名暂不支持修改,否则客户端无法读取到补丁。

适用场景

补丁式热更新支持Android项目的热更新,同时也支持前端资源的热更新。更新的过程也比较方便,但是缺点就是在打包时需要提供基准包。

4、前端更新

当客户端请求到上述的更新响应值时,App同样会判断webVersion,如果大于App当前的前端版本号,那么App会自动通过webPath下载并自动热更新。

打包方式

将前端资源打包压缩成zip,放到服务器相应目录即可。但注意压缩包中不可嵌套多余目录,并且解压后的目录名称和结构要和WebUrl保持一致。

适用场景

前端热更新只支持前端资源的热更新,如果遇到只需要更新资源的场景,可以使用这种方式。但是这个方式的缺点是,热更新的资源缓存在本地,有可能通过删除数据的方式被用户误删,所以持久性比较差,推荐调试或小版本更新使用。

四、长连接

1、使用步骤

本项目的Android部分为前端提供了Websocket长连接。使用步骤如下:

  • 首先,在hybrid.json中配置Websocket的地址。
"WebsocketUrl": "ws://192.168.1.5:8081/ws"
  • 然后,调用插件注册Websocket消息的监听,参考第二章的1.9小节。注册之后即可监听Websocket消息。

2、服务端消息格式

服务端在推送消息时,需要遵守如下的消息格式:

{ "messageType": "a", "messageData": "" }

Android客户端在收到上述消息时,会根据messageType来判断消息类型,从而分发到指定的Js监听回调函数中。

1.10.3

5 years ago

1.10.2

5 years ago

1.10.1

5 years ago

1.10.0

5 years ago

1.9.12

5 years ago

1.9.11

5 years ago

1.9.10

5 years ago

1.9.9

5 years ago

1.9.8

5 years ago

1.9.7

5 years ago

1.9.6

5 years ago

1.9.5

5 years ago

1.9.4

5 years ago

1.9.3

5 years ago

1.9.2

5 years ago

1.9.1

5 years ago

1.9.0

5 years ago

1.8.4

5 years ago

1.8.3

5 years ago

1.8.2

5 years ago

1.8.1

5 years ago

1.8.0

5 years ago

1.7.0

5 years ago

1.6.7

5 years ago

1.6.6

5 years ago

1.6.5

5 years ago

1.6.4

5 years ago

1.6.3

5 years ago

1.6.2

5 years ago

1.6.1

5 years ago

1.6.0

5 years ago

1.5.11

5 years ago

1.5.10

5 years ago

1.5.9

5 years ago

1.5.8

5 years ago

1.5.7

5 years ago

1.5.6

5 years ago

1.5.5

5 years ago

1.5.4

5 years ago

1.5.3

5 years ago

1.5.2

5 years ago

1.5.1

5 years ago

1.5.0

5 years ago

1.4.7

5 years ago

1.4.6

5 years ago

1.4.5

5 years ago

1.4.4

5 years ago

1.4.3

5 years ago

1.4.2

5 years ago

1.4.1

5 years ago

1.4.0

5 years ago

1.3.2

5 years ago

1.3.1

5 years ago

1.3.0

5 years ago

1.2.0

5 years ago

1.1.1

5 years ago

1.1.0

5 years ago

1.0.0

5 years ago