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
5brew 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
2npm 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
先来看几个截图
划了几下,从抽屉的行为上来看应该是fb自己写的,和DrawerLayout没什么关系(没有左边的EDGE_SIZE触发事件)
How it works
XML
先看看layout xml…恩…很简单很暴力,直接加了个match_parent的ReactRootView,啥都没了。
1 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
Java
那看来tricky的东西都在activity里面了,看看
1 | public class UIExplorerActivity extends Activity implements DefaultHardwareBackBtnHandler { |
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
127var 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的问题,见图