CodePush是什么?
CodePush是一個(gè)微軟開發(fā)的云服務(wù)器。通過它,開發(fā)者可以直接在用戶的設(shè)備上部署手機(jī)應(yīng)用更新。CodePush相當(dāng)于一個(gè)中心倉庫,開發(fā)者可以推送當(dāng)前的更新(包括JS/HTML/CSS/IMAGE等)到CoduPush,然后應(yīng)用將會(huì)查詢是否有更新,本篇主要介紹使用微軟云服務(wù)器。
當(dāng)然,我們也可以自己搭建code-push-server服務(wù)器,詳情可以查看本地?zé)岣路?wù)器搭建。
實(shí)現(xiàn)環(huán)境
1.react-native:0.62.2
2.react-native-code-push:^6.2.0
首先介紹下Code Push相關(guān)命令
/**前言——code-pus相關(guān)指令**/
/**注冊(cè)登錄相關(guān)指令**/
code-push login /*進(jìn)行身份驗(yàn)證以開始管理您的應(yīng)用*/
code-push logout /*注銷當(dāng)前會(huì)話*/
code-push access-key /*查看和管理與您的帳戶關(guān)聯(lián)的訪問密鑰*/
code-push access-key ls /*列出登陸的token*/
code-push access-key rm <accessKye> 刪除某個(gè) access-key
/**管理App相關(guān)指令**/
code-push app /*查看和管理您的CodePush應(yīng)用*/
code-push app add 在賬號(hào)里面添加一個(gè)新的app
code-push app remove 或者 rm 在賬號(hào)里移除一個(gè)app
code-push app rename 重命名一個(gè)存在app
code-push app list 或則 ls 列出賬號(hào)下面的所有app
code-push app transfer 把a(bǔ)pp的所有權(quán)轉(zhuǎn)移到另外一個(gè)賬號(hào)
/**查看deployment key**/
code-push deployment /*查看和管理您的應(yīng)用程序部署*/
code-push deployment ls Appname -k /*查看deployment key*/
/**其他**/
code-push collaborator /*查看和管理應(yīng)用協(xié)作者*/
code-push debug /*查看正在運(yùn)行的應(yīng)用程序的CodePush調(diào)試日志*/
code-push link /*將其他身份驗(yàn)證提供程序(例如GitHub)鏈接到現(xiàn)有的Mobile Center帳戶*/
code-push patch /*更新現(xiàn)有版本的元數(shù)據(jù)*/
code-push promote /*將最新版本從一種應(yīng)用程序部署升級(jí)到另一種*/
code-push register /*注冊(cè)一個(gè)新的Mobile Center帳戶*/
code-push release /*發(fā)布更新到應(yīng)用程序部署*/
code-push release-cordova /*將Cordova更新發(fā)布到應(yīng)用程序部署*/
code-push release-react /*將React Native更新發(fā)布到應(yīng)用程序部署*/
code-push rollback /*回滾最新版本的應(yīng)用程序部署*/
接入流程
1.安裝 CodePush CLI
2.注冊(cè) CodePush賬號(hào)
3.在CodePush服務(wù)器注冊(cè)App
4.React Native(JavaScript)端集成CodePush
5.原生應(yīng)用中配置CodePush
6.發(fā)布更新的版本
react-native-code-push Demo示例:
首先新建一個(gè)React Native項(xiàng)目CodePushDemo
1.CodePush CLI
使用CodePush之前,需要先安裝CodePush命令行工具,并注冊(cè)CodePush賬號(hào)和應(yīng)用,安裝命令如下:
注意:這個(gè)CodePush指令只需要全局安裝一次即可,如果第一次安裝成功了,那后面就不在需要安裝。
npm install -g code-push-cli

安裝完成后可以通過code-push -v命令進(jìn)行驗(yàn)證

2.注冊(cè) CodePush賬號(hào)
注冊(cè)CodePush賬號(hào)也很簡(jiǎn)單,同樣是只需簡(jiǎn)單的執(zhí)行下面的命令,同樣這個(gè)注冊(cè)操作也是全局只需要注冊(cè)一次即可
code-push register
注意:當(dāng)執(zhí)行完上面的命令后,會(huì)自動(dòng)打開一個(gè)授權(quán)網(wǎng)頁,讓你選擇使用哪種方式進(jìn)行授權(quán)登錄,這里我們統(tǒng)一就選擇使用GitHub即可

當(dāng)注冊(cè)登錄成功后,CodePush會(huì)給我們一個(gè)key:

我們直接復(fù)制這個(gè)key,然后在終端中將這個(gè)key填寫進(jìn)去即可,填寫key登錄成功顯示效果如下

