• ReactNative封装Android原生模块


    我们来看下如何封装原生模块。具体的参考文档,大家参考下面链接:https://reactnative.cn/docs/native-modules-android
    我们废话不多说,笔者通过三个案例,来简介如何自定义Android的原生模块给rn端使用。

    1. Toast模块
    2. 存储模块
    3. 网络请求模块

    Toast模块

    Toast这个东西,只要接触过Android原生开发的都十分熟悉。那么我们如何在rn端调用原生的Toast呢?
    首先,我们必须先搭建好reactnative的运行环境,没有运行环境一切免谈。
    第一步,我们必须写一个类继承ReactContextBaseJavaModule类。具体如下:

    package com.example.demo.reactnative.base.module;
    
    import android.util.Log;
    import android.widget.Toast;
    
    import androidx.annotation.NonNull;
    import androidx.annotation.Nullable;
    
    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 brett.li
     * on 2022/10/17
     */
    public class ToastModule extends ReactContextBaseJavaModule {
    
        private static ReactApplicationContext reactContext;
    
        private static final String DURATION_SHORT_KEY = "SHORT";
        private static final String DURATION_LONG_KEY = "LONG";
    
        public ToastModule(ReactApplicationContext context) {
            super(context);
            reactContext = context;
        }
    
        //1.该方法的名称将作为该模块在rn端的名称
        @NonNull
        @Override
        public String getName() {
            return "CustomToast";
        }
    
        @Nullable
        @Override
        public Map<String, Object> getConstants() {
            final Map<String, Object> constants = new HashMap<>();
            //2.key将作为rn端的常量名
            constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
            constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
            return constants;
        }
    
       //3.这个方法是自定义的,可以在rn端调用,这种自定义的方法想要在rn端被调用必须遵循以下几点规则:
       //· 方法必须是public访问权限,函数返回值必须是void
       //  方法必须被ReactMethod注解修饰
        @ReactMethod
        public void show(String message,int duration){
            Log.e("ToastModule","show 方法被调用");
            Toast.makeText(getReactApplicationContext(),message,duration).show();
        }
    }
    
    
    • 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

    第二步,ToastModule类必须添加进ReactPackage中。如何添加呢?需要实现ReactPackage接口,具体看下面代码

    public class RNBasePackage implements ReactPackage {
       //1.原生模块专属方法
        @NonNull
        @Override
        public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
            modules.add(RNBaseReactModule.getInstance(reactContext).setReactApplicationContext(reactContext));
            modules.add(new ToastModule(reactContext));
            return modules;
        }
    
    //2.原生UI专属方法
        @NonNull
        @Override
        public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
            List<ViewManager> modules = new ArrayList<>();
            return modules;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如果读者是想要原生模块,那么只需要修改createNativeModules方法即可,将自己写的原生模块,例如上述中的ToastModule添加进去即可,而createViewManagers方法,就返回一个空数组就行了。
    第三步,将我们上面新建的RNBasePackage类添加到ReactNativeHost类的getPackages方法中,具体做法如下所示:

    package com.example.demo;
    
    import android.app.Application;
    import android.content.Context;
    import com.facebook.soloader.SoLoader;
    
    /**
     * Created by Brett.li on 2021/9/21.
     */
    public class MyReactApplication extends Application implements ReactApplication {
        @Override
        public ReactNativeHost getReactNativeHost() {
           return new ReactNativeHost(this) {
                @Override
                public boolean getUseDeveloperSupport() {
                    return BuildConfig.DEBUG;
                }
    
                @Override
                protected List<ReactPackage> getPackages() {
                    List<ReactPackage> packages= new PackageList(this).getPackages();
                    packages.add(new RNBasePackage());
                    return packages;
                }
    
                @Nullable
                @Override
                protected String getBundleAssetName() {
                //就是我们打包出来的bundle的名字,不能写错,不然就加载不到bundle
                    return "main.bundle";//bundle的名字,默认是index.android.bundle
                }
    
                @Override
                protected String getJSMainModuleName() {
                    //即打包脚本中--entry-file后面的参数名。不能写错
                    return "index";
                }
            };
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            SoLoader.init(this, /* native exopackage */ false);
        }
    }
    //application中需要做两件事
    //1.实现getReactNativeHost接口
    //2.添加SoLoader.init(this, /* native exopackage */ false);这句代码
    
    
    • 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

    第四步,rn端调用即可

    import React from "react";
    //1.导入NativeModules
    import {NativeModules, Platform, requireNativeComponent, StyleSheet, Text, TouchableOpacity, View} from "react-native";
    
    
    const styles = StyleSheet.create({
        container: {
            // flex: 1,
            justifyContent: 'center',
            alignItems:'center'
        },
        hello: {
            fontSize: 20,
            textAlign: 'center',
            margin: 10
        }
    });
    
    interface Props{
        test:string
    }
    
    interface State{
        test:string
    }
    
    export class HelloWorld extends React.Component<Props,State> {
        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity onPress={()=>{
                    //这个CustomToast正是原生ToastModule类中getName方法的返回值,该方法返回什么字符串,这里就是什么
                    //show方法正是我们在原生中自定义的被ReactMethod修饰方法
                    //NativeModules.CustomToast.SHORT这个变量正是getConstants方法中,我们写给map的其中一个键,底层会通过这个键,找到对应的值
                    //这里多加一个小提醒:rn端如果想要调用原生的方法、常量等东西,必须通过NativeModules.xxx.[方法|常量]来调用,否则根本找不到对应的方法。例如,本例中的NativeModules.CustomToast.show和NativeModules.CustomToast.SHORT
                        NativeModules.CustomToast.show('Brett', NativeModules.CustomToast.SHORT);
                    }}>
                        <Text style={styles.hello}>{"setData"}</Text>
                    </TouchableOpacity>
                </View>
            );
        }
    }
    
    
    • 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

    通过上面这四步即可实现rn端和原生的调用。其中前3步是原生端的,第四步才是rn端的。
    接下来,我们更深入一点,上面这个Toast模块的调用逻辑是rn调用原生,但是是否调用成功rn端是不知道的。如果rn端想要知道原生是否调用成功,那该怎么办呢?更具体的说是:原生方法想要返回一个值给rn,这该如何实现呢?别急,我们看第二个案例。

    存储模块

    老规矩,我们照抄原生端的三步曲

    package com.example.demo.reactnative.base.module;
    
    import android.util.Log;
    
    import androidx.annotation.NonNull;
    
    import com.example.demo.reactnative.utils.SPUtils;
    import com.facebook.react.bridge.Promise;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    
    /**
     * Created by brett.li
     * on 2022/10/18
     */
    public class StorageModule extends ReactContextBaseJavaModule {
    
        private ReactApplicationContext applicationContext = null;
    
        public StorageModule(ReactApplicationContext context) {
            super(context);
            applicationContext = context;
        }
    
        @NonNull
        @Override
        public String getName() {
            return "BaseStorageModule";
        }
    
      //这里着重解释下Promise这个参数,这个参数不是给rn端调用的,举个例子
      //原生方法为show(String key) ---> rn端调用如下:BaseStorageModule.show(“xxxxx”)
      //原生方法为show(String key,Promise promise) ---> rn端调用如下:BaseStorageModule.show(“xxxxx”),原生只传了key这个参数给rn
      //原生方法为show() ---> rn端调用如下:BaseStorageModule.show(),原生没有传参数给rn
      //原生方法为show(String key,int value) ---> rn端调用如下:BaseStorageModule.show(“xxxxx”,10),原生只传了两个参数给rn
      //原生方法为show(String key,int value,Promise promise) ---> rn端调用如下:BaseStorageModule.show(“xxxxx”,10),原生只传了两个参数给rn
        @ReactMethod
        public void setStringData(String key, String data,Promise promise) {
            Log.e("StorageModule", "key is "+key +" ,data is " + data);
            SPUtils.saveStringData(applicationContext, key,data);
            //通过promise.resolve或者promise.reject将原生端的值发送给rn端
            promise.resolve(null);
        }
    
        @ReactMethod
        public void getStringData(String key ,Promise promise) {
            Log.e("StorageModule", "getData");
            String data = SPUtils.getStringData(applicationContext,key,"0");
            promise.resolve("{test:" + data + "}");
        }
    
    }
    
    
    • 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
    package com.example.demo.reactnative.base.hook;
    
    import androidx.annotation.NonNull;
    
    import com.example.demo.reactnative.base.module.RNBaseReactModule;
    import com.example.demo.reactnative.base.module.StorageModule;
    import com.example.demo.reactnative.base.module.ToastModule;
    import com.example.demo.reactnative.base.viewManager.MyTextviewManager;
    import com.example.demo.reactnative.base.viewManager.MyWebViewManager;
    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.Collections;
    import java.util.List;
    
    /**
     * Created by brett.li
     * on 2022/10/17
     */
    public class RNBasePackage implements ReactPackage {
        @NonNull
        @Override
        public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
            modules.add(RNBaseReactModule.getInstance(reactContext).setReactApplicationContext(reactContext));
            modules.add(new ToastModule(reactContext));
            modules.add(new StorageModule(reactContext));
            return modules;
        }
    
        @NonNull
        @Override
        public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
            List<ViewManager> modules = new ArrayList<>();
            return modules;
        }
    }
    
    
    • 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

    rn端调用如下:

    import React from "react";
    //1.导入NativeModules
    import {NativeModules, Platform, requireNativeComponent, StyleSheet, Text, TouchableOpacity, View} from "react-native";
    
    
    const styles = StyleSheet.create({
        container: {
            // flex: 1,
            justifyContent: 'center',
            alignItems:'center'
        },
        hello: {
            fontSize: 20,
            textAlign: 'center',
            margin: 10
        }
    });
    
    interface Props{
        test:string
    }
    
    interface State{
        test:string
    }
    
    export class HelloWorld extends React.Component<Props,State> {
    
     //原生中的getStringData方法里面,我们通过promise.resolve("{test:" + data + "}")将“{test:" + data + "}”这串字符串传递给了rn,
     //因此NativeModules.BaseStorageModule.getStringData("brett")返回了Promise这种数据类型,如果想要拿到Promise包裹的数据类型,有两种方式,await或者then。
      private async getDataFromStorage(key: string): Promise<String>{
            const data = await NativeModules.BaseStorageModule.getStringData("brett")//这样我们拿到的数据就是"{test:" + data + "}"
            if(data == null) {
                return "0"
            }
            return data
        }
    
        private async setDataFromStorage(key:string,value:any):Promise<any>{
            return await NativeModules.BaseStorageModule.setStringData(key,value)
        }
        render() {
            return (
                <View style={styles.container}>
                    <TouchableOpacity onPress={()=>{
                        NativeModules.CustomToast.show('Brett', NativeModules.CustomToast.SHORT);
                    }}>
                        <Text style={styles.hello}>{"setData"}</Text>
                    </TouchableOpacity>
                </View>
            );
        }
    }
    
    • 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

    上述的rn代码中提及到了async-await这个语法机制,不太清楚的同学,参考下面的资料,这里就不展开来讲解了。https://reactnative.cn/docs/native-modules-android#promises
    这样,我们便完成了原生给rn的通信。有同学会问:因为我们上面都是rn先通知原生的,然后原生才回复rn。可不可以省掉第一步,直接原生通知rn呢?这样的话,笔者认为直接存数据到本地就行了。在原生页面存一个数据到磁盘中,进入rn页面直接通过存储模块获取该数字,进而改变rn页面的状态。
    其实,原生模块最重要的一个应用场景是封装网络通信接口,一般的app开发我们原生都会封装好网络通信模块来访问后端接口,有时候后端的接口需要携带一些公共的请求头字段才能访问成功。如果我们原生以及将其模块化了,那么rn端的通信难道又得要重新再搞一份吗?
    这里有一个网络模块封装的案例大家参考下:

    @ReactMethod
        public void nativePost(ReadableMap httpRequest, Promise promise){
            try {
                OkHttpClient okHttpClient = new OkHttpClient();
                RequestBody body = RequestBody.create(httpRequest.getString("body"), JSON);
                Request request = new Request.Builder()
                        .url(httpRequest.getString("url"))
                        .post(body)
                        .build();
                String resp = okHttpClient.newCall(request).execute().body().string();
                Log.e("BaseNativeManager","resp is "+resp);
                promise.resolve(resp);
            } catch (Throwable e) {
                promise.reject(e);
                e.printStackTrace();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    //rn端调用
    //获取Promise包裹的数据的第二种方法:then。
    //await只能获取resolve回调方法,reject获取不到,如果使用await this.post(),那么异常情况 promise.reject(e)就获取不到了,当然,我们在原生可以不使用 promise.reject(e);而是 使用promise.resolve(e);这样,我们在rn端就需要对饭回来的值做下处理。
    this.post().then(resp=>{
              //原生通过resolve方法的会回调到这里来
                NativeModules.CustomToast.show("resp is : "+resp, NativeModules.CustomToast.SHORT);
                this.setState({test:"获取网络数据成功"})
            },rej=>{
            //原生通过reject方法的会回调到这里来
                NativeModules.CustomToast.show("rej is : "+rej, NativeModules.CustomToast.SHORT);
                this.setState({test:"获取网络数据失败"})
            })
    private async post():Promise<String>{
            let request={
           url:"http://api.m.mtime.cn/PageSubArea/TrailerList.api",
                 body:""
            }
            const data = NativeModules.BaseNativeManager.nativePost(request)//注意:这里并没有await,因此data的数据类型是Promise而不是String
            return data
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    最后

    牢记四步法便可以完成rn与原生模块的通信功能,希望大家多多实践。

  • 相关阅读:
    linux安装jdk17
    flutter播放rtmp视频
    vue案例
    接口测试实战工具如何选择?这6个工具首选(建议收藏)
    记录一个笔误引发的bug导致生产环境报错,但是本地环境,测试环境运行正常
    LeetCode75-06:移动零
    废品回收功能文档
    JS 常见报错及异常处理办法总结
    SQL函数和高级语句
    JavaScript 28 JavaScript 数组 Const
  • 原文地址:https://blog.csdn.net/qq_36828822/article/details/127438021