最近在研究热更新技术,看了网上各个大佬的博客,整体流程上总是卡壳。跳了几天坑,刚刚终于把简单的热更新流程跑通,现在也正在一边学习更新,一边整理资料,在此篇博客上记录操作流程,希望我的实践能帮助各位同行少走弯路,快速掌握热更新技术。

热更新流程图

热更新步骤

1、热更新服务器部署

2、客户端配置

3、客户端热更新代码

4、打包最新代码

5、推送你的新包到客户端

6、疑问

Q: “苹果应用商店和android应用商店允不允许使用热更新?”

A: “都允许。”

苹果允许使用热更新Apple's developer agreement 条款3.3.2, 但是规定不能弹框提示用户更新,影响用户体验。苹果禁的是 rollout.io, JSPatch 这类具备修改原生代码能力的框架。 Google Play也允许热更新,但必须弹框告知用户更新。在中国的android市场发布时,都必须关闭更新弹框,否则会在审核应用时以“请上传最新版本的二进制应用包”驳回应用。

Q: “react-native 开发环境更新模式是否可以直接用在生产环境下?”

A: “不能。”

Q: “code-push使用复杂么?”

A: “不复杂。很多网上的文章说复杂,是因为作者没有仔细理解官方文档,而且认为踩坑了。”

Q: “为什么推荐code-push?”

A: ”非常好。除了满足基本更新功能外,还有统计,hash计算容错和补丁更新功能。微软的项目,大公司技术有保障,而且开源。1.直接对用户部署代码更新2.管理 Alpha,Beta 和生产环境应用3.支持 React Native 和 Cordova4.支持JavaScript 文件与图片资源的更新5.CodePush开源了react-native版本,react-native-code-push托管在GitHub上。

codepushserver本地部署,动动脑,动动手,更新速度蹭蹭的

此处是在mac平台上搭建的codepush服务器。

安装完成后,会弹出初始密码,粗细的我,随手点了OK,此处挖坑半天。

权限密码问题可以参考这篇博客,其它的都是小问题

将mysql的命令添加到系统中

(1).进入/usr/local/mysql/bin,查看此目录下是否有mysql

(2).执行vim ~/.bash_profile

在该文件中添加mysql/bin的目录

PATH=$PATH:/usr/local/mysql/bin

添加完成后,按esc,然后输入:wq保存。

(3).最后在命令行输入source ~/.bash_profile

(4). 通过mysql -uroot -p登录mysql, 输入之前保存的密码

(5).重置mysql初始密码

登录mysql后输入如下命令重置密码:

SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpassword');

若数据库初始化失败,修改给初始密码后便可成功

$ cd 存放code-push-server文件夹

$ git clone https://github.com/lisong/code-push-server.git

$ cd code-push-server

$ npm install

$ vim ./bin/db

#别高兴,路漫漫其修远兮

#初始化mysql 数据库

$ ./bin/db init --dbhost localhost --dbuser root --dbpassword xx00(刚刚刚安 装的数据库密码)

#若错误使用vim编辑器或者subtext修改code-push-server/bin/db里面的默认数据库密码

修改配置文件:code-push-server/config/config.js

如果文件放在七牛上,就修改七牛的配置,本次搭建在电脑本地

修改数据库密码就行,端口都不用修改

db: {

username: process.env.RDS_USERNAME || "root",

password: process.env.RDS_PASSWORD || 'gyq123456',

database: process.env.DATA_BASE || "codepush",

host: process.env.RDS_HOST || "127.0.0.1",

port: process.env.RDS_PORT || 3306,

dialect: "mysql",

logging: false

},

local: {

// 一些产生的二进制文件会存放在这里,需要你创建文件夹

storageDir:"/Users/chmtech003/Sites/storage",

// ip,改为你电脑的ip,其他不用换

downloadUrl: "http://192.168.163.108:3000/download",

// 照着写

public: '/download'

},

jwt: {

// Recommended: 63 random alpha-numeric characters

// 网址打开: https://www.grc.com/passwords.htm

// 找到63 random alpha-numeric characters 复制放在放在里面就行

tokenSecret:'pq7fHGHvsv3JPZwMZqj79I7eEPvErMUPlM5hFZUyGdPDV9MSQfLZTCLn25sehhR'

},

获取登录token

#启动服务 浏览器中打开 http://127.0.0.1:3000

$ ./bin/www

#使用 code-push-cli 管理 CodePushServer

