今天我们继续来看一下原生模块的一些特性例如:回调方法函数,Promises,多线程,事件发送到JavaScript,监听生命周期事件,获取Activity Result等相关的特性。当前所讲解内容适配Android开发。
本示例代码地址:https://github.com/jiangqqlmj/NativeModules1
刚创建的React Native技术交流4群(458982758),欢迎各位大牛,React Native技术爱好者加入交流!同时博客右侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!
前面我们已经学习过了封装原生模块给React Native前端进行调用(点击进入查看详情),现在我们一起来分别看一下其他的特性:
原生模块有一种特殊的参数那就是回调函数,在绝大多数情况下该用来提供一个回调方法进行传值给JavaScript。具体该怎么样提供回调方法呢?还记得前面的文章中讲到原生模块封装给RN前端调用不?被调用的方法使用@ReactMethod注解。YES这边也差不多。我们来看一下官方的实例怎么样写的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class UIManagerModule extends ReactContextBaseJavaModule {
...
@ReactMethod
public void measureLayout(
int tag,
int ancestorTag,
Callback errorCallback,
Callback successCallback) {
try
{
measureLayout(tag, ancestorTag, mMeasureBuffer);
float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
successCallback.invoke(relativeX, relativeY, width, height);
}
catch
(IllegalViewOperationException e) {
errorCallback.invoke(e.getMessage());
}
}
...
|
然后该方法在JavaScript中进行如下调用:
1
2
3
4
5
6
7
8
9
10
|
UIManager.measureLayout(
100,
100,
(msg) => {
console.log(msg);
},
(x, y, width, height) => {
console.log(x +
':'
+ y +
':'
+ width +
':'
+ height);
}
);
|
这样原生模块只会调用回调方法一次,但是该可以保持让callback进行调用。
[注意].该回调方法在调用之后不会立即有返回,因为该为桥接方式的异步调用。该通过消息循环完成。
具体的实例代码在文章顶部的项目地址中,实例如下:
实例使用方法在之前的项目中ToastCustomModule添加measureLayout方法如下:
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
|
package com.modules.custom;
import android.widget.Toast;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.uimanager.IllegalViewOperationException;
import java.util.HashMap;
import java.util.Map;
/**
* 当前类注释:测试原生Toast模块
* 项目名:android
* 包名:com.modules.custom
* 作者:江清清 on 16/3/31 10:18
* 邮箱:[email protected]
* QQ: 781931404
* 来源:<a href="http://www.lcode.org">江清清的技术专栏</>
*/
public class ToastCustomModule extends ReactContextBaseJavaModule {
private static final String DURATION_SHORT=
"SHORT"
;
private static final String DURATION_LONG=
"LONG"
;
public ToastCustomModule(ReactApplicationContext reactContext) {
super
(reactContext);
}
@Override
public String getName() {
return
"ToastCustomAndroid"
;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants =
new
HashMap<>();
constants.put(DURATION_SHORT, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG, Toast.LENGTH_LONG);
return
constants;
}
/**
* 该方法用于给JavaScript进行调用
* @param message
* @param duration
*/
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
/**
* 这边只是演示相关回调方法的使用,所以这边的使用方法是非常简单的
* @param errorCallback 数据错误回调函数
* @param successCallback 数据成功回调函数
*/
@ReactMethod
public void measureLayout(Callback errorCallback,
Callback successCallback){
try
{
successCallback.invoke(100, 100, 200, 200);
}
catch
(IllegalViewOperationException e) {
errorCallback.invoke(e.getMessage());
}
}
}
|
具体前端JavaScript文件中的调用方式如下:
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
|
/**
* Sample React Native App
*https://github.com/facebook/react-native
*/
import React, {
AppRegistry,
Component,
StyleSheet,
Text,
View,
TouchableHighlight,
ToastAndroid,
} from
'react-native'
;
var
{ NativeModules } = require(
'react-native'
);
class CustomButton extends React.Component {
render() {
return
(
<TouchableHighlight
style={styles.button}
underlayColor=
"#a5a5a5"
onPress={
this
.props.onPress}>
<Text style={styles.buttonText}>{
this
.props.text}</Text>
</TouchableHighlight>
);
}
}
//onPress={()=>NativeModules.ToastCustomAndroid.show("我是ToastCustomAndroid弹出消息",NativeModules.ToastCustomAndroid.SHORT)}
class ModulesDemo extends Component {
render() {
return
(
<View>
<Text style={styles.welcome}>
自定义弹出Toast消息
</Text>
<CustomButton
text=
"点击弹出Toast消息"
onPress={()=>NativeModules.ToastCustomAndroid.measureLayout((msg) => {
console.log(msg);
},
(x, y, width, height) => {
console.log(x +
'坐标,'
+ y +
'坐标,'
+ width +
'宽,'
+ height+
'高'
);
})}
/>
</View>
);
}
}
const styles = StyleSheet.create({
welcome: {
fontSize: 20,
textAlign:
'center'
,
margin: 10,
},
button: {
margin:5,
backgroundColor:
'white'
,
padding: 15,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor:
'#cdcdcd'
,
},
});
AppRegistry.registerComponent(
'ModulesDemo'
, () => ModulesDemo);
|
运行截图效果:
看了上面的回调函数的使用,大家有没有发现上面的写法还有有一些繁琐的?OK 当然原生模块还可以支持使用Promise,这样可以简化我们编写的代码。如果大家搭配使用ES2016标准的async/await的语法使用会更加好。如果被桥接的原生方法的最后一个参数是一个Promise对象,那么该JS方法会返回一个Promise对象。下面我们使用Promise对象来进行重构之前的回调函数方法。具体官方代码如下:
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
|
public class UIManagerModule extends ReactContextBaseJavaModule {
...
@ReactMethod
public void measureLayout(
int tag,
int ancestorTag,
Promise promise) {
try
{
measureLayout(tag, ancestorTag, mMeasureBuffer);
WritableMap map = Arguments.createMap();
map.putDouble(
"relativeX"
, PixelUtil.toDIPFromPixel(mMeasureBuffer[0]));
map.putDouble(
"relativeY"
, PixelUtil.toDIPFromPixel(mMeasureBuffer[1]));
map.putDouble(
"width"
, PixelUtil.toDIPFromPixel(mMeasureBuffer[2]));
map.putDouble(
"height"
, PixelUtil.toDIPFromPixel(mMeasureBuffer[3]));
promise.resolve(map);
}
catch
(IllegalViewOperationException e) {
promise.reject(e);
}
}
...
|
那么现在你可以声明一个async的方法用来返回Promise对象,方法内部使用await关键字进行等待其返回。注意该方法还是异步的。具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
async
function
measureLayout() {
try
{
var
{
relativeX,
relativeY,
width,
height,
} = await UIManager.measureLayout(100, 100);
console.log(relativeX +
':'
+ relativeY +
':'
+ width +
':'
+ height);
}
catch
(e) {
console.error(e);
}
}
measureLayout();
|
根据官方说明,该模块在将来有可能会发生变化。但是如果在主线程有一些耗时的操作,那么该操作需要被分配到工作子线程进行执行,最后通过回调方法进行获取执行结果即可。
值得注意的是原生模块可以在没有被直接调用的情况下就可以往JavaScript发送消息事件。最简单的方式就是使用RCTDeviceEventListener接口,该可以通过ReactContext如下点来获取:
1
2
3
4
5
6
7
8
9
10
11
12
|
...
private void sendEvent(ReactContext reactContext,
String eventName,
@Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
...
WritableMap params = Arguments.createMap();
...
sendEvent(reactContext,
"keyboardWillShow"
, params);
|
之后JavaScript模块可以通过Subscribable mixin的addListenerOn进行注册以及接受消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import { DeviceEventEmitter } from
'react-native'
;
...
var
ScrollResponderMixin = {
mixins: [Subscribable.Mixin],
componentWillMount:
function
() {
...
this
.addListenerOn(DeviceEventEmitter,
'keyboardWillShow'
,
this
.scrollResponderKeyboardWillShow);
...
},
scrollResponderKeyboardWillShow:
function
(e: Event) {
this
.keyboardWillOpenTo = e;
this
.props.onKeyboardWillShow &&
this
.props.onKeyboardWillShow(e);
},
|
你也可以直接使用DeviceEventEmitter模块来监听事件
1
2
3
4
5
6
7
|
...
componentWillMount:
function
() {
DeviceEventEmitter.addListener(
'keyboardWillShow'
,
function
(e: Event) {
// handle event.
});
}
...
|
(六)通过startActivityForResult启动Activity,获取Activity Result值回调信息
如果你是通过startActivityForResult方法进行启动Activity的话,那么你可以需要获取activity的回调信息,那么你就需要在当前的Activity中重写onActivityResult方法了。为了实现这一目标,当前的模块类必须实现ActivtyEventListener接口,然后当前模块注册该接口,如下所示:
1
|
reactContext.addActivityEventListener(
this
);
|
接着你可以重写onActivityResult方法
1
2
3
4
|
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
// Your logic here 写的你业务逻辑代码
}
|
下面我们来进行实现一个简单的图片选择器模块,该图片选择器模块提供pickImage方法给JavaScript,该会返回被选中的图片的地址。
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
|
public class ImagePickerModule extends ReactContextBaseJavaModule implements ActivityEventListener {
private static final int IMAGE_PICKER_REQUEST = 467081;
private static final String E_ACTIVITY_DOES_NOT_EXIST =
"E_ACTIVITY_DOES_NOT_EXIST"
;
private static final String E_PICKER_CANCELLED =
"E_PICKER_CANCELLED"
;
private static final String E_FAILED_TO_SHOW_PICKER =
"E_FAILED_TO_SHOW_PICKER"
;
private static final String E_NO_IMAGE_DATA_FOUND =
"E_NO_IMAGE_DATA_FOUND"
;
private Promise mPickerPromise;
public ImagePickerModule(ReactApplicationContext reactContext) {
super
(reactContext);
// Add the listener for `onActivityResult`
reactContext.addActivityEventListener(
this
);
}
@Override
public String getName() {
return
"ImagePickerModule"
;
}
@ReactMethod
public void pickImage(final Promise promise) {
Activity currentActivity = getCurrentActivity();
if
(currentActivity ==
null
) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST,
"Activity doesn't exist"
);
return
;
}
// Store the promise to resolve/reject when picker returns data
mPickerPromise = promise;
try
{
final Intent galleryIntent =
new
Intent(Intent.ACTION_PICK);
galleryIntent.setType(
"image/*"
);
final Intent chooserIntent = Intent.createChooser(galleryIntent,
"Pick an image"
);
currentActivity.startActivityForResult(chooserIntent, PICK_IMAGE);
}
catch
(Exception e) {
mPickerPromise.reject(E_FAILED_TO_SHOW_PICKER, e);
mPickerPromise =
null
;
}
}
// You can get the result here
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
if
(requestCode == IMAGE_PICKER_REQUEST) {
if
(mPickerPromise !=
null
) {
if
(resultCode == Activity.RESULT_CANCELED) {
mPickerPromise.reject(E_PICKER_CANCELLED,
"Image picker was cancelled"
);
}
else
if
(resultCode == Activity.RESULT_OK) {
Uri uri = intent.getData();
if
(uri ==
null
) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND,
"No image data found"
);
}
else
{
mPickerPromise.resolve(uri.toString());
}
}
mPickerPromise =
null
;
}
}
}
}
|
如果我们需要监听Activity的生命周期,例如:onResume,onPause等等方法,我们通过实现ActivityEventListenr接口可以完成类似的实现。如果要实现这样的功能,那么当前模块类需要实现LifcycleEventListener接口并且在该模块类的构造函数中进行注册该接口。
1
|
reactContext.addLifecycleEventListener(
this
);
|
然后我们可以使用如下的代码进行监听Activity的生命周期啦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Override
public void onHostResume() {
// Actvity `onResume`
}
@Override
public void onHostPause() {
// Actvity `onPause`
}
@Override
public void onHostDestroy() {
// Actvity `onDestroy`
}
|
今天我们主要讲解学习了原生模块的一些特性例如:回调方法函数,Promises,多线程,事件发送到JavaScript,监听生命周期事件,获取Activity Result等相关的特性。当前所讲解内容适配Android开发。大家有问题可以加一下群React Native技术交流4群(458982758).或者底下进行回复一下。
尊重原创,未经授权不得转载:From Sky丶清(http://www.lcode.org/) 侵权必究!
出处地址:http://www.lcode.org
本文出自:【江清清的技术专栏】
本React Native讲解专题:主要讲解了React Native开发,由基础环境搭建配置入门,基础,进阶相关讲解。
刚创建的React Native技术交流3群(496508742),React Native技术交流4群(458982758)。欢迎各位大牛,React Native技术爱好者加入交流!同时博客右侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!
关于React Native各种疑难杂症,问题深坑总结方案请点击查看:
Mac和Windows安装搭建React Native环境教程如下:
(一).基本介绍:
React Native For Android是伟大的互联网公司Facebook与2015年9月15日发布的,该可以让我们广大开发者使用JavaScript和React开发我们的应用,该提倡组件化开发,也就是说React Native给我们提供一个个封装好的组件让开发者来进行使用,甚至我们可以相关嵌套形成新的组件。使用React Native我们可以维护多种平台(Web,Android和IOS)的同一份业务逻辑核心代码来创建原生应用。现阶段Web APP的的体验还是无法达到Native APP的体验,所以这边fackbook更加强调的是learn once,write everywhere,应用前端我们使用js和React来开发不同平台的UI,下层核心模块编写复用的业务逻辑代码,提供应用开发效率。
[特别注意]目前react native在ios上仅支持ios7以上,Android仅支持Android4.1以上。
React Native项目github地址:https://github.com/facebook/react-native
React Native项目官网文档:http://facebook.github.io/react-native/docs/getting-started.html
(二).基础入门:
1.React Native For Android环境配置以及第一个实例
2.React Native开发IDE安装及配置
3.React Native应用设备运行(Running)以及调试(Debugging)
4.React Native移植原生Android项目
5.React Native进行签名打包成Apk
6.React Native库版本升级(Upgrading)与降级讲解
7.React Native VSCode IDE超强开发插件介绍(智能,代码提醒,运行调试…)
8.React Native特定平台代码说明
9.React Native基础之Linking Libraries链接库配置-适配iOS开发
10.React Native基础之真机设备运行调试应用-适配iOS开发
11.React Native基础之从源代码编译详解-适配Android开发
12.React Native进阶之原生UI组件封装详解-适配Android开发
(三).组件学习:
1.React Native控件之View视图讲解
2.1.React Native配置运行官方例子-初学者的福音(Mac OS X版本)
2.2.React Native配置超详细编译运行React Native官方实例UIExplorer项目(Windows版本)
3.React Native控件之Text组件讲解
4.React Native控件之Image组件讲解与美团首页顶部效果实例
5.React Native控件之TextInput组件讲解与QQ登录界面实现
6.React Native控件之ProgressBarAndroid进度条讲解
7.React Native控件之DrawerLayoutAndroid抽屉导航切换组件讲解
8.React Native控件之ScrollView组件讲解
9.WebStorm开发工具设置React Native代码智能提醒
10.React Native控件之ToolbarAndroid工具栏控件讲解以及使用
11.React Native控件之Switch开关与Picker选择器组件讲解以及使用
12.React Native控件之ViewPagerAndroid讲解以及美团首页顶部效果实例
13.React Native控件之Touchable*系列组件详解
14.React Native控件之ListView组件讲解以及详细实例
15.React Native控件之PullToRefreshViewAndroid下拉刷新组件讲解
16.React Native控件之RefreshControl组件详解
17.React Native控件之WebView组件详解以及实例使用
18.React Native控件之Navigator组件详解以及实例
19.React Native 控件之Cilpboard粘贴板使用详解
20.React Native控件之DatePickerAndroid时间日期选择器组件讲解
21.React Native控件之StatusBar状态栏详解
22.React Native控件之PickerIOS选择器详解-适配iOS开发
22.React Native 控件之SegmentedControlIOS分段组件详解-适配iOS开发
23.React Native控件之SliderIOS滑块组件详解-适配iOS开发
24.React Native控件之TabBarIOS和TabBarIOS.Item组件详解及实例
25.React Native控件之ProgressViewIOS进度加载组件详解及实例
26.React Native控件之ActivityIndicatorIOS进度指示器组件详解及实例
27.React Native控件之TimePickerAndroid时间选择器组件详解及实例
(三).API模块学习:
1.React Native API模块之ToastAndroid详解及使用
2.React Native API模块之Alert弹出框详解及使用
3.React Native API模块之AppState详解
4.React Native API模块之NetInfo(网络信息)使用详解
5.React Native API模块之AsyncStorage(持久化存储)使用详解
6.React Native API模块Dimensions屏幕宽高详解
7.React Native API模块BackAndroid拦截返回键事件处理详解
8.React Native API模块StyleSheet样式表详解
9.React Native API模块PixelRatio设备像素密度详解
10.React Native API模块之AlertIOS弹框详解-适配iOS开发
11.React Native API模块之AppStateIOS运行状态详解-适配iOS开发
12.React Native API模块之ActionSheetIOS可点击弹框详解-适配iOS开发
13.React Native API模块之Vibration控制设备震动详解
13.React Native API模块之AppRegistry应用注册入口详解
14.React Native模块之Linking详解以及实例-Android/iOS双平台通用
(四).React Native进阶:
1.React Native超棒的LayoutAnimation(布局动画)
2.React Native控件之组件封装实例(Button按钮)
3.React Native进阶之原生模块封装基础篇1-适配Android开发
4.React Native进阶之原生模块特性篇详解-适配Android开发
(五).React Native开源项目:
1.Pober Wong_17童鞋为gank.io做的纯React Native项目,开源地址:https://github.com/Bob1993/React-Native-Gank
2.聂风童鞋做的《都看影视》React Native项目,开源地址:https://github.com/changfuguo/doukanmv
3.大大做的《新闻阅读Reading》React Native项目,项目地址:http://www.lcode.org/reading-app-react-native/
4.公子小白做的《亲戚称谓计算器》React Native项目,项目地址:http://www.lcode.org/counterrelative-react-native/
5.﹌海云天♂做的纯React Native iOS开源项目,开源地址:http://www.lcode.org/react-native-lagou/ 或者文章地址:http://www.lcode.org/react-native-lagou-source/
6.成都 - just4fun做的纯React Native iOS开源项目,项目地址:http://www.lcode.org/uestc-bbs-react-native/
7.lookingstars做的纯React Native 仿美团iOS开源项目,项目地址:http://www.lcode.org/react-native-meituan-source/
8.race604做的纯React Native 知乎日报开源项目(Android/iOS),开源地址:https://github.com/race604/ZhiHuDaily-React-Native
9.@vczero做的纯React Native豆瓣搜索客户端,开源地址:http://www.lcode.org/react-native-dou-source/
10.@tabalt做的纯React Native新闻客户端,开源地址:http://www.lcode.org/react-native-news-source/
11.@starzhy做的纯React Native码农iOS客户端,项目地址:http://www.lcode.org/react-native-source-manong/
12.@iSimar做的纯React Native Hacker新闻客户端(Android、iOS),项目地址:http://www.lcode.org/react-native-source-hacker/
13.@xiekw2010做的纯React Native Github客户端,兼容Android、iOS平台,开源地址:http://www.lcode.org/react-native-source-gitfeed/
14.@kailuo99做的纯React Native 资讯头条客户端,主要适配iOS平台,项目地址:http://www.lcode.org/react-native-source-zixunapp/
15.@SFantasy做的纯React Native 资讯头条客户端,主要适配iOS平台,项目地址:http://www.lcode.org/react-native-source-weibo/
16.@Kennytian做的纯React Native 仿拉勾网客户端,兼容Android、iOS双平台,项目地址:http://www.lcode.org/react-native-source-lagou-duo/
17.@soliury做的纯React Native CNode论坛客户端,项目地址: