美文网首页
React Native-原生模块开发

React Native-原生模块开发

作者: Jeffrey599 | 来源:发表于2018-05-23 21:31 被阅读0次

    前言:

    所谓原生模块开发,很重要的就是Android/iOS原生代码和ReactNative代码的交互,以及在ReactNative代码中使用原生的组件。下面将从这两个地方开始讲解(示例以Android平台为基础):

    先回顾一下RN组件的生命周期:

    constructor():
    在组件被加载前最先调用,第一个语句必须是super(props)。

    componentWillMount():
    在初始渲染(render函数被RN框架执行之前)前被执行。在函数中调用setState函数改变了某些状态机变量的值,RN框架不会立马执行渲染操作,而是等该函数执行完之后执行初始渲染。

    componentDidMount():
    执行完初始渲染之后立马被调用。将从网络侧请求数据的代码放在这里比较合适。

    componentWillReceiveProps(nextProps):
    RN初始化渲染执行完成后,当RN组件收到新的props时,这个函数被调用。函数接受的参数是一个object为新的props。

    shouldComponentUpdate(nextProps, nextState):
    RN初始化渲染执行完成后,当RN组件接收到新的state或者props时,该函数被调用。接受两个参数:第一个是新的props,第二个是新的state。该函数返回一个布尔类型的值,表示RN框架针对此次改变是否重新渲染该组件。

    componentWillUpdate(nextProps, nextState):
    RN组件在初始化渲染执行完成之后,RN框架在重新渲染RN组件前会调用这个函数。

    componentDidUpdate(prevProps, prevState):
    RN组件在初始化渲染执行完成之后,RN框架在重新渲染RN组件完成后会调用这个函数,传入的两个参数是渲染前的Props和state。

    componentWillUnmount():
    在RN组件被卸载前,这个函数被执行。

    一、原生代码和RN代码的交互:

    1 RN侧主动调用原生模块方法:回调函数和Promise机制

    首先在电脑中部署Android开发和RN开发所需要的环境:
    打开Android端的初始化项目(名为MyRNProject):
    工程中只有一个Activity,且继承于ReactActivity,将该Activity设置为应用的入口,getMainComponentName()返回服务器上对应的组件名称

    package com.myrnproject;
    
    import android.os.Bundle;
    
    import com.facebook.react.ReactActivity;
    
    public class MainActivity extends ReactActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
        /**
         * Returns the name of the main component registered from JavaScript.
         * This is used to schedule rendering of the component.
         */
        @Override
        protected String getMainComponentName() {
            return "MyRNProject";
        }
    }
    
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.myrnproject"
        android:versionCode="1"
        android:versionName="1.0">
    
        <uses-sdk
            android:minSdkVersion="16"
            android:targetSdkVersion="22" />
    
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.READ_CONTACTS" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
        <application
            android:name=".MainApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme">
            <activity
                android:name=".MainActivity"
                android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
                android:label="@string/app_name"
                android:windowSoftInputMode="adjustResize">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
        </application>
    </manifest>
    

    MainApplication为工程的Application,同样需要继承ReactApplication,getJSMainModuleName()函数返回加载的第一个js文件名称。将需要注册的ReactPackage添加到这里protected List<ReactPackage> getPackages(){},new MyRNPackage()为我们自己定义的ReactPackage,该类用来创建工程中的NativeModule以及ViewManager等类。NativeModule为原生为RN提供的方法,ViewManager为供RN使用的原生组件。

    package com.myrnproject;
    
    import android.app.Application;
    
    import com.facebook.react.ReactApplication;
    import com.facebook.react.ReactNativeHost;
    import com.facebook.react.ReactPackage;
    import com.facebook.react.shell.MainReactPackage;
    import com.facebook.soloader.SoLoader;
    
    import java.util.Arrays;
    import java.util.List;
    
    public class MainApplication extends Application implements ReactApplication {
    
        private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
            @Override
            public boolean getUseDeveloperSupport() {
                return BuildConfig.DEBUG;
            }
    
            @Override
            protected List<ReactPackage> getPackages() {
                return Arrays.<ReactPackage>asList(
                        new MainReactPackage(),
                        new MyRNPackage()
          );
            }
    
            @Override
            protected String getJSMainModuleName() {
                return "index";
            }
        };
    
        @Override
        public ReactNativeHost getReactNativeHost() {
            return mReactNativeHost;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            SoLoader.init(this, /* native exopackage */ false);
        }
    }
    
    package com.myrnproject;
    
    import com.facebook.react.ReactPackage;
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.uimanager.ViewManager;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by tianxiying on 2017/12/6.
     */
    
    public class MyRNPackage implements ReactPackage {
        @Override
        public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
            List<NativeModule> moudles = new ArrayList<>();
            moudles.add(new AddressModule(reactContext));   //加入开发接口
            moudles.add(new ToastModule(reactContext));
            moudles.add(new EventModule(reactContext));
            return moudles;
        }
    
        @Override
        public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
            List<ViewManager> list = new ArrayList<ViewManager>();
            list.add(new MyTextViewManager());
            list.add(new MyCalendarViewManager());
            return list;
        }
    }
    

    接下来我要为RN提供一个安卓原生代码的方法,用Toast举例:我创建了一个ToastModule继承于ReactContextBaseJavaModule,getName() 函数返回模块名称,在RN中通过该名称找到该模块,在上面的MyRNPackage中我们已将其加入管理器中。
    内部代码如下:

    package com.myrnproject;
    
    import android.os.Handler;
    import android.widget.Toast;
    
    import com.facebook.react.bridge.Callback;
    import com.facebook.react.bridge.Promise;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Created by tianxiying on 2017/12/14.
     */
    
    public class ToastModule extends ReactContextBaseJavaModule {
        private ReactApplicationContext aContext;
        private Promise interfacePromise;
        private Handler handler;
        private static final String DURATION_SHORT_KEY = "SHORT";
        private static final String DURATION_LONG_KEY = "LONG";
    
        public ToastModule(ReactApplicationContext reactContext) {
            super(reactContext);
            aContext = reactContext;
    
        }
    
        @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;
        }
    
        @Override
        public String getName() {
            return "ToastModule";
        }
    
        @ReactMethod
        public void showToast(String message, int duration, Callback callback) {
            try {
                Toast.makeText(aContext, message, duration).show();
                callback.invoke("success");
            } catch (Exception e) {
                callback.invoke("exception" + e.toString());
            }
        }
    
        @ReactMethod
        public void showToastLater(String message, int duration, final Callback callback) {
            try {
                Toast.makeText(aContext, message, duration).show();
                handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        doCallBack(callback);
                    }
                }, 5000);
            } catch (Exception e) {
                callback.invoke("exception" + e.toString());
            }
        }
    
        public void doCallBack(Callback callback) {
            callback.invoke("原生5秒后回调CallBack");
        }
    }
    

    1.1 回调函数:
    RN调用原生方法,然后在原生侧调用RN侧提供的回调函数,实现交互。
    以ToastModule为例,加上@ReactMethod注解的方法为RN模块可以直接调用方法:

    @ReactMethod
        public void showToast(String message, int duration, Callback callback) {
            try {
                Toast.makeText(aContext, message, duration).show();
                callback.invoke("success");
            } catch (Exception e) {
                callback.invoke("exception" + e.toString());
            }
        }
    

    该方法有三个参数,分别是信息字符串、显示时长以及RN端传入的回调,getConstants()中约定了对应的变量方便找RN端使用。

    在RN端的代码如下:首先将ToastModule导入,在showToast函数中直接调用ToastModule.showToast(),将需要参数包含回调函数传入。回调成功将改变state中的变量值。

    import React, {Component} from 'react';
    import {
        View,
        Text,
        Image,
        StyleSheet,
        PixelRatio,
        TouchableOpacity,
        NativeModules,
        DeviceEventEmitter,
        Platform,
    } from 'react-native';
    
    let ToastModule = NativeModules.ToastModule;
    ...
    <Text style={styles.text_title}>Toast(回调函数)</Text>
                    <TouchableOpacity onPress={(message) => this.showToast("hello world!")}>
                        <Text style={styles.text_item}>
                            {"回调结果:" + this.state.interfaceResult}
                        </Text>
                    </TouchableOpacity>
    ...
    showToast(message) {
            console.log("showToast!");
            if (Platform.OS === "android") {
                ToastModule.showToast(message,
                    ToastModule.SHORT,
                    (msg) => {
                        this.setState({
                            interfaceResult: msg
                        })
                    });
            }
        }
    

    下面是运行截图:


    image
    image

    原生代码调用了回调函数,回调函数并不会马上执行,因为是混合开发的桥接方式本来就是异步的)
    注意事项:需要注意的是,回调函数必须要RN侧提供的情况下才能调用,调用Callback.invoke()即可执行回调方法,也可以将Callback引用暂时保存起来,不立即调用,但是该回调方法只能调用一次,多于一次将会如下错误:

    image

    1.2 Promise机制
    建议使用,因为Promise在RN中使用非常广泛,能力也非常强大。
    下面我在RN模块中,调用原生代码读取手机通讯录并将其显示在界面上:

    package com.myrnproject;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Bundle;
    import android.provider.ContactsContract;
    
    import com.facebook.react.bridge.ActivityEventListener;
    import com.facebook.react.bridge.BaseActivityEventListener;
    import com.facebook.react.bridge.Promise;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    
    import org.json.JSONObject;
    
    import static android.app.Activity.RESULT_OK;
    
    /**
     * Created by tianxiying on 2017/12/6.
     */
    
    public class AddressModule extends ReactContextBaseJavaModule {
        private ReactApplicationContext aContext;
        private Promise interfacePromise;
        private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
            @Override
            public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
                if (requestCode != 1 || resultCode != RESULT_OK) return;
                Uri contactData = data.getData();
                Cursor cursor = activity.managedQuery(contactData, null, null, null, null);
                cursor.moveToFirst();
                String toRNMessage = getContactInfo(cursor);
                if (toRNMessage != null) {
                    interfacePromise.resolve(toRNMessage);
                }
            }
        };
    
        private String getContactInfo(Cursor cursor) {
            try {
                String name = "";
                String phoneNumber = "";
                int idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID);
                String contactId = cursor.getString(idColumn);
                String queryString = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId;
                Uri aUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
                Cursor phone = aContext.getContentResolver().query(aUri, null, queryString,
                        null, null);
                String dn = ContactsContract.Contacts.DISPLAY_NAME;
                String pn = ContactsContract.CommonDataKinds.Phone.NUMBER;
                if (phone.moveToFirst()) {
                    for (; !phone.isAfterLast(); phone.moveToNext()) {
                        dn = name = cursor.getString(cursor.getColumnIndex(dn));
                        phoneNumber = phone.getString(phone.getColumnIndex(pn));
                    }
                    phone.close();
                }
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("msgType","pickContactResult");
                jsonObject.put("displayName",name);
                jsonObject.put("peerNumber",phoneNumber);
                return jsonObject.toString();
            } catch (Exception e) {
                interfacePromise.reject("error while get contact", e);
            }
            return null;
        }
    
        public AddressModule(ReactApplicationContext reactContext) {
            super(reactContext);
            aContext = reactContext;
            reactContext.addActivityEventListener(mActivityEventListener);
        }
    
        @Override
        public String getName() {
            return "AddressModule";
        }
    
        @ReactMethod
        public void handleMessage(String aMessage, Promise aPromise) {
            interfacePromise = aPromise;
            Intent aIntent = new Intent(Intent.ACTION_PICK);
            aIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);
            Bundle b = new Bundle();
            aContext.startActivityForResult(aIntent, 1, b);
        }
    }
    

    重点关注一下 public void handleMessage(String aMessage, Promise aPromise)函数,它有两个参数,分别是一个消息字符串和一个Promise引用,在该方法被调用时我们将Promise引用保存下来为interfacePromise,ActivityEventListener负责监听Activity的生命周期,我们已将其注册,在通讯录页面返回的时候,onActivityResult将被调用,我们将用户点击的通讯录好友信息获取到保存为Json格式,使用interfacePromise.resolve(toRNMessage)将其返回到RN端。

    RN端代码如下:在then函数中处理成功回调,在catch处理异常回调。

    import React, {Component} from 'react';
    import {
        View,
        Text,
        Image,
        StyleSheet,
        PixelRatio,
        TouchableOpacity,
        NativeModules,
        DeviceEventEmitter,
        Platform,
    } from 'react-native';
    
    let AddressModule = NativeModules.AddressModule;
    ...
    <Text style={styles.text_title}>读取通讯录(Promise机制)</Text>
                    <TouchableOpacity onPress={() => this.userPressAddressBook()}>
                        <Text style={styles.text_item}>
                            {this.state.ContactName + ":" + this.state.PhoneNumber}
                        </Text>
                    </TouchableOpacity>
    ...
    userPressAddressBook() {
            console.log("clickUserPressAddressBook!");
            AddressModule.handleMessage("testMessage").then(
                (result) => {
                    console.log(result);
                    let aObj = JSON.parse(result);
                    this.setState({
                        PhoneNumber: aObj.peerNumber,
                        ContactName: aObj.displayName,
                    })
                }
            ).catch(
                (error) => {
                    console.log(error);
                }
            );
        }
    

    运行图如下:


    image
    image
    image

    Promise对比回调机制不需要设置和实现事件的监听函数,通过Promise机制让异步处理便于书写、阅读与理解。

    2 原生代码主动调用RN代码:通过SendEvent——发送消息的方式。

    首先,我定义了一个EventModule用来主动发送消息:

    package com.myrnproject;
    
    import com.facebook.react.bridge.Arguments;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    import com.facebook.react.bridge.WritableMap;
    import com.facebook.react.modules.core.DeviceEventManagerModule;
    
    import javax.annotation.Nullable;
    
    /**
     * Created by tianxiying on 2017/12/14.
     */
    
    public class EventModule extends ReactContextBaseJavaModule {
        private ReactApplicationContext aContext;
    
        public EventModule(ReactApplicationContext reactContext) {
            super(reactContext);
            aContext = reactContext;
        }
    
        @Override
        public String getName() {
            return "EventModule";
        }
    
        public void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
            reactContext
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit(eventName, params);
        }
    
        @ReactMethod
        public void testSendEvent() {
            WritableMap parms = Arguments.createMap();
            sendEvent(aContext,"testEvent",parms);
        }
    }
    
    

    重点关注sendEvent方法,通过reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params),其中eventName代表消息名,params代表参数。
    为了方便测试,我写了一个testSendEvent方法,由RN界面来触发sendEvent方法。

    RN中代码如下:在componentDidMount进行了监听事件的注册。

    import React, {Component} from 'react';
    import {
        View,
        Text,
        Image,
        StyleSheet,
        PixelRatio,
        TouchableOpacity,
        NativeModules,
        DeviceEventEmitter,
        Platform,
    } from 'react-native';
    
    let EventModule = NativeModules.EventModule;
    ...
    componentDidMount() {
            DeviceEventEmitter.addListener('testEvent', (message) => {
                // handle event.
                console.log("testEvent!" + message);
                this.setReceivedResult();
            });
        }
    ...
    <Text style={styles.text_title}>原生调RN(sendEvent)</Text>
                    <TouchableOpacity onPress={() => this.testSendEvent()}>
                        <Text style={styles.text_item}>
                            {"消息结果:" + this.state.sendEventResult}
                        </Text>
                    </TouchableOpacity>
    ...
     testSendEvent() {
            console.log("testSendEvent!");
            EventModule.testSendEvent();
        }
    ...
     setReceivedResult() {
            console.log("set received!");
            this.setState({
                sendEventResult: 'received sendevent'
            });
        }
    

    运行截图如下:


    image
    image

    相关文章

      网友评论

          本文标题:React Native-原生模块开发

          本文链接:https://www.haomeiwen.com/subject/azpdjftx.html