F8App-ReactNative項目源碼分析3-Android端

近期開始研究Facebook f8app項目,目標(biāo)是理解Facebook官方React Native f8app的整體技術(shù)架構(gòu),給公司目前幾個的React Native項目開發(fā)提供官方經(jīng)驗借鑒,并對原生開發(fā)和React Native開發(fā)進(jìn)行框架層面的融合。
本文分析f8app android代碼的結(jié)構(gòu)和技術(shù)實(shí)現(xiàn),閱讀本文的前提是熟悉Android開發(fā)。

f8app android代碼結(jié)構(gòu)分析

ReactNative項目Android相關(guān)的代碼都在android目錄下,可以直接使用Android Studio打開這個目錄方便查看和修改。android目錄結(jié)構(gòu)如下

├── F8v2.iml
├── app
│   ├── app.iml
│   ├── build.gradle
│   ├── proguard-rules.pro
│   ├── react.gradle
│   └── src
│       └── main
│           ├── AndroidManifest.xml
│           ├── java
│           │   └── com
│           │       └── facebook
│           │           └── f8
│           │               └── MainActivity.java
│           └── res
│               ├── mipmap-hdpi
│               │   ├── ic_launcher.png
│               │   └── ic_notification.png
│               ├── mipmap-mdpi
│               │   ├── ic_launcher.png
│               │   └── ic_notification.png
│               ├── mipmap-xhdpi
│               │   ├── ic_launcher.png
│               │   └── ic_notification.png
│               ├── mipmap-xxhdpi
│               │   ├── ic_launcher.png
│               │   └── ic_notification.png
│               ├── mipmap-xxxhdpi
│               │   ├── ic_launcher.png
│               │   └── ic_notification.png
│               ├── raw
│               │   └── third_party_notices.html
│               └── values
│                   ├── strings.xml
│                   └── styles.xml
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

在android Studio下顯示如下:


as文件結(jié)構(gòu)

MainActivity 代碼分析

通過AndroidManifest.xml可以知道App的頁面入口Activity是android/app/src/main/java/com/facebook/f8/MainActivity.java,代碼比較簡單,繼承了ReactActivity,關(guān)鍵的方法是getJSBundleFile,可以看到使用了CodePush推送更新技術(shù),返回了一個bundle文件,分析代碼可知默認(rèn)的bundle文件是index.android.bundle。
CodePush是微軟提供的一個開發(fā)者服務(wù),支持對React Native和Cordova App在不重新安裝App的前提下熱更新App,是目前很流行的一種App熱部署技術(shù)。
getPackages方法初始化了CodePush,初始了ReactNativePushNotificationPackage推送服務(wù),初始化了用到的一些RN第三方包:

  • FBSDKPackage是Android Facebook SDK的React Native封裝,讓React Native App能夠使用Facebook的接口,提供了Facebook用戶登錄token,分享等功能;
  • LinearGradientPackage是一個漸變UI控件;
  • RNSharePackage是個分享插件,提供了通過Android Intent.ACTION_SEND分享文字和鏈接的簡單分享功能;
  • RNSendIntentPackage是一個發(fā)送Android Intent的插件,提供了通過Intent撥打電話,發(fā)送短信,在系統(tǒng)日歷添加事件提醒等功能。
  • ReactNativePushNotificationPackage是一個本地和遠(yuǎn)程通知推送插件,可以接受Google GCM推送的消息,并在狀態(tài)欄上顯示。
public class MainActivity extends ReactActivity {
  private CodePush _codePush;
  private ReactNativePushNotificationPackage _pushNotification;
  private CallbackManager mCallbackManager;

