探索一个 React Native 应用是如何启动的 — Android 篇

当拿到一个项目的时候,我们要知道项目是如何运行起来的,这样方便我们更能理解项目的原理,也能帮助我们以后排错和优化。

本文的大致思路可以分为三步:

  • 找到整个应用的入口文件
  • 找到 native 端和 React Native 相连接的方法
  • 找到 React Native 的入口文件

根据以上三步,就可以知道整个项目是如何运行的了。下面我们一起来寻找吧~

用 Android Stutio 打开项目根目录下的 Android 文件夹,我们能看到下面的文件分布:

android文件夹

  • app 文件夹里面是 Android 的配置文件
  • app 文件下面的文件都是我们需要的原生组件

    整个应用的入口文件

    打开 app - manifest,我们看到里面有一个 AndroidManifest.xml 文件。此文件是整个应用的入口,配置了程序运行所必须的组件、启动位置以及一些其他的信息。

打开 AndroidManifest.xml 文件我们能看到下面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"/>
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
<application
android:name=".MainApplication"
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

我们看到在这个文件中主要有以下节点:

  • manifest
  • uses-permission
  • uses-sdk
  • application
  • activity

下面就各个节点来做一个介绍:

AndroidManifest.xml 文件里主要是一个 manifest 节点,然后包裹其他的节点,通过配置不同的属性来得到我们想要的效果。

manifest

  • xmlns:android
    它是用来定义 android 的命名空间的,一般为 http://schemas.android.com/apk/res/android。有了它之后,就能够使用 Android 中各种的标准属性了。

  • package
    是本项目内 java 主程序包的包名,也是一个应用进程的默认名称。

  • android:versionCode
    interger 值。设备程序通过它来识别版本信息,代表 app 更新过多少次,1 代表 1 次,再更新的话就设置为 2,以此类推等等。

  • android:versionName
    这个值是给用户看的,设备可以是 1.0 版本,后续更新的话可以设置为 1.1、1.2 等等。

uses-permission

通过字面意思我们可以大致的认为它是需要和用户进行操作的。其实就是,app 安装的时候需要向用户索要的一些权限,用户可以同意也可以拒绝。

  • android:name
    即需要使用的权限的名字,可以是系统自带的权限,也可以是自定义的权限。

uses-sdk

uses-sdk 是用来设置 appandroid 系统的兼容性的。我们用了以下两个配置项,配置项的值是一个代表 Android API Level 参数的整数。每个 API Level 对应着一个 Android 系统的版本。下图为官方给出的 API Level 和 Android 系统版本的对照表。

android level 对照表

  • android:minSdkVersion
    一个用于指定应用运行所需最低 API 级别的整数。 如果系统的 API 级别低于该属性中指定的值,Android 系统将阻止用户安装应用。

  • android:targetSdkVersion
    一个用于指定应用的目标 API 级别的整数。如果未设置,其默认值与为 minSdkVersion 指定的值相等。一般情况下应该将这个属性的值设置为最新的 API level 值,这样我们才可以利用新版本系统上的新特性。

Application

每个 Android 应用程序启动的时候都会初始化一个 Application 类。我们可以通过它来定义一些全局的和一些上下文都要用到的变量和方法。

Application 的生命周期是整个应用中最长的,可以说它的生命周期就是整个应用的生命周期。

  • android:name
    一般情况下系统会自动创建一个 Application 类。如果我们需要自定义一个 Application 类的话,需要创建一个类并且继承 Application。然后在 name 属性中写上自定义的 Application 类名即可。

  • android:allowBackup
    Android API Level 8 及其以上 Android 系统提供了为应用程序数据的备份和恢复功能。此属性就是控制该功能的开关。系统默认为 true。当 allowBackup 标志为 true 时,用户可通过 adb backupadb restore 来进行对应用数据的备份和恢复。

  • android:label
    显示给用户的 APP 的名称

  • android:icon
    APP 的图标所在的路径。

  • android:theme
    它给所有的 Activity 定义了一个默认的主题风格,也可以在自己的 theme 里设置。

Activity

