欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

CodePush自定义更新弹框及下载进度条

程序员文章站 2024-01-13 22:18:04
CodePush 热更新之自定义更新弹框及下载进度 先来几张弹框效果图 非强制更新场景 image 强制更新场景 image 更新包下载进度效果 image 非强制更新场景 image image 强制更新场景 image image 更新包下载进度效果 image image 核心代码 这里的热更 ......

codepush 热更新之自定义更新弹框及下载进度

先来几张弹框效果图

  • 非强制更新场景


    CodePush自定义更新弹框及下载进度条
    image
  • 强制更新场景


    CodePush自定义更新弹框及下载进度条
    image
  • 更新包下载进度效果


    CodePush自定义更新弹框及下载进度条
    image

核心代码

这里的热更新modal框,是封装成一个功能独立的组件来使用的,需不需要更新以及是否为强制更新等逻辑均在组件内实现

CodePush自定义更新弹框及下载进度条
image

updatecomp 热更新组件核心代码如下:

/**
 * created by guangqiang on 2018/3/29.
 */
import react, {component} from 'react'
import {view, text, stylesheet, modal, touchableopacity, image} from 'react-native'
import progress from './index'
import {globalstyles} from '../../../constants/globalstyles'
import {deviceinfo} from "../../../constants/deviceinfo"
import {icon} from '../../../utils/iconfont'
import codepush from "react-native-code-push"
import {toast} from "../../../utils/toast"

const code_push_key = 'je39cjdnkzqfpxgrylpxddnkezjm3ac740b8-b071-474f-afbf-369c6e4642ab'
let codepushoptions = {
  checkfrequency : codepush.checkfrequency.on_app_start
}

class progressbar extends component {

  constructor(props) {
    super(props)
    this.currprogress = 0.0
    this.syncmessage = ''
    this.state = {
      modalvisible: false,
      ismandatory: false,
      immediateupdate: false,
      updateinfo: {}
    }
  }

  codepushstatusdidchange(syncstatus) {
    if (this.state.immediateupdate) {
      switch(syncstatus) {
        case codepush.syncstatus.checking_for_update:
          this.syncmessage = 'checking for update'
          break;
        case codepush.syncstatus.downloading_package:
          this.syncmessage = 'downloading package'
          break;
        case codepush.syncstatus.awaiting_user_action:
          this.syncmessage = 'awaiting user action'
          break;
        case codepush.syncstatus.installing_update:
          this.syncmessage = 'installing update'
          break;
        case codepush.syncstatus.up_to_date:
          this.syncmessage = 'app up to date.'
          break;
        case codepush.syncstatus.update_ignored:
          this.syncmessage = 'update cancelled by user'
          break;
        case codepush.syncstatus.update_installed:
          this.syncmessage = 'update installed and will be applied on restart.'
          break;
        case codepush.syncstatus.unknown_error:
          this.syncmessage = 'an unknown error occurred'
          toast.showerror('更新出错,请重启应用!')
          this.setstate({modalvisible: false})
          break;
      }
    }
  }

  codepushdownloaddidprogress(progress) {
    if (this.state.immediateupdate) {
      this.currprogress = parsefloat(progress.receivedbytes / progress.totalbytes).tofixed(2)
      if(this.currprogress >= 1) {
        this.setstate({modalvisible: false})
      } else {
        this.refs.progressbar.progress = this.currprogress
      }
    }
  }

  syncimmediate() {
    codepush.checkforupdate(code_push_key).then((update) => {
      console.log('-------' + update)
      if (!update) {
        toast.showlongsuccess('已是最新版本!')
      } else {
        this.setstate({modalvisible: true, updateinfo: update, ismandatory: update.ismandatory})
      }
    })
  }

  componentwillmount() {
    codepush.disallowrestart()
    this.syncimmediate()
  }

  componentdidmount() {
    codepush.allowrestart()
  }

