本文永久地址:github.com/rccoder/blo…,其餘平臺可能不是最新文章。文章評論等也但願去原文進行。javascript
個人畢業設計是用 React Native 寫一款校園 APP,服務端採用 egg + MongoDB。java
選用 React Native 一來是想借助他更加的學習鞏固 React、Redux 生態系統;二來是作成 APP 而不是網站會在老師面前顯得不是那麼的 Low,同時藉助雙平臺爲忽悠填一份色彩;三來是 React Native 確實在性能上是優於 H5,不須要 XX 內核(如 UC、QQ等)來抹平雜亂機型的性能與兼容問題,同時還能和 H5 同樣保持熱更新。node
爲何不用 Weex?react
曾經作過簡單的 Weex 開發,不使用 Weex 有如下幾點:一是定題目的時候(沒錯,題目中就有 React Native) Weex 的上層 DSL 還只有 Vue,當時還沒有出現 Rax;二來是相比於 React Native,Weex 的生態與社區還比較年輕,懼怕本身跳進去爬不出來致使畢業延期。android
若是感興趣的話,代碼在這裏:ios
React Native 的原理大體是上層寫 React 式的代碼,而後利用相關的 loader 打包成 bundle 和相關的靜態文件,而後利用 Android、iOS 裏的 SDK 解析 bundle,而後以 Native 的方式執行。git
在大型 APP 中,針對 H5 中的靜態文件會設置離線包,以達到秒出加速的目的,固然離線包的增大會致使 APP 體積的增大。針對一些場景(好比營銷頁面等),並不但願離線加載,這樣的場景就不使用離線包進行加載。github
離線包也須要進行更新,這裏就須要一套比較完善的更新機制來保證(大公司本身造,小公司找有沒有開源的)。npm
熱更新指的就是離線包(React Native 中的 bundle)更新的這個機制。咱們能夠在 APP 運行的過程當中 「偷偷」 下載 bundle,而後在下次 APP 開啓的時候(或者某種自定義的時機,好比:彈窗提示、直接重啓等)使用新的 bundle。react-native
如何去維護這樣一個比較完善的更新機制呢?Microsoft 給出了一個很好的答案 —— CodePush。幸運地,他還沒被「牆「,咱們能夠直接的使用他的服務。
CodePush 集成了微軟的一個雲服務器,他至關因而一箇中心發佈器,APP 能夠詢問他是否有新的 bundle 更新,而後進行下載等操做。
CodePush 官方提供了 React Native 的集成方案,能夠比較容易的集成到原有代碼中(固然,也存在一些坑或者其餘的地方,這也是原本出現的目的之一)。
CodePush 的官網在 這。
npm install -g code-push-cli複製代碼
code-push register複製代碼
會自動打開瀏覽器彈出註冊界面,註冊完成以後會獲得一個 token,複製後填寫在 Terminal 裏面便可。
code-push app add WeHIT-Android
code-push app add WeHIT-iOS複製代碼
每一個 APP 都會獲得 Production
與 Production
狀態的兩個 Key:
爲何要註冊兩個 APP
CodePush 在發佈新 bundle 的時候目前只能一個一個平臺發,爲了保證你的 history 看起來不是那麼的亂,建議直接分紅兩個 APP 處理
SDK 的集成包含三部分,分別是 JS 源碼、iOS 客戶端、Android 客戶端。
SDK 直接用 CodePush 官方發佈的 react-native-code-push 便可。
值得注意是,react-native-code-push 最近發佈的 2.0-beta 版本,而改版本只支持 React Native 0.43 及其以上的版本中。若是是 0.43 如下版本的須要使用 1.17.x(最新 1.17.4-beta)。
我這裏使用的 RN 版本是 0.40,因此安裝 1.17.4-beta 版本的 react-native-code-push。
yarn add react-native-code-push@1.17.4-beta複製代碼
在進行下面的三個以前,咱們先用 React Native 使用的 rnpm(React Native Package Manage) link 如下這個包 (自動改變 ios 和 android 目錄下的一些文件)。
npm install rnpm -g
rnpm link react-native-code-push複製代碼
期間會提示你輸入 iOS 和 Android 端的 key,這裏輸入你用 CodePush 註冊 APP 時產生的 key,這裏咱們先能夠都輸入每一個 Staging 的 key。
若是不幸你忘記了以前產生的 key,能夠輸入如下的命令查看:
code-push deployment ls WeHIT-Android複製代碼
在 React Native 源碼中,咱們須要引入 react-native-code-push
,而後用 CodePush 包裹一下最外層的組件。
沒有 CodePush 以前咱們的代碼這樣寫:
// index.ios[android].js
...
import { AppRegistry } from 'react-native';
import AppContainer from './src'
AppRegistry.registerComponent('WeHIT', () => AppContainer);
// src/index.js
export default function AppContainer () {
return (
<Navigator initialRoute={routeMap.home} configureScene={configureScene} renderScene={renderScene} /> )複製代碼
如今咱們只須要改動一下 src/index.js
,用 CodePush 包裹如下這個組件便可:
// src/index.js
import codePush from "react-native-code-push";
class App extends Component{
componentDidMount() {}
render() {
return (
<Navigator initialRoute={routeMap.intro} configureScene={configureScene} renderScene={renderScene} /> ) } } /** * Configured with a MANUAL check frequency for easy testing. For production apps, it is recommended to configure a * different check frequency, such as ON_APP_START, for a 'hands-off' approach where CodePush.sync() does not * need to be explicitly called. All options of CodePush.sync() are also available in this decorator. */ let codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME }; const AppContainer = codePush(codePushOptions)(App); export default AppContainer;複製代碼
這裏 checkFrequency
定義了什麼時候進行 bundle 更新,這裏 ON_APP_RESUME
是指在 APP 從新打開的時候進行更新替換。更加詳細的狀況能夠參加 react-native-code-push 的 example 進行編寫。目前這樣僅僅是夠用。
爲何這樣說?
據說在 Android 端和 iOS 端對更新要求是不同的。Android 是要求你在更新的時候提示用戶更新,用戶須要點擊確認以後才進行更新;而 iOS 端是但願這個更新是用戶無感知的(上面這種就行),默默的在後臺更新好而且使用最新的便可。
在上面使用 rnpm link 以後,其實大部分的問題都已經基本解決了。
這裏你須要打開 git diff 看一下 rnpm link 有沒有作一些 」壞事「 可能會對你的代碼形成影響。
這裏咱們發現他改變了下面幾個文件:
重點看一下是在可辨識的範圍內修改了 Info.plist
,在裏面加入了 key 方便調用。
除此以外還重點修改了一下 AppDelegate.m
:
這是指在 debug 包中繼續以前的老套路遠程加載 bundle,在 release 模式中託管給 CodePush 管理 bundle 的加載。
對,這和咱們以前預想的徹底同樣。
接着,用 Xcode 打開 iOS 工程,檢查一下版本號是否是三位,若是不是的話,修改成三位(好比:1.0.0):
爲何要修改成三位
CodePush 只支持三位的,否則在推包的時候沒法肯定推包給哪一個版本。
這部分你能夠先跳過,直接看 build 部分,若是有問題再回開看。
我在 build 的時候發生了錯誤,log 以下:
這看起來時測試出了問題,忽略掉他便可。
commmand + shift + ,
,在彈出的框裏面選擇 build,而後取消掉 Test 部分的勾。
而後繼續 build。
在以前的 AppDelegate.m
中,咱們代碼的意圖是在 debug
模式下經過遠程加載 bundle,在 release
模式下用 CodePush 來處理。這樣保證了咱們在開發的時候不受 CodePush 的影響,因此要測試 CodePush,就打在 release
包。
command + shift + ,
,在 run 裏面 Build Configuration
裏面選擇 release。
而後 command + R
或者點擊按鈕進行打包。
理論上你會注意到打了一個不須要加載遠程 bundle 的包。
如何打包到物理機上
這裏不作討論,只作簡單的說明:首先須要一個開發者帳號,在項目工程的
Singning
裏面進行配置(不想買的話能夠去淘寶看看),而後插上手機以後進行 build。和其餘 iOS 工程打包沒有什麼區別。
和 iOS 端同樣,利用 rnpm link 就能解決大部分的問題。rnpm link 以後,咱們 git diff 一下看看有哪些變更:
核心是修改了上面的幾個文件:
自動在 setting.gradle
裏面加入了 CodePush:
include ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')複製代碼
自動在 build.gradle
加入了 CodePush 的編譯依賴:
自動在 string.xml
裏面加入了 key,方便 java 代碼中的調用。
自動在 MainActivity.java
裏面引入的 CodePush。
自動在 MainApplication.java
中重寫的 getJSBundleFile
函數。
到這裏,進行 run 的時候會提示找不到 bundle,出現下面的錯誤:
Caused by: com.microsoft.codepush.react.CodePushNotInitializedException: A CodePush instance has not been created yet. Have you added it to your app's list of ReactPackages?複製代碼
模擬器也是紅紅的提示找不到 bundle。
這和咱們猜想的一直,整個 rnpm link 以後的代碼中沒有找到如何使用那個 key。
參考官方 example 中的代碼,最終找到要在 MainApplication.java
中使用那個 Key:
那個 id 是 string.xml
裏面的 id。
這樣從新進行 run,可能會發現又提示:
Could not get BatchedBridge, make sure your bundle is packaged properly」 on start of app複製代碼
模擬是也是大大的紅色錯誤提示:
參考 React Native 的 issues#9336 和 stackoverflow。
不要使用 Android Studio 啓動,使用:
react-native run-android
react-native start --reset-cache複製代碼
很好,能跑起來了!
如今已經打了 debug 的包,那如何打一個 release 包來測試咱們的 CodePush 呢?
首先咱們須要一個簽名,關於這個簽名,咱們在開發的時候其實是使用了 Android Studio 自帶的一個 debug 簽名,在打 release 包的時候,咱們就須要本身簽名了:
以下圖,在 build 裏面選擇 Generator Singed Apk:
而後點擊 Create New...
:
而後填寫即將生成的 patch 路徑,密碼,Alias的名字,密碼,還有一些公司我的相關的東西:
OK,看看你填寫的證書路徑裏是否有一個 xx.jks 的證書了。經過這個證書,咱們就能夠獲得 App 的 Sha1 等用於一些第三方 SDK,更多細節能夠參考 獲取Android SHA1 、生成jks密鑰、簽名Apk
理論上在剛纔的基礎下點擊下一步,選擇 Release
包就能獲得 Release 包了,但用:
adb install ./Android/app-release.apk複製代碼
安裝以後,打開會直接 crash(須要先卸載以前的 APP),解壓 APK,發現 asset 裏面沒有和 CodePush 有關的任何東西,猜想多是這裏引發 Crash。
因此這種打包方式不能用。
部分解釋參見: www.jianshu.com/p/1cff76e20…
找到以前生成的證書,複製到 app 目錄下,好比個人證書叫 WeHIT.jks
。
接着修改 android/gradle.properties
,加入證書的相關信息,方便其餘文件調用:
MYAPP_RELEASE_STORE_FILE=WeHIT.jks
MYAPP_RELEASE_KEY_ALIAS=WeHITKey
MYAPP_RELEASE_STORE_PASSWORD=123456
MYAPP_RELEASE_KEY_PASSWORD=123456複製代碼
而後在 app/build.gradle
裏配置打包簽名:
這裏會用到以前保存在 android/gradle.properties
裏的證書信息。
而後,切換到 andriod 目錄下執行:
./gradlew assembleRelease複製代碼
在 app/build/outputs/apk 裏面會獲得 release 包:
adb install ./WeHIT/WeHIT/android/app/build/outputs/apk/app-release.apk複製代碼
注1:
ADB 可能不在環境變量中,若是你是用 Android Studio 安裝的 Android SDK,先設置一下。
好比我用的是 zsh,修改
~/.zsh.rc
,加入:export ANDROID_HOME=${HOME}/Library/Android/sdk export PATH=${PATH}:${ANDROID_HOME}/tools export PATH=${PATH}:${ANDROID_HOME}/platform-tools複製代碼
注2:
理論上 Android Studio 集成了 gradlew 的功能,在 IDE 右側 Gradle 中就有,但打包以後依然缺失 CodePush,因此仍是用命令行打包吧.
在運行模擬器的時候,提示:
Starting emulator for AVD 'x86_QVGA_Level10'
emulator: device fd:1044
HAX is working and emulator runs in fast virt mode
emulator: Failed to sync vcpu reg
emulator: Failed to sync HAX vcpu context複製代碼
發現是 Docker 這種虛擬機在跑,關掉便可正常打開模擬器。
改完代碼後,咱們須要更新 bundle,這樣就須要咱們打包而後把 bundle 推送到 CodePush 服務器上。
在項目根目錄執行:
Android 推包
code-push release-react WeHIT-Android android複製代碼
iOS 推包
code-push release-react WeHIT-ios ios複製代碼
看到 log:
Detecting android app version:
Using the target binary version value "1.0.0" from "android/app/build.gradle".
Running "react-native bundle" command:
node node_modules/react-native/local-cli/cli.js bundle --assets-dest /var/folders/qv/3gzgsb153qj0gk8ljwl0kg0w0000gn/T/CodePush --bundle-output /var/folders/qv/3gzgsb153qj0gk8ljwl0kg0w0000gn/T/CodePush/index.android.bundle --dev false --entry-file index.android.js --platform android
[05/08/2017, 23:06:39] <START> Initializing Packager
[05/08/2017, 23:06:40] <START> Building Haste Map
[05/08/2017, 23:06:41] <END> Building Haste Map (1632ms)
[05/08/2017, 23:06:41] <END> Initializing Packager (2679ms)
[05/08/2017, 23:06:41] <START> Transforming files
[05/08/2017, 23:07:14] <END> Transforming files (32488ms)
bundle: start
bundle: finish
bundle: Writing bundle output to: /var/folders/qv/3gzgsb153qj0gk8ljwl0kg0w0000gn/T/CodePush/index.android.bundle
bundle: Done writing bundle output
bundle: Copying 15 asset files
bundle: Done copying assets
Releasing update contents to CodePush:
Upload progress:[==================================================] 100% 0.0s
Successfully released an update containing the "/var/folders/qv/3gzgsb153qj0gk8ljwl0kg0w0000gn/T/CodePush" directory to the "Staging" deployment of the "WeHIT" app.複製代碼
表示已經成功。
上面的一步流是把 打包和推送 結合到了一塊兒,從 log 中也能簡單看到先是執行打包,而後在 Push 的。
在 APP 的測試包中,咱們可能但願能保留開發 Log 等,或者說咱們想比較好的自定義 history 等。這樣就須要咱們手動的兩次進行打包和推送了。
首先咱們建立一個 bundles 文件夾來存儲打包後的 bundles
mkdir bundles複製代碼
而後進行打包
// react-native bundle --platform 平臺 --entry-file 啓動文件 --bundle-output 打包js輸出文件 --assets-dest 資源輸出目錄 --dev 是否調試
react-native bundle --platform android --entry-file index.android.js --bundle-output ./bundles/index.android.bundle --dev false
react-native bundle --platform ios --entry-file index.ios.js --bundle-output ./bundles/index.ios.bundle --dev false複製代碼
這樣就在 bundles 文件夾下面生成 bundle
bundle 已經打好,如今須要推送到 CodePush 平臺上。
// code-push release <應用名稱> <Bundles所在目錄> <對應的應用版本>
--deploymentName 更新環境 staging時不須要這個
--description 更新描述
--mandatory 是否強制更新 默認否
code-push release WeHIT-Android ./bundles 1.0.0 --description 「Android Update」
code-push release WeHIT-iOS ./bundles 1.0.0 --description 「iOS Update」複製代碼
code-push deployment history WeHIT-Android Staging複製代碼
若是不想看這麼多的版本,能夠用:
code-push deployment ls WeHIT-Android -k複製代碼
同時,你會發現裝了 release 包的客戶端會自動更新。
若是以前沒有客戶端的開發經驗,配置 CodePush SDK 仍是有點複雜,若是你在配置過程當中遇到了什麼問題,或者發現文章中有錯誤,歡迎指出。
與文章的互動請去原文 github.com/rccoder/blo…