原文地址:https://github.com/remobile/react-native-update
React Native 更新 (remobile)
更新js版本和app版本
安装
npm install @remobile/react-native-update --save
安装 (Android)
...
include ':react-native-update'
project(':react-native-update').projectDir
= new File(settingsDir, '../node_modules/@remobile/react-native-update/android')
- 在
android/app/build.gradle
...
dependencies {
...
compile project(':react-native-update')
}
- 注册模块 (在 MainActivity.java)
......
import com.remobile.update.RCTUpdateMgr; // <--- import
......
public class MainApplication extends Application implements ReactApplication {
private RCTUpdateMgr mUpdateMgr; // <------ add here
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
......
@Override
protected String getJSBundleFile() {
return mUpdateMgr.getBundleUrl(); // <------ change here
}
@Override
protected List<ReactPackage> getPackages() {
mUpdateMgr = new RCTUpdateMgr(MainActivity.activity);
return Arrays.<ReactPackage>asList(
......
mUpdateMgr.getReactPackage(), // <------ add here
......
);
}
};
......
}
用法
例子
UpdatePage.js
定义全局变量
'use strict';
import React, { Component } from 'react';
import {
View,
StyleSheet,
Platform,
Text,
Image,
TouchableHighlight
} from 'react-native';
import ReactNative form 'react-native'
import Update form '@remobile/react-native-update'
var
STATUS_GET_VERSION = 0, // 获得版本号状态
STATUS_HAS_VEW_VERSION = 1, // 有新版本状态
STATUS_HAS_NOT_VEW_VERSION = 2, // 没有新版本号状态
STATUS_DOWNLOAD_APK_PROGESS = 3, // 下载apk状态
STATUS_DOWNLOAD_JS_PROGESS = 4, // 下载js状态
STATUS_UNZIP_JS_PROGESS = 5, // 解压js状态
STATUS_GET_VERSION_ERROR = 6, // 获得版本异常状态
STATUS_DOWNKOAD_APK_ERROR = 7, // 下载apk异常状态
STATUS_DOWNKOAD_JS_ERROR = 8, // 下载js异常状态
STATUS_UNZIP_JS_ERROR = 9, // 解压js异常状态
STATUS_FAILED_INSTALL_ERROR = 10, //失败安装异常状态
STATUS_UPDATE_END = 11; //更新结束状态
var
ERROR_NULL = 0,
ERROR_DOWNKOAD_APK = 1, // 异常下载apk
ERROR_DOWNKOAD_JS = 2, // 异常下载js
ERROR_FAILED_INSTALL = 3, // 失败安装
ERROR_UNZIP_JS = 4; // 解压js
var PROGRESS_WIDTH = sr.tw*0.7;
var {Button, ProgressBar} = COMPONENTS;
进度view
var ProgressInfo = React.createClass({
render() {
const { progress } = this.props;
if (progress < 1000) {
return (
<View>
<Text>{this.props.title} [{progress}%]</Text>
<ProgressBar
fillStyle={{}}
backgroundStyle={{backgroundColor: '#cccccc', borderRadius: 2}}
style={{marginTop: 10, width:PROGRESS_WIDTH}}
progress={progress/100.0}
/>
<View style={styles.progressText}>
<Text>0</Text>
<Text>100</Text>
</View>
</View>
);
} else {
let size = progress/1000/1024/1024;
return (
<View style={{flex: 1, alignItems: 'center'}}>
<Text>{this.props.title} [{size.toFixed(2)} M]</Text>
</View>
);
}
}
});
页面显示view
函数
module.exports = React.createClass({
getInitialState() {
const {options} = this.props;
return {
options,
status: !options ?
STATUS_GET_VERSION : options.newVersion ? STATUS_HAS_VEW_VERSION : STATUS_HAS_NOT_VEW_VERSION,
progress: 0,
};
},
componentWillMount() {
if (!this.state.options) {
Update.checkVersion({
versionUrl: app.route.ROUTE_VERSION_INFO_URL,
iosAppId: CONSTANTS.IOS_APPID,
}).then((options)=>{
this.setState({options, status: !options ?
STATUS_GET_VERSION_ERROR : options.newVersion ?
STATUS_HAS_VEW_VERSION : STATUS_HAS_NOT_VEW_VERSION});
})
}
},
onError(errCode) {
if (errCode == ERROR_DOWNKOAD_APK) {
this.setState({status: STATUS_DOWNKOAD_APK_ERROR});
} else if (errCode == ERROR_DOWNKOAD_JS) {
this.setState({status: STATUS_DOWNKOAD_JS_ERROR});
} else if (errCode == ERROR_FAILED_INSTALL) {
this.setState({status: STATUS_FAILED_INSTALL_ERROR});
} else if (errCode == ERROR_UNZIP_JS) {
this.setState({status: STATUS_UNZIP_JS_ERROR});
}
},
doUpdate() {
const {jsVersionCode, trackViewUrl} = this.state.options;
if (jsVersionCode !== undefined) {
Update.updateJS({
jsVersionCode,
jsbundleUrl: app.isandroid?app.route.ROUTE_JS_ANDROID_URL:app.route.ROUTE_JS_IOS_URL,
onDownloadJSProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_JS_PROGESS,progress})},
onUnzipJSProgress:(progress)=>{this.setState({status: STATUS_UNZIP_JS_PROGESS,progress})},
onUnzipJSEnd:()=>{this.setState({status: STATUS_UPDATE_END})},
onError:(errCode)=>{this.onError(errCode)},
});
} else {
Update.updateApp({
trackViewUrl,
androidApkUrl:app.route.ROUTE_APK_URL,
androidApkDownloadDestPath:'/sdcard/yxjqd.apk',
onDownloadAPKProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_APK_PROGESS,progress})},
onError:(errCode)=>{this.onError(errCode)},
});
}
},
页面的render
render() {
var components = {};
const {
currentVersion,
newVersion,
description
} = this.state.options||{currentVersion:Update.getVersion()};
components[STATUS_GET_VERSION] = (
<Text style={styles.textInfo}>正在获取版本号</Text>
);
components[STATUS_HAS_NOT_VEW_VERSION] = (
<Text style={styles.textInfo}>当前版本已经是最新版本</Text>
);
components[STATUS_GET_VERSION_ERROR] = (
<Text style={styles.textInfo}>获取版本信息失败,请稍后再试</Text>
);
components[STATUS_DOWNKOAD_APK_ERROR] = (
<Text style={styles.textInfo}>下载apk文件失败,请稍后再试</Text>
);
components[STATUS_DOWNKOAD_JS_ERROR] = (
<Text style={styles.textInfo}>下载js bundle失败,请稍后再试</Text>
);
components[STATUS_UNZIP_JS_ERROR] = (
<Text style={styles.textInfo}>解压js bundle失败,请稍后再试</Text>
);
components[STATUS_FAILED_INSTALL_ERROR] = (
<Text style={styles.textInfo}>你放弃了安装</Text>
);
components[STATUS_HAS_VEW_VERSION] = (
<View style={styles.textInfoContainer}>
<Text style={styles.textInfo}>发现新版本{newVersion}</Text>
<View style={styles.descriptionContainer}>
{
description && description.map((item, i)=>{
return (
<Text style={styles.textInfo}
key={i}>{(i+1)+'. '+item}
</Text>
)
})
}
</View>
<Button onPress={this.doUpdate}
style={styles.button_layer}
textStyle={styles.button_text}>立即更新
</Button>
</View>
);
components[STATUS_DOWNLOAD_APK_PROGESS] = (
<ProgressInfo
title="正在下载APK"
progress={this.state.progress} />
);
components[STATUS_DOWNLOAD_JS_PROGESS] = (
<ProgressInfo
title="正在下载Bundle文件"
progress={this.state.progress} />
);
components[STATUS_UNZIP_JS_PROGESS] = (
<ProgressInfo
title="正在解压Bundle文件"
progress={this.state.progress} />
);
components[STATUS_UPDATE_END] = (
<Text>正在重启...</Text>
);
return (
……
);
},
});
样式
var styles = StyleSheet.create({
……
});
UpdateInfoBox.js
全局变量
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var {
StyleSheet,
View,
Text,
Image,
TouchableOpacity,
} = ReactNative;
var Update = require('@remobile/react-native-update');
var
STATUS_HAS_VEW_VERSION = 0,
STATUS_DOWNLOAD_APK_PROGESS = 1,
STATUS_DOWNLOAD_JS_PROGESS = 2,
STATUS_UNZIP_JS_PROGESS = 3,
STATUS_DOWNKOAD_APK_ERROR = 4,
STATUS_DOWNKOAD_JS_ERROR = 5,
STATUS_UNZIP_JS_ERROR = 6,
STATUS_FAILED_INSTALL_ERROR = 7,
STATUS_UPDATE_END = 8;
var
ERROR_NULL = 0,
ERROR_DOWNKOAD_APK = 1,
ERROR_DOWNKOAD_JS = 2,
ERROR_FAILED_INSTALL = 3,
ERROR_UNZIP_JS = 4;
var PROGRESS_WIDTH = sr.tw*0.7;
var {Button, ProgressBar} = COMPONENTS;
进度view
var ProgressInfo = React.createClass({
render() {
const { progress } = this.props;
if (progress < 1000) {
return (
<View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
<Text>{this.props.title} [{progress}%]</Text>
<ProgressBar
fillStyle={{}}
backgroundStyle={{backgroundColor: '#cccccc', borderRadius: 2}}
style={{marginTop: 10, width:PROGRESS_WIDTH}}
progress={progress/100.0}
/>
<View style={styles.progressText}>
<Text>0</Text>
<Text>100</Text>
</View>
</View>
);
} else {
let size = progress/1000/1024/1024;
return (
<View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
<Text>{this.props.title} [ {size.toFixed(2)} M ]</Text>
</View>
);
}
}
});
页面view
函数
module.exports = React.createClass({
getInitialState() {
return {
status:STATUS_HAS_VEW_VERSION,
progress: 0,
};
},
onError(errCode) {
if (errCode == ERROR_DOWNKOAD_APK) {
this.setState({status: STATUS_DOWNKOAD_APK_ERROR});
} else if (errCode == ERROR_DOWNKOAD_JS) {
this.setState({status: STATUS_DOWNKOAD_JS_ERROR});
} else if (errCode == ERROR_FAILED_INSTALL) {
this.setState({status: STATUS_FAILED_INSTALL_ERROR});
} else if (errCode == ERROR_UNZIP_JS) {
this.setState({status: STATUS_UNZIP_JS_ERROR});
}
},
doUpdate() {
const {jsVersionCode, trackViewUrl} = this.props.options;
if (jsVersionCode !== undefined) {
Update.updateJS({
jsVersionCode,
jsbundleUrl: app.isandroid?app.route.ROUTE_JS_ANDROID_URL:app.route.ROUTE_JS_IOS_URL,
onDownloadJSProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_JS_PROGESS,progress})},
onUnzipJSProgress:(progress)=>{this.setState({status: STATUS_UNZIP_JS_PROGESS,progress})},
onUnzipJSEnd:()=>{this.setState({status: STATUS_UPDATE_END})},
onError:(errCode)=>{this.onError(errCode)},
});
} else {
Update.updateApp({
trackViewUrl,
androidApkUrl:app.route.ROUTE_APK_URL,
androidApkDownloadDestPath:'/sdcard/yxjqd.apk',
onDownloadAPKProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_APK_PROGESS,progress})},
onError:(errCode)=>{this.onError(errCode)},
});
}
},
render
render() {
const components = {};
const {newVersion, description} = this.props.options;
components[STATUS_HAS_VEW_VERSION] = (
<View style={styles.functionContainer}>
<Text style={styles.title}>{`发现新版本(${newVersion})`}</Text>
<Text style={styles.redLine}>
</Text>
<Text style={styles.content}>
{"更新内容:"}
</Text>
{
description.map((item, i)=>{
return (
<Text style={styles.contentItem} key={i}>{'- '+item}</Text>
)
})
}
<View style={styles.buttonViewStyle}>
<TouchableOpacity
onPress={app.closeModal}
style={styles.buttonStyleContainCannel}>
<Text style={styles.buttonStyleCannel}>以后再说</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.doUpdate}
style={styles.buttonStyleContain}>
<Text style={styles.buttonStyle} >立即更新</Text>
</TouchableOpacity>
</View>
</View>
);
components[STATUS_DOWNKOAD_APK_ERROR] = (
<View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
<Text style={styles.textInfo}>下载apk文件失败,请在设置里重新更新</Text>
<TouchableOpacity
onPress={app.closeModal}
style={styles.buttonStyleContainCannel}>
<Text style={styles.buttonStyleCannel}>我知道了</Text>
</TouchableOpacity>
</View>
);
components[STATUS_DOWNKOAD_JS_ERROR] = (
<View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
<Text style={styles.textInfo}>下载js bundle失败,请在设置里重新更新</Text>
<TouchableOpacity
onPress={app.closeModal}
style={styles.buttonStyleContainCannel}>
<Text style={styles.buttonStyleCannel}>我知道了</Text>
</TouchableOpacity>
</View>
);
components[STATUS_UNZIP_JS_ERROR] = (
<View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
<Text style={styles.textInfo}>解压js bundle失败,请在设置里重新更新</Text>
<TouchableOpacity
onPress={app.closeModal}
style={styles.buttonStyleContainCannel}>
<Text style={styles.buttonStyleCannel}>我知道了</Text>
</TouchableOpacity>
</View>
);
components[STATUS_FAILED_INSTALL_ERROR] = (
<View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
<Text style={styles.textInfo}>你放弃了安装</Text>
<TouchableOpacity
onPress={app.closeModal}
style={styles.buttonStyleContainCannel}>
<Text style={styles.buttonStyleCannel}>我知道了</Text>
</TouchableOpacity>
</View>
);
components[STATUS_DOWNLOAD_APK_PROGESS] = (
<ProgressInfo
title="正在下载APK"
progress={this.state.progress} />
);
components[STATUS_DOWNLOAD_JS_PROGESS] = (
<ProgressInfo
title="正在下载Bundle文件"
progress={this.state.progress} />
);
components[STATUS_UNZIP_JS_PROGESS] = (
<ProgressInfo
title="正在解压Bundle文件"
progress={this.state.progress} />
);
components[STATUS_UPDATE_END] = (
<Text>即将重启...</Text>
);
return (
<View style={styles.container}>
{components[this.state.status]}
</View>
);
},
});
样式
var styles = StyleSheet.create({
……
});
Update的方法
-
Update.getVersion
- 获得当前版本- 返回值是 x.x.x
-
Update.checkVersion
- 检查服务器版本 -
Update.updateApp
- 更新apk或ios appstore -
Update.updateJS
- 更新js捆绑文件
检查版本并且显示更新对话框
const Update = require('@remobile/react-native-update');
const UpdateInfoBox = require('../modules/update/UpdateInfoBox');
Update.checkVersion({
versionUrl: app.route.ROUTE_VERSION_INFO_URL,
iosAppId: CONSTANTS.IOS_APPID,
}).then((options)=>{
if (options && options.newVersion) {
app.showModal(<UpdateInfoBox options={options} />, {backgroundColor:'rgba(0, 0, 0, 0.6)'})
}
})
检查版本和显示更新页面
const Update = require('@remobile/react-native-update');
const UpdatePage = require('../modules/update/UpdatePage');
Update.checkVersion({
versionUrl: app.route.ROUTE_VERSION_INFO_URL,
iosAppId: CONSTANTS.IOS_APPID,
}).then((options)=>{
app.navigator.push({
title: '在线更新',
component: UpdatePage,
passProps: {options},
});
})
选项
* versionUrl:服务器上的版本的url:
* format: { "versionCode":1, "versionName":"1.0", "jsVersionCode":2, "description(描述)":"hello"}
这个结构体将传递给needUpdateApp和updateUpdateJS,如果你设置它们, 所以你可以
自定义格式,但是versionCode和jsVersionCode必须保持一致
* iosAppId:the appid on app Store
## Update App Or JS
```js
doUpdate() {
const {jsVersionCode, trackViewUrl} = this.props.options;
if (jsVersionCode !== undefined) {
Update.updateJS({
jsVersionCode,
jsbundleUrl: app.isandroid?app.route.ROUTE_JS_ANDROID_URL:app.route.ROUTE_JS_IOS_URL,
onDownloadJSProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_JS_PROGESS,progress})},
onUnzipJSProgress:(progress)=>{this.setState({status: STATUS_UNZIP_JS_PROGESS,progress})},
onUnzipJSEnd:()=>{this.setState({status: STATUS_UPDATE_END})},
onError:(errCode)=>{this.onError(errCode)},
});
} else {
Update.updateApp({
trackViewUrl,
androidApkUrl:app.route.ROUTE_APK_URL,
androidApkDownloadDestPath:'/sdcard/yxjqd.apk',
onDownloadAPKProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_APK_PROGESS,progress})},
onError:(errCode)=>{this.onError(errCode)},
});
}
},
选项
* jsbundleUrl:js捆绑网址
* 当只有js代码更改或图像资源更改,我希望您发布jsbundle,称为次要版本
* 包括www / index.android.bundle或www / index.ios.bundle
* 包括android上的图像目录,ios上的资源目录
* 在android上,image dir包含一些动态图像(这个版本添加的新图像)
* 发布小版本你应该修改jsVersionCode
* androidApkUrl:apk的网址
* 只有native代码改变了,你需要在Android上发布apk或者在ios上发布ipa,叫做Marjor版本
* 发布Marjor版本,您应该增加versionCode并将jsVersionCode设置为0
* androidApkDownloadDestPath:apk文件下载路径,例如/ sdcard / download,确保它存在
* onDownloadAPKStart:function
* 当apk开始下载时回调
* onDownloadAPKProgress:function
* 当apk正在下载时回调
* 将通过{total:xx,loaded:xx} 1参数
* onDownloadAPKEnd:function
* 当apk下载结束时回调
* onDownloadJSStart:function
* onDownloadJSProgress:function
* 当apk正在下载时回调,
* 将通过{total:xx,loaded:xx} 1参数
* onDownloadJSEnd:function
* onUnzipJSStart:function
* onUnzipJSProgress:function
* 解压时候回调,
* 将通过{total:xx,loaded:xx} 1参数
* onUnzipJSEnd:function
* onError:function
* 将通过errorCode
* var ERROR_NULL = 0,
ERROR_DOWNKOAD_APK = 1,
ERROR_DOWNKOAD_JS = 2,
ERROR_GET_VERSION = 3,
ERROR_UNZIP_JS = 4;
生成捆绑
#!/bin/bash
distpath=../../localServer/public/download
function genIOSBundle() {
react-native bundle \
--platform ios \
--reset-cache \
--verbose \
--entry-file index.ios.js \
--bundle-output ./tools/www/index.ios.bundle \
--assets-dest ./tools/www/ \
--dev false
}
function genAndroidBundle() {
react-native bundle \
--platform android \
--reset-cache \
--verbose \
--entry-file index.android.js \
--bundle-output ./tools/www/index.android.bundle \
--assets-dest ./tools/www/ \
--dev false
}
function zipWWW() {
node -e "!function(){function i(e,r){var o=n.readdirSync(e);o.forEach(function(o){var s=e+'/'+o;n.statSync(s).isDirectory()?i(s,r+'/'+o):c.folder(r).file(o,n.readFileSync(s))})}function e(e,r,o){r=r||'',o=o||e+'.zip',i(e,r);var s=c.generate({base64:!1,compression:'DEFLATE'});n.writeFile(o,s,'binary',function(){console.log('success')})}var r=require('jszip'),n=require('fs'),c=new r,o=process.argv.splice(1);e.apply(null,o)}();" www _www www.zip
}
function genMd5List() {
cd ./tools
# git co head ${distpath}/${1}_md5.json
node -e "var o=process.argv;require('./getMd5List')(o[1],o[2])" ${1} ${distpath}
}
function zipFile() {
zipWWW
mv ./www.zip ${distpath}/js${1}.zip
rm -fr www
mv ./${1}_md5.json ${distpath}/${1}_md5.json
echo "${distpath}"
}
function buildAndroid() {
rm -fr www
mkdir www
cd ..
genAndroidBundle
genMd5List android
zipFile android
}
function buildIos() {
rm -fr www
mkdir www
cd ..
genIOSBundle
genMd5List ios
zipFile ios
}
function main() {
if [ "$1" = "android" ];then
buildAndroid
elif [ "$1" = "ios" ];then
buildIos
elif [ "$1" = "all" ];then
buildAndroid
buildIos
else
echo "Usage: ./genbundle ios|android|all"
fi
}
main $@
- 确保安装jszip在npm在全局(global),我们使用它的zip
服务器端 version.json
{
"iosPassed": true,
"iosJsVersionCode":0,
"iosDescription":["修正bug", "添加新功能"],
"androidPassed": {
"baidu": false,
"default": true
},
"versionName":"1.0",
"versionCode": 1048576,
"androidJsVersionCode":0,
"androidDescription": ["修改bug", "添加新功能"]
}
网友评论