  _immediateupdate() {
    this.setstate({immediateupdate: true})
    codepush.sync(
        {deploymentkey: code_push_key, updatedialog: {}, installmode: codepush.installmode.immediate},
        this.codepushstatusdidchange.bind(this),
        this.codepushdownloaddidprogress.bind(this)
    )
  }

  rendermodal() {
    return (
        <modal
            animationtype={"none"}
            transparent={true}
            visible={this.state.modalvisible}
            onrequestclose={() => alert("modal has been closed.")}>
          <view style={styles.modal}>
            <view style={styles.modalcontainer}>
              {
                !this.state.immediateupdate ?
                    <view>
                      <image style={{width: deviceinfo.devicewidth - 60}} source={require('../../../assets/images/me/updatebg.png')} resizemode={'stretch'}/>
                      <view style={{backgroundcolor: globalstyles.white}}>
                        <view style={{marginhorizontal: 15}}>
                          <text style={{marginvertical: 20, fontsize: 17, color: globalstyles.textblockcolor, fontweight: 'bold'}}>更新内容</text>
                          <text style={{lineheight: 20}}>{this.state.updateinfo.description}</text>
                        </view>
                        <view style={{alignitems: globalstyles.center, margintop: 20}}>
                          <text style={{fontsize: 14, color: globalstyles.textgraycolor}}>wifi情况下更新不到30秒</text>
                        </view>
                        {
                          !this.state.ismandatory ?
                              <view style={{flexdirection: globalstyles.row, height: 50, alignitems: globalstyles.center, margintop: 20, bordertopcolor: globalstyles.linecolor, bordertopwidth: 1 }}>
                                <touchableopacity
                                    onpress={() => this.setstate({modalvisible: false})}>
                                  <view style={{flexdirection: globalstyles.row, alignitems: globalstyles.center, width: (deviceinfo.devicewidth - 60) / 2, height: 50, borderrightcolor: globalstyles.linecolor, borderrightwidth: 1, alignitems: globalstyles.center, justifycontent: globalstyles.center}}>
                                    <icon name={'oneicon|reject_o'} size={20} color={'#b6b6b6'}/>
                                    <text style={{fontsize: 17, fontweight: 'bold', color: globalstyles.textgraycolor, marginleft: 10}}>残忍拒绝</text>
                                  </view>
                                </touchableopacity>
                                <touchableopacity
                                    style={{flexdirection: globalstyles.row, alignitems: globalstyles.center, width: (deviceinfo.devicewidth - 60) / 2, height: 50, alignitems: globalstyles.center, justifycontent: globalstyles.center}}
                                    onpress={() => this._immediateupdate()}
                                >
                                  <view style={{backgroundcolor: '#3496fa', flex: 1, height: 40, alignitems: globalstyles.center, justifycontent: globalstyles.center, margin: 10, borderradius: 20}}>
                                    <text style={{fontsize: 17, color: globalstyles.white, fontweight: 'bold'}}>极速下载</text>
                                  </view>
                                </touchableopacity>
                              </view> :
                              <view style={{flexdirection: globalstyles.row, height: 60, alignitems: globalstyles.center, margintop: 20, bordertopcolor: globalstyles.linecolor, bordertopwidth: 1, width: deviceinfo.devicewidth - 60}}>
                                <touchableopacity
                                    style={{flexdirection: globalstyles.row, alignitems: globalstyles.center, width: (deviceinfo.devicewidth - 60), height: 50, alignitems: globalstyles.center, justifycontent: globalstyles.center}}
                                    onpress={() => this._immediateupdate()}
                                >
                                  <view style={{backgroundcolor: '#3496fa', flex: 1, height: 40, alignitems: globalstyles.center, justifycontent: globalstyles.center, borderradius: 20, marginhorizontal: 40}}>
                                    <text style={{fontsize: 17, color: globalstyles.white, fontweight: 'bold'}}>立即更新</text>
                                  </view>
                                </touchableopacity>
                              </view>
                        }
                      </view>
                    </view> :
                    <view>
                      <image style={{width: deviceinfo.devicewidth - 60}} source={require('../../../assets/images/me/updatebg.png')} resizemode={'stretch'}/>
                      <view style={{backgroundcolor: globalstyles.white, paddingvertical: 20, backgroundcolor: globalstyles.white, alignitems: globalstyles.center}}>
                        <progress
                            ref="progressbar"
                            progresscolor={'#89c0ff'}
                            style={{
                              margintop: 20,
                              height: 10,
                              width: deviceinfo.devicewidth - 100,
                              backgroundcolor: globalstyles.bgcolor,
                              borderradius: 10,
                            }}
                        />
                        <view style={{alignitems: globalstyles.center, marginvertical: 20}}>
                          <text style={{fontsize: 14, color: globalstyles.textgraycolor}}>版本正在努力更新中,请等待</text>
                        </view>
                      </view>
                    </view>
              }
            </view>
          </view>
        </modal>
    )
  }

