0
点赞
收藏
分享

微信扫一扫

CodePush热更新之Cordova项目实践

未定义变量 2021-09-23 阅读 34

CodePush

微软开发的,可以实时更新 React Native 和 Cordova 应用,给 React Native 和 Cordova 开发者直接部署移动应用更新给用户设备的云服务。

作为一个云仓库,作为开发者可以直接推送更新到 JS, HTML, CSS and images,应用可以从客户端 SDKs 里面查询更新。

可以让我们在修复一些小问题和添加新特性的时候,不需要经过二进制打包,可以直接推送代码进行实时更新。

CodePush 推送代码更新:

  • 直接对用户部署代码更新 管理 Alpha,Beta 和生产环境应用
  • 支持 Cordova 和 React Native
  • CodePush提供两个客户端 SDKs(Cordova 和React Native)
  • 相关插件cordova-plugin-code-push
  • 另一个热更新的插件,但是已经在2018.09.30宣布不再维护了,可以了解下(cordova-hot-code-push)
  • 苹果App允许使用热更新Apple's developer agreement, 为了不影响用户体验,规定必须使用静默更新。 Google Play不能使用静默更新,必须弹框告知用户App有更新。中国的android市场必须采用静默更新(如果弹框提示,App会被“请上传最新版本的二进制应用包”原因驳回)。

准备环境

全局安装typescript、typings、tslint、code-push-cli、cordova

npm install -g typescript
npm install -g typings
npm install -g tslint
npm install -g cordova
npm install -g code-push-cli

注:code-push-cli是连接热更新服务端的工具,我们把要更新的代码上传至服务端,客户端安装cordova-plugin-code-push插件来从服务端下载代码.

注册登录CodePush服务器

CodePush终端安装完成后就可以使用code-push命令了。
在终端输入code-push registercode-push login(有账号),会跳转授权网页。在这个网页可以选择Github。或者微软作为授权提供者,不过我觉得90%的开发者都会选择Github。


授权完成后,CodePush会显示你的Access Key,复制输入到终端即可完成注册并登陆。


若选择CodePush Server 私有化部署,则只需登录自己的code push Server的url,code-push login url,然后输入运维给你的Access Key即可。

在CodePush服务器中创建App

在终端输入code-push app add <appName>即可完成创建,注册完成之后会返回一套deployment key,包括Staging和Production,分别代表开发和生产环境


添加完成后,可以登录https://appcenter.ms/apps查看创建的MyApp.

创建Cordova项目并整合CodePush

创建项目

添加平台

添加插件

在config.xml中配置deployment keys:
当在code-push服务器添加app应用目录时,会生成生产和开发的key,需要配置在这里,app通过此key更新

若不知道上面的key,可以用命令查看:

config.xml中配置访问权限:

index.html配置白名单安全协议:

更新config.xml后重新 build/prepare 项目,相关CLI命令如下:

  • Help:显示可用CLI命令的信息。
  • Create:创建Cordova项目并关联项目文件夹和文件。
  • Plateform:管理Cordova项目使用的移动平台。
  • Plugin:管理Cordova插件的安装和卸载。
  • Prepare:从Cordova项目的www文件夹复制web应用内容到项目移动平台项目文件夹中。
  • Compile:把web应用打包成Cordova应用。
  • Build:先执行Prepare命令然后打包web应用。
  • Emulate:在一个或多个移动设备平台的设备模拟器中运行Cordova应用。
  • Run:在一个或多个移动设备中运行Cordova应用。
  • Serve:启动一个服务器加载web内容以便于用浏览器访问。

在app启动的时候调用code-push同步方法

