轉載請註明出處:王亟亟的大牛之路
開篇之前日常安利
https://github.com/ddwhan0123/Useful-Open-Source-Android (各種庫的收納,長期維護)
上一篇提到了入口類ReactActivity和他的代理實現類ReactActivityDelegate,這一次繼續我們的分析之路
寫着一片之前,沒有看過任何其他兄弟對相關內容的分析,不是覺得自己牛逼。
是怕別人的思維影響到我的理解,如果講得不對,歡迎指出!
上一篇的傳送門RN安卓實現分析之ReactActivity的前世今生
ReactRootView
這是一個被ReactActivity.setContentView(mReactRootView)
的UI控件,我們先來看下他的實現
public class ReactRootView extends SizeMonitoringFrameLayout
implements RootView, MeasureSpecProvider {...}
- SizeMonitoringFrameLayout 它是一個繼承
FrameLayout
的一個ViewGroup,實現沒什麼複雜的,主要是可以監聽尺寸的變化,由OnSizeChangedListener這個接口對外暴露內容。OnSizeChangedListener可以回傳4個屬性,分別是 新的寬高和舊的寬高。 - RootView 它是一個接口,子控件手勢回傳時實現,通過onChildStartedNativeGesture方法傳遞一個MotionEvent對象
- MeasureSpecProvider 它是一個接口,
getWidthMeasureSpec() getHeightMeasureSpec()
兩個方法用來計算重新計算根視圖的長和寬的值
既然是一個爲了計算尺寸而自定義的的Layout那麼一定會有onMeasure(),onLayout(),
等方法
首先獲取了Mode類型,判斷如果是MeasureSpec.AT_MOST
或者MeasureSpec.UNSPECIFIED
就對子控件進行循環計算複製給width
變量,如果不是的話直接調用MeasureSpec.getSize()
方法進行賦值。
MeasureSpec有三種模式:
UNSPECIFIED: 父元素部隊自元素施加任何束縛,子元素可以得到任意想要的大小;
EXACTLY: 父元素決定自元素的確切大小,子元素將被限定在給定的邊界裏而忽略它本身大小;
AT_MOST: 子元素至多達到指定大小的值。
MeasureSpec.getSize(measureSpec): 根據提供的測量值提取大小值(尺寸級別的數值變化)
高度同上,我們就得到了 2個具體的寬高值。然後調用setMeasuredDimension(width, height);
,設置當前View的大小。
計算完把ReactRootView的類變量mWasMeasured設置爲true,表示控件已經計算過了!
經過判斷決定是刷新位置信息還是構建ReactInstanceManager實例
在mReactInstanceManager爲null時,enableLayoutCalculation()方法直接返回,否則會對當前mReactInstanceManager對象的ReactContext進行一輪設置。
那麼mReactInstanceManager又是在哪初始化的呢?
這個就是我們在ReactActivity調用的那個方法,傳入的是
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this)
所攜帶的mReactNativeHost裏的mReactInstanceManager。這個對象一定不會爲空因爲,該對象爲空的話他會創建個新的!
/**
* Get the current {@link ReactInstanceManager} instance, or create one.
*/
public ReactInstanceManager getReactInstanceManager() {
if (mReactInstanceManager == null) {
mReactInstanceManager = createReactInstanceManager();
}
return mReactInstanceManager;
}
所以startReactApplication方法後執行的方法爲其內部的attachToReactInstanceManager();
private void attachToReactInstanceManager() {
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachToReactInstanceManager");
try {
if (mIsAttachedToInstance) {
return;
}
mIsAttachedToInstance = true;
Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
該方法把mIsAttachedToInstance
值改爲了true,然後添加了一個自定義OnGlobalLayoutListener
ViewTreeObserver是用來幫助我們監聽某些View的某些變化的。
ViewTreeObserver.OnGlobalLayoutListener當在一個視圖樹中全局佈局發生改變或者視圖樹中的某個視圖的可視狀態發生改變時,所要調用的回調函數的接口類
private CustomGlobalLayoutListener getCustomGlobalLayoutListener() {
if (mCustomGlobalLayoutListener == null) {
mCustomGlobalLayoutListener = new CustomGlobalLayoutListener();
}
return mCustomGlobalLayoutListener;
}
無論怎麼走都會有一個私有類CustomGlobalLayoutListener
的實例,它實現了ViewTreeObserver.OnGlobalLayoutListener
接口
CustomGlobalLayoutListener有點長,我們一步步看
private final Rect mVisibleViewArea; //可視的一個方塊區域
private final int mMinKeyboardHeightDetected;//最小鍵盤高度 60
private int mKeyboardHeight = 0;//鍵盤高度
private int mDeviceRotation = 0;//旋轉後會賦值
//以下是屏幕屬性的兩個對象
private DisplayMetrics mWindowMetrics = new DisplayMetrics();
private DisplayMetrics mScreenMetrics = new DisplayMetrics();
構造函數會給創建小方塊以及給鍵盤最小高度賦值
/* package */ CustomGlobalLayoutListener() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
mVisibleViewArea = new Rect();
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
}
可視回調觸發後,分別檢驗鍵盤,橫豎屏和設備可用尺寸的變化
@Override
public void onGlobalLayout() {
if (mReactInstanceManager == null || !mIsAttachedToInstance ||
mReactInstanceManager.getCurrentReactContext() == null) {
return;
}
checkForKeyboardEvents();
checkForDeviceOrientationChanges();
checkForDeviceDimensionsChanges();
}
getWindowVisibleDisplayFrame()是View類下的一個方法,從方法的名字就可以看出,它是用來獲取當前窗口可視區域大小的。
各種噼裏啪啦的計算後把結果用sendEvent(String eventName, @Nullable WritableMap params)
方法進行傳遞
sendEvent方法會最終會調用mReactInstanceManager
的emit(String eventName, @Nullable Object data);
方法把結果傳給JS部分,返回鍵啥的也是走emit方法
旋轉方法checkForDeviceOrientationChanges()最終會傳遞一個key爲namedOrientationDidChange的事件
檢測屏幕尺寸的方法checkForDeviceDimensionsChanges()
最終會傳遞一個key爲didUpdateDimensions的事件
雖然計算場景有所差異 但是最終都是調用emit
在繪製的時候調用過updateRootLayoutSpecs()
也就是當內容發現變化的時候由他來實現真實當更新操作
首先拿到上下文對象 ReactContext,因爲它是一個volatile
的變量所以是時不時會刷新一下值,但是不會爲空
然後就是handler的UI操作了
調用的是com.facebook.react.uimanager
下面的UIManagerModuleupdateRootLayoutSpecs(int rootViewTag, int widthMeasureSpec, int heightMeasureSpec)
方法
傳入一個Tag和我們計算的結果進行UI操作(這部分怎麼實現的之後再找時間分析)
那麼看下這個Tag ,找了一圈是UIManagerModuleaddRootView( final T rootView)
方法的返回值,也就是拿這個ReactRootView類裏的Tag變量和當前業務UIManagerModule類中rootView的Tag做了關聯
有啓動就一定有銷燬,不然強行等GC麼?unmountReactApplication()
public void unmountReactApplication() {
if (mReactInstanceManager != null && mIsAttachedToInstance) {
mReactInstanceManager.detachRootView(this);
mIsAttachedToInstance = false;
}
mShouldLogContentAppeared = false;
}
官方建議在外部Activity或者容器Fragment的onDestroy()/onDestroyView()
方法調用即可
一開始有提到這個容器控件還傳遞子控件的手勢,在onChildStartedNativeGesture()
方法把子控件的事件用UIManagerModule
的mEventDispatcher
屬性調用JS事件分發類JSTouchDispatcher
的onChildStartedNativeGesture(MotionEvent androidEvent, EventDispatcher eventDispatcher)
方法把事件傳遞給JS邏輯處理
在好幾個容器控件都有用到,該實現
主要流程的方法都介紹完整了,這一篇還是比較細的,當然還有幾個自定義入口的方法沒介紹,但是並不影響你對ReactRootView
的理解
總結:
ReactRootView主要的功能是提供強大的控件能力和事件傳遞
startReactApplication
方法調用後綁定上OnGlobalLayoutListener
監聽
然後對屏幕,頁面旋轉,鍵盤相關進行了着重計算處理。
onMeasure()
方法計算完結果通過UIManagerModule
對UI進行渲染麼不是本身自身實現繪製操作。
unmountReactApplication()
方法可以卸載不用的視圖對象,以防內存泄漏
onChildStartedNativeGesture(MotionEvent androidEvent)
方法把事件傳遞給RTC控件處理業務邏輯
如果有不對的歡迎留言糾正!
插一段廣告
蔚來汽車
上海 安亭/徐家彙/漕河涇 (安亭有班車)
收Android/iOS/.Net/Java/Vue/RN開發
標準五險一金(不避稅)
不強制加班,彈性工作
有意向的可以加我微信,必須註明來意