探索 react-native run-ios(android)

咱們都知道React Navite在開發的時候,須要在React Native根目錄下運行react-native run-ios(或run-android),或者在Xcode中運行原生iOS項目(對於Android則是在Android Studio中運行原生Android項目),而後在對應的React Native根目錄下運行npm start(開啓nodejs服務,開啓JS Server)。javascript

寫這篇文章的目的

一、梳理react-native run-ios(android)的完整流程,並識別ios和android的區別。
二、理解debug下ios的詭異現象,在沒開JS Server時,有時加載條爲何會變成load pre bundle並能正常運行,有時爲何閃崩。
三、幫助不懂原生的朋友快速進入code狀態,不要每次都等待react-native run-ios(android)
四、最後的黑魔法,在團隊協做下,寫js的能夠根本不須要iOS和Android環境(固然,這須要原生開發夥伴的幫助),原生也不須要裝nodejs。java

探索「react-native run-ios」到底作了什麼


react-native run-ios:
在控制檯能夠看到輸出:node

> $ react-native run-ios
Found Xcode project LayoutDemo.xcodeproj
We couldn't boot your defined simulator due to an already booted simulator. We are limited to one simulator lau
nched at a time.
Launching iPhone 6 (iOS 10.3)...
Building using "xcodebuild -project LayoutDemo.xcodeproj -configuration Debug -scheme LayoutDemo -destination i
d=BB4E36F2-D6B3-447F-91E9-8D1F5B56022E -derivedDataPath build"複製代碼

一、使用xcodebuild來編譯項目
在輸出中能夠看到React Native首先是尋找Xcode project(尋找方式後面會說明),而後使用 xcodebuild來編譯項目。你能夠想象就是用xcode打開項目,而後按運行,同樣的效果。
二、定義了打bundle包的腳本
在編譯參數中設置了React Native的編譯腳本(能夠在project.pbxproj中查看到react-native-xcode.sh腳本的設置),目錄是在:/node_modules/react-native/packager/react-native-xcode.sh。
react-native-xcode.shreact

case "$CONFIGURATION" in
  Debug)  //在Debug模式下
    # Speed up build times by skipping the creation of the offline package for debug
    # builds on the simulator since the packager is supposed to be running anyways.
    if [[ "$PLATFORM_NAME" == *simulator ]]; then
      echo "Skipping bundling for Simulator platform"  //使用模擬器跑時不打bundle包,退出sh腳本
      exit 0;
    fi

    DEV=true //設置DEV爲true
    ;;
  "")
    echo "$0 must be invoked by Xcode" 
    exit 1
    ;;
  *)
    DEV=false //設置DEV爲false
    ;;
esac
...
# Xcode project file for React Native apps is located in ios/ subfolder
cd ${REACT_NATIVE_DIR}/../.. //進入React Native根目錄
...
if [[ "$CONFIGURATION" = "Debug" && ! "$PLATFORM_NAME" == *simulator ]]; then
  ...
  //若是在Debug環境在,而且不是由模擬器運行則對localhost和ip.txt中的內容設置ATS爲容許http(由於node.js服務是開在http://localhost:8081上的)
 NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST"
  $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP.xip.io:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST"
  echo "$IP.xip.io" > "$DEST/ip.txt"
fi
...
BUNDLE_FILE="$DEST/main.jsbundle"  //設置輸出main.jsbundle的目錄
//運行react-native下的bundle命令,至關於本身運行`react-native bundle`
$NODE_BINARY "$REACT_NATIVE_DIR/local-cli/cli.js" bundle \
  --entry-file "$ENTRY_FILE" \
  --platform ios \
  --dev $DEV \
  --reset-cache \
  --bundle-output "$BUNDLE_FILE" \
  --assets-dest "$DEST"複製代碼

從腳本能看出在Debug模式下,不會爲模擬器打bundle包,可是會爲真機打bundle包,這也就是爲何咱們在真機調試後,而後斷開nodejs服務(不能打開remote debug js模式,否則會閃崩),從新進入應用時也會正確的加載bundle,屏幕上方會出現'load pre bundle'的字樣,而且是黑色的背景條,若是是加載nodejs服務器的則會是綠色的背景條並出現加載進度。可是當咱們在模擬器上作一樣的操做時,好比先正常打開nodejs服務器加載調試,而後在從新打開應用,它加載不到bundle會崩潰。
當咱們設置Rlease模式時,必須用xcode編譯纔會打bundle包,此時不管是在模擬器仍是在真機運行,都會打bundle包。
輸出以下:android

