React Native带你一步步实现热更新(CodePush-Android) 淡淡的烟草味﹌ 2022-06-08 13:11 249阅读 0赞 **前言:无奈研究了一下CodePush,遇到了很多坑~~ 但是原理呢不是很难理解,就是配置有点多,原理可以简单的参考一下我之前的一篇博客[React-Native 热更新尝试(Android)][React-Native _Android],下面说一下期间遇到的坑~** 大家可以看一下官网:[https://github.com/Microsoft/react-native-code-push][https_github.com_Microsoft_react-native-code-push], 如果觉得自己英文不太好的话可以看一下这哥们的博客: [React Native热更新部署/热更新-CodePush最新集成总结(新)][React Native_-CodePush] 下面带大家一步一步实现一下传说中的rn热更新: 首先我们创建一个rn项目叫UpdateDemo,然后运行android: ![这里写图片描述][SouthEast] ![这里写图片描述][SouthEast 1] 好啦! 很干净的一个app(不要在问我怎么创建和运行rn了)~~ **开始之前小伙伴可以自己去看看CodePush做一个简单的了解,然后你需要的是一台mac电脑~** **一、安装 CodePush** 进入命令栏执行: npm install -g code-push-cli 然后短暂等待一会: ![这里写图片描述][SouthEast 2] **二、创建一个CodePush账号, 并登入** 执行在命令栏里执行: code-push register 然后会弹出一个注册页面,我们直接github登入,登入成功后会显示你的access-key,我们直接copy一下: ![这里写图片描述][SouthEast 3] 然后复制到命令栏中: ![这里写图片描述][SouthEast 4] 可以看到,我们已经成功的登入了~~ **三、在CodePush注册一个我们的app** 我们在终端输入: code-push app add <appname> android react-native ![这里写图片描述][SouthEast 5] **我们这里是以android为例子的~~** 然后我们把Production和Staging对应的可以copy一下,后面需要用到~~ **四、集成Android开发环境** 1、进入到项目个根目录然后执行: npm install --save react-native-code-push ![这里写图片描述][SouthEast 6] 然后短暂停留几秒~~~ 2、进到android目录,然后执行: npm i -g rnpm ![这里写图片描述][SouthEast 7] 3、回到项目根目录,直接命令集成android环境: rnpm link react-native-code-push ![这里写图片描述][SouthEast 8] 一路回车~~~~ 然后用 AndroidStudio 打开android项目,找到/xxxx/UpdateDemo/android/app/build.gradle,你会发现多了几行代码: compile project(':react-native-code-push') apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" ![这里写图片描述][SouthEast 9] 这就是脚本文件为我们自动生成的,然后/xxx/UpdateDemo/android/settings.gradle这个文件也多了几行代码: include ':react-native-code-push' project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') 最后我们点击重新编译app: ![这里写图片描述][SouthEast 10] 点击”Sync Now”~~ 然后我们试着运行app,你会发现报了一个不明的错误,我们继续找到/xxx/UpdateDemo/android/app/src/main/java/com/updatedemo/MainApplication.java文件: package com.updatedemo; import android.app.Application; import com.facebook.react.ReactApplication; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import com.facebook.soloader.SoLoader; import com.microsoft.codepush.react.CodePush; import java.util.Arrays; import java.util.List; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override protected String getJSBundleFile() { return CodePush.getJSBundleFile(); } @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new CodePush("deployment-key-here", MainApplication.this, BuildConfig.DEBUG) ); } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); } } 如果为了偷懒一下的话,直接把“deployment-key-here”用我们之前获取的Production的key替换就可以了 ![这里写图片描述][SouthEast 11] 当然,我们是需要切换Production跟Staging的,所以我们得动态的配置我们的key,我们需要变成这样: ![SouthEast 12][] 你会看到CODEPUSH\_KEY变红色了,那么这个变量我们怎么配置呢? 我们找到xxxx/UpdateDemo/android/app/build.gradle文件, 改成: buildTypes { release { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" buildConfigField "String", "CODEPUSH_KEY", '"Ce2Lap6BN2ZqSrf6GyQ4U2AZlvpc55514efb-34f8-4da9-8ce5-b8c65a00e283"' } debug { buildConfigField "String", "CODEPUSH_KEY", '"3RCKlOnnPmFdxolA0_BKzwH85IkL55514efb-34f8-4da9-8ce5-b8c65a00e283"' } releaseStaging { minifyEnabled enableProguardInReleaseBuilds buildConfigField "String", "CODEPUSH_KEY", '"3RCKlOnnPmFdxolA0_BKzwH85IkL55514efb-34f8-4da9-8ce5-b8c65a00e283"' } } 里面的CODEPUSH\_KEY即为我们之前获取的Deployment Key , release对应的Production releaseStaging跟debug对应的Staging 然后重新编译一下as,会发现之前的地方不报红色了: new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG) package com.updatedemo; import android.app.Application; import com.facebook.react.ReactApplication; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import com.facebook.soloader.SoLoader; import com.microsoft.codepush.react.CodePush; import java.util.Arrays; import java.util.List; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override protected String getJSBundleFile() { return CodePush.getJSBundleFile(); } @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG) ); } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); } } 然后我们顺便把我们的keystore打包key配置一下: 不懂的小伙伴可以去看我之前的一篇博客: [ React-Native打包发布(Android)][React-Native_Android] 然后配置好keystore之后,然后我们的/xxxx/UpdateDemo/android/app/build.gradle文件就变成了这样: apply plugin: "com.android.application" import com.android.build.OutputFile /** * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets * and bundleReleaseJsAndAssets). * These basically call `react-native bundle` with the correct arguments during the Android build * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the * bundle directly from the development server. Below you can see all the possible configurations * and their defaults. If you decide to add a configuration block, make sure to add it before the * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle * bundleAssetName: "index.android.bundle", * * // the entry file for bundle generation * entryFile: "index.android.js", * * // whether to bundle JS and assets in debug mode * bundleInDebug: false, * * // whether to bundle JS and assets in release mode * bundleInRelease: true, * * // whether to bundle JS and assets in another build variant (if configured). * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants * // The configuration property can be in the following formats * // 'bundleIn${productFlavor}${buildType}' * // 'bundleIn${buildType}' * // bundleInFreeDebug: true, * // bundleInPaidRelease: true, * // bundleInBeta: true, * * // whether to disable dev mode in custom build variants (by default only disabled in release) * // for example: to disable dev mode in the staging build type (if configured) * devDisabledInStaging: true, * // The configuration property can be in the following formats * // 'devDisabledIn${productFlavor}${buildType}' * // 'devDisabledIn${buildType}' * * // the root of your project, i.e. where "package.json" lives * root: "../../", * * // where to put the JS bundle asset in debug mode * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", * * // where to put the JS bundle asset in release mode * jsBundleDirRelease: "$buildDir/intermediates/assets/release", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in debug mode * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in release mode * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", * * // by default the gradle tasks are skipped if none of the JS files or assets change; this means * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to * // date; if you have any other folders that you want to ignore for performance reasons (gradle * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ * // for example, you might want to remove it from here. * inputExcludes: ["android/**", "ios/**"], * * // override which node gets called and with what additional arguments * nodeExecutableAndArgs: ["node"], * * // supply additional arguments to the packager * extraPackagerArgs: [] * ] */ apply from: "../../node_modules/react-native/react.gradle" apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" /** * Set this to true to create two separate APKs instead of one: * - An APK that only works on ARM devices * - An APK that only works on x86 devices * The advantage is the size of the APK is reduced by about 4MB. * Upload all the APKs to the Play Store and people will download * the correct one based on the CPU architecture of their device. */ def enableSeparateBuildPerCPUArchitecture = false /** * Run Proguard to shrink the Java bytecode in release builds. */ def enableProguardInReleaseBuilds = false android { signingConfigs { release { keyAlias 'update' keyPassword '123456' storeFile file('/Users/yasin/SelfRnWorkSpace/UpdateDemo/android/update.keystore') storePassword '123456' } } compileSdkVersion 23 buildToolsVersion "23.0.1" defaultConfig { applicationId "com.updatedemo" minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0" ndk { abiFilters "armeabi-v7a", "x86" } } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86" } } buildTypes { release { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" buildConfigField "String", "CODEPUSH_KEY", '"Ce2Lap6BN2ZqSrf6GyQ4U2AZlvpc55514efb-34f8-4da9-8ce5-b8c65a00e283"' signingConfig signingConfigs.release } debug { buildConfigField "String", "CODEPUSH_KEY", '"3RCKlOnnPmFdxolA0_BKzwH85IkL55514efb-34f8-4da9-8ce5-b8c65a00e283"' } releaseStaging { minifyEnabled enableProguardInReleaseBuilds buildConfigField "String", "CODEPUSH_KEY", '"3RCKlOnnPmFdxolA0_BKzwH85IkL55514efb-34f8-4da9-8ce5-b8c65a00e283"' signingConfig signingConfigs.release } } // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits def versionCodes = ["armeabi-v7a": 1, "x86": 2] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = versionCodes.get(abi) * 1048576 + defaultConfig.versionCode } } } } dependencies { compile project(':react-native-code-push') compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:appcompat-v7:23.0.1' compile 'com.facebook.react:react-native:+' // From node_modules } // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { from configurations.compile into 'libs' } 好啦~~~ 有点偏题了哈~ 配置完key后,我们的android配置到这就结束了 **五、配置React Native环境** 我们什么时候更新我们的app呢? 我们为了简单一点就直接在rn的第一个页面中作更新了,我们直接在我们的index.android.js文件的componentDidMount方法: componentDidMount() { AppState.addEventListener("change", (newState) => { newState === "active" && CodePush.sync(); }); } 全部内容: /** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, {Component} from 'react'; import { AppRegistry, StyleSheet, Text, View, AppState, } from 'react-native'; import CodePush from 'react-native-code-push'; const VERSION = '1.0.0'; export default class UpdateDemo extends Component { render() { return ( <View style={styles.container}> <Text>{ '当前版本:' + VERSION}</Text> <Text style={styles.welcome}> Welcome to React Native! </Text> <Text style={styles.instructions}> To get started, edit index.android.js </Text> <Text style={styles.instructions}> Double tap R on your keyboard to reload,{ '\n'} Shake or press menu button for dev menu </Text> </View> ); } componentDidMount() { AppState.addEventListener("change", (newState) => { newState === "active" && CodePush.sync(); }); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, }, }); AppRegistry.registerComponent('UpdateDemo', () => UpdateDemo); 然后我们打个生产包运行一下,我们直接在根目录执行: React-native bundle --entry-file index.android.js --bundle-output ./android/app/src/main/assets/index.android.bundle --platform android --assets-dest ./android/app/src/main/res/ --dev false ![这里写图片描述][SouthEast 13] 然后到我们android studio中把 buildtype改为release: ![这里写图片描述][SouthEast 14] 然后直接运行我们的app: ![这里写图片描述][SouthEast 15] 可以看到,我们的app出现了,我们加了一个版本控制为:1.0.0, 然后我们怎么发布我们的jsbundle让它热更新呢? **六、发布jsbundle到codepush** 比如我们现在要升级了,我们模拟一下,把rn页面的当前版本1.0.0的提示改为1.0.1: const VERSION = '1.0.1'; export default class UpdateDemo extends Component { render() { return ( <View style={styles.container}> <Text>{ '当前版本:' + VERSION}</Text> 然后我们在项目根目录创建一个bundles文件夹: ![这里写图片描述][SouthEast 16] 然后打一个jsbundle包到bundles文件夹中: react-native bundle --platform android --entry-file index.android.js --bundle-output ./bundles/index.android.bundle --dev false ![这里写图片描述][SouthEast 17] index.android.bundle即为我们需要上传到codepush的文件~~ **最后到codepush** code-push release UpdateDemo ./bundles/index.android.bundle 1.0.0 --deploymentName Production --description "更改版本为1.0.1" --mandatory true ![这里写图片描述][SouthEast 18] 然后查看一下我们的发布情况: code-push deployment ls UpdateDemo ![这里写图片描述][SouthEast 19] 好啦~~~ 我们改变一下我们android中的版本为1.0.0,因为要跟我们codepush上的版本对应起来,所以我们找到andoid的/xxx/UpdateDemo/android/app/build.gradle文件,然后把版本号改为:1.0.0: defaultConfig { applicationId "com.updatedemo" minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0.0" ndk { abiFilters "armeabi-v7a", "x86" } } 然后重新编译运行一下: ![这里写图片描述][SouthEast 20] 可以看到,我们运行后当前版本先是1.0.0,然后过了一会变成了1.0.1,也就是我们的热更新已经集成好了~~ **注意:android中的versionName一定要跟codepush中的version一样,我就是这里卡了很久~~~** 那如果我们要针对1.0.0再做一次升级呢? 我们继续操作一下~~ const VERSION = '1.0.1'; export default class UpdateDemo extends Component { render() { return ( <View style={styles.container}> <Text>{ '我添加了热更新:' + VERSION}</Text> 可以看到,我改了几个文字,然后我们重新打包: ![这里写图片描述][SouthEast 21] 然后重新上传codepush: ![这里写图片描述][SouthEast 22] 我们重新打开我们的app,顺便看一下as的log: ![这里写图片描述][SouthEast 23] 好啦~到这里我们的热更新就全部完毕了 小伙伴正在项目的情况可能是这样的:进入app请求后台接口–>根据后台接口判断是否需要更新—>弹出dialog提示用户—>点击更新—>执行CodePush.sync(); 具体我就不掩饰了~~~ 不懂的童鞋可以进群联系我,欢迎交流~~ qq交流群: ![这里写图片描述][SouthEast 24] 参考: > [http://www.jianshu.com/p/9e3b4a133bcc][React Native_-CodePush] > [https://github.com/Microsoft/react-native-code-push][https_github.com_Microsoft_react-native-code-push], [React-Native _Android]: http://blog.csdn.net/vv_bug/article/details/60883436 [https_github.com_Microsoft_react-native-code-push]: https://github.com/Microsoft/react-native-code-push [React Native_-CodePush]: http://www.jianshu.com/p/9e3b4a133bcc [SouthEast]: /images/20220608/e531f66a470f42eea6271f6e98e8198a.png [SouthEast 1]: /images/20220608/e295b9449c2546608f7001faf895a3b5.png [SouthEast 2]: /images/20220608/9512b977f9c5421a9d018b032ac92995.png [SouthEast 3]: /images/20220608/ea7701d6d6ba4a4ba579d306a4311bdf.png [SouthEast 4]: /images/20220608/3e642a1c77ef4d34999812bb842c01d3.png [SouthEast 5]: /images/20220608/e0627cff425d41a687e1fa283a8c6400.png [SouthEast 6]: /images/20220608/a21489d39b5c4e40921a9520be1ac46f.png [SouthEast 7]: /images/20220608/6ae364d8df2247029156855e88920072.png [SouthEast 8]: /images/20220608/97984d4a70944de3bcd2d412a79bd78f.png [SouthEast 9]: /images/20220608/dd40f864035d4beb8d089f8d420cdaa8.png [SouthEast 10]: /images/20220608/c764a7033a5242089daf9c63d587d41b.png [SouthEast 11]: /images/20220608/20ab5b6658b44827a5c463cbb56260a5.png [SouthEast 12]: /images/20220608/2e25b5bca81e4575845b0f9c27bd76e4.png [React-Native_Android]: http://blog.csdn.net/vv_bug/article/details/60581405 [SouthEast 13]: /images/20220608/cdd6d88d83bb42e68b28a25cc3421a1f.png [SouthEast 14]: /images/20220608/54adb57e99a7496d95682dc216376c08.png [SouthEast 15]: /images/20220608/90999bf1b7324adc82033989de2304d8.png [SouthEast 16]: /images/20220608/0c26d417e68742c7b4ef227e427c5dd5.png [SouthEast 17]: /images/20220608/2c69e1bcd0d743e5b3129e584ec6e25a.png [SouthEast 18]: /images/20220608/351d79779c884a69b0960fa20e1c2c61.png [SouthEast 19]: /images/20220608/1547a141fccc46ab91494e80ba8af19d.png [SouthEast 20]: /images/20220608/5c190a3ad3cd4aaeaebd18b15f31baad.png [SouthEast 21]: /images/20220608/5dcb2f73874c4464a47b3000d552fdc4.png [SouthEast 22]: /images/20220608/9136f49dde504e7ba42ef9e88098f512.png [SouthEast 23]: /images/20220608/b3da6d86669241998394f4d89e9c0184.png [SouthEast 24]: /images/20220608/882fbc35e2fc4f8ea83fcb960bb56f27.png
还没有评论,来说两句吧...