react-native-code-push基础篇

作者: 油菜又矮吹 | 来源:发表于2020-08-28 15:30 被阅读0次

    CodePush是什么?

    CodePush是一个微软开发的云服务器。通过它,开发者可以直接在用户的设备上部署手机应用更新。CodePush相当于一个中心仓库,开发者可以推送当前的更新(包括JS/HTML/CSS/IMAGE等)到CoduPush,然后应用将会查询是否有更新,本篇主要介绍使用微软云服务器

    当然,我们也可以自己搭建code-push-server服务器,详情可以查看本地热更新服务器搭建。

    实现环境

    1.react-native:0.62.2

    2.react-native-code-push:^6.2.0

    首先介绍下Code Push相关命令

    /**前言——code-pus相关指令**/
      /**注册登录相关指令**/
        code-push login    /*进行身份验证以开始管理您的应用*/
        code-push logout    /*注销当前会话*/
        code-push access-key     /*查看和管理与您的帐户关联的访问密钥*/
        code-push access-key ls    /*列出登陆的token*/
        code-push access-key rm <accessKye> 删除某个 access-key
      /**管理App相关指令**/
        code-push app    /*查看和管理您的CodePush应用*/
        code-push app add 在账号里面添加一个新的app
        code-push app remove 或者 rm 在账号里移除一个app
        code-push app rename 重命名一个存在app
        code-push app list 或则 ls 列出账号下面的所有app
        code-push app transfer 把app的所有权转移到另外一个账号
      /**查看deployment key**/
        code-push deployment    /*查看和管理您的应用程序部署*/
        code-push deployment ls Appname -k    /*查看deployment key*/
      /**其他**/
        code-push collaborator    /*查看和管理应用协作者*/
        code-push debug    /*查看正在运行的应用程序的CodePush调试日志*/
        code-push link    /*将其他身份验证提供程序(例如GitHub)链接到现有的Mobile Center帐户*/
        code-push patch    /*更新现有版本的元数据*/
        code-push promote    /*将最新版本从一种应用程序部署升级到另一种*/
        code-push register    /*注册一个新的Mobile Center帐户*/
        code-push release    /*发布更新到应用程序部署*/
        code-push release-cordova    /*将Cordova更新发布到应用程序部署*/
        code-push release-react    /*将React Native更新发布到应用程序部署*/
        code-push rollback    /*回滚最新版本的应用程序部署*/
    

    接入流程

    1.安装 CodePush CLI
    2.注册 CodePush账号
    3.在CodePush服务器注册App
    4.React Native(JavaScript)端集成CodePush
    5.原生应用中配置CodePush
    6.发布更新的版本

    react-native-code-push Demo示例

    首先新建一个React Native项目CodePushDemo

    1.CodePush CLI

    使用CodePush之前,需要先安装CodePush命令行工具,并注册CodePush账号和应用,安装命令如下:
    注意:这个CodePush指令只需要全局安装一次即可,如果第一次安装成功了,那后面就不在需要安装。

    npm install -g code-push-cli
    
    安装code-push-cli.png

    安装完成后可以通过code-push -v命令进行验证


    查看code-push -v.png

    2.注册 CodePush账号

    注册CodePush账号也很简单,同样是只需简单的执行下面的命令,同样这个注册操作也是全局只需要注册一次即可

    code-push register
    

    注意:当执行完上面的命令后,会自动打开一个授权网页,让你选择使用哪种方式进行授权登录,这里我们统一就选择使用GitHub即可

    登录页面.png
    当注册登录成功后,CodePush会给我们一个key: 获取CodePus Key.png

    我们直接复制这个key,然后在终端中将这个key填写进去即可,填写key登录成功显示效果如下


    登录成功.png

    3.在CodePush服务器注册App

    为了使CodePush服务器有我们的App,我们需要CodePush注册App,输入下面命令即可完成注册:
    注意:如果我们的应用分为iOS和Android两个平台,这时我们需要分别注册两套key:

     code-push app add 应用平台命名 平台名称 使用的框架/语言
    
    注册Android平台应用
    code-push app add CodePushDemo-Android android react-native
    
    注册安卓平台.png
    注册iOS平台应用ios
    code-push app add CodePushDemo-iOS ios react-native
    
    注册苹果平台.png

    应用添加成功后就会返回对应的 ProductionStaging 两个key。
    Production 代表生产版的热更新部署,
    Staging 代表开发版的热更新部署,
    在ios中将staging的部署key复制在info.plist的CodePushDeploymentKey值中,
    在android中复制在Application的getPackages的CodePush中

    我们可以输入如下命令来查看我们刚刚添加的App
    code-push app list
    
    查看App列表.png

    4.React Native(JavaScript)端集成CodePush

    安装组件
    npm install react-native-code-push --save
    
    安装组件.png

    添加原生依赖,这里添加依赖我们使用自动添加依赖的方式

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

    JS逻辑代码实现

    主页面(App.js)
    /**
     * 热更新主页面
     * Created by 技术渣渣 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,默认为false
                        descriptionPrefix: '更新内容:', //更新说明的前缀。 默认是” Description:
                        mandatoryContinueButtonLabel: '立即更新', //强制更新的按钮文字,默认为continue
                        mandatoryUpdateMessage: '', //- 强制更新时,更新通知. Defaults to “An update is available that must be installed.”.
                        optionalIgnoreButtonLabel: '稍后', //非强制更新时,取消按钮文字,默认是ignore
                        optionalInstallButtonLabel: '后台更新', //非强制更新时,确认文字. Defaults to “Install”
                        optionalUpdateMessage: '有新版本了,是否更新?', //非强制更新时,更新通知. Defaults to “An update is available. Would you like to install it?”.
                        title: '更新提示', //要显示的更新通知的标题. 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;
    
    
    进度条页面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: () => {},
    };
    
    /* 更新进度条Modal */
    class ProgressBarModal extends PureComponent {
    
        constructor(props) {
            super(props);
            this.state = {
                title: '正在下载更新文件' // 更新提示标题
            };
        }
    
        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)}}>*温馨提示:下载完更新文件,应用会重启</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)'
        },
        // 默认进度条背景底色
        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)
        }
    })
    
    进度条页面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,
    };
    
    /* 进度条组件 */
    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.原生应用中配置CodePush

    配置Android平台

    1.编辑 android/app/build.gradle,新增依赖:

      dependencies {
           implementation project(':react-native-code-push')
      }
    

    2.编辑 android/app/build.gradle,修改buildTypes,输入对应的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.发布更新的版本

    使用前应考虑到应用检查更新的时机,是否要求强制更新,是否要求即时更新等问题。

    常见的更新时机有两种

    1.打开App就自动检查更新

    可以在页面的componentDidMount生命周期方法里通过codePush.sync来触发检查更新,如果有更新包可供下载则会在重启后生效。
    注意:这种下载和安装都是静默的,即用户不可见,本文额外增加了更新下载进度条的 modal

    codePush.sync({
             installMode: CodePush.InstallMode.IMMEDIATE,
             updateDialog: {
                  appendReleaseDescription: true, //是否显示更新description,默认为false
                  descriptionPrefix: '更新内容:', //更新说明的前缀。 默认是” Description:
                  mandatoryContinueButtonLabel: '立即更新', //强制更新的按钮文字,默认为continue
                  mandatoryUpdateMessage: '', //- 强制更新时,更新通知. Defaults to “An update is available that must be installed.”.
                  optionalIgnoreButtonLabel: '稍后', //非强制更新时,取消按钮文字,默认是ignore
                  optionalInstallButtonLabel: '后台更新', //非强制更新时,确认文字. Defaults to “Install”
                  optionalUpdateMessage: '有新版本了,是否更新?', //非强制更新时,更新通知. Defaults to “An update is available. Would you like to install it?”.
                  title: '更新提示', //要显示的更新通知的标题. Defaults to “Update available”.
                    },
      }),
    
    2.用户点击检查更新并安装

    在用户点击按钮触发事件时,在调用上面的codePush.sync方法,在有更新时弹出提示框让用户选择是否更新,如果用户点击立即更新按钮,则会进行安装包的下载,下载完成后会立即重启并生效。

    常见的更新时机有两种

    如果是强制更新需要在发布的时候指定,发布命令中配置--m true

    更新是否要求即时

    在更新配置中通过指定installMode来决定安装完成的重启时机,亦即更新生效时机
    1.codePush.InstallMode.IMMEDIATE :安装完成立即重启更新
    2.codePush.InstallMode.ON_NEXT_RESTART :安装完成后会在下次重启后进行更新
    3.codePush.InstallMode.ON_NEXT_RESUME :安装完成后会在应用进入后台后重启更新

    如何发布CodePush更新包

    本文介绍楼主使用过的发布方式,通过以下指令发布更新包:

    code-push release-react <Appname> <Platform> --t <本更新包面向的旧版本号> --dev false --d Production --des <本次更新说明> --m true
    

    注意:CodePush默认是更新Staging 环境的,如果发布生产环境的更新包,需要指定--d参数:--d Production,如果发布的是强制更新包,需要加上 --m true强制更新

    相关文章

      网友评论

        本文标题:react-native-code-push基础篇

        本文链接:https://www.haomeiwen.com/subject/ykcjwhtx.html