  render(){
    return(
        <view style={styles.container}>
          {this.rendermodal()}
        </view>
    )
  }
}

const styles = stylesheet.create({
  container: {
    flex: 1,
    justifycontent: 'center',
    alignitems: 'center',
    backgroundcolor: globalstyles.bgcolor
  },
  modal: {
    height: deviceinfo.deviceheight,
    width: deviceinfo.devicewidth,
    alignitems: 'center',
    justifycontent: 'center',
    backgroundcolor: 'rgba(0,0,0,0.3)'
  },
  modalcontainer: {
    marginhorizontal: 60,
    borderbottomleftradius: 10,
    borderbottomrightradius: 10,
  }
})

export default codepush(codepushoptions)(progressbar)

下载进度条组件progress 这里也是封装成一个组件,核心代码如下:

CodePush自定义更新弹框及下载进度条
image
/**
 * created by guangqiang on 2018/3/29.
 */
import react, {component}from 'react'
import {view, stylesheet, animated, easing}from 'react-native'

import proptypes from 'prop-types'

export default class cusprogressbar extends component {

  static proptypes = {
    ...view.proptypes,
    // 当前进度
    progress: proptypes.number,
    // second progress进度
    buffer: proptypes.number,
    // 进度条颜色
    progresscolor: proptypes.string,
    // buffer进度条颜色
    buffercolor: proptypes.string,
    // 进度动画时长
    progressaniduration: proptypes.number,
    // buffer动画时长
    bufferaniduration: proptypes.number
  }

  static defaultprops = {
    // 进度条颜色
    progresscolor: 'white',
    // buffer进度条颜色
    buffercolor: 'rgba(255,0,0,0.7)',
    // 进度条动画时长
    progressaniduration: 100,
    // buffer进度条动画时长
    bufferaniduration: 100
  }

  constructor(props) {
    super(props)
    this._progressani = new animated.value(0)
    this._bufferani = new animated.value(0)
  }

  componentwillreceiveprops(nextprops) {
    this._progress = nextprops.progress
    this._buffer = nextprops.buffer
  }

  componentwillmount() {
    this._progress = this.props.progress
    this._buffer = this.props.buffer
  }

  render() {
    return (
        <view
            style={[styles.container,this.props.style]}
            onlayout={this._onlayout.bind(this)}>
          <animated.view
              ref="progress"
              style={{
                position:'absolute',
                width: this._progressani,
                backgroundcolor:this.props.progresscolor,
                borderradius: 10
              }}/>
          <animated.view
              ref="buffer"
              style={{
                position:'absolute',
                width: this._bufferani,
                backgroundcolor:this.props.buffercolor,
                borderradius: 10,
              }}/>
        </view>
    )
  }

