Try React Native for Android

Overview

Facebook 昨天发布了 React Native for Android,把 Web 和原生平台的 JavaScript 开发技术扩展到了 Google 的流行移动平台。Android开发者们终于也能试试React了。

本文就从 https://github.com/facebook/react-native 上的Android Sample来看看react Android是怎么跑起来的。

要求

OSX - 目前只支持OS X(Windows泪奔,不过不要弃疗,说不定你就跑成了呢)

Set up

Refers: http://facebook.github.io/react-native/docs/getting-started.html

首先配置一下一些android和ios通用的

1
2
3
4
5
brew install nvm
nvm install node && nvm alias default node
brew install watchman
brew install flow
brew update && brew upgrade

Facebook还是比较友善的,直接把Android工程的gradle配置也上传了,可以直接import react-native目录,里面包含了ReactAndroid以及三个Example模块,都是已经写好的sample,本文选择UIExplorer工程来运行。

发现不能编译,当然了,还没有编译配置React Native for Android。

需要确保这三个安装了

  • Android SDK version 23 (compileSdkVersion in build.gradle)
  • SDK build tools version 23.0.1 (buildToolsVersion in build.gradle)
  • Android Support Repository 17 (for Android Support Library)

local.properties里面应该要有
sdk.dir=absolute_path_to_android_sdk
ndk.dir=absolute_path_to_android_ndk
即还需要安装ndk。

build React Native for Android

然后要在react-native目录下执行

1
2
npm install
./gradlew :ReactAndroid:assembleDebug

就把React Native for Android给编译好了(可能会要挺久的,还要下载一些依赖)

run demo

然后就可以开始运行例子了:

1
2
3
4
5
6
7
8
# 这步其实也可以自己导入as工程然后run
./gradlew :Examples:UIExplorer:android:app:installDebug

# 运行后发现提示没有load到js?

# 另起一个shell运行(记得在这之前要npm install安装一下各种依赖)
./packager/packager.sh
# 打开安装的应用,点击RELOAD JS,这下应该有东西了

PS

  • 这里我碰到提示说const符号在strict mode下不能用,于是直接暴力把package.js里面的const都换成了var
  • 如果要在实机上运行,需要连接usb后adb reverse tcp:8081 tcp:8081

Let’s cc

先来看几个截图

应用首页

ProgressBar Example

抽屉

划了几下,从抽屉的行为上来看应该是fb自己写的,和DrawerLayout没什么关系(没有左边的EDGE_SIZE触发事件)

How it works

XML

先看看layout xml…恩…很简单很暴力,直接加了个match_parent的ReactRootView,啥都没了。

1
2
3
4
5
6
7
8
9
10
11
12
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".UIExplorerApp">


<com.facebook.react.ReactRootView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/react_root_view"/>


</RelativeLayout>

Java

那看来tricky的东西都在activity里面了,看看

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
39
40
public class UIExplorerActivity extends Activity implements DefaultHardwareBackBtnHandler {

private ReactInstanceManager mReactInstanceManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 用builder模式构建一个在ReactRootView上运行JS应用必须的ReactInstanceManager
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
// 从应用的raw assets去读得JS bundle文件,这个例子里貌似没有实际用处
.setBundleAssetName("UIExplorerApp.android.bundle")
// JS文件路径,用于在开发时reload js,直接运行时没法读到JS就是因为文件都在这儿
.setJSMainModuleName("Examples/UIExplorer/UIExplorerApp.android")
// Package defining basic modules and view managers
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(true)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();

// 把刚才构建的ReactInstanceManager和ReactRootView做了绑定,运行起react application
((ReactRootView) findViewById(R.id.react_root_view))
.startReactApplication(mReactInstanceManager, "UIExplorerApp", null);
}

...

@Override
protected void onPause() {
super.onPause();
// 把事件传到ReactInstanceManager,会更新Lifecycle和一些对应操作(如pause时候暂停一些监听)
if (mReactInstanceManager != null) {
mReactInstanceManager.onPause();
}
}

...
}

React for Android的java sdk代码就在ReactAndroid目录下,可以看到里面还有一个com.facebook.jni包,目测是通过来作中转,C++的源码在ReactAndroid/src/main/jni下。