3.在CodePush服務(wù)器注冊(cè)App
為了使CodePush服務(wù)器有我們的App,我們需要CodePush注冊(cè)App,輸入下面命令即可完成注冊(cè):
注意:如果我們的應(yīng)用分為iOS和Android兩個(gè)平臺(tái),這時(shí)我們需要分別注冊(cè)兩套key:
code-push app add 應(yīng)用平臺(tái)命名 平臺(tái)名稱 使用的框架/語言
注冊(cè)Android平臺(tái)應(yīng)用
code-push app add CodePushDemo-Android android react-native

注冊(cè)iOS平臺(tái)應(yīng)用ios
code-push app add CodePushDemo-iOS ios react-native

應(yīng)用添加成功后就會(huì)返回對(duì)應(yīng)的 Production 和 Staging 兩個(gè)key。
Production 代表生產(chǎn)版的熱更新部署,
Staging 代表開發(fā)版的熱更新部署,
在ios中將staging的部署key復(fù)制在info.plist的CodePushDeploymentKey值中,
在android中復(fù)制在Application的getPackages的CodePush中
我們可以輸入如下命令來查看我們剛剛添加的App
code-push app list

4.React Native(JavaScript)端集成CodePush
安裝組件
npm install react-native-code-push --save

添加原生依賴,這里添加依賴我們使用自動(dòng)添加依賴的方式
react-native link react-native-code-push

JS邏輯代碼實(shí)現(xiàn)
主頁面(App.js)
/**
* 熱更新主頁面
* Created by 技術(shù)渣渣 on 2020/4/26
**/
import React, {Component} from 'react'
import {View, Text, StyleSheet, TouchableOpacity} from 'react-native'
import CodePush from 'react-native-code-push';
import ProgressBarModal from './ProgressBarModal';
class App extends Component{
constructor(props) {
super(props);
this.state={
progressModalVisible:false
}
}
componentDidMount() {
this.syncImmediate(); //開始檢查更新
}
syncImmediate() {
CodePush.sync(
{
installMode: CodePush.InstallMode.IMMEDIATE,
updateDialog: {
appendReleaseDescription: true, //是否顯示更新description,默認(rèn)為false
descriptionPrefix: '更新內(nèi)容:', //更新說明的前綴。 默認(rèn)是” Description:
mandatoryContinueButtonLabel: '立即更新', //強(qiáng)制更新的按鈕文字,默認(rèn)為continue
mandatoryUpdateMessage: '', //- 強(qiáng)制更新時(shí),更新通知. Defaults to “An update is available that must be installed.”.
optionalIgnoreButtonLabel: '稍后', //非強(qiáng)制更新時(shí),取消按鈕文字,默認(rèn)是ignore
optionalInstallButtonLabel: '后臺(tái)更新', //非強(qiáng)制更新時(shí),確認(rèn)文字. Defaults to “Install”
optionalUpdateMessage: '有新版本了,是否更新?', //非強(qiáng)制更新時(shí),更新通知. Defaults to “An update is available. Would you like to install it?”.
title: '更新提示', //要顯示的更新通知的標(biāo)題. Defaults to “Update available”.
},
},
this.codePushStatusDidChange.bind(this),
this.codePushDownloadDidProgress.bind(this),
);
}
codePushStatusDidChange(syncStatus) {
switch (syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
this.setState({syncMessage: 'Checking for update.'});
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
this.setState({syncMessage: 'Downloading package.',progressModalVisible:true});
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
this.setState({syncMessage: 'Awaiting user action.'});
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
this.setState({syncMessage: 'Installing update.',progressModalVisible:true});
break;
case CodePush.SyncStatus.UP_TO_DATE:
this.setState({syncMessage: 'App up to date.', progress: false});
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
this.setState({syncMessage: 'Update cancelled by user.', progress: false,});
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
this.setState({syncMessage: 'Update installed and will be applied on restart.', progress: false,});
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
this.setState({syncMessage: 'An unknown error occurred.', progress: false,});
break;
}
}
codePushDownloadDidProgress(progress) {
this.setState({progress});
}
render(){
let progressView;
if (this.state.progress) {
let total = (this.state.progress.totalBytes/(1024*1024)).toFixed(2);
let received =(this.state.progress.receivedBytes/(1024*1024)).toFixed(2);
let progress = parseInt((received/total)*100);
progressView = (
<ProgressBarModal
progress={progress}
totalPackageSize={total}
receivedPackageSize={received}
progressModalVisible={this.state.progressModalVisible}
/>
);
}
return(
<View style={styles.container}>
<Text style={styles.welcome}>歡迎使用熱更新!</Text>
<Text>版本1</Text>
<TouchableOpacity onPress={this.syncImmediate.bind(this)}>
<Text style={styles.syncButton}>Press for dialog-driven sync</Text>
</TouchableOpacity>
{ progressView }
</View>
)
}
}
const styles = StyleSheet.create({
container:{
flex: 1,
alignItems: 'center',
backgroundColor: '#F5FCFF',
paddingTop: 50,
},
welcome:{
fontSize: 20,
textAlign: 'center',
margin: 20,
},
syncButton: {
color: 'green',
fontSize: 17,
},
})
let codePushOptions = {checkFrequency: CodePush.CheckFrequency.MANUAL};
App = CodePush(codePushOptions)(App);
export default App;
進(jìn)度條頁面1(ProgressBarModal.js)
import React, { PureComponent } from 'react';
import {
View,
Modal,
Text,
ImageBackground,
StyleSheet
} from 'react-native';
import Bar from './Bar';
import scale from './scale';
const propTypes = {
...Modal.propTypes,
};
const defaultProps = {
animationType: 'none',
transparent: true,
progressModalVisible: false,
onRequestClose: () => {},
};
/* 更新進(jìn)度條Modal */
class ProgressBarModal extends PureComponent {
constructor(props) {
super(props);
this.state = {
title: '正在下載更新文件' // 更新提示標(biāo)題
};
}
render() {
const {
animationType,
transparent,
onRequestClose,
progress,
progressModalVisible,
totalPackageSize,
receivedPackageSize,
} = this.props;
return (
<Modal
animationType={animationType}
transparent={transparent}
visible={progressModalVisible}
onRequestClose={onRequestClose}
>
<View style={styles.progressBarView}>
<View style={styles.imageBg}>
<Text style={styles.title}>
{this.state.title}
</Text>
</View>
<View style={styles.subView}>
<Bar
style={{ width: scale(540), borderRadius: scale(30) }}
progress={progress}
backgroundStyle={styles.barBackgroundStyle}
/>
<Text style={styles.textPackageSize}>
{`${receivedPackageSize}M/${totalPackageSize}M`}
</Text>
<Text style={{color:'red',marginTop:scale(100)}}>*溫馨提示:下載完更新文件,應(yīng)用會(huì)重啟</Text>
</View>
<View style={styles.bottomContainer} />
</View>
</Modal>
);
}
}
ProgressBarModal.propTypes = propTypes;
ProgressBarModal.defaultProps = defaultProps;
export default ProgressBarModal;
const styles = StyleSheet.create({
imageBg: {
width: scale(600),
height: scale(100),
justifyContent: 'center',
alignItems: 'center',
backgroundColor:'#1083E6',
borderTopLeftRadius:scale(26),
borderTopRightRadius:scale(26),
},
progressBarView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.2)'
},
// 默認(rèn)進(jìn)度條背景底色
barBackgroundStyle: {
backgroundColor: '#e0e0e0'
},
subView: {
width: scale(600),
height: scale(296),
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
},
bottomContainer: {
width: scale(600),
height: scale(39),
borderBottomLeftRadius: scale(26),
borderBottomRightRadius: scale(26),
backgroundColor: '#FFF'
},
textPackageSize: {
fontSize: scale(40),
color: '#686868',
marginTop: scale(36)
},
title: {
color: '#FFF',
fontSize: scale(30)
}
})
進(jìn)度條頁面1(Bar.js)
import React, { PureComponent } from 'react';
import {
View,
Animated,
} from 'react-native';
import PropTypes from 'prop-types';
import scale from './scale';
const propTypes = {
progress: PropTypes.number.isRequired,
backgroundStyle: PropTypes.number.isRequired,
style: PropTypes.object.isRequired,
};
/* 進(jìn)度條組件 */
class Bar extends PureComponent {
constructor(props) {
super(props);
this.progress = new Animated.Value(0);
this.update = this.update.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.progress >= 0 && this.props.progress !== nextProps.progress) {
this.update(nextProps.progress);
}
}
update(progress) {
Animated.spring(this.progress, {
toValue: progress
}).start();
}
render() {
return (
<View style={[this.props.backgroundStyle, this.props.style]}>
<Animated.View style={{
backgroundColor: '#1083E6',
height: scale(28),
borderRadius: scale(30),
width: this.progress.interpolate({
inputRange: [0, 100],
outputRange: [0, 1 * this.props.style.width],
}) }}
/>
</View>
);
}
}
Bar.propTypes = propTypes;
export default Bar;
5.原生應(yīng)用中配置CodePush
配置Android平臺(tái)
1.編輯 android/app/build.gradle,新增依賴:
dependencies {
implementation project(':react-native-code-push')
}
2.編輯 android/app/build.gradle,修改buildTypes,輸入對(duì)應(yīng)的Key:
buildTypes {
debug {
signingConfig signingConfigs.debug
resValue "string", "CodePushDeploymentKey", ""
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://facebook.github.io/react-native/docs/signed-apk-android.
signingConfig signingConfigs.release
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
//這里的key為Productionde的key
resValue "string", "CodePushDeploymentKey", "9DSt0TxqNuDlsX1SneATSr8qrrkJ4ksvOXqog"
}
releaseStaging {
//這里的key為Staging的key
resValue "string", "CodePushDeploymentKey", "HmTsl4j3G7qlMtxmIG93beLpF6ns4ksvOXqog"
matchingFallbacks = ['release']
}
}
3.編輯 android/app/build.gradle,修改defaultConfig,將versionName 修改為三位(1.0.0):
defaultConfig {
applicationId "com.codepushdemo"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
}
6.發(fā)布更新的版本
使用前應(yīng)考慮到應(yīng)用檢查更新的時(shí)機(jī),是否要求強(qiáng)制更新,是否要求即時(shí)更新等問題。
常見的更新時(shí)機(jī)有兩種
1.打開App就自動(dòng)檢查更新
可以在頁面的componentDidMount生命周期方法里通過codePush.sync來觸發(fā)檢查更新,如果有更新包可供下載則會(huì)在重啟后生效。
注意:這種下載和安裝都是靜默的,即用戶不可見,本文額外增加了更新下載進(jìn)度條的 modal
codePush.sync({
installMode: CodePush.InstallMode.IMMEDIATE,
updateDialog: {
appendReleaseDescription: true, //是否顯示更新description,默認(rèn)為false
descriptionPrefix: '更新內(nèi)容:', //更新說明的前綴。 默認(rèn)是” Description:
mandatoryContinueButtonLabel: '立即更新', //強(qiáng)制更新的按鈕文字,默認(rèn)為continue
mandatoryUpdateMessage: '', //- 強(qiáng)制更新時(shí),更新通知. Defaults to “An update is available that must be installed.”.
optionalIgnoreButtonLabel: '稍后', //非強(qiáng)制更新時(shí),取消按鈕文字,默認(rèn)是ignore
optionalInstallButtonLabel: '后臺(tái)更新', //非強(qiáng)制更新時(shí),確認(rèn)文字. Defaults to “Install”
optionalUpdateMessage: '有新版本了,是否更新?', //非強(qiáng)制更新時(shí),更新通知. Defaults to “An update is available. Would you like to install it?”.
title: '更新提示', //要顯示的更新通知的標(biāo)題. Defaults to “Update available”.
},
}),
2.用戶點(diǎn)擊檢查更新并安裝
在用戶點(diǎn)擊按鈕觸發(fā)事件時(shí),在調(diào)用上面的codePush.sync方法,在有更新時(shí)彈出提示框讓用戶選擇是否更新,如果用戶點(diǎn)擊立即更新按鈕,則會(huì)進(jìn)行安裝包的下載,下載完成后會(huì)立即重啟并生效。
常見的更新時(shí)機(jī)有兩種
如果是強(qiáng)制更新需要在發(fā)布的時(shí)候指定,發(fā)布命令中配置--m true
更新是否要求即時(shí)
在更新配置中通過指定installMode來決定安裝完成的重啟時(shí)機(jī),亦即更新生效時(shí)機(jī)
1.codePush.InstallMode.IMMEDIATE :安裝完成立即重啟更新
2.codePush.InstallMode.ON_NEXT_RESTART :安裝完成后會(huì)在下次重啟后進(jìn)行更新
3.codePush.InstallMode.ON_NEXT_RESUME :安裝完成后會(huì)在應(yīng)用進(jìn)入后臺(tái)后重啟更新
如何發(fā)布CodePush更新包
本文介紹樓主使用過的發(fā)布方式,通過以下指令發(fā)布更新包:
code-push release-react <Appname> <Platform> --t <本更新包面向的舊版本號(hào)> --dev false --d Production --des <本次更新說明> --m true
注意:CodePush默認(rèn)是更新Staging 環(huán)境的,如果發(fā)布生產(chǎn)環(huán)境的更新包,需要指定--d參數(shù):--d Production,如果發(fā)布的是強(qiáng)制更新包,需要加上 --m true強(qiáng)制更新