$ npm install code-push-cli@latest -g

$ code-push login http://127.0.0.1:3000 #login in browser account:admin password:123456

#登录成功后,将浏览器窗口获取到的token粘贴到需要输入token的终端窗口,登录成功

# code-push logout 登出

# 修改登录密码方式,不推荐修改,难记

$ curl -X PATCH -H "Authorization: Bearer mytoken" -H "Accept: application/json" -H "Content-Type:application/json" -d '{"oldPassword":"123456","newPassword":"654321"}' http://127.0.0.1:3000/users/password

获取客户端key

$ code-push app add 工程名-android #android版

$ code-push app add 工程名-ios #ios版

Successfully added the "HotUpdateDemo-android" app, along with the following default deployments:

┌────────────┬────────────────────────────────── ─────┐

│ Name │ Deployment Key │

├────────────┼────────────────────────────────── ─────┤

│ Production │ Praagnzym0XuijhvH1LRNSdK4kPO4ksvOXqog │

├────────────┼────────────────────────────────── ─────┤

│ Staging │ jcgp79y1MT8YSZzPmfs60LbY8vYP4ksvOXqog │

└────────────┴────────────────────────────────── ─────┘

分别获得iOS和安卓的Deployment Key,推送的时候通过key将app和服务器端关联,推送是分开的,所以iOS,Android分别在项目名后加入后缀区分

# 命令汇总

$code-push app list 可以查看创建好的APP

$code-push deployment ls -k 查看Deployment Key

$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的所有权转移到另外一个账号

客户端配置code-push提供的demo(建议自己创建,避免环境问题)

客户端环境搭建

创建一个RN的练手项目,然后配置上面获取到的Deployment Key 配置

$cd ./ 存放项目目录

$react-native init HotUpdateDemo

$ cd ./HotUpdateDemo

$ npm install --save react-native-code-push #安装react-native-code-push

$ react-native link react-native-code-push #连接到项目中,提示输入配置可以先行忽略

iOS添加Deployment Key

在info.plist中添加key

CodePushDeploymentKey

Deployment Key

CodePushServerURL

填入你的电脑IP:eg. http://192.168.0.7:3000

Android添加Deployment Key

在MainApplication.java文件中

@Override

protected List getPackages() {

return Arrays.asList(

new MainReactPackage(),

new CodePush(

"填入Deployment Key ",

MainApplication.this,

BuildConfig.DEBUG,

"填入你的电脑IP:eg. http://192.168.0.7:3000"

)

);

}

CodePush的API有个大致的了解点击查看来源

import CodePush from "react-native-code-push";

--CodePush.sync--

CodePush.sync(options: Object, syncStatusChangeCallback: function(syncStatus: Number), downloadProgressCallback: function(progress: DownloadProgress)): Promise;

通过调用该方法CodePush会帮我们自动完成检查更新,下载,安装等一系列操作。除非我们需要自定义UI表现,不然直接用这个方法就可以了。

sync方法,提供了如下属性以允许你定制sync方法的默认行为

deploymentKey (String): 部署key,指定你要查询更新的部署秘钥,默认情况下该值来自于Info.plist(Ios)和MianActivity.java(Android)文件,你可以通过设置该属性来动态查询不同部署key下的更新。

installMode (codePush.InstallMode): 安装模式,用在向CodePush推送更新时没有设置强制更新(mandatory为true)的情况下,默认codePush.InstallMode.ON_NEXT_RESTART即下一次启动的时候安装。

mandatoryInstallMode (codePush.InstallMode):强制更新,默认codePush.InstallMode.IMMEDIATE。

minimumBackgroundDuration (Number):该属性用于指定app处于后台多少秒才进行重启已完成更新。默认为0。该属性只在installMode为InstallMode.ON_NEXT_RESUME情况下有效。

updateDialog (UpdateDialogOptions) :可选的,更新的对话框,默认是null,包含以下属性

appendReleaseDescription (Boolean) - 是否显示更新description,默认false

descriptionPrefix (String) - 更新说明的前缀。 默认是” Description: “

mandatoryContinueButtonLabel (String) - 强制更新的按钮文字. 默认 to “Continue”.

mandatoryUpdateMessage (String) - 强制更新时,更新通知. Defaults to “An update is available that must be installed.”.

optionalIgnoreButtonLabel (String) - 非强制更新时,取消按钮文字. Defaults to “Ignore”.

