1. 程式人生 > >Cordova 代碼熱更新 - 簡書

Cordova 代碼熱更新 - 簡書

項目目錄 外部存儲 簡單 plugin 新增 port html cal llb

原文:Cordova 代碼熱更新 - 簡書

Cordova 代碼熱更新

[圖片上傳失敗...(image-a19be7-1521624289049)]

基於 Cordova 框架能將網頁應用 (js, html, css, 圖片等) 打包成 App。當 App 在應用商店上架後,如何快速更新是我們需要考慮的問題。??

  • 本地打包新版本 App 發布到應用商店,但這中發布流程耗費時間,尤其是 Apple Store
  • 應用加載網絡資源,這樣 App 展示的內容就可以保證是最新的,但當應用斷網時,應用就無法正常使用

我們能想的這兩種方式都存在的很大的弊端,不適合實際應用!

插件 Cordova Hot Code Push (CHCP)

插件 Cordova Hot Code Push 正是針對 Cordava 應用如何快速更新問題而提供的解決方案,可以自動更新 web 相關的靜態文件。

該插件提供了詳細的 wiki 文檔,請參考:wiki 文檔

App Store 支持麽

近日,蘋果App Store審核團隊向一些開發者下最後通牒:2017年6月12日之前移除所有熱更新相關代碼、框架或SDK,並重新提交版本。如果不作調整,App可能會從App Store下架。

蘋果應用商店已經禁止使用類似 JSPatch 等熱修復的框架或者SDK,那麽這個插件提供的代碼熱更新功能是否違法這一規定呢? ??
?? 答案是否定的!此插件提供的代碼熱更新是 web 靜態文件,蘋果是允許這一做法的。但有兩點值得註意:

  • ① 不能明顯告知用戶有新版本可用,詢問用戶是否需要更新到最新代碼。這一做法會使用戶產生困惑,這種更新方式和通過 App Store 更新有何區別。所以正確的做法是,在應用啟動的時候,下載和安裝熱更新代碼;或者在某個時機下載熱更新代碼,在應用下次打開時進行安裝
  • ② 如果通過此插件進行代碼熱更新後,應用功能發生巨大變化,譬如原來是一個計算器應用,代碼熱更新後,變成了一個音樂播放器,這種欺騙用戶的做法也是會被蘋果拒絕的

添加插件到項目中

1、下載插件
要求 Cordova 版本 5.0+

cordova plugin add cordova-hot-code-push-plugin --save

2、下載插件的命令行工具 cordova-hot-code-push-cli

npm install -g cordova-hot-code-push-cli

該命令行工具可幫助我們自動生成配置文件 chcp.jsonchcp.manifest,同時還提供了一些其他功能,詳細可參考其 README。

3、下載插件後,執行 cordova platform add ios 時可能會遇到如下報錯:

Error: Cannot find module ‘xml2js/lib/processors‘

參考 xml2js is not installed 解決方法很簡單:npm install xml2js

熱更新相關配置

Cordova Hot Code Push 下載安裝到項目中後,需要對其進行相關的配置才能讓其工作。

插件配置

Cordova Hot Code Push 熱更新插件需要兩個配置文件:

  • Application config:chcp.json 包含發布相關信息:熱更新代碼版本號,應用 native side 版本號等等
  • Content manifest:chcp.manifest 包含項目熱更新代碼(靜態)文件信息:文件名和文件哈希值

這兩個配置文件對於插件的運行缺一不可,前者描述了熱更新代碼的版本信息,後者提供了熱更新代碼文件的變更信息。借助 cordova-hot-code-push-cli 這個命令行工具可以輔助我們創建這兩個配置文件。

Application config

Application config holds information about the current release of the web project.

chcp.json 置於 www 目錄根目錄,例子如下:

{

  "name": "wps-*****",

  "content_url": "https://kss.ksyun.com/*****/*****/",

  "ios_identifier": "326CN*****",

  "android_identifier": "com.**********.*****.*****.*****.*****",

  "update": "resume",

  "release": "2017.06.07-16.30.20",

  "min_native_interface": 1

}

1、配置項
name 項目名稱
content_url web 項目文件在服務器上的存儲路徑(即 www 目錄在雲存儲中的目錄路徑),熱更新插件將此 URL 作為 base url,用於下載配置文件和項目更新文件(必需的配置項)
release 描述 web 項目版本號,每一次發布的版本號必須唯一(默認使用時間戳,格式為:yyyy.MM.dd-HH.mm.ss),插件是將版本號進行字符串相等比較來判斷是否存在新版本(必需的配置項)
min_native_interface