  _onlayout({nativeevent: {layout:{width, height}}}) {
    // 防止多次调用,当第一次获取后,后面就不再去获取了
    if (width > 0 && this.totalwidth !== width) {
      // 获取progress控件引用
      let progress = this._getprogress()
      // 获取buffer控件引用
      let buffer = this._getbuffer()
      // 获取父布局宽度
      this.totalwidth = width
      //给progress控件设置高度
      progress.setnativeprops({
        style: {
          height: height
        }
      })

      // 给buffer控件设置高度
      buffer.setnativeprops({
        style: {
          height: height
        }
      })

      // 开始执行进度条动画
      this._startaniprogress(this.progress)
      // 开始执行buffer动画
      this._startanibuffer(this.buffer)
    }
  }

  _startaniprogress(progress) {
    if (this._progress >= 0 && this.totalwidth !== 0) {
      animated.timing(this._progressani, {
        tovalue: progress * this.totalwidth,
        duration: this.props.progressaniduration,
        easing: easing.linear
      }).start()
    }
  }

  _startanibuffer(buffer) {
    if (this._buffer >= 0 && this.totalwidth !== 0) {
      animated.timing(this._bufferani, {
        tovalue: buffer * this.totalwidth,
        duration: this.props.bufferaniduration,
      }).start()
    }
  }

  _getprogress() {
    if (typeof this.refs.progress.refs.node !== 'undefined') {
      return this.refs.progress.refs.node
    }
    return this.refs.progress._component
  }

  _getbuffer() {
    if (typeof this.refs.buffer.refs.node !== 'undefined') {
      return this.refs.buffer.refs.node;
    }
    return this.refs.buffer._component;
  }
}

object.defineproperty(cusprogressbar.prototype, 'progress', {
  set(value){
    if (value >= 0 && this._progress !== value) {
      this._progress = value;
      this._startaniprogress(value);
    }
  },
  get() {
    return this._progress;
  },
  enumerable: true,
})

object.defineproperty(cusprogressbar.prototype, 'buffer', {
  set(value){
    if (value >= 0 && this._buffer !== value) {
      this._buffer = value;
      this._startanibuffer(value);
    }
  },
  get() {
    return this._buffer;
  },
  enumerable: true,
})

const styles = stylesheet.create({
  container: {
    height: 4,
    backgroundcolor: 'blue'
  }
})

updatecomp组件中的热更新核心代码讲解

CodePush自定义更新弹框及下载进度条
image

这我们在updatecomp 组件中,在 componentwillmount 的生命周期函数中,我们调用codepush提供的这两个函数:并在syncimmediate 函数中,我们调用codepush的checkforupdate 函数来检查是否已有新版本,以及新版本的信息等,具体代码实现如下:

CodePush自定义更新弹框及下载进度条
image

注意:

codepush有两个代理函数我们需要调用:

CodePush自定义更新弹框及下载进度条
image
  • codepushstatusdidchange: codepush状态的变化的钩子函数

  • codepushdownloaddidprogress: codepush下载更新包的进度钩子函数

当我们处理完上面的内容,codepush的基本功能我们就处理完毕了,剩下的工作就是处理一些逻辑了,包括该不该弹更新框,以及更新弹框和更新进度的处理

总结:

本篇教程主要是讲解codepush中如何处理安装包的下载进度,以及如何自定义更新弹框和下载进度条,上面的弹框功能和下载进度条功能基本都已处理完毕,可以直接复制两个组件代码到自己项目中,稍作修改即可使用。如果还有小伙伴对codepush详细的接入流程不熟悉的,请点击查看作者的codepush热更新详细接入教程一文,如果还有其他的问题,也可以简书留言或者进群提问

rn实战总结

    • 作者react native开源项目onem地址(按照企业开发标准搭建框架完成开发的):https://github.com/guangqiang-liu/onem:欢迎小伙伴们 star
    • 作者简书主页:包含60多篇rn开发相关的技术文章欢迎小伙伴们:多多关注,多多点赞
    • 作者react native qq技术交流群:620792950 欢迎小伙伴进群交流学习
    • 友情提示:在开发中有遇到rn相关的技术问题,欢迎小伙伴加入交流群(620792950),在群里提问、互相交流学习。交流群也定期更新最新的rn学习资料给大家,谢谢大家支持!