/Users/lyxia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Intermediates/LayoutDemo.build/Debug-iphoneos/LayoutDemo.build/Script-00DD1BFF1BD5951E006B06BC.sh
+DEST=/Users/lyxia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Products/Debug-iphoneos/LayoutDemo.app
+ [[ Debug = \D\e\b\u\g ]]
+ [[ ! iphoneos == *simulator ]]
+ PLISTBUDDY=/usr/libexec/PlistBuddy

+PLIST=/Users/lyxia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Products/Debug-iphoneos/LayoutDemo.app/Info.plist
++ ipconfig getifaddr en0
+ IP=192.168.16.111
+ '[' -z 192.168.16.111 ']'
+ /usr/libexec/PlistBuddy -c 'Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllow
sInsecureHTTPLoads bool true' /Users/lyxia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Products/Debug
-iphoneos/LayoutDemo.app/Info.plist
+ /usr/libexec/PlistBuddy -c 'Add NSAppTransportSecurity:NSExceptionDomains:192.168.16.111.xip.io:NSTemporaryEx
ceptionAllowsInsecureHTTPLoads bool true' /Users/lyxia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Pr
oducts/Debug-iphoneos/LayoutDemo.app/Info.plist
+ echo 192.168.16.111.xip.io
+BUNDLE_FILE=/Users/lyxia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Products/Debug-iphoneos/LayoutDemo.app/main.jsbundle

+ node /Users/lyxia/Documents/ios/React_Native/LayoutDemo/node_modules/react-native/local-cli/cli.js bundle --entry-file index.ios.js --platform ios --dev true --reset-cache --bundle-output/Users/lyxia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Products/Debug-iphoneos/LayoutDemo.app/main.jsbundle --assets-dest /Users/ly
xia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Products/Debug-iphoneos/LayoutDemo.app

[2017-04-01 11:26:20] <START> Initializing Packager
[2017-04-01 11:26:21] <START> Building Haste Map
[2017-04-01 11:26:21] <END>   Building Haste Map (575ms)
[2017-04-01 11:26:21] <END>   Initializing Packager (1644ms)
[2017-04-01 11:26:21] <START> Transforming files

Warning: The transform cache was reset.

[2017-04-01 11:26:40] <END>   Transforming files (18750ms)
bundle: start
bundle: finish
bundle: Writing bundle output to: /Users/lyxia/Documents/ios/React_Native/LayoutDemo/ios/build/Build/Products/D
ebug-iphoneos/LayoutDemo.app/main.jsbundle
bundle: Copying 5 asset files
bundle: Done writing bundle output
bundle: Done copying assets
+ [[ ! -n true ]]複製代碼

三、自動彈出一個框來啓動nodejs服務
../node_modules/react-native/React/React.xcodeproj/project.pbxproj文件中咱們能夠看到PBXShellScriptBuildPhase section中定義了Start Packager的shell執行,最終會執行到../node_modules/react-native/packager/launchPackager.command。一路追溯,能夠看到最後執行到node "$THIS_DIR/../local-cli/cli.js" start "$@"也就是咱們經常使用的npm startios

總結:因此如今咱們整理一下這整個流程:shell

  • 使用xcodebuild編譯項目
  • 由於在使用react-native init <項目名>生成的項目中,project.pbxproj裏面會添加生成bundle的Bundle React Native code and images編譯參數,因此咱們在真機完成調試後,nodejs關了,也能去加載本地的bundle,由於它已經幫咱們打包好了。
  • 由於在Reactproject.pbxproj中添加了Start Packager的編譯參數,因此它會判斷是否已經開啓nodejs服務,若是沒有則會幫咱們開啓。

組合:按需求組合使用
這個流程使咱們只要運行react-native run-ios就能夠編譯項目,開啓nodejs服務。
所以咱們能夠換個方向來想,若是咱們是在原生的iOS項目中接入RN,那應該怎麼作呢?
只需用Xcode運行原生項目,而後npm start便可。固然咱們也能夠在編譯參數中加入Bundle React Native code and images讓它每次執行爲咱們自動打包最新的bundle,這樣當咱們調試好後,就算關了nodejs服務,也能繼續運行。npm

問題:如何尋找.xcodeproj文件
../node_modules/react-native/local-cli/runIOS/runIOS.js裏面能夠找到react-native run-ios的實現,而且有各類參數的舉例說明:react-native

{
    desc: 'Pass a non-standard location of iOS directory',
    cmd: 'react-native run-ios --project-path "./app/ios"',
}
...
{
    command: '--project-path [string]',
    description: 'Path relative to project root where the Xcode project '
      + '(.xcodeproj) lives. The default is \'ios\'.',
    default: 'ios',
  }複製代碼