Minimum version of the native side that is required to run this web content

  • cordova 項目主要包含兩部分:web content 和 native side。前者是網頁內容,後者是 cordova 插件,為網頁提供原生 API 支持,web content 的運行是基於 native side。
  • 該配置項指明 web content 運行時 native side 的最低版本。在 native side 代碼有變更後(cordova 插件新增/刪除,native side 版本號更新),為了確保 web content 能正常運行,需要更新 min_native_interface 的值

在應用 config.xml 配置中可以定義了 native side 的版本號,例如

<chcp>

    <native-interface version="5" />

</chcp>

例如當前項目 native side 的版本號是5:

  • 如果服務器上配置文件 chcp.json 中的 min_native_interface 值為 5,那麽符合要求,熱更新後的 web content 能夠在正常運行
  • 如果服務器上配置文件 chcp.json 中的 min_native_interface 值為 10,高於 config.xml文件中 <native-interface />,那麽熱更新將無法正常進行。此時,插件會提示錯誤 chcp_updateLoadFailed,提示應用需要更新升級

update 何時觸發進行安裝(install)代碼熱更新
代碼熱更新涉及兩個主要過程:fetch update 和 install update。前者獲取熱更新變更文件,後者將獲取到的更新文件安裝到 App 中生效。此字段是針對後者,何時 install update,可選值:

  • start:應用啟動,默認項(install update when application is launched)
  • resume:應用從後臺恢復(install the update when application is resumed from background state)
  • now:下載更新後立即執行(install update as soon as it has been downloaded)

當然也可以禁用自動 install update,手動調用相關 API 進行 install
android_identifier / ios_identifier

  • android_identifier: Package name of the Android version of the application
  • ios_identifier: Identification number of the application
    用於跳轉到 Google Play Store 或者 App Store 該應用頁面

2、如何生成該文件:

  • 在 cordova 項目根目錄執行 cordova-hcp init ,會通過命令行交互的方式,提示輸入配置有關信息,創建該文件,會在項目根目錄創建一個默認 Application config 文件 cordova-hcp.json
  • 然後在每次應用打包時,再執行 cordova-hcp build 即可在 web 項目 www 根目錄生成一個 chcp.json 文件。

Content manifest

Content manifest describes the state of the files inside your web project.

通過執行 cordova-hcp buildwww 根目錄自動生成 chcp.manifest 文件

[

  {

    "file": "import.html",

    "hash": "fc9301d4bd7381ba6033aa51884ed2dd"

  },

  {

    "file": "index.html",

    "hash": "f73630f62a531ab6c41cd067eb4f9b07"

  },

  {

    "file": "lib/lib.min.js",

    "hash": "6ecb0251f4c54f80586d9059dfc61de8"

  },

  ...

]

chcp.manifest 文件中包含的是 web content 靜態文件信息,每一個項都包括兩個字段:

  • file: 相對於 www 目錄的文件路徑
  • hash: 文件的 MD5 哈希值,用於判斷文件是否發生變更

基於 chcp.manifest 文件

  • 在 fetch update 階段,從服務器上獲取新增、修改文件
  • 在 install update 階段,移除被刪除文件

Cordova config.xml 配置

Cordova 項目的 config.xml 文件用於設置項目配置選項,Cordova Hot Code Push 熱更新插件的配置項也需要在該文件中進行相應的配置。

<chcp>

    <config-file url="https://kss.ksyun.com/********/chcp.json" />

    <auto-download enabled="false" />

    <auto-install enabled="false" />

    <native-interface version="1" />

</chcp>

</pre>
  • config-file:配置文件 chcp.json 從服務器上加載的路徑(必須的配置項)
  • auto-download:是否自動下載熱更新代碼,默認是 true
  • auto-install:是否自動安裝熱更新代碼,默認是 true
  • native-interface:當前 native side 的版本號

可以禁用自動下載,安裝熱更新代碼,通過手動調用執行。

代碼熱更新原理

熱更新流程

[圖片上傳失敗...(image-e57e54-1521624148035)]

  • ① 應用啟動
  • ② 熱更新插件初始化,並在後臺加載更新模塊 (update loader)
  • ③ 更新模塊 (update loader) 從 Cordova 項目配置 config.xml 文件中獲取 config-file (熱更新插件配置文件 chcp.json 的加載路徑),然後加載配置文件 chcp.json,獲取其中的 release 版本號,對比當前的版本號,若二者不同,說明有新版本,執行下一步
  • ④ 更新模塊 (update loader) 從 chcp.json 配置文件中獲取 content_url 作為 base url,然後加載 chcp.manifest 文件,或者新版本文件變更信息
  • ⑤ 更新模塊 (update loader) 根據 content_url 作為 base url,下載所有變更、新增文件
  • ⑥ 如果一切順利, 更新模塊 (update loader) 發送通知,該更新已準備好進行安裝
  • ⑦ 安裝更新,應用重定向到新版本頁面