    @Override
    protected String getJSBundleFile() {
        return this._codePush.getBundleUrl("index.android.bundle");
    }

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "F8v2";
    }

    /**
     * Returns whether dev mode should be enabled.
     * This enables e.g. the dev menu.
     */
    @Override
    protected boolean getUseDeveloperSupport() {
        return BuildConfig.DEBUG;
    }

   /**
   * A list of packages used by the app. If the app uses additional views
   * or modules besides the default ones, add more packages here.
   */
    @Override
    protected List<ReactPackage> getPackages() {
      this._codePush = new CodePush("qwfkzzq7Y8cSrkiuU7aRCkIP7XYLEJ6b-AFoe", this, BuildConfig.DEBUG);
      this._pushNotification = new ReactNativePushNotificationPackage(this);
      mCallbackManager = new CallbackManager.Factory().create();

      return Arrays.<ReactPackage>asList(
        new MainReactPackage(),
        new FBSDKPackage(mCallbackManager),
        new LinearGradientPackage(),
        new RNSharePackage(),
        new RNSendIntentPackage(),
        this._codePush.getReactPackage(),
        this._pushNotification
      );
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FacebookSdk.sdkInitialize(getApplicationContext());
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
      super.onActivityResult(requestCode, resultCode, data);
      mCallbackManager.onActivityResult(requestCode, resultCode, data);
    }

   @Override
   protected void onNewIntent (Intent intent) {
     super.onNewIntent(intent);
     _pushNotification.newIntent(intent);
   }

  @Override
  protected void onResume() {
      super.onResume();
      AppEventsLogger.activateApp(getApplicationContext());
  }

  @Override
  protected void onPause() {
      super.onPause();
      AppEventsLogger.deactivateApp(getApplicationContext());
  }

  @Override
  protected void onStop() {
      super.onStop();
      AppEventsLogger.onContextStop();
  }
}

可以查看settings.gradle,發(fā)現(xiàn)f8app Android引用了5個子工程,子工程源碼放在../node_modules目錄下。

rootProject.name = 'F8v2'

include ':app', ':react-native-linear-gradient'
include ':app', ':react-native-code-push'
include ':react-native-push-notification'
include ':react-native-share', ':app'
include ':react-native-fbsdk'
include ':react-native-send-intent', ':app'
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
project(':react-native-push-notification').projectDir = file('../node_modules/react-native-push-notification/RNPushNotificationAndroid')
project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android')
project(':react-native-fbsdk').projectDir = new File(settingsDir, '../node_modules/react-native-fbsdk/Android')
project(':react-native-send-intent').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-send-intent')

可以看到,可以通在settings.gradle引入工程的方法增加新的自定義RN插件。實(shí)際上,這些插件是通過rnpm install命令安裝的。React Native Package Manager (RNPM) 是一個簡化React Native插件安裝體驗的工具。
下面我們分析react-native-linear-gradient插件看看插件是如何實(shí)現(xiàn)的。

React Native android 插件實(shí)現(xiàn)分析

LinearGradientView是一個典型的原生UI插件,可以先看看官方的文檔NativeComponentsAndroid, 我感覺這個文檔有點(diǎn)過時,沒有提到ReactPackage。ReactPackage接口是現(xiàn)在RN插件的主入口。LinearGradientPackage實(shí)現(xiàn)了ReactPackage接口
ReactPackage接口代碼如下:

/**
 * Main interface for providing additional capabilities to the catalyst framework by couple of
 * different means:
 * 1) Registering new native modules
 * 2) Registering new JS modules that may be accessed from native modules or from other parts of the
 * native code (requiring JS modules from the package doesn't mean it will automatically be included
 * as a part of the JS bundle, so there should be a corresponding piece of code on JS side that will
 * require implementation of that JS module so that it gets bundled)
 * 3) Registering custom native views (view managers) and custom event types
 * 4) Registering natively packaged assets/resources (e.g. images) exposed to JS
 *
 * TODO(6788500, 6788507): Implement support for adding custom views, events and resources
 */
public interface ReactPackage {

  /**
   * @param reactContext react application context that can be used to create modules
   * @return list of native modules to register with the newly created catalyst instance
   */
  List<NativeModule> createNativeModules(ReactApplicationContext reactContext);