能夠看到默認是在\ios.目錄下尋找以.xcworkspace或者.xcodeproj結尾的文件,不過可使用--project-path [string]來指定文件目錄。xcode

探索「react-native run-android」到底作了什麼


一樣咱們在控制檯運行react-native run-android,看輸出:

> $ react-native run-android
Starting JS server...
Running /Users/lyxia/Documents/Android/adt-bundle-mac-x86_64-20140702/sdk//platform-tools/adb -s C
oolpad5890-a1778fd9 reverse tcp:8081 tcp:8081
Building and installing the app on the device (cd android && ./gradlew installDebug)...
Starting a new Gradle Daemon for this build (subsequent builds will be faster).
> Configuring > 1/2 projects > :app > Compiling script into cache^C
:app:preBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:app:checkDebugManifest
:app:preReleaseBuild UP-TO-DATE
:app:prepareComAndroidSupportAppcompatV72301Library
:app:prepareComAndroidSupportRecyclerviewV72301Library
:app:prepareComAndroidSupportSupportV42321Library複製代碼

是的,Android和咱們的iOS寶寶是不同的,他一開始就去查看JS Server是否開啓,若是沒開他就會去開啓,而後使用gradle來編譯項目並安裝。
能夠在../node_modules/react-native/local-cli/runAndroid/runAndroid.js中查看react-native run-android的實現,這裏不貼出來了。

注意點
一、在Release下,會打包bundle:

if (args.configuration.toUpperCase() === 'RELEASE') {
      console.log(chalk.bold(
        'Generating the bundle for the release build...'
      ));
      child_process.execSync(
        'react-native bundle ' +
        '--platform android ' +
        '--dev false ' +
        '--entry-file index.android.js ' +
        `--bundle-output ${androidProjectDir}/app/src/main/assets/index.android.bundle ` +
        `--assets-dest ${androidProjectDir}/app/src/main/res/`,
        {
          stdio: [process.stdin, process.stdout, process.stderr],
        }
      );複製代碼

二、如何驗證android項目是否存在:
在React Native根目錄下尋找android/gradlew,是的,你沒看錯,全程不可配,乖乖放好路徑吧,否則就使用Android Studio運行,而後執行npm start

總結:整理react-native run-android的完整流程

  • 檢查是否須要開啓JS Server
  • 檢查是否存在android/gradlew
  • 檢查是否有可用的Android設備已鏈接
  • 檢查是否在release環境下,若是是則打包bundle
  • 使用gradle編譯android項目並安裝

條理比iOS清晰許多,由於都寫在一個文件中。

對比「react-native run-ios(android)的相同與不一樣」


相同點:

  • 都是要去尋找原生項目是否存在,iOS是存在/ios/*.xcworkspace或者/ios/.xcodeproj結尾的文件,Android是存在android/gradlew文件。
  • 都會爲咱們開啓JS Server。
  • 都會在Release環境下爲咱們打包Bundle。
  • 均可以真機運行。

不一樣點:

  • iOS的項目路徑可配,當咱們在原生的iOS中接入React Native時,項目路徑極可能不符合它默認的路徑,此時能夠經過--project-path修改。而Android不能夠指定項目路徑,若是/android/gradlew不存在,則不可使用react-native run-android
  • Debug環境下,iOS在真機上運行時(react-native run-ios --device),也會咱們打包bundle,可是Android不會。
  • iOS使用xcodebuild來編譯項目,Android使用gradle來編譯。

使用以上知識來解決常見問題


問題1:
我需不須要天天開機都react-native run-ios(android)
回答:
在沒有更改原生代碼的狀況下,是不須要的,只要設備或者是模擬器上安裝了應用,只須要npm start開啓JS Server便可,讓應用能加載到nodejs服務上的js bundle便可。

問題2:
若是我電腦上沒有Android和iOS環境,能不能調試RN項目。
回答:
能夠跑,只須要手機上已經裝好RN的Debug版的項目,而且電腦開啓了JS Server。Android機須要在開發者選項裏把ip和端口改爲開了JS Server的ip和端口便可。iOS就要麻煩些了,須要在原生添加ip.txt文件,而後指定ip爲開了JS Server的ip,重編,便可(這裏就須要有Xcode了)。

問題3:
可不可使用1個JS Server同時調試iOS和Android項目。
回答:
能夠的。


歡迎在評論區提出問題和錯誤。

簡書同步更新地址:www.jianshu.com/u/b92ab7b3a…

相關文章
相關標籤/搜索