Cordova web project 存儲與更新

Cordova 項目中都包含一個 www 目錄,存儲網頁靜態文件,Cordova 打包移動應用時,會將其拷貝到各自的項目目錄,同時會被打包到應用中。

  • Android: platforms/android/assets/www.
  • iOS: platforms/ios/www.

www 目錄打包到應用中之後,我們就沒辦法對其進行更新了,因為只有可讀權限。為了解決這一問題,在應用第一次啟動的時候,從應用 bundle 中加載網頁內容的同時,將 www 目錄拷貝到外部目錄中,在後續應用啟動時,都從這個外部存儲的靜態文件中加載文件,而對於外部的這個存儲目錄,我們就有讀寫權限,這樣就為我們動態更新網頁代碼提供了可能。

在 safari 調試頁面執行 cordova.file.applicationStorageDirectory 可以得到應用的存儲路徑,點擊可以打開 Finder 目錄。
Library/Application Support 目錄下就可以找到存儲 web content 的外部目錄。
[圖片上傳失敗...(image-d0c218-1521624148035)]
[圖片上傳失敗...(image-5ff6fd-1521624148035)]

Cordova Hot Code Push 插件為每一個版本內容都創建了一個對應的目錄,以配置文件 chcp.jsonrelease 字段值為目錄名,存放不同版本 www 目錄中的靜態文件,這種處理方式的好處是:

  • 避免了文件緩存問題。例如 iOS UIWebView 緩存 css 文件,即使刷新頁面,也不會清除緩存,除非重啟應用才能強制清除緩存。不同版本置於不同的目錄,由於加載路徑不同,這樣就可以解決文件的緩存問題
  • 避免在更新代碼文件時,和當前已有文件出現沖突
  • 方便回滾到前一個版本

?? 下面了解一下,獲取更新內容和安裝更新內容時都發生了什麽?

1、獲取更新內容

  • 根據 release 版本號,創建一個新的目錄
  • 在新目錄中,創建 update 目錄,根據 chcp.manifest 文件,將所有變更、新增文件下載到該目錄中
  • 新版本對應的 chcp.jsonchcp.manifest 文件也會置於 update 目錄中

[圖片上傳失敗...(image-b49207-1521624148035)]

2、安裝更新內容

  • 將當前版本對應目錄下的 www 目錄拷貝到新版本對應的目錄下
  • 在新版本對應目錄下,將 update 目錄中變更、新增文件拷貝到 www 目錄中,同時根據 chcp.manifest 移除被刪除文件
  • 移除 update 目錄
  • 應用重定向到新版本目錄下加載網頁內容

插件 JS 接口

默認情況下,Cordova Hot Code Push (CHCP) 插件不需要額外的代碼,就可以自動執行 checking->downloading->installation 這個更新循環。當然也可以通過其提供的接口來控制這更新流程,這時,我們需要在項目 config.xml 文件中配置 auto-downloadauto-installfalse

<chcp>

    <config-file url="https://kss.ksyun.com/******/chcp.json" />

    <auto-download enabled="false" />

    <auto-install enabled="false" />

    <native-interface version="1" />

</chcp>

事件監聽

Cordova Hot Code Push 插件提供了一系列事件監聽,方便我們對不同情況進行不同的處理。例如:chcp_updateInstalled 事件,當更新安裝完成時會發出這個通知;chcp_updateInstallFailed 事件,當更新安裝失敗時發出這個通知,等等。

值得註意的是,需要在 deviceready 事件回調後,才進行 CHCP 插件的事件監聽註冊。

var app = {

  // Application Constructor

  initialize: function() {

    this.bindEvents();

  },

  // Bind any events that are required.

  // Usually you should subscribe on ‘deviceready‘ event to know, when you can start calling cordova modules

  bindEvents: function() {

    document.addEventListener(‘deviceready‘, this.onDeviceReady, false);

    document.addEventListener(‘chcp_updateIsReadyToInstall‘, this.onUpdateReady, false);

  },

  // deviceready Event Handler

  onDeviceReady: function() {

    console.log(‘Device is ready for work‘);

  },

  // chcp_updateIsReadyToInstall Event Handler

  onUpdateReady: function() {

    console.log(‘Update is ready for installation‘);

  }

};