optionalInstallButtonLabel (String) - 非强制更新时,确认文字. Defaults to “Install”.

optionalUpdateMessage (String) - 非强制更新时,更新通知. Defaults to “An update is available. Would you like to install it?”.

title (String) - 要显示的更新通知的标题. Defaults to “Update available”.

--CodePush.allowRestart--

CodePush.allowRestart(): void;

允许重新启动应用以完成更新。

如果一个CodePush更新将要发生并且需要重启应用(e.g.设置InstallMode.IMMEDIATE模式),但由于调用了disallowRestart方法而导致APP无法通过重启来完成更新,可以调用此方法来解除disallowRestart限制。

但在如下四种情况下,CodePush将不会立即重启应用:

自上一次disallowRestart被调用,没有新的更新。

有更新,但installMode为InstallMode.ON_NEXT_RESTART的情况下。

有更新,但installMode为InstallMode.ON_NEXT_RESUME,并且程序一直处于前台,并没有从后台切换到前台的情况下。

自从上次disallowRestart被调用,没有再调用restartApp。

--CodePush.checkForUpdate--

codePush.checkForUpdate(deploymentKey: String = null): Promise;

向CodePush服务器查询是否有更新。

该方法返回Promise,有如下两种值:

null 没有更新

通常有如下情况导致RemotePackage为null:

当前APP版本下没有部署新的更新版本。也就是说没有想CodePush服务器推送基于当前版本的有关更新。

CodePush上的更新和用户当前所安装的APP版本不匹配。也就是说CodePush服务器上有更新,但该更新对应的APP版本和用户安装的当前版本不对应。

当前APP已将安装了最新的更新。

部署在CodePush上可用于当前APP版本的更新被标记成了不可用。

部署在CodePush上可用于当前APP版本的更新是"active rollout"状态,并且当前的设备不在有资格更新的百分比的设备之内。

A RemotePackage instance

有更新可供下载。

--CodePush.disallowRestart--

CodePush.disallowRestart(): void;

不允许立即重启用于以完成更新。

--CodePush.getUpdateMetadata --

CodePush.getUpdateMetadata(updateState: UpdateState = UpdateState.RUNNING): Promise;

获取当前已安装更新的元数据(描述、安装时间、大小等)。

--CodePush.notifyAppReady --

codePush.notifyAppReady(): Promise;

通知CodePush,一个更新安装好了。当你检查并安装更新,(比如没有使用sync方法去handle的时候),这个方法必须被调用。否则CodePush会认为update失败,并rollback当前版本,在app重启时。

当使用sync方法时,不需要调用本方法,因为sync会自动调用。

--CodePush.restartApp --

CodePush.restartApp(onlyIfUpdateIsPending: Boolean = false): void;

立即重启app。

当以下情况时,这个方式是很有用的:

app 当 调用 sync 或 LocalPackage.install 方法时,指定的 install mode是 ON_NEXT_RESTART 或 ON_NEXT_RESUME时 。 这两种情况都是当app重启或resume时,更新内容才能被看到。

在特定情况下,如用户从其它页面返回到APP的首页时,这个时候调用此方法完成过更新对用户来说不是特别的明显。因为强制重启,能马上显示更新内容。

熟悉了上面的api,现在再来看CodePush提供的更新demo是不是很简单了

点击查看demo

这个是测试代码,全选,替换app.js文件中的代码即可,有大兄弟使用下面代码更新报错,若遇到同类问题,点击上面的Demo替换。

/**

* Sample React Native App

* https://github.com/facebook/react-native

* @flow

*/

import React, { Component } from 'react';

import {

AppRegistry,

Dimensions,

Image,

StyleSheet,

Text,

TouchableOpacity,

Platform,

View,

} from 'react-native';

import CodePush from "react-native-code-push";

//var Dimensions = require('Dimensions');

const instructions = Platform.select({

ios: 'Press Cmd+R to reload,\n' +

'Cmd+D or shake for dev menu',

android: 'Double tap R on your keyboard to reload,\n' +

'Shake or press menu button for dev menu',

});

export default class App extends Component {

constructor() {

super();

this.state = { restartAllowed: true ,

syncMessage: "我是小更新" ,

progress: false};

}

// 监听更新状态

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." });

break;

case CodePush.SyncStatus.AWAITING_USER_ACTION:

this.setState({ syncMessage: "Awaiting user action." });

break;

case CodePush.SyncStatus.INSTALLING_UPDATE:

this.setState({ syncMessage: "Installing update." });

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 });

}

