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();
    }
}

第二步,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;
    }
}

如果读者是想要原生模块,那么只需要修改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);这句代码

第四步,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>
        );
    }
}

通过上面这四步即可实现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 + "}");
    }

}

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;
    }
}

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<String>这种数据类型,如果想要拿到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>
        );
    }
}

上述的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();
        }
    }
//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>而不是String
        return data
    }

最后

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在React Native中封装原生第三方SDK的步骤如下: 1. 创建一个原生模块(Native Module),以便React Native应用程序可以与原生代码进行交互。对于iOS,你需要创建一个Objective-C或Swift类;对于Android,你需要创建一个Java类。 2. 在原生模块中编写代码,调用第三方SDK的API并将其封装为JavaScript可以调用的函数。你可以使用React Native提供的RCT_EXPORT_MODULE宏将该模块导出到JavaScript中。例如,下面是一个在iOS中封装Facebook SDK的例子: ```objective-c #import <FBSDKCoreKit/FBSDKCoreKit.h> #import <React/RCTBridgeModule.h> @interface MyFacebookModule : NSObject <RCTBridgeModule> @end @implementation MyFacebookModule RCT_EXPORT_MODULE(); RCT_EXPORT_METHOD(login:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { FBSDKLoginManager *loginManager = [[FBSDKLoginManager alloc] init]; [loginManager logInWithPermissions:@[@"public_profile", @"email"] fromViewController:nil handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) { if (error) { reject(@"login_error", error.localizedDescription, error); } else if (result.isCancelled) { reject(@"login_cancelled", @"User cancelled login", nil); } else { resolve(result.token.tokenString); } }]; } @end ``` 这个模块导出了一个名为`login`的函数,该函数会登录Facebook,并返回一个Promise,该Promise将在登录成功后解析为一个Facebook访问令牌,或在登录失败时拒绝。 3. 在JavaScript中导入原生模块,并使用它提供的函数。例如,你可以使用以下代码在React Native应用程序中调用上述iOS模块: ```javascript import { NativeModules } from 'react-native'; const { MyFacebookModule } = NativeModules; MyFacebookModule.login() .then(token => console.log(`Facebook access token: ${token}`)) .catch(error => console.error(`Failed to login: ${error}`)); ``` 这个代码导入了名为`MyFacebookModule`的原生模块,并调用其`login`函数。当该函数返回时,它会返回一个Promise,你可以使用该Promise来处理成功或失败的情况。 通过将原生第三方SDK封装到React Native模块中,你可以轻松地在React Native应用程序中使用该SDK,并将其与JavaScript代码无缝集成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值