今天升級了一下reaact-native 版本從0.57.0 -> 0.57.2,metro-react-native-babel-preset:0.48.0javascript
運行packager時候有個報錯 ```javascript error: bundling failed: Error: Unable to resolve module `./../../../react-transform-hmr/lib/index.js` from `F:\RN\TS\ph_front_android_medical\src\App.tsx`: The module `./../../../react-transform-hmr/lib/index.js` could not be found from `F:\R N\TS\ph_front_android_medical\src\App.tsx`. Indeed, none of these files exist: * `F:\RN\react-transform-hmr\lib\index.js(.native||.android.js|.native.js|.js|.android.json|.native.json|.json|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx)` * `F:\RN\react-transform-hmr\lib\index.js\index(.native||.android.js|.native.js|.js|.android.json|.native.json|.json|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx)` at ModuleResolver.resolveDependency (F:\RN\TS\ph_front_android_medical\node_modules\metro\src\node-haste\DependencyGraph\ModuleResolution.js:209:697) at ResolutionRequest.resolveDependency (F:\RN\TS\ph_front_android_medical\node_modules\metro\src\node-haste\DependencyGraph\ResolutionRequest.js:83:16) at DependencyGraph.resolveDependency (F:\RN\TS\ph_front_android_medical\node_modules\metro\src\node-haste\DependencyGraph.js:222:485) at Object.resolve (F:\RN\TS\ph_front_android_medical\node_modules\metro\src\lib\transformHelpers.js:149:25) at dependencies.map.result (F:\RN\TS\ph_front_android_medical\node_modules\metro\src\DeltaBundler\traverseDependencies.js:316:29) at Array.map (<anonymous>) at resolveDependencies (F:\RN\TS\ph_front_android_medical\node_modules\metro\src\DeltaBundler\traverseDependencies.js:312:16) at F:\RN\TS\ph_front_android_medical\node_modules\metro\src\DeltaBundler\traverseDependencies.js:169:33 at Generator.next (<anonymous>) at step (F:\RN\TS\ph_front_android_medical\node_modules\metro\src\DeltaBundler\traverseDependencies.js:271:307) BUNDLE [android, dev] ./index.js ░░░░░░░░░░░░░░░░ 0.0% (2/174), failed. ``` 解決辦法:我用 `react-native start --reset-cache`解決了, 可是貌似其餘人用這個並不必定能解決,具體參考這個issues的討論,遇到的人還挺多
bundling failed: Error: Unable to resolve module /../react-transform-hmr/lib/index.jshtml
進入rn時調用原生方法後,原生方法裏面獲取不到當前activity的問題
安卓經過寫nativeModule給rn提供了方法java
@ReactMethod public void init(Promise promise) { Activity currentActivity = getCurrentActivity(); Bundle bundle = currentActivity.getIntent().getExtras(); SerializableMap serializableMap = (SerializableMap) bundle.get("map"); WritableMap map = Arguments.createMap(); Iterator<Map.Entry<String, Object>> it = serializableMap.getMap().entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Object> entry = it.next(); map.putString(entry.getKey(), entry.getValue() + ""); if(entry.getValue() instanceof ArrayList){//若是是arraylist須要單獨處理 WritableArray writableArray=Arguments.createArray(); for (int i=0;i<((ArrayList) entry.getValue()).size();i++) { writableArray.pushString((String) ((ArrayList) entry.getValue()).get(i)); } map.putArray(entry.getKey(), writableArray); } } promise.resolve(map); }
上面的代碼在某些手機中會報 currentActivity爲null,由於我調用這個init方法是在進入rn後js馬上調用這個方法的,在某些狀況可能activity尚未onHostResume,致使activity爲null,因此這裏須要作些額外處理
首先在MainActivity中添加一下代碼node
public static WeakReference<MainActivity> currentActivity;//添加靜態弱引用 ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); currentActivity = new WeakReference<>(this); }
而後再上面的init方法裏添加一行代碼react
... if(currentActivity==null&& MainActivity.currentActivity!=null){// 防止進入rn調用init方法時activity尚未onHostResume currentActivity = MainActivity.currentActivity.get(); } ...
如今onCreate裏面保存當前activity,而後其餘地方就能夠用了android
此時包爲0.57.0版本的react-native
最近了解了一下安卓相關知識,在reactModule裏面,咱們能夠用 getCurrentActivity
來獲取MainActivity,可是據我瞭解,getCurrentActivity怎麼就獲取的是MainActivity呢?也就是咱們RN所在的activity呢?咱們從MainActivity能夠看到它是繼承了 ReactActivity的,而ReactActivity的構造方法裏面是調用了createReactActivityDelegate() 這個方法會new 一個ReactActivityDelegate 實例,同時傳入this,即當前activity,也就是從這個地方傳入的git
/** * Called at construction time, override if you have a custom delegate implementation. */ protected ReactActivityDelegate createReactActivityDelegate() { return new ReactActivityDelegate(this, getMainComponentName()); }
那麼還有個疑問就是,爲何在這裏傳入了這個activity後,咱們在本身新建的modules裏面獲取的就是這裏傳入的呢?咱們經過getCurrentActivity()拿到的是github
protected @Nullable final Activity getCurrentActivity() { return mReactApplicationContext.getCurrentActivity(); }
也就是咱們要找到mReactApplicationContext才能看到activity來自哪裏,那再看mReactApplicationContextweb
public ReactContextBaseJavaModule(ReactApplicationContext reactContext) { mReactApplicationContext = reactContext; }
他是來自咱們初始化咱們寫的package包的時候傳入的npm
public class ReactBridgePackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } @Override public List<NativeModule> createNativeModules( ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new ReactBridge(reactContext)); return modules; } }
也就是這裏,在構造函數裏面傳入了reactContext,那這裏的是什麼地方傳入的呢?繼續看
在NativeModuleRegisterBuilder.java中
構造函數中
public NativeModuleRegistryBuilder( ReactApplicationContext reactApplicationContext, ReactInstanceManager reactInstanceManager) { mReactApplicationContext = reactApplicationContext; mReactInstanceManager = reactInstanceManager; }
這裏保存了調用時傳入的reactContext,
接着這個文件中,有個方法
public void processPackage(ReactPackage reactPackage) { if (reactPackage instanceof LazyReactPackage) { LazyReactPackage lazyReactPackage = (LazyReactPackage) reactPackage; List<ModuleSpec> moduleSpecs = lazyReactPackage.getNativeModules(mReactApplicationContext); Map<String, ReactModuleInfo> reactModuleInfoMap = lazyReactPackage.getReactModuleInfoProvider().getReactModuleInfos(); for (ModuleSpec moduleSpec : moduleSpecs) { String className = moduleSpec.getClassName(); ReactModuleInfo reactModuleInfo = reactModuleInfoMap.get(className); ModuleHolder moduleHolder; if (reactModuleInfo == null) { NativeModule module; ReactMarker.logMarker( ReactMarkerConstants.CREATE_MODULE_START, moduleSpec.getClassName()); try { module = moduleSpec.getProvider().get(); } finally { ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_END); } moduleHolder = new ModuleHolder(module); } else { moduleHolder = new ModuleHolder(reactModuleInfo, moduleSpec.getProvider()); } String name = moduleHolder.getName(); putModuleTypeAndHolderToModuleMaps(className, name, moduleHolder); } } else { FLog.d( ReactConstants.TAG, reactPackage.getClass().getSimpleName() + " is not a LazyReactPackage, falling back to old version."); List<NativeModule> nativeModules; if (reactPackage instanceof ReactInstancePackage) { ReactInstancePackage reactInstancePackage = (ReactInstancePackage) reactPackage; nativeModules = reactInstancePackage.createNativeModules( mReactApplicationContext, mReactInstanceManager); } else { nativeModules = reactPackage.createNativeModules(mReactApplicationContext); } for (NativeModule nativeModule : nativeModules) { addNativeModule(nativeModule); } } }
有個代碼 nativeModules = reactPackage.createNativeModules(mReactApplicationContext);
這裏終於有點熟悉了,咱們在平時給rn添加模塊時都會寫一個XXXpackage.java,裏面有個方法
@Override public List<NativeModule> createNativeModules( ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new ReactBridge(reactContext)); return modules; }
看到了吧,咱們平時在本身寫的模塊裏面用的reactContext來自上面 reactPackage.createNativeModules(mReactApplicationContext);
傳入的,這個代碼是在NativeModuleRegisterBuilder.java中的,這個類保存了它被調用時傳入的context,那它在哪裏被調用的呢?
答案是在ReactInstanceManager.java中,
這個會傳入reactContext,它來自於同一個ReactInstanceManager.java文件中的
是new出來的,他new出來後被方法返回了,這個方法再這裏有調用
從名字能夠看出,是在線程中建立reactContext的,裏面會執行
此刻真正的reactContext被建立了,它會傳入這個方法setupReactContext
此刻這個ReactInstanceManager的這個mCurrentReactContext終於有值了,而且他是 private @Nullable volatile ReactContext mCurrentReactContext;
ReactContext類型,那咱們記得咱們上面提到的疑問1的地方,那裏的mCurrentReactContext 就是這裏的,他倆同樣,那什麼時候給他綁定activity呢
看這裏仍是那個ReactInstanceManager.java文件
咱們能夠看到onHostResume中傳入了一個activity,以後複製給mCurrentActivity,這個mCurrentActivity會在getCurrentActivity中返回出去,
這個方法定義在ReactContext中,而ReactApplicationContext 繼承自ReactContext,因此ReactApplicationContext 的實例mCurrentReactContext就有了這個方法,那關鍵就在onHostResume在什麼地方調用的時候傳入了當前activity呢
答案是在ReactActivityDelegate.java中
protected void onResume() { if (getReactNativeHost().hasInstance()) { getReactNativeHost().getReactInstanceManager().onHostResume( getPlainActivity(), (DefaultHardwareBackBtnHandler) getPlainActivity()); } if (mPermissionsCallback != null) { mPermissionsCallback.invoke(); mPermissionsCallback = null; } }
咱們看到這裏傳入了getPlainActivity(),這個方法
private Activity getPlainActivity() { return ((Activity) getContext()); }
這個返回的是當前context綁定的activity,咱們知道ReactActivity是把本身交給了ReactActivityDelegate.java這個代理類,因此在這個代理類中獲得的activity就是你那個繼承ReactActivity的類,好比MainActivity,到了這裏終於找到了。
總結一下:
大體流程是reactactivity代理類在onHostResume中調用getReactInstanceManager().onHostResume方法傳入activity,然後被賦值爲ReactInstanceManager類實例的私有變量mCurrentActivity.
因爲我不是安卓開發,因此以上理解若有不對請提示
開發時用到了createSwitchNavigator 可是這個導航器不支持過渡動畫,由於它能夠實現認證流程,也就是登陸後按返回無法返回登陸頁面,可是沒動畫就很難受,社區不少人在提這個需求,具體能夠看這裏 switchnavigator-animate-between-screens
不少人要求加這個功能,我感受他們實現起來不難啊,爲何到了如今還在計劃中,可是之後應該會有這個功能吧,目前沒有,這個文章回復裏面也有幾個解決方案,我試了這個https://www.npmjs.com/package/react-navigation-switch-transitioner
可是我運行的時候有報錯,提了issues,我最終放棄了,可是我想到了一個解決方法
好比咱們在Login.tsx頁面
componentDidMount(){ this. didBlurSubscription = this.props.navigation.addListener( 'didBlur', payload => { console.log('Login blur') const resetActions=StackActions.reset({ index:0, actions:[NavigationActions.navigate({routeName:"Register"})] }) this.props.navigation.dispatch(resetActions) } ); } componentWillUnmount(){ console.log('Login unmount') this.didBlurSubscription.remove(); }
上面的代碼中咱們在頁面失去焦點的時候reset當前路由state,使得只剩下一個Register頁面,這樣按返回鍵時就直接退出了。
咱們還要建立createStackNavigator由於只有它支持動畫啊
順便貼下修改react-navigation頁面轉場動畫的代碼
import {createStackNavigator, StackViewTransitionConfigs} from 'react-navigation'; const LoginStack=createStackNavigator({ Main:{ screen:LoginHome }, Register:{ screen:Register } }, { initialRouteName:"Main", transitionConfig:()=>StackViewTransitionConfigs.SlideFromRightIOS, navigationOptions:{ header:null } })
這樣就改爲了從右向左進入的方式。
用react-native開發安卓會有白屏問題,實際上是有兩個白屏階段,第一個是安卓本身啓動白屏,第二個是RN代碼初始化時的白屏問題,RN白屏咱們能夠用react-native-splash-screen這個插件解決,通常大部分人以爲是否是完全沒有白屏了?其實不是,還有安卓本身啓動的白屏也要解決下面說下辦法:
首先咱們在安卓res文件夾的values或者其餘values-vXX,在裏面找到styles.xml,沒有的話新建一個,內容以下
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> </style> <!--這裏咱們定義一個主題樣式,叫startTheme而後設置windowBackground屬性--> <style name="startTheme" parent="AppTheme"> <item name="android:windowBackground">@mipmap/launch_screen</item> </style> </resources>
在這以前咱們要在res裏面添加mipmap文件夾或者drawable文件夾相似這樣
這樣上面的樣式就能引用這個圖片了,也就是開屏頁的圖片,
而後咱們在咱們的AndroidManifest.xml中找到咱們RN所在的MainActivity在上面添加剛纔的樣式
此時若是運行APP,你點擊後應該毫秒級別出現你剛纔添加的圖片,接着安卓啓動完後會啓動RN的渲染,RN在咱們組件渲染完成前藉助
react-native-splash-scree 會展現一個圖片,通常建議這個圖片跟咱們樣式的圖片一致,這樣用戶感受不到是兩個過程。
接下來還有東西要處理,咱們要理解一下那個
<style name="startTheme" parent="AppTheme"> <item name="android:windowBackground">@mipmap/launch_screen</item> </style>
我設置它後,它在個人整個MainActivity都設置了個背景圖,以致於個人RN界面默認是這個背景圖,因此咱們要設置RN界面默認背景白色
這樣MainActivity背景色就改爲白色了,而不是剛纔那個背景圖了
OK,問題完美解決
平時用谷歌瀏覽器調試代碼的時候,輸入 debugger 這種調試方法有時候在谷歌瀏覽器看到的斷點位置跟實際斷點位置不是很一致,因此最近又從新研究了下使用webstrom直接打紅點斷點來調試。
1: 首先建議使用android studio在手動安裝debug App,也就是下圖箭頭所指的位置,使用這個安裝,報錯了比直接用命令行運行好排查問題,有時候用react-native run-android老是報錯,可是用as運行就沒問題,很奇怪的問題
2:等app安裝成功後,接下來要配置webstrom
在webstrom工具欄頂部找到這個東西,通常在右上角
點擊Edit Configurations
打開後點擊+好找到ReactNative
點擊OK
出現這個界面
3.而後我再建議加個這個配置
點擊 ... 出現下面的彈窗
進行圖中的操做
這個是讓谷歌瀏覽器靜默運行 不彈出那個debug 的瀏覽器窗口的
會出現這個界面,同時你在命令行運行 yarn start 跑起來packger服務
最後你還要打開rn的debug菜單
打開debug JS
而後你就能夠在編輯器打紅色斷點了
這個錯尼瑪找了不少方法沒解決,以前也有一個 Multiple dex 的錯,那個錯是包重複引入的問題,可是這個查看引用BuildConfig的只有一個包引入了,並無重複,後來開始瞎加配置起來了,忽然加了這個配置就能夠打包了
在app 下build.gradle中andriod 配置項添加這個就打包成功了
dexOptions { preDexLibraries = false }
在我本身的小米6手機上偶然發現一個bug,就是我第一次打開app後,不殺掉進程,按返回鍵退出,當我再次打開app後我設置的沉浸式狀態欄失效了,具體表現看下圖:
第一次打開時的正常樣式:
能夠看到內容是沉浸到狀態欄下面了
當我按物理返回按鈕退出後再次點擊app啓動後是這樣子:
尼瑪,沉浸式沒了!
研究了一下午最後纔想到是否是我在代碼裏面設置狀態欄沉浸式沒有生效?
最後看了看那react-native的StatusBarModule.java中的代碼發現有個判斷activity是否存在的判斷
後來測試發現確實是第二次打開就會走activity==null的判斷,而後直接return再也不設置狀態欄的樣式了,
但是爲何activity是null呢?
咱們經過研究react-native的源碼能夠知道rn會在mainActivity走到onResume生命週期時纔給getCurrentActivity返回的activity設置值爲MainActivity,也就是MainActivity徹底可見時,那咱們RN的代碼初始化是從onCreate就開始了,會不會是RN的代碼執行太快了,等執行到咱們在js代碼調用StatusBarModule的時候mainActivity此時並無執行onResume裏面的代碼呢?應該是有可能的,因此在渲染react-native組件的時候咱們這樣寫的組件
<View style={{flex:1}}> <StatusBar translucent={true} backgroundColor="rgba(0,0,0,0)" barStyle={store.statusBarColor}/> <RouterView ref={this.setNavigator} /> </View>
它渲染的時候咱們rnModule裏面還沒法獲取到activity,那咱們只能加個延時了,等onResume執行後再從新設置狀態欄
咱們在app組件的componentDidMount中加個延時
setTimeout(()=>{ StatusBar.setTranslucent(true); StatusBar.setBackgroundColor("rgba(0,0,0,0)") },10)
這樣就解決這個問題了,可是有個隱患,到底加多少的延時呢?萬一有些手機很卡唉,目前還不知道這個問題如何處理,暫且加個10ms吧