react-native-code-push基礎(chǔ)篇

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-cli.png

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


查看code-push -v.png

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即可

登錄頁面.png

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

獲取CodePus Key.png

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


登錄成功.png

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è)安卓平臺(tái).png
注冊(cè)iOS平臺(tái)應(yīng)用ios
code-push app add CodePushDemo-iOS ios react-native
注冊(cè)蘋果平臺(tái).png

應(yīng)用添加成功后就會(huì)返回對(duì)應(yīng)的 ProductionStaging 兩個(gè)key。
Production 代表生產(chǎn)版的熱更新部署,
Staging 代表開發(fā)版的熱更新部署,
在ios中將staging的部署key復(fù)制在info.plist的CodePushDeploymentKey值中,
在android中復(fù)制在Application的getPackages的CodePush中

我們可以輸入如下命令來查看我們剛剛添加的App
code-push app list
查看App列表.png

4.React Native(JavaScript)端集成CodePush

安裝組件
npm install react-native-code-push --save
安裝組件.png

添加原生依賴,這里添加依賴我們使用自動(dòng)添加依賴的方式

react-native link react-native-code-push
添加原生依賴.png

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)制更新

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容