每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。

  • android:name
    如果第一个字节是小数点,那么会自动的在类别名称前加上这个项目的 package 名称。看到上面的例子中,咱们把 android:name 设为了 .MainActivity,所以项目读取的时候应该为 com.myapp.MainActivity

  • android:label
    Activity 的标题,可以覆盖 Application 的 label。

  • android:configChanges
    当手机进行横屏和竖屏的切换时,是否调用 onConfigurationChanged() 方法。
    如果不设置此属性,当 Android 手机旋转后,会把当前 Activity 杀掉,然后根据方向重新加载这个Activity,就会从 onCreate 开始重新加载。
    如果设置了此属性,当手机旋转后,当前 Activity 就会调用 onConfigurationChanged() 方法,而不是调用 onCreate 方法。

  • android:screenOrientation
    activity 显示的模式。
    默认为 unspecified:由系统自动判断显示方向
    landscape 横屏模式,宽度比高度大
    portrait 竖屏模式, 高度比宽度大
    user 模式,用户当前首选的方向
    behind 模式:和该Activity下面的那个Activity的方向一致(在Activity堆栈中的)
    sensor 模式:有物理的感应器来决定。如果用户旋转设备这屏幕会横竖屏切换
    nosensor 模式:忽略物理感应器,这样就不会随着用户旋转设备而更改了

  • android:windowSoftInputMode
    activity 主窗口与软键盘的交互模式,可以用来避免输入法面板遮挡问题,Android1.5 后的一个新特性。
    adjustResize:该 Activity 总是调整屏幕的大小以便留出软键盘的空间

注意: 看到上面的代码里 activity 内还有 intent-filter 元素,说明这个 activity 会在应用启动的时候第一个被执行。

intent-filter

  • action
    android:name 值为 android.intent.action.MAIN,表明此 Activity 是作为应用程序的入口。

  • category
    android:name 值为 android.intent.category.LAUNCHER,表明应用程序是否显示在程序列表里。

native 端和 React Native 相连接的方法

MainActivity

通过上面的一些介绍,我们可以知道,有 intent-filter 节点,并且它的 actionandroid:nameandroid.intent.action.MAIN 的 Activity 为本项目的入口。那我们就可以顺藤摸瓜,一步一步的往下走。

打开 com.myapp 下的 MainActivity 文件,找到 onCreate 方法。这个方法主要是显示悬浮窗,并且加载 App。如果无权使用悬浮窗,则会提醒用户授权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(this)) { // 如果有权限使用悬浮窗,则显示,并且执行 loadapp 方法
SplashScreen.show(this,true);
loadapp();
}else{
Toast.makeText(MainActivity.this, "当前无权限使用悬浮窗,请授权!", Toast.LENGTH_SHORT).show();
Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(serviceIntent,ALERT_WINDOW_PERMISSION_CODE);
}
} else {
SplashScreen.show(this,true);
loadapp();
}
}

加载 app 的方法是 loadapp() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void loadapp() {
mReactRootView = new ReactRootView(this);
ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.addPackage(new RNJavaReactPackage())
.addPackage(new LinearGradientPackage())
.addPackage(new SvgPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED);
File bundleFile = new File(getExternalCacheDir(),"offline/index.android.bundle");
if(bundleFile.exists()){
builder.setJSBundleFile(bundleFile.getAbsolutePath());
} else {
builder.setBundleAssetName("index.android.bundle");
}
mReactInstanceManager = builder.build();
mReactRootView.startReactApplication(mReactInstanceManager, "MyApp", null);
setContentView(mReactRootView);
}
  • 第一步 mReactRootView = new ReactRootView(this) 的意思是实例化 ReactRootView,并将它作为 Activity 的根 view
  • setApplication 的意思是把 native 端的 Application 对象赋给 React Native。
  • setJSMainModuleName 的参数是 js 的入口文件 —— index.android.jsindex.android.js 文件的路径是相对于 package.json 的路径。
  • addPackage 是自定义 React Native 的 Android 组件。
  • setUseDeveloperSupport 表示是否启用开发者模式。这里写 BuildConfig.DEBUG 就可以自动根据 gradle 构建的类型(debug或release)来决定。
  • mReactRootView.startReactApplication 方法是启动整个 React Native 程序。
  • setContentView 方法是将 mReactRootView 作为子布局加载到 Activity 中。

React Native 的入口文件

AppRegistry 是JS运行所有React Native应用的入口。通过 AppRegistry.registerComponent 来注册项目的根组件,然后原生系统才可以加载应用的代码包并且在启动完成之后通过调用 AppRegistry.runApplication 来真正运行应用。

1
2
3
4
import { AppRegistry } from 'react-native'
import MyApp from './js/app'
AppRegistry.registerComponent('MyApp', () => MyApp)

支持原创