app.initialize();

詳細事件監聽列表參考文檔:Listen for update events

獲取/安裝更新

① fetch update chcp.fetchUpdate
調用 API 從服務器中獲取更新

function fetchUpdate(cb) {

    var options = {

        ‘config-file‘: ‘https://kss.ksyun.com/******/chcp.json‘

    };

    chcp.fetchUpdate(updateCallback, options);

    function updateCallback(error, data) {

        if (error) {

            console.log(‘--fetchUpdate error--‘, error.code, error.description);

        }

        console.log(‘--fetchUpdate--‘, data, data.config);

        cb && cb(error, data);

    }

}

② install update chcp.installUpdate
調用 API 安裝更新

function installUpdate(cb) {

    chcp.installUpdate(installationCallback);

    function installationCallback(error) {

        if (error) {

            console.log(‘Failed to install the update with error code: ‘ + error.code);

            console.log(error.description);

        } else {

            console.log(‘Update installed!‘);

        }

        cb && cb(error);

    }

}

在安裝更新之前,還需要檢測是否有更新可用於安裝
chcp.isUpdateAvailableForInstallation

function checkIsUpdateAvailableForInstallation(cb) {

    chcp.isUpdateAvailableForInstallation(callbackMethod);

    function callbackMethod(error, data) {

        if (error) {

            console.log(‘No update was loaded => nothing to install‘);

        } else {

            console.log(‘Current content version: ‘ + data.currentVersion);

            console.log(‘Ready to be installed:‘ + data.readyToInstallVersion);

        }

        cb && cb(error, data);

    }

}

獲取版本信息

function getVersionInfo(cb) {

    chcp.getVersionInfo((err, data) => {

        console.log(‘Current web version: ‘ + data.currentWebVersion);

        console.log(‘Previous web version: ‘ + data.previousWebVersion);

        console.log(‘Loaded and ready for installation web version: ‘ + data.readyToInstallWebVersion);

        console.log(‘Application version name: ‘ + data.appVersion);

        console.log(‘Application build version: ‘ + data.buildVersion);

        cb && cb(err, data);

    });

}

錯誤代碼

在下載,安裝更新過程中都有可能出現錯誤,詳細的錯誤代碼參考:Error codes

請求到應用商店進行 APP 升級

插件配置文件 chcp.jsonmin_native_interface 選項是網頁內容執行時要求 native side 最低版本號。每一次熱更新過程中,都會去檢查這個邏輯,判斷當前 native side 的版本是否符合要求。如果當前 APP 中的 native side 版本號低於 chcp.jsonmin_native_interface 的選項值,那麽執行熱更新就會提示錯誤:chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW,這個時候,我們應當提示用戶前往應用商店對 APP 進行升級。

恰當的處理方式是,在出現 chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW 錯誤時,彈框提示用戶前往應用商店進行升級,彈框有兩個按鍵:一個點擊後跳轉到應用商店該 APP 對應下載頁面;另一個點擊後關閉彈框。插件也提供了 API 處理這個過程,我們只需:

  • chcp.json 配置文件中設置 android_identifierios_identifier
  • 調用 chcp.requestApplicationUpdate 方法

監聽 chcp_updateLoadFailed 事件,判斷錯誤代碼為 chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW 時,調用 chcp.requestApplicationUpdate 方法。

var app = {

  // Application Constructor

  initialize: function() {

    this.bindEvents();

  },

  // Bind any events that are required.

  // Usually you should subscribe on ‘deviceready‘ event to know, when you can start calling cordova modules

  bindEvents: function() {

    document.addEventListener(‘deviceready‘, this.onDeviceReady, false);

    document.addEventListener(‘chcp_updateLoadFailed‘, this.onUpdateLoadError, false);

  },

  // deviceready Event Handler

  onDeviceReady: function() {

  },

  onUpdateLoadError: function(eventData) {

    var error = eventData.detail.error;

    if (error && error.code == chcp.error.APPLICATION_BUILD_VERSION_TOO_LOW) {

        console.log(‘Native side update required‘);

        var dialogMessage = ‘New version of the application is available on the store. Please, update.‘;

        chcp.requestApplicationUpdate(dialogMessage, this.userWentToStoreCallback, this.userDeclinedRedirectCallback);

    }

  },

  userWentToStoreCallback: function() {

    // user went to the store from the dialog

  },

  userDeclinedRedirectCallback: function() {

    // User didn‘t want to leave the app.

    // Maybe he will update later.

  }

};

app.initialize();

Usage Limitations

1、Don’t rename/delete/move your index page
Cordova 項目 config.xml 文件中都會定義一個入口頁面 index.html