// 允许重启后更新

toggleAllowRestart() {

this.state.restartAllowed

? CodePush.disallowRestart()

: CodePush.allowRestart();

this.setState({ restartAllowed: !this.state.restartAllowed });

}

// 获取更新数据

getUpdateMetadata() {

CodePush.getUpdateMetadata(CodePush.UpdateState.RUNNING)

.then((metadata: LocalPackage) => {

this.setState({ syncMessage: metadata ? JSON.stringify(metadata) : "Running binary version", progress: false });

}, (error: any) => {

this.setState({ syncMessage: "Error: " + error, progress: false });

});

}

/** Update is downloaded silently, and applied on restart (recommended) 自动更新,一键操作 */

sync() {

CodePush.sync(

{},

this.codePushStatusDidChange.bind(this),

this.codePushDownloadDidProgress.bind(this)

);

}

/** Update pops a confirmation dialog, and then immediately reboots the app 一键更新,加入的配置项 */

syncImmediate() {

CodePush.sync(

{ installMode: CodePush.InstallMode.IMMEDIATE, updateDialog: true },

this.codePushStatusDidChange.bind(this),

this.codePushDownloadDidProgress.bind(this)

);

}

render() {

let progressView;

if (this.state.progress) {

progressView = (

{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received

);

}

return (

可以修改此处文字,查看是否更新成功!

Press for background sync

Press for dialog-driven sync

{progressView}

Restart { this.state.restartAllowed ? "allowed" : "forbidden"}

Press for Update Metadata

{this.state.syncMessage || ""}

);

}

}

const styles = StyleSheet.create({

container: {

flex: 1,

alignItems: "center",

backgroundColor: "#F5FCFF",

paddingTop: 50

},

image: {

margin: 30,

width: Dimensions.get("window").width - 100,

height: 365 * (Dimensions.get("window").width - 100) / 651,

},

messages: {

marginTop: 30,

textAlign: "center",

},

restartToggleButton: {

color: "blue",

fontSize: 17

},

syncButton: {

color: "green",

fontSize: 17

},

welcome: {

fontSize: 20,

textAlign: "center",

margin: 20

},

});

热更新操作

最简单的方式

cd ./工程目录

测试环境执行:

$ code-push release-react 项目名-android android

生产环境执行:

$ code-push release-react 项目名-android android -d Production

一直没成功过,不知道为什么,这种一句命令的更新超级简单,适不适用,待考虑

通过上传包来进行更新

--离线包--

$ cd ./工程目录

$ mkdir bundles

$ react-native bundle --platform 平台 --entry-file 启动文件 --bundle-output 打包js输出文件 --assets-dest 资源输出目录 --dev 是否调试。

eg.

$react-native bundle --platform ios --entry-file index.js --bundle-output ./ios/bundles/main.jsbundle --dev false

--发布更新--

$ code-push release --deploymentName: 更新环境 --description: 更新描述 --mandatory: 是否强制更新

code-push release HotUpdateDemo-ios ./ios/bundles/main.jsbundle 1.0.0 --deploymentName Production --description "我是新包,很新的那种" --mandatory true

调试技巧

***iOS***

把基础版离线包 main.jsbundle放入工程,在appdelegate中,只保留 jsCodeLocation = [CodePush bundleURL];安装初始版本

// #ifdef DEBUG

// jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];

// #else

jsCodeLocation = [CodePush bundleURL];

// #endif

***Android***

在Android可以将开发环境的调试地址改为一个不可用的地址,这样APP就无法连接到NodeJS服务器了,自然也就不能从NodeJS服务器下载bundle进行更新了,它也只能乖乖的等待从CodePush服务器下载更新包进行更新了。

热更新技术持续补充中。。。

