Android React Native uses Native modules
Sometimes our App needs to access the platform API, And the React Native may not have the corresponding module packaging; or you need to reuse some Java code instead of implementing it again using Javascript; or you need to implement some high-performance, multi-threaded code, batch processing, database, or various advanced extensions.
With React Native, you can write real Native code based on it and access all the capabilities of the platform. If React Native does not support a Native feature you need, you should be able to encapsulate it yourself.
However, before writing code to use the native module, you must master a knowledge point to avoid further pitfalls.
When using React Native, we often see this piece of code.
var React = require('react-native');
So what is the role of the require statement? The following process is extracted from the require () source code
When require (X) is encountered, it is processed in the following order.
(1) If X is a built-in module (such as require ('HTTP '))
A. Return to this module.
B. Continue execution.
(2) If X starts with "./", "/", or "./",
A. Determine the absolute path of X based on the parent module of X.
B. Use X as a file and search for the following files in sequence. If one of them exists, the file will be returned and will not be executed.
XX. jsX. jsonX. node
C. Use X as the Directory and search for the following files in sequence. If one of them exists, the file will be returned and will not be executed.
X/package. json (main field) X/index. jsX/index. jsonX/index. node
(3) If X does not contain a path
A. determine the possible installation directory of X based on the parent module of X.
B. Load X as a file name or directory name in each directory in sequence.
(4) throw "not found"
The above is the entire execution process of the require statement. So what exactly is the request?Node_modules \ react-native \ Libraries \ react-native \ react-native.js this file, This file exports some common components, the source code is as follows
/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @flow */'use strict';// Export React, plus some native additions.//// The use of Object.create/assign is to work around a Flow bug (#6560135).// Once that is fixed, change this back to//// var ReactNative = {...require('React'), /* additions */}//var ReactNative = Object.assign(Object.create(require('React')), { // Components ActivityIndicatorIOS: require('ActivityIndicatorIOS'), DatePickerIOS: require('DatePickerIOS'), DrawerLayoutAndroid: require('DrawerLayoutAndroid'), Image: require('Image'), ListView: require('ListView'), MapView: require('MapView'), Modal: require('Modal'), Navigator: require('Navigator'), NavigatorIOS: require('NavigatorIOS'), PickerIOS: require('PickerIOS'), ProgressBarAndroid: require('ProgressBarAndroid'), ProgressViewIOS: require('ProgressViewIOS'), ScrollView: require('ScrollView'), SegmentedControlIOS: require('SegmentedControlIOS'), SliderIOS: require('SliderIOS'), SnapshotViewIOS: require('SnapshotViewIOS'), Switch: require('Switch'), SwitchAndroid: require('SwitchAndroid'), SwitchIOS: require('SwitchIOS'), TabBarIOS: require('TabBarIOS'), Text: require('Text'), TextInput: require('TextInput'), ToastAndroid: require('ToastAndroid'), ToolbarAndroid: require('ToolbarAndroid'), TouchableHighlight: require('TouchableHighlight'), TouchableNativeFeedback: require('TouchableNativeFeedback'), TouchableOpacity: require('TouchableOpacity'), TouchableWithoutFeedback: require('TouchableWithoutFeedback'), View: require('View'), ViewPagerAndroid: require('ViewPagerAndroid'), WebView: require('WebView'), // APIs ActionSheetIOS: require('ActionSheetIOS'), AdSupportIOS: require('AdSupportIOS'), AlertIOS: require('AlertIOS'), Animated: require('Animated'), AppRegistry: require('AppRegistry'), AppStateIOS: require('AppStateIOS'), AsyncStorage: require('AsyncStorage'), BackAndroid: require('BackAndroid'), CameraRoll: require('CameraRoll'), Dimensions: require('Dimensions'), Easing: require('Easing'), ImagePickerIOS: require('ImagePickerIOS'), InteractionManager: require('InteractionManager'), LayoutAnimation: require('LayoutAnimation'), LinkingIOS: require('LinkingIOS'), NetInfo: require('NetInfo'), PanResponder: require('PanResponder'), PixelRatio: require('PixelRatio'), PushNotificationIOS: require('PushNotificationIOS'), Settings: require('Settings'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), VibrationIOS: require('VibrationIOS'), // Plugins DeviceEventEmitter: require('RCTDeviceEventEmitter'), NativeAppEventEmitter: require('RCTNativeAppEventEmitter'), NativeModules: require('NativeModules'), Platform: require('Platform'), processColor: require('processColor'), requireNativeComponent: require('requireNativeComponent'), // Prop Types EdgeInsetsPropType: require('EdgeInsetsPropType'), PointPropType: require('PointPropType'), // See http://facebook.github.io/react/docs/addons.html addons: { LinkedStateMixin: require('LinkedStateMixin'), Perf: undefined, PureRenderMixin: require('ReactComponentWithPureRenderMixin'), TestModule: require('NativeModules').TestModule, TestUtils: undefined, batchedUpdates: require('ReactUpdates').batchedUpdates, cloneWithProps: require('cloneWithProps'), createFragment: require('ReactFragment').create, update: require('update'), },});if (__DEV__) { ReactNative.addons.Perf = require('ReactDefaultPerf'); ReactNative.addons.TestUtils = require('ReactTestUtils');}module.exports = ReactNative;
After learning about this knowledge point, we will define a module to use the native module. Suppose there is such a requirement, we need to useLog class in AndoridBut React Native is not encapsulated for us, so let's implement it by ourselves.
We need to inherit
ReactContextBaseJavaModuleThis abstract class is rewritten.
GetName ()Function, used to return a string that marks this module on the JavaScript end, exposes a function to the javascript end, and uses annotations
@ ReactMethodMark, the return value of this function must be void, and the cross-language access to React Native is asynchronous, so the only way to return a value to JavaScript is to use
Callback FunctionOr
Send event. We need to implement a class implementation
ReactPackageInterface, which has three Abstract Functions to be implemented:
CreateNativeModules,
CreateJSModules,
CreateViewManagersAmong the three functions, the most critical function we need to implement is
CreateNativeModulesIn this function, we need to add
ReactContextBaseJavaModuleSubclass Construction
ReactInstanceManagerBy calling
AddPackage ()Function to add the ReactPackage implemented in the previous step.
Next we will implement the code. For simplicity and convenience, only the d Method in the Log class is demonstrated here, that isLog. d (String tag, String msg)
The first step is to inherit the ReactContextBaseJavaModule class and override the getName () method. Because it is a Log module, the string Log is directly returned, and a d method is exposed to the javascript end. The return value is void, only annotations are used for marking. The final code is as follows.
public class LogModule extends ReactContextBaseJavaModule{ private static final String MODULE_NAME="Log"; public LogModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return MODULE_NAME; } @ReactMethod public void d(String tag,String msg){ Log.d(tag,msg); }}
Step 2: implement the ReactPackage interface and add our log module to the createNativeModules function. The other two functions return an empty List.
createNativeModules(ReactApplicationContext reactContext) { List
modules=new ArrayList<>(); modules.add(new LogModule(reactContext)); return modules; } @Override public List
> createJSModules() { return Collections.emptyList(); } @Override public List
createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); }}" data-snippet-id="ext.9f07b33a14eabf3cbd00c1fb0b5009f1" data-snippet-saved="false" data-csrftoken="4TcSqJMN-qwxVPXurbJ8DIBEzePIUnitFBt4" data-codota-status="done">
public class AppReactPackage implements ReactPackage { @Override public List
createNativeModules(ReactApplicationContext reactContext) { List
modules=new ArrayList<>(); modules.add(new LogModule(reactContext)); return modules; } @Override public List
> createJSModules() { return Collections.emptyList(); } @Override public List
createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); }}
Step 3: add the AppReactPackage to the ReactInstanceManager instance. You can see this code in our MainActivity.
mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build();
We can call addPackage to add the function before building the function. The final code is as follows.
mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) .addPackage(new AppReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build();
We can see that we have added a line.. AddPackage (new AppReactPackage ())
In this way, what we have to do on the Java end is done, and the next step is the javascript end. Compile the apk and run it again. Then we will compile the javascript end.
If you are not botheredNativeModulesTo access our Log, now you can access it directly in javascript. Just like this.
var React = require('react-native');var { NativeModules,} = React;var Log1= NativeModules.Log;Log1.d("Log1","LOG");
However, if I add another requirement, that is, when the Log class prints a Log on the java layer and wants to output the following Log on the js end, what would you do, maybe you will say that this is simple. I will output the js log again and it will be OK. Just like this.
var React = require('react-native');var { NativeModules,} = React;var Log1= NativeModules.Log;Log1.d("Log1","LOG");console("Log1","LOG");
Yes, that's right. It means you have to write the code as many times as it looks like it hurts and is difficult to maintain.
At this time, we need to encapsulate the javascript code. Create a log. js file in the same directory as the index. android. js file and enter the following code.
'use strict';var { NativeModules } = require('react-native');var RCTLog= NativeModules.Log;var Log = { d: function ( tag: string, msg: string ): void { console.log(tag,msg); RCTLog.d(tag, msg); },};module.exports = Log;
The code is very simple. We use NativeModules to get the implementation of our Log Module locally, assign the value to the variable RCTLog, and declare a Log variable, which contains a function d, the d function of RCTLog is called, and the javascript log is output before the call. Finally, use module. exports = Log to export the Log variable.
The next step is to reference the log. js file and read the parsing of the above require statement. This should not be a problem for you.
var Log=require('./log');Log.d("TAG","111");
This is not complete yet. Another requirement is that we hope this Log module can provide a constant, that is, a TAG, which is defined at the java layer, if you do not want to enter a TAG for future use, you can directly use this default TAG, just like this
var Log=require('./log');Log.d(Log.TAG,"111");
So how can we implement this? Obviously, we need to add this variable to log. js, just like this.
'use strict';var { NativeModules } = require('react-native');var RCTLog= NativeModules.Log;var Log = { TAG: RCTLog.TAG, d: function ( tag: string, msg: string ): void { console.log(tag,msg); RCTLog.d(tag, msg); },};module.exports = Log;
In this way, although we can use Log. TAG to return this value, because we do not have a TAG defined in the java layer, an error will be reported at this time. Therefore, we need to return this value at the java layer. What should we do? Don't worry. Let's look back at the class we implemented.LogModule, We continue to define two constants in this class.
private static final String TAG_KEY = "TAG";private static final String TAG_VALUE = "LogModule";
What is the usage? We can see that the constant name is "key", "value", and "key-value". We hope to get the TAG_VALUE through TAG_KEY, that is, the TAG used in our logs. How can we achieve this. RewriteGetConstantsFunction.
getConstants() { final Map
constants = MapBuilder.newHashMap(); constants.put(TAG_KEY, TAG_VALUE); return constants; }" data-snippet-id="ext.0008550a573f1889c56ea9ffc26b61d8" data-snippet-saved="false" data-csrftoken="8CPAguR7-Ms31GPk3usjjppD11aB6bcq_vDo" data-codota-status="done">
@Override public Map
getConstants() { final Map
constants = MapBuilder.newHashMap(); constants.put(TAG_KEY, TAG_VALUE); return constants; }
At this time, you can rewrite the compilation and run it on the javascript layer through Log. the TAG can access the corresponding value, the value is LogModule, and why is it Log. TAG instead of other values, because the key put in constants is TAG.
So what is the purpose of this? Do you still remember the use of Toast in android? Does the display time have two values?Toast. LENGTH_SHORTAnd the other isToast. LENGTH_LONGIn the javascript layer, we hope that such two constants can be used. Let's look at the implementation of ToastAndroid.
First, let's look at the java layer.
getConstants() { final Map
constants = MapBuilder.newHashMap(); constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT); constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG); return constants; } @ReactMethod public void show(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); }}" data-snippet-id="ext.b6ffc6da30953242ea41fd1abab455a3" data-snippet-saved="false" data-csrftoken="Ws1sIYY7-0-swsiDcqXaHz8nq3x0PdVnFdw4" data-codota-status="done">
public class ToastModule extends ReactContextBaseJavaModule { private static final String DURATION_SHORT_KEY = "SHORT"; private static final String DURATION_LONG_KEY = "LONG"; public ToastModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "ToastAndroid"; } @Override public Map
getConstants() { final Map
constants = MapBuilder.newHashMap(); constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT); constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG); return constants; } @ReactMethod public void show(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); }}
No. Two values are exposed to the javascript layer in the getConstants function. SHORT corresponds to Toast. LENGTH_SHORT of the java layer, and LONG corresponds to Toast. LENGTH_LONG of the java layer. Next, let's look at the javascript layer code.
'use strict';var RCTToastAndroid = require('NativeModules').ToastAndroid;var ToastAndroid = { SHORT: RCTToastAndroid.SHORT, LONG: RCTToastAndroid.LONG, show: function ( message: string, duration: number ): void { RCTToastAndroid.show(message+"lizhangqu", duration); },};module.exports = ToastAndroid;
We can directly access it through the defined variable SHORT or LONG. This is what we use in the end.
var React = require('react-native');var { ToastAndroid} = React;ToastAndroid.show("toast",ToastAndroid.SHORT);
This is not complete yet. This is only the case where no return value is returned. If there is a return value, for example, javascript calls the java layer method, but the java layer needs to return the result to javascript. Yes, the answer is:Callback!, The most typical scenario is that the javascript layer calls the network Request Method of the java layer. After the java layer obtains the network data, it needs to return the result to the javascript layer. In general, we can implement this module at the fastest speed.
Inherit ReactContextBaseJavaModule and implement the getName method. The returned value is Net. Expose a getResult method to javascript and annotate it. Note that this function has a Callback type input parameter, and the returned result is called back through this method.
Public class NetModule extends ReactContextBaseJavaModule {private static final String MODULE_NAME = "Net"; public NetModule (ReactApplicationContext reactContext) {super (reactContext) ;}@ Override public String getName () {return MODULE_NAME;} @ ReactMethod public void getResult (String url, final Callback callback) {Log. e ("TAG", "requesting data"); new Thread (new Runnable () {@ Override public void run () {try {String result = "this is the result "; thread. sleep (1000); // simulates network request callback. invoke (true, result);} catch (Exception e) {e. printStackTrace ();}}}). start ();}}
Callback is defined as follows. It is an interface, and the number of input parameters of the invoke function is arbitrary.
public interface Callback { /** * Schedule javascript function execution represented by this {@link Callback} instance * * @param args arguments passed to javascript callback method via bridge */ public void invoke(Object... args);}
Register this module in the previous createNativeModules function of the AppReactPackage class.
modules.add(new NetModule(reactContext));
Create a net. js file to implement the javascript layer.
'use strict';var { NativeModules } = require('react-native');var RCTNet= NativeModules.Net;var Net = { getResult: function ( url: string, callback:Function, ): void { RCTNet.getResult(url,callback); },};module.exports = Net;
For use
{ console.log("callback",code,result); });" data-snippet-id="ext.107e880f3149ba75eadfdf8a2d0708f1" data-snippet-saved="false" data-csrftoken="j4Ei9vvD-5rn_ieDw5bD-7eRkjmp4yKJgSW8" data-codota-status="done">var Net=require('./net');Net.getResult( "http://baidu.com", (code,result)=>{ console.log("callback",code,result); });
If no exception occurs, logs are output at the java layer.
11-20 22:30:53. 598 25323-1478/com. awesomeproject E/TAG: requesting data
At the javascript layer, the console will output
Callback true. This is the result.
The above is an example of callback. You can simply think of it as a network request model at the java layer. The main thread can start the subthread request data, after the sub-thread obtains the data, it calls back the corresponding method and uses handler to notify the main thread to return the result.
Basically, I have mastered the above content and used the original module almost. This article is based on the best practices of the official document Native Modules, but this document has too many pitfalls, you also need to be cautious with your reference. In this document, the [send event to javascript] section does not carry out practical practices, basically the same principle, and will be studied again when you are free.