[深入剖析React Native]熱更新之react-native-pushy使用指南(Android)
本文使用RN版本:0.33.0
準備工作
首先你應該有一個基於React Native開發的應用,我們把具有package.json的目錄叫做你的”應用根目錄”。
如果你還沒有初始化應用,請參閱開始使用React Native。
所以我們也假設你已經擁有了開發React Native應用的一切環境,包括Node.js、npm、Android Studio、Android SDK等等。
如果你之前沒安裝過,你還必須安裝Android NDK,並設定環境變數ANDROID_NDK_HOME,指向你的NDK根目錄。
安裝
$ npm install -g react-native -update-cli
$ npm install --save react-native-update
$ react-native link react-native-update
其中,npm install -g react-native-update-cli這一句在每一臺電腦上僅需執行一次。
執行結果:
localhost:PushyReact wangdongdong$ npm install -g react-native-update-cli
/usr/local/bin/pushy -> /usr/local/lib/node_modules/react-native-update-cli/lib/cli.js
/usr/local /lib
└── react-native-update-cli@0.1.0
localhost:PushyReact wangdongdong$ npm install --save react-native-update
npm WARN deprecated lodash-node@2.4.1: This package has been discontinued in favor of lodash@^4.0.0.
npm WARN deprecated minimatch@2.0.10: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
PushyReact@0 .0.1 /Users/wangdongdong/workingcode/react/PushyReact
└─┬ react-native-update@3.0.2
├── cli-arguments@0.2.1
├─┬ fs-promise@0.4.1
│ └── any-promise@1.3.0
├─┬ ipa-metadata@1.4.0
│ ├── async@1.2.1
│ ├─┬ chalk@1.0.0
│ │ ├─┬ has-ansi@1.0.3
│ │ │ └── ansi-regex@1.1.1
│ │ ├── strip-ansi@2.0.1
│ │ └── supports-color@1.3.1
│ ├─┬ decompress-zip@0.2.0
│ │ ├─┬ binary@0.3.0
│ │ │ ├── buffers@0.1.1
│ │ │ └─┬ chainsaw@0.1.0
│ │ │ └── traverse@0.3.9
│ │ ├─┬ graceful-fs@3.0.11
│ │ │ └── natives@1.1.0
│ │ ├── mkpath@0.1.0
│ │ └─┬ touch@0.0.3
│ │ └── nopt@1.0.10
│ ├─┬ entitlements@1.2.0
│ │ └─┬ simple-plist@0.0.4
│ │ └─┬ plist@1.1.0
│ │ ├── base64-js@0.0.6
│ │ ├── util-deprecate@1.0.0
│ │ └── xmlbuilder@2.2.1
│ ├─┬ glob@5.0.10
│ │ └── minimatch@2.0.10
│ ├── lodash@3.9.3
│ ├─┬ meow@3.1.0
│ │ ├─┬ camelcase-keys@1.0.0
│ │ │ └── camelcase@1.2.1
│ │ ├── indent-string@1.2.2
│ │ └── object-assign@2.1.1
│ ├─┬ provisioning@1.4.0
│ │ ├── cert-downloader@0.1.0
│ │ └─┬ simple-plist@0.0.4
│ │ └─┬ plist@1.1.0
│ │ ├── base64-js@0.0.6
│ │ ├── util-deprecate@1.0.0
│ │ └── xmlbuilder@2.2.1
│ ├─┬ rimraf@2.4.0
│ │ └── glob@4.5.3
│ ├─┬ simple-plist@0.0.4
│ │ └─┬ plist@1.1.0
│ │ ├── base64-js@0.0.6
│ │ ├── util-deprecate@1.0.0
│ │ └─┬ xmlbuilder@2.2.1
│ │ └── lodash-node@2.4.1
│ └─┬ temporary@0.0.8
│ └── package@1.0.1
├── mkdir-recursive@0.2.3
├─┬ node-apk-parser@0.2.3
│ ├── adm-zip@0.4.7
│ └── debug@0.7.4
├── read@1.0.7
├─┬ request@2.75.0
│ ├── aws-sign2@0.6.0
│ ├── aws4@1.4.1
│ ├── caseless@0.11.0
│ ├─┬ combined-stream@1.0.5
│ │ └── delayed-stream@1.0.0
│ ├── forever-agent@0.6.1
│ ├─┬ form-data@2.0.0
│ │ └── asynckit@0.4.0
│ ├─┬ har-validator@2.0.6
│ │ └─┬ is-my-json-valid@2.14.0
│ │ ├── generate-function@2.0.0
│ │ ├─┬ generate-object-property@1.2.0
│ │ │ └── is-property@1.0.2
│ │ └── jsonpointer@2.0.0
│ ├─┬ hawk@3.1.3
│ │ ├── boom@2.10.1
│ │ ├── cryptiles@2.0.5
│ │ └── sntp@1.0.9
│ ├─┬ http-signature@1.1.1
│ │ ├── assert-plus@0.2.0
│ │ ├─┬ jsprim@1.3.1
│ │ │ ├── extsprintf@1.0.2
│ │ │ ├── json-schema@0.2.3
│ │ │ └── verror@1.3.6
│ │ └─┬ sshpk@1.10.0
│ │ ├── asn1@0.2.3
│ │ ├── assert-plus@1.0.0
│ │ ├─┬ bcrypt-pbkdf@1.0.0
│ │ │ └── tweetnacl@0.14.3
│ │ ├─┬ dashdash@1.14.0
│ │ │ └── assert-plus@1.0.0
│ │ ├── ecc-jsbn@0.1.1
│ │ ├─┬ getpass@0.1.6
│ │ │ └── assert-plus@1.0.0
│ │ ├── jodid25519@1.0.2
│ │ ├── jsbn@0.1.0
│ │ └── tweetnacl@0.13.3
│ ├── is-typedarray@1.0.0
│ ├── isstream@0.1.2
│ ├── json-stringify-safe@5.0.1
│ ├── oauth-sign@0.8.2
│ ├── qs@6.2.1
│ ├── stringstream@0.0.5
│ └── tough-cookie@2.3.1
├── yauzl@2.4.1
└── yazl@2.3.0
localhost:PushyReact wangdongdong$ react-native link react-native-update
rnpm-install info Linking react-native-update android dependency
rnpm-install info Android module react-native-update has been successfully linked
rnpm-install info Linking react-native-update ios dependency
rnpm-install info iOS module react-native-update has been successfully linked
手動安裝(本步驟可選)
如果第一步已成功(安卓工程均能看到依賴),可以跳過此步驟。
1.在android/settings.gradle中新增如下程式碼:
include ':react-native-update'
project(':react-native-update').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-update/android')
2.在android/app/build.gradle的 dependencies 部分增加如下程式碼:
compile project(':react-native-update')
3.檢查你的RN版本,如果是0.29及以上, 開啟android/app/src/main/java/[…]/MainApplication.java,否則開啟android/app/src/main/java/[…]/MainActivity.java:
- 在檔案開頭增加 import cn.reactnative.modules.update.UpdatePackage;
- 在getPackages() 方法中增加 new UpdatePackage()(注意上一行可能要增加一個逗號)
效果如圖:
配置Bundle URL
在你的MainApplication.java中增加如下程式碼:
// ... 其它程式碼
import cn.reactnative.modules.update.UpdateContext;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected String getJSBundleFile() {
return UpdateContext.getBundleUrl(MainApplication.this);
}
// ... 其它程式碼
}
}
登入與建立應用
先到Pushy註冊註冊地址
到命令列,在你的專案根目錄下執行以下命令:
$ pushy login
email: <輸入你的註冊郵箱>
password: <輸入你的密碼>
這會在專案資料夾下建立一個.update檔案,注意不要把這個檔案上傳到Git等CVS系統上。你可以在.gitignore末尾增加一行.update來忽略這個檔案。
登入之後可以建立應用。注意iOS平臺和安卓平臺需要分別建立:
(本文先演示Android的,IOS請見下一篇文章。)
$ pushy createApp --platform android
注意:目前(2016.09.22)一個賬戶只能建立最多3個應用,多了會報錯。
建立好後,會在你的Pushy管理頁面中生成你的專案。
注意:如果你已經在網頁端或者其它地方建立過應用,也可以直接選擇應用:
$ pushy selectApp --platform android
3627) WangReact(android)
3692) PushyReact(android)
Total 2 ios apps
Enter appId: <輸入應用前面的編號>
選擇或者建立過應用後,你將可以在資料夾下看到update.json檔案,其內容類似如下形式:
{
"android": {
"appId": 3692,
"appKey": "<一串隨機字串>"
}
}
你可以安全的把update.json上傳到Git等CVS系統上,與你的團隊共享這個檔案,它不包含任何敏感資訊。當然,他們在使用任何功能之前,都必須首先輸入pushy login進行登入。
至此應用的建立/選擇就已經成功了。
在程式碼中新增熱更新功能
獲取appKey
檢查更新時必須提供你的appKey,這個值儲存在update.json中,並且根據平臺不同而不同。你可以用如下的程式碼獲取:
import {
Platform,
} from 'react-native';
import _updateConfig from './update.json';
const {appKey} = _updateConfig[Platform.OS];
如果你不使用pushy命令列,你也可以從網頁端檢視到你的應用appKey,並根據平臺的不同來選擇。
檢查更新、下載更新
非同步函式checkUpdate可以檢查當前版本是否需要更新:
checkUpdate(appKey)
.then(info => {
})
返回的info有三種情況:
- {expired: true}:該應用包(原生部分)已過期,需要前往應用市場下載新的版本。
- {upToDate: true}:當前已經更新到最新,無需進行更新。
- {update: true}:當前有新版本可以更新。 info的name、description欄位可 以用於提示使用者,而metaInfo欄位則可以根據你的需求自定義其它屬性(如是否靜默更新、 是否強制更新等等)。另外還有幾個欄位,包含了完整更新包或補丁包的下載地址, react-native-update會首先嚐試耗費流量更少的更新方式。將info物件傳遞給downloadUpdate作為引數即可。
切換版本
downloadUpdate的返回值是一個hash字串,它是當前版本的唯一標識。
你可以使用switchVersion函式立即切換版本(此時應用會立即重新載入),或者選擇呼叫 switchVersionLater,讓應用在下一次啟動的時候再載入新的版本。
downloadUpdate(info).then(hash => {
Alert.alert('提示', '下載完畢,是否重啟應用?', [
{text: '是', onPress: ()=>{switchVersion(hash);}},
{text: '否',},
{text: '下次啟動時', onPress: ()=>{switchVersionLater(hash);}},
]);
}).catch(err => {
Alert.alert('提示', '更新失敗.');
});
首次啟動、回滾
在每次更新完畢後的首次啟動時,isFirstTime常量會為true。 你必須在應用退出前合適的任何時機,呼叫markSuccess,否則應用下一次啟動的時候將會進行回滾操作。 這一機制稱作“反觸發”,這樣當你應用啟動初期即遭遇問題的時候,也能在下一次啟動時恢復運作。
你可以通過isFirstTime來獲知這是當前版本的首次啟動,也可以通過isRolledBack來獲知應用剛剛經歷了一次回滾操作。 你可以在此時給予使用者合理的提示。
componentWillMount(){
if (isFirstTime) {
Alert.alert('提示', '這是當前版本第一次啟動,是否要模擬啟動失敗?失敗將回滾到上一版本', [
{text: '是', onPress: ()=>{throw new Error('模擬啟動失敗,請重啟應用')}},
{text: '否', onPress: ()=>{markSuccess()}},
]);
} else if (isRolledBack) {
Alert.alert('提示', '剛剛更新失敗了,版本被回滾.');
}
}
完整的示例
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Platform,
Text,
View,
Alert,
TouchableOpacity,
Linking,
} from 'react-native';
import {
isFirstTime,
isRolledBack,
packageVersion,
currentVersion,
checkUpdate,
downloadUpdate,
switchVersion,
switchVersionLater,
markSuccess,
} from 'react-native-update';
import _updateConfig from './update.json';
const {appKey} = _updateConfig[Platform.OS];
class PushyReact extends Component {
componentWillMount(){
if (isFirstTime) {
Alert.alert('提示', '這是當前版本第一次啟動,是否要模擬啟動失敗?失敗將回滾到上一版本', [
{text: '是', onPress: ()=>{throw new Error('模擬啟動失敗,請重啟應用')}},
{text: '否', onPress: ()=>{markSuccess()}},
]);
} else if (isRolledBack) {
Alert.alert('提示', '剛剛更新失敗了,版本被回滾.');
}
}
doUpdate = info => {
downloadUpdate(info).then(hash => {
Alert.alert('提示', '下載完畢,是否重啟應用?', [
{text: '是', onPress: ()=>{switchVersion(hash);}},
{text: '否',},
{text: '下次啟動時', onPress: ()=>{switchVersionLater(hash);}},
]);
}).catch(err => {
Alert.alert('提示', '更新失敗.');
});
}
checkUpdate = () => {
checkUpdate(appKey).then(info => {
//Alert.alert('提示', 'info expired:'+info.expired+ ' upToDate:'+info.upToDate+' update:'+info.update);
if (info.expired) {
Alert.alert('提示', '您的應用版本已更新,請前往應用商店下載新的版本', [
{text: '確定', onPress: ()=>{info.downloadUrl && Linking.openURL(info.downloadUrl)}},
]);
} else if (info.upToDate) {
Alert.alert('提示', '您的應用版本已是最新.');
} else {
Alert.alert('提示', '檢查到新的版本'+info.name+',是否下載?\n'+ info.description, [
{text: '是', onPress: ()=>{this.doUpdate(info)}},
{text: '否',},
]);
}
}).catch(err => {
Alert.alert('提示', '更新失敗.');
});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
歡迎使用熱更新服務
</Text>
<Text style={styles.instructions}>
這是版本一 {'\n'}
當前包版本號: {packageVersion}{'\n'}
當前版本Hash: {currentVersion||'(空)'}{'\n'}
</Text>
<TouchableOpacity onPress={this.checkUpdate}>
<Text style={styles.instructions}>
點選這裡檢查更新
</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('PushyReact', () => PushyReact);
現在,你的應用已經可以通過update服務檢查版本並進行更新了。下一步,你可以開始嘗試釋出應用包和版本。
釋出應用包和版本
現在你的應用已經具備了檢測更新的功能,下面我們來嘗試釋出並更新它。
注意,從update上傳發布版本到釋出版本正式上線期間,不要修改任何指令碼和資源,這會影響update 獲取原生代碼,從而導致版本不能更新。如果在釋出之前修改了指令碼或資源,請在網頁端刪除之前上傳的版本並重新上傳。
釋出Android應用
首先參考文件-生成已簽名的APK設定簽名, 然後在android資料夾下執行./gradlew assembleRelease,你就可以在android/app/build/outputs/apk/app-release.apk中找到你的應用包。
然後執行如下命令
$ pushy uploadApk android/app/build/outputs/apk/app-release.apk
即可上傳apk以供後續版本比對之用。
隨後你可以選擇往應用市場釋出這個版本,也可以先往裝置上直接安裝這個apk檔案以進行測試。
釋出新的熱更新版本
你可以嘗試修改一行程式碼(譬如將版本一修改為版本二),然後生成新的熱更新版本。
$ pushy bundle --platform android
Bundling with React Native version: 0.33.0
<各種進度輸出>
Bundled saved to: build/output/android.1474609357661.ppk
Would you like to publish it?(Y/N)
如果想要立即釋出,此時輸入Y。當然,你也可以在將來使用pushy publish --platform android <ppkFile>
來發布版本。
Uploading [========================================================] 100% 0.0s
Enter version name: <輸入版本名字,如1.0.10>
Enter description: <輸入版本描述>
Enter meta info: {"ok":1}
Ok.
Would you like to bind packages to this version?(Y/N)
此時版本已經提交到update服務,但使用者暫時看不到此更新,你需要先將特定的包版本繫結到此熱更新版本上。
此時輸入Y立即繫結,你也可以在將來使用pushy update --platform android
來使得對應包版本的使用者更新。 除此以外,你還可以在網頁端操作,簡單的將對應的包版本拖到此版本下即可。
Would you like to bind packages to this version?(Y/N) Y
2273) 1.0(normal) (newest)
Total 1 packages.
Enter packageId: 2273
Ok.
版本繫結完畢後,客戶端就應當可以檢查到更新並進行更新了。
恭喜你,至此為止,你已經完成了植入程式碼熱更新的全部工作。