native react 更新机制_react-native热更新全方位讲解相关推荐

  1. native react 更新机制_React Native 热更新实现(客户端 + 服务器端)

    1. 背景 目前,大家考虑使用React Native 技术的关键点主要有三个: iOS和Android端可以使用统一的语言进行构建,并且部分组件代码可以实现共用 热更新能力,无需发布版本即可实现升级 ...

  2. native react 更新机制_React Native - 组件的生命周期详解(附:各阶段调用的方法)...

    一个 React Native 组件从它被 React Native框架加载,到最终被 React Native 框架卸载,会经历一个完整的生命周期. 在这个生命周期中,我们可以定义一些生命周期函数, ...

  3. native react 常用指令_React Native入门基础篇(一)

    学习一次,随处书写.(以下文字来自各大网上资料整理而来,侵删!) 概述 使用React为Android和iOS创建本机应用 React Native将本机开发的最佳部分与React(用于构建用户界面的 ...

  4. native react 折线图_react native中使用echarts

    开发平台:mac pro node版本:v8.11.2 npm版本:6.4.1 react-native版本:0.57.8 native-echarts版本:^0.5.0 目标平台:android端收 ...

  5. native react 常用指令_React Native 常用的 15 个库

    点赞再看,养成习惯 本文 GitHub https://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料.欢迎Star和完善,大家 ...

  6. native react 常用指令_React Native 常用命令或快捷键合集

    命令: react-native init "项目名称" : 运行React Native的初始化命令,创建一个项目 npm install : 需要在工程目录下用此命令将依赖包下 ...

  7. native react 图片裁剪_react native 头像上传 react-native-image-crop-picker

    之前用的是react-native-image-picker,但是当往服务器端传的时候才发现,因为没有图片裁切,所以图片过大,无法保存,所以只好更换成react-native-image-crop-p ...

  8. Android热更新初探,Bugly热更新的集成和使用(让你的应用轻松具备热更新能力)

    介绍   在介绍Bugly之前,需要先向大家简单介绍下一些热更新的相关内容.当前市面的热补丁方案有很多,其中比较出名的有阿里的AndFix.美团的Robust以及QZone的超级补丁方案.但它们都存在 ...

  9. springboot基于Elasticsearch6.x版本进行ES同义词、停用词(停止词)插件配置,远程词典热加载及数据库词典热加载总结,es停用词热更新,es同义词热更新

    前言:ES版本差异较大,建议跨版本的同学,可以先了解一下版本区别,建议不要跨版本使用插件或者进行项目调试. 本总结主要基于6.x版本的6.5.1(6.2.2实测可用),分词器为IK,下载地址:http ...

  10. React Native带你一步步实现热更新(CodePush-Android)

    前言:无奈研究了一下CodePush,遇到了很多坑-- 但是原理呢不是很难理解,就是配置有点多,原理可以简单的参考一下我之前的一篇博客React-Native 热更新尝试(Android),下面说一下 ...

最新文章

  1. 从零开始在ubuntu上安装和使用k8s集群及报错解决
  2. 、PHP只能访问mysql_php中 mysql函数不能调用,只有mysql_query()可以用
  3. python真假命题_程序员冒死揭开家暴内幕:教女友学Python是道送命题!
  4. php打开目录文件类型,php中打开目录并输出目录文件实现代码
  5. 关于Ubuntu的默认python版本
  6. 2021牛客OI赛前集训营-提高组(第五场)D-牛牛的border【SAM】
  7. Linux网络故障排查命令(ifconfig、ping、telnet、netstat、lsof、nc、curl、tcpdump)
  8. Python 本身真的没什么用!
  9. 链上体育和游戏平台Rage.Fan完成160万美元私募轮融资
  10. 【ACM ICPC 2011–2012, Northeastern European Regional Contest】Interactive Permutation Guessing【交互题】
  11. java基础——李兴华视频
  12. 一次性说清楚秒验(本机号码一键登录)
  13. 大华条码秤数据同步发送数据格式
  14. 详细vue脚手架安装教程
  15. ZIGBEE 工程内区分终端与协调器
  16. 建无根树+无根树转有根树
  17. 风险偏好情绪有所改善,非美低位反弹
  18. 18年下半年读书清单一览
  19. 启发:vs运行时提示:应用程序无法正常启动(oxc000007b)。请单击确定关闭应用程序
  20. 人员招聘与培训实务【3】

热门文章

  1. mysql 重置表索引_MySQL如何进行索引重建操作?
  2. 关于Oxygen版 Eclipse JSP或html 中option标签使用c:if报错的问题
  3. 3行Python代码采集B站(弹幕、评论、用户)数据
  4. Python操作数据库(二)
  5. Hadoop 生态系列之 1.0 和 2.0 架构
  6. 微信公众平台如何获得openid
  7. Android中文语音合成(TTS)各家引擎对比 .
  8. 客户端脚本调用服务器端动态内容,移动到链接显示预览
  9. 【opencv学习】RANSAC算法在图像拼接中的应用实战
  10. [可视化-tableau]tableau的学习实践入门篇