Android Native Moudule
在RN控件或功能无法满足需求时,需要Native Module来帮忙,需要平台API。
使用场景:React Native还不支持某个你需要的原生特性,你可以自己实现该特性的封装。
如何封装这些RN没有的模块呢?现有步骤介绍和Sample Code
Argument Types
@ReactMethod RN参数跟JS参数的对比map,我们demo中用到了String,WritableMap,Promise,Callback,int
Boolean -> Bool Integer -> Number Double -> Number Float -> Number String -> String Callback -> function ReadableMap -> Object ReadableArray -> Array
Promise -> Promise WritableMap -> Object
Toast 模块例子
我们希望可以从Javascript发起一个Toast消息(Android中的一种会在屏幕下方弹出、保持一段时间的消息通知)
-
创建一个原生模块 (UserNativeModule.java)
核心Code: private static final String DURATION_SHORT_KEY = "SHORT"; private static final String DURATION_LONG_KEY = "LONG"; public UserNativeModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "UserNativeModule"; } @Override public Map<String, Object> getConstants() { final Map<String, Object> constants = new HashMap<>(); constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT); constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG); return constants; } /** * @param message the text to show in toast * @param duration toast show time */ @ReactMethod public void showToast(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); }
-
注册模块 (UserReactPackage.java)
核心Code: @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new UserNativeModule(reactContext)); return modules; }
-
add to getPackages (MainApplication.java)
核心Code: @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new UserReactPackage() ); }
-
JS中使用封装的方法
把原生模块封装成一个JavaScript模块,可以省下了每次都从NativeModules中获取对应模块的步骤。
UserNativeModule.js 核心Code
'use strict';
/**
* This exposes the native UserNativeModules module as a JS module.
* This has a some function of UserNativeModules. such as 'showToast', 'showActivity'
* for detail parametoers, please check UserNativeModule.java in android folder
*
* @param message the text to show in toast
* @param duration toast show time
@ReactMethod
public void showToast(String message, int duration) {...}
*/
let { NativeModules } = require('react-native');
module.exports = NativeModules.UserNativeModule;
JS中使用核心Code
import UserNativeModule from './common/UserNativeModule';
export default class Root extends Component {
onPress() {
UserNativeModule.showToast('Hi ASUS', UserNativeModule.SHORT);
}
render() {
return (
<View>
<Text style={styles.text} onPress={this.onPress.bind(this) }>Native Toast</Text>
</View>
)
}
}
const styles = StyleSheet.create({
text: {
flex: 1,
fontSize: 20,
margin:20,
}
});
Activity例子
我们希望可以从Javascript叫起Android端Activity
-
原生模块UserNativeModule.java中添加方法
核心Code: /** * @param module activity id that which activity you want to call up * @param map * @param callback */ @ReactMethod public void startActivity(int module, final ReadableMap map, Callback callback) { mCallback = callback; Activity currentActivity = getCurrentActivity(); switch (module) { case ACTIVITY_TEST_ID: startTestActivity(map, currentActivity); } } /** * @param map * @param activity */ public void startTestActivity(final ReadableMap map, Activity activity) { Intent it = new Intent(getReactApplicationContext(), TestActivity.class); it.putExtra("status", map.getInt("status")); it.putExtra("text", map.getString("text")); activity.startActivityForResult(it, ACTIVITY_TEST_ID); } @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { switch (requestCode) { case ACTIVITY_TEST_ID: String result = intent.getStringExtra("result"); if (mCallback != null) { mCallback.invoke(result); } break; } }
-
实现Android TestActivity Android正常步骤创建Activity,这里不多赘述,Code放在这里
-
JS中使用
核心Code
const map = { 'status': 0, 'text': 'ASUS from RN', }; UserNativeModule.startActivity(UserNativeModule.ACTIVITY_TEST, map, (result) => { ToastAndroid.show(result, ToastAndroid.SHORT); });
Promise
JS的异步变成模块。 RN也直接传入Promise作为参数。下面以获取Android app版号方法为例: Android核心Code @ReactMethod public void getPackageVersion(final Promise promise) { String versionName = “1.0”; int versionCode = 1; try { PackageManager packageManager = getReactApplicationContext().getPackageManager(); PackageInfo info = packageManager.getPackageInfo(getReactApplicationContext().getPackageName(), 0); versionName = info.versionName; versionCode = info.versionCode;
WritableMap ret = Arguments.createMap();
ret.putString("versionName", versionName);
ret.putInt("versionCode", versionCode);
promise.resolve(ret);
} catch (Exception ex) {
Log.d(TAG, "getPackageVersion:", ex);
promise.reject(ex);
}
}
JS使用
UserNativeModule.getPackageVersion()
.then(result => {
let version = JSON.stringify(result);
ToastAndroid.show('Version : ' + version, ToastAndroid.LONG);
})
.catch(result => {
ToastAndroid.show('exception : ' + result);
});
RN android端调用JS模块 - RCTDeviceEventEmitter 组件
RCTDeviceEventEmitter通讯组件不仅仅可以用于组件全局事件交互,还可以用做android调用JS模块使用
Android核心Code
TestActivity.java
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.jsBtn:
sendEvent();
break;
}
}
private void sendEvent() {
WritableMap params = Arguments.createMap();
params.putString("text", "event from android");
UserNativeModule.sendEvent(EVENT_NAME, params);
}
UserNativeModule.java
public static void sendEvent(String eventName, WritableMap params) {
sUserNativeModule.mReactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
JS使用
import {
DeviceEventEmitter
} from 'react-native';
componentWillMount() {
DeviceEventEmitter.addListener('TEST', this.handleTest.bind(this));
}
componentWillUnmount() {
DeviceEventEmitter.removeListener('TEST');
}
handleTest(param) {
ToastAndroid.show('JS rev event: ' + JSON.stringify(param), ToastAndroid.SHORT);
}
PS: ReactContext在其他Activity中不太好获取,因此我将方法放在UserNativeModule.java中。同时在UserNativeModule中设置singleton给外部使用