C++ ships what

待补

JS

再接着看看js吧, UIEXplorer目录下一堆JS,看得头都大了,还是看看activity里面设置的那个入口js吧。

UIExplorerApp.android.js:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
var React = require('react-native');
var {
// 在Libraries下都可以找到,各种通用的库
AppRegistry,
BackAndroid,
Dimensions,
DrawerLayoutAndroid,
StyleSheet,
ToolbarAndroid,
View,
} = React;
// 包含了具体的各个例子列表(Image、ProgressBar、ScrollView等),也就是在目录下看到的那一堆js了
var UIExplorerList = require('./UIExplorerList.android');

var DRAWER_WIDTH_LEFT = 56;

var UIExplorerApp = React.createClass({
getInitialState: function() {
return {
example: this._getUIExplorerHome(),
};
},

_getUIExplorerHome: function() {
return {
title: 'UIExplorer',
component: this._renderHome(),
};
},

componentWillMount: function() {
BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress);
},

// 渲染抽屉,这里是不是有点像xml了
render: function() {
return (
<DrawerLayoutAndroid
drawerPosition={DrawerLayoutAndroid.positions.Left}
drawerWidth={Dimensions.get('window').width - DRAWER_WIDTH_LEFT}
keyboardDismissMode="on-drag"
ref={(drawer) => { this.drawer = drawer; }}

renderNavigationView={this._renderNavigationView}>
{this._renderNavigation()}
</DrawerLayoutAndroid>
);

},

// 抽屉内的列表
_renderNavigationView: function() {
return (
<UIExplorerList
onSelectExample={this.onSelectExample}
isInDrawer={true}
/>

);

},

// 点击处理
onSelectExample: function(example) {
this.drawer.closeDrawer();
// 返回首页
if (example.title === this._getUIExplorerHome().title) {
example = this._getUIExplorerHome();
}
this.setState({
example: example,
});
},

// 渲染首页列表
_renderHome: function() {
var onSelectExample = this.onSelectExample;
return React.createClass({
render: function() {
return (
<UIExplorerList
onSelectExample={onSelectExample}
isInDrawer={false}
/>

);

}
});
},

// 渲染导航栏
_renderNavigation: function() {
var Component = this.state.example.component;
return (
<View style={styles.container}>
<ToolbarAndroid
logo={require('image!launcher_icon')}
navIcon={require('image!ic_menu_black_24dp')}
onIconClicked={() => this.drawer.openDrawer()}

style={styles.toolbar}
title={this.state.example.title}
/>
<Component />
</View>
);

},

// 回退按钮事件处理
_handleBackButtonPress: function() {
// 不在首页
if (this.state.example.title !== this._getUIExplorerHome().title) {
this.onSelectExample(this._getUIExplorerHome());
return true;
}
return false;
},
});

var styles = StyleSheet.create({
container: {
flex: 1,
},
toolbar: {
backgroundColor: '#E9EAED',
height: 56,
},
});

// 还记得Activity调用的startReactApplication吗,这里就是注册了那里调用的moduleName
AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp);

module.exports = UIExplorerApp;

Write our own react app

继续回到react-native目录

1
2
3
4
// 安装react-native命令行工具
npm install -g react-native-cli
// 初始化项目,又是一个漫长的等待
react-native init OurSampleApp

待续…

总结

React native并不能带来1份代码2个平台跑,正如Facebook说的,Learn once, write anywhere,由于组件库和Hybrid API不同,所以一份代码运行在Android/iOS/Web无法通过直接使用react-native实现,可能需要改造和包装。但至少可以规划好,从而使得三个平台的react结构类似(说好的Android/iOS不同设计呢)。

从UI流畅度来看,远远超越了过去的那些hybrid framework像是phonegap。

对现有应用的集成,由于网络/缓存/原应有的复杂业务逻辑,集成可能还是会遇到不少麻烦的。

在打开抽屉后也发现了overdraw的问题,见图
抽屉Overdraw

Mark Zhai (翟一帆) wechat
欢迎您扫一扫上面的微信公众号,订阅我们的公众号!
坚持原创技术分享,您的支持将鼓励我继续创作!