var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        /* Invoke sync with the custom options, which enables user interaction.
           For customizing the sync behavior, see SyncOptions in the CodePush documentation. */
        window.codePush.sync(
            function (syncStatus) {
                switch (syncStatus) {
                    // Result (final) statuses
                    case SyncStatus.UPDATE_INSTALLED:
                        app.displayMessage("The update was installed successfully. For InstallMode.ON_NEXT_RESTART, the changes will be visible after application restart. ");
                        break;
                    case SyncStatus.UP_TO_DATE:
                        app.displayMessage("The application is up to date.");
                        break;
                    case SyncStatus.UPDATE_IGNORED:
                        app.displayMessage("The user decided not to install the optional update.");
                        break;
                    case SyncStatus.ERROR:
                        app.displayMessage("An error occured while checking for updates");
                        break;
                    
                    // Intermediate (non final) statuses
                    case SyncStatus.CHECKING_FOR_UPDATE:
                        console.log("Checking for update.");
                        break;
                    case SyncStatus.AWAITING_USER_ACTION:
                        console.log("Alerting user.");
                        break;
                    case SyncStatus.DOWNLOADING_PACKAGE:
                        console.log("Downloading package.");
                        break;
                    case SyncStatus.INSTALLING_UPDATE:
                        console.log("Installing update");
                        break;
                }
            },
            {
                installMode: InstallMode.ON_NEXT_RESTART, updateDialog: true
            },
            function (downloadProgress) {
                console.log("Downloading " + downloadProgress.receivedBytes + " of " + downloadProgress.totalBytes + " bytes.");
            });
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        var parentElement = document.getElementById(id);
        var listeningElement = parentElement.querySelector('.listening');
        var receivedElement = parentElement.querySelector('.received');

        listeningElement.setAttribute('style', 'display:none;');
        receivedElement.setAttribute('style', 'display:block;');

        console.log('Received Event: ' + id);
    },
      // Displays an alert dialog containing a message.
    displayMessage: function (message) {
        navigator.notification.alert(
            message,
            null,
            'CodePush',
            'OK');
    }
};

app.initialize();

发布更新

先添加应用到code-push,如code-push app add MyApp android cordova,注意配置生成的key.
使用命令code-push deployment list <appName>查看发布状态
如下图可以看到目前android应用"Production"和"Staging"两种部署状态

使用命令code-push release-cordova <appName> <platform> [options]发布更新,如下图所示.


发布完成后再看就有相关信息了,如下:


然后启动app就可以连接到服务器并检查更新,若有则自动更新。
注意:这里有2个版本,主版本app:1.0.0(在config.xml中找到),子版本v1(每次修改www代码提交都会变),若修改主版本则会同时改变。

调试技巧

使用android studio导入工程,然后使用真机联调,可以看到完整的日志输出,方便定位问题。
注意:修改cordova代码,需要同步到platform,执行cordova prepare [platform]cordova prepare,否则打包的app代码不是最新的。

code-push常用命令

//给app在热更新服务器上创建应用
code-push app add <appName> <os> <platform> 

//删除应用
code-push app rm <appName>

//查看热更新服务器上有哪些应用
code-push app list

//发布应用
code-push release-cordova <appName> <platform> [options]
 Options参数:
  --deploymentName, -d ..指定部署的类型.默认"Staging",可以选择"Production"或其他  自定义类型
  --description, --des ..添加描述
  --mandatory, -m .......指定此版本是否为强制更新版本
  例1:发布更新
  code-push release-cordova ionic2_tabs_android android --des ""
  例2:部署"Production"状态的更新,即生产环境的热更新部署使用这句命令
  code-push release-cordova ionic2_tabs_android android  -d "Production" --des ""
  注意:一般生产环境的app是压缩过的,所以在发布正式环境热更新之前,先执行"ionic build --prod"压缩代码
  例3:部署ios应用的更新
  code-push release-cordova ionic2_tabs_ios ios --des ""
  例4:添加-m参数强制更新,code-push插件从服务端下载完代码,会立即自动重启app
  code-push release-cordova ionic2_tabs_android android  -m --des ""

//查看部署状态
code-push deployment list <appName>
  例1:
  code-push deployment list ionic2_tabs_android
  例2:查看部署状态及key值,忘记key就这样找
  code-push deployment list ionic2_tabs_android -k

//清空部署记录
code-push deployment clear <appName> <deploymentName>
如:清空Staging状态的部署记录
code-push deployment clear ionic2_tabs_android Staging

//添加部署状态,默认只有"Staging"和"Production"两中状态
code-push deployment add <appName> [deploymentName]

//删除自定义的部署状态
code-push deployment rm <appName> <deploymentName>

其它

  • 官方文档教程中,要给index.html添加如下<meta>,只是增加安全性
<meta http-equiv="Content-Security-Policy" content="default-src https://codepush.azurewebsites.net 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *" />

安全规则:若从图片库取图片,需要加content: 从远程取则加 http://ip:port

<meta http-equiv="Content-Security-Policy" content="default-src http://192.168.102.253:3000 'self' data: gap: content: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *" />

使用zeptor或ajax

移动端开发一般使用zeptor代替jq,注意白明单规则,即只能请求白明单中的源,如下

<script type="text/javascript" src="js/zepto.js"></script>
...
receivedEvent: function(id) {
        var parentElement = document.getElementById(id);
        var listeningElement = parentElement.querySelector('.listening');
        var receivedElement = parentElement.querySelector('.received');
        var btn = parentElement.querySelector('#btn');

        listeningElement.setAttribute('style', 'display:none;');
        receivedElement.setAttribute('style', 'display:block;');
        var self = this;
        btn.addEventListener('click', function(){
            /*let xhr = new XMLHttpRequest();
            xhr.open('GET', 'http://192.168.102.253:3000/updateCheck?deploymentKey=ufwYib6UBu0tl2NlS2jRxMYsL6DT4ksvOXqog&appVersion=1.0.0&packageHash=066dc68c79dacb16da8dd97a10fa45d681927cd480a7b576b7eb5daa8dc87187&isCompanion=false&label=v15&clientUniqueId=2b42c07102b21ac3', true);
            xhr.onload=function(){
                app.print(xhr.response)
            }
            xhr.send();*/
            $.get('http://192.168.102.253:3000/updateCheck?deploymentKey=ufwYib6UBu0tl2NlS2jRxMYsL6DT4ksvOXqog&appVersion=1.0.0&packageHash=066dc68c79dacb16da8dd97a10fa45d681927cd480a7b576b7eb5daa8dc87187&isCompanion=false&label=v15&clientUniqueId=2b42c07102b21ac3', function(response){
                app.print(response)
            })
        })

        console.log('Received Event: ' + id);
    }

与Vue项目整合

在main.js中配置环境变量控制,如只在生产环境才需要使用cordova,如下

if(process.env.NODE_ENV === "development") {
    new Vue({
        el: '#app',
        store,
        router,
        components: { App },
        template: '<App/>'
      })
  } else {
  // 生产环境需要确保cordova加载完成
  console.log('deviceready start')
  document.addEventListener('deviceready', function() {
    console.log('deviceready end')
    new Vue({
          el: '#app',
          store,
          router,
          components: { App },
          template: '<App/>'
        })
      // new Promise(function(resolve) {
      //   ExpressPlugin.getAppReleaseVersion(function(releaseVersion) {
      //       try {
      //         var len = releaseVersion.indexOf("V");
      //         var numArr = releaseVersion.substr(len + 1).split(".");
      //         if((Number(numArr[0]) >= 4) || (Number(numArr[0]) == 3 && Number(numArr[1]) >= 5)) {
      //           window.showHeader = false;
      //         } else {
      //           window.showHeader = true;
      //         }
      //       } catch(e) {
      //         window.showHeader = true;
      //       }
      //       resolve();
      //     },
      //     function() {
      //       resolve();
      //     })
      // }).then(() => {
      //   new Vue({
      //     el: '#app',
      //     store,
      //     router,
      //     components: { App },
      //     template: '<App/>'
      //   })
      // })
  }, false);
  }

这样开发完成时,可使用npm run build打包vue,再将build之后的内容放到cordova工程www目录运行即可。
注意,打包CLI需配置publicPath为'./',默认为'/'则必须通过server方式访问。

移动端开发调试工具

  • vconsole 该工具方便在移动端查看web的数据请求,即移动端的控制台。
  • android studio 该工具方便整合cordova,因cordova app运行日志都在该工具输出。

下面看下vconsole简单配置,以下是在vue工程中main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import VConsole from 'vconsole'
// 最好加上process.env.NODE_ENV控制,只在移动端才显示
new VConsole()  //会在屏幕右下角生成一个按钮,点击弹出控制台

if(process.env.NODE_ENV === "development") {
  new Vue({
    router,
    render: h => h(App)
  }).$mount('#app')
} else {
  // 生产环境需要确保cordova加载完成
  console.log('deviceready start')
  document.addEventListener('deviceready', function() {
    console.log('deviceready end')
    new Vue({
      router,
      render: h => h(App)
    }).$mount('#app')
  }, false);
}

运行结果如下:



  • 关于苹果 App Store禁止热更新的说明
    目前还没有全面禁止热更新,收到警告邮件的开发者绝大部分是使用了 JS-Patch 或 Rollout 的热更新类库

参考文档:

  1. CodePush 官方文档
  2. code-push-server
  3. https://github.com/lisong/code-push-server
  4. https://github.com/lisong/code-push-web
  5. Cordova Client SDK
举报

相关推荐

0 条评论