  /**
   * @return list of JS modules to register with the newly created catalyst instance.
   *
   * IMPORTANT: Note that only modules that needs to be accessible from the native code should be
   * listed here. Also listing a native module here doesn't imply that the JS implementation of it
   * will be automatically included in the JS bundle.
   */
  List<Class<? extends JavaScriptModule>> createJSModules();

  /**
   * @return a list of view managers that should be registered with {@link UIManagerModule}
   */
  List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}

LinearGradientPackage類主要實(shí)現(xiàn)了createViewManagers方法,返回了一個LinearGradientManager實(shí)例,代碼如下:

public class LinearGradientPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
            new LinearGradientManager());
    }
}

下面看LinearGradientManager類,包含了一個LinearGradientView實(shí)例,LinearGradientView是漸變View的具體實(shí)現(xiàn),通過@ReactProp注解可以在js層設(shè)置漸變View的一些屬性,例如漸變顏色,漸變開始和結(jié)束位置等的。代碼如下:

public class LinearGradientManager extends SimpleViewManager<LinearGradientView> {

    public static final String REACT_CLASS = "BVLinearGradient";
    public static final String PROP_COLORS = "colors";
    public static final String PROP_LOCATIONS = "locations";
    public static final String PROP_START_POS = "start";
    public static final String PROP_END_POS = "end";
    public static final String PROP_BORDER_RADIUS = "borderRadius";

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @Override
    protected LinearGradientView createViewInstance(ThemedReactContext context) {
        return new LinearGradientView(context);
    }

    @ReactProp(name=PROP_COLORS)
    public void setColors(LinearGradientView gradientView, ReadableArray colors) {
        gradientView.setColors(colors);
    }

    @ReactProp(name=PROP_LOCATIONS)
    public void setLocations(LinearGradientView gradientView, ReadableArray locations) {
        gradientView.setLocations(locations);
    }

    @ReactProp(name=PROP_START_POS)
    public void setStartPosition(LinearGradientView gradientView, ReadableArray startPos) {
        gradientView.setStartPosition(startPos);
    }

    @ReactProp(name=PROP_END_POS)
    public void setEndPosition(LinearGradientView gradientView, ReadableArray endPos) {
        gradientView.setEndPosition(endPos);
    }

    // temporary solution until following issue is resolved:
    // https://github.com/facebook/react-native/issues/3198
    @ReactProp(name=PROP_BORDER_RADIUS, defaultFloat = 0f)
    public void setBorderRadius(LinearGradientView gradientView, float borderRadius) {
        gradientView.setBorderRadius(PixelUtil.toPixelFromDIP(borderRadius));
    }
}

CodePush 熱更新技術(shù)介紹

f8app使用了CodePush技術(shù)熱更新App,可以先看下微軟官方的文檔React Native Client SDK
CodePush提供了對React Native App的熱更新能力,目前只能對js bundle進(jìn)行熱更新,如果原生代碼修改了是不能熱更新的,只能通過重新安裝App的方式升級。
CodePush的一個問題是是對React Native版本的兼容性,由于React Native版本更新很快,CodePush是由微軟而不是Facebook維護(hù)的,所以對React Native0.18以前的版本支持有各種兼容問題,具體參考文檔
CodePush的具體配置可以參考這篇文章,配置需要注意的事項還是挺多的,我參考這篇文章配置成功了,本文不做詳細(xì)介紹。

總結(jié)

本文分析了f8app android代碼結(jié)構(gòu),以react-native-linear-gradient插件為例子分析了React Native插件的結(jié)構(gòu),介紹了CodePush熱更新技術(shù),通過本文可以對f8app有比較初步的認(rèn)識。
下篇文章將分析React Native js代碼,這塊是f8app的核心實(shí)現(xiàn),代碼量比較大。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內(nèi)容