F8App-ReactNative项目源码分析3-Android端

近期开始研究Facebook f8app项目,目标是理解Facebook官方React Native f8app的整体技术架构,给公司目前几个的React Native项目开发提供官方经验借鉴,并对原生开发和React Native开发进行框架层面的融合。
本文分析f8app android代码的结构和技术实现,阅读本文的前提是熟悉Android开发。

f8app android代码结构分析

ReactNative项目Android相关的代码都在android目录下,可以直接使用Android Studio打开这个目录方便查看和修改。android目录结构如下

├── 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文件结构

MainActivity 代码分析

通过AndroidManifest.xml可以知道App的页面入口Activity是android/app/src/main/java/com/facebook/f8/MainActivity.java,代码比较简单,继承了ReactActivity,关键的方法是getJSBundleFile,可以看到使用了CodePush推送更新技术,返回了一个bundle文件,分析代码可知默认的bundle文件是index.android.bundle。
CodePush是微软提供的一个开发者服务,支持对React Native和Cordova App在不重新安装App的前提下热更新App,是目前很流行的一种App热部署技术。
getPackages方法初始化了CodePush,初始了ReactNativePushNotificationPackage推送服务,初始化了用到的一些RN第三方包:

  • FBSDKPackage是Android Facebook SDK的React Native封装,让React Native App能够使用Facebook的接口,提供了Facebook用户登录token,分享等功能;
  • LinearGradientPackage是一个渐变UI控件;
  • RNSharePackage是个分享插件,提供了通过Android Intent.ACTION_SEND分享文字和链接的简单分享功能;
  • RNSendIntentPackage是一个发送Android Intent的插件,提供了通过Intent拨打电话,发送短信,在系统日历添加事件提醒等功能。
  • ReactNativePushNotificationPackage是一个本地和远程通知推送插件,可以接受Google GCM推送的消息,并在状态栏上显示。
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,发现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插件。实际上,这些插件是通过rnpm install命令安装的。React Native Package Manager (RNPM) 是一个简化React Native插件安装体验的工具。
下面我们分析react-native-linear-gradient插件看看插件是如何实现的。

React Native android 插件实现分析

LinearGradientView是一个典型的原生UI插件,可以先看看官方的文档NativeComponentsAndroid, 我感觉这个文档有点过时,没有提到ReactPackage。ReactPackage接口是现在RN插件的主入口。LinearGradientPackage实现了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类主要实现了createViewManagers方法,返回了一个LinearGradientManager实例,代码如下:

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实例,LinearGradientView是渐变View的具体实现,通过@ReactProp注解可以在js层设置渐变View的一些属性,例如渐变颜色,渐变开始和结束位置等的。代码如下:

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 热更新技术介绍

f8app使用了CodePush技术热更新App,可以先看下微软官方的文档React Native Client SDK
CodePush提供了对React Native App的热更新能力,目前只能对js bundle进行热更新,如果原生代码修改了是不能热更新的,只能通过重新安装App的方式升级。
CodePush的一个问题是是对React Native版本的兼容性,由于React Native版本更新很快,CodePush是由微软而不是Facebook维护的,所以对React Native0.18以前的版本支持有各种兼容问题,具体参考文档
CodePush的具体配置可以参考这篇文章,配置需要注意的事项还是挺多的,我参考这篇文章配置成功了,本文不做详细介绍。

总结

本文分析了f8app android代码结构,以react-native-linear-gradient插件为例子分析了React Native插件的结构,介绍了CodePush热更新技术,通过本文可以对f8app有比较初步的认识。
下篇文章将分析React Native js代码,这块是f8app的核心实现,代码量比较大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容