<content src="index.html" />

應用啟動的時候,就會加載 index.html 頁面作為入口,在代碼熱更新過程中,這是唯一不能刪除,移動和重命名的文件,否則,代碼熱更新後,應用就無法正常加載到 index.html 入口頁面,所以會出錯。

誠然,如果你需要重命名,或者修改其存儲路徑,那麽需要在 config.xml 文件中修改 content 配置。

2、Do not clean plugin’s inner preferences with cordova-plugin-nativestorage
cordova-plugin-nativestorage 插件提供了讀寫本地存儲數據的能力,例如在 iOS 中對應的本地存儲是 NSUserDefault,CHCP 熱更新插件在其中存儲了一些屬性。

調用 cordova-plugin-nativestorage 插件中的 NativeStorage.clear() 方法會清除本地存儲數據,這就會影響到 CHCP 插件的正常運行,導致下一次應用啟動時加載的是應用 bundle 中 www 目錄中的網頁內容,而非外部目錄存儲的當前版本網頁內容。

www 目錄打包上傳到服務器或者雲存儲目錄

新版本發布時,都需要執行如下處理:

  • www 目錄下的靜態文件進行打包,包括代碼壓縮,合並等等
  • 執行 cordova-hcp build 生成 chcp.jsonchcp.manifest 文件
  • www 目錄下的靜態文件上傳至服務器或者雲存儲目錄

問題總結:
1 ERROR: Plugin ‘HotCodePush‘ not found

The problem is resolved. Looking at forums, below are the correct steps

1 Run cordova plugin add cordova-hot-code-push-plugin in the project
2 Install npm install -g cordova-hot-code-push-cli on the system
3 Run cordova-hcp init and enter details
4 Run cordova-hcp build
5 Add the following in config.xml

<chcp>
    <config-file url="https://example.com/chcp.json"/>
</chcp>
Run cordova build

2 創建模版cordova-hcp.json,放在項目根目錄下,避免每次修改chcp.json文件

{

  "name": "wps-*****",

  "content_url": "https://kss.ksyun.com/*****/*****/",

  "ios_identifier": "326CN*****",

  "android_identifier": "com.**********.*****.*****.*****.*****",

  "update": "resume",

  "release": "2017.06.07-16.30.20",

  "min_native_interface": 1

}

之後每次修改代碼後,在項目根目錄運行 cordova-hcp build
就可以在www目錄生成跟模版一樣的chcp.json,只是release時間不一樣

3 補充,手動更新

以上教程的熱更新屬於自動化的,在用戶完全不知情的情況下,於是就有了可選擇更新的需求:

    <chcp>
        <auto-download enabled="false" />
        <auto-install enabled="false" />
        <config-file url="http://120.24.77.175:8080/ehospital/views/MSUI/chcp.json" />
    </chcp>

在config.xml改成以上配置,取消自動下載,取消自動安裝,然後通過插件提供的jsAPI進行手動更新,以下在deviceready事件觸發中示例(僅供參考):

    document.addEventListener(‘deviceready‘, () => {
      let chcp = window.chcp;
      // 檢測更新
      chcp.fetchUpdate((error, data) => {

        // 表示沒有更新版本,或者其他錯誤,詳情的信息參考上面的chcp error鏈接
        if (error) {
          console.log(‘--fetchUpdate error--‘, error.code, error.description);
          return;
        }

        // 這次更新的版本信息
        console.log(‘--fetchUpdate--‘, data, data.config);
        // 檢測是否是否可以進行安裝了,雙重判斷吧,有時候會出現有更新版本但是暫時無法安裝的情況(也可以去掉這一層)
        chcp.isUpdateAvailableForInstallation((error, data) => {

          if (error) {
            console.log(‘No update was loaded => nothing to install‘);
          } else {
            // 詢問用戶是否更新
            if ( window.confirm(‘檢測到新版本,是否更新‘) ) {
              // 更新中
              chcp.installUpdate((error) => {
                if (error) {
                  // 更新失敗
                  console.log(‘Failed to install the update with error code: ‘ + error.code);
                  console.log(error.description);
                } else {
                  // 更新成功
                  console.log(‘Update installed!‘);
                }
              });
            } else {
              window.alert(‘您已拒絕更新‘);
            }

            // 對比版本號
            console.log(‘Current content version: ‘ + data.currentVersion);
            console.log(‘Ready to be installed:‘ + data.readyToInstallVersion);
          }

        });
      });
    });

#Cordova Hot Code Push

Cordova 代碼熱更新 - 簡書