React Native 调用原生Android/iOS代码实现拨号功能

React Native 调用原生Android、iOS模块实现拨号功能

一 前言

由于前几个月公司2.0项目开发技术选型为React Native,技术部相关人员开始学习React Native相关的技术,笔者是一名Android开发者,下文所描述的React Native调用Android/iOS模块中关于iOS的部分如有误的地方,请指出。为了让从Android或iOS学习React Native的同志更加清楚的了解另一移动端,笔者尽可能写的详细点。

二 效果

下面两张图分别为iOS和Android上效果图,其中iOS效果图中点击电话号码会打印log,并不会调起iOS拨号界面,因为iOS模拟器不支持此功能,所以要想看效果只能用真机查看。这里打印log是为了证明React Native成功调起了原生iOS模块功能。

react native调用iOS
React Native调用Android

三 实现方案

关于调用拨号功能以及调用浏览器、短信、邮箱等功能,可有两种实现方案。

一种是按照React Native提供的调用原生的过程方案来调用,这种适合大部分React Native调用原生功能的需求,掌握这种后,基本以后再有调用原生需求即可按照此过程方案解决,此文也会选用这种方案进行描述。

另一种是React Native帮我们封装的Linking模块可以实现这类的需求,这种相比上一种来说相对简单,主要适用于调用原生的电话、短信、邮箱、浏览器等功能。

四 实现原生Android模块

1.在自己新建的Reacat Native项目中android/app/src/main/java/xxx(项目包名)/ 目录下(为了和其他文件分离,笔者又在此目录下新建一个native文件夹),需要新建一个java类文件,例如文件名为CallPhoneModule.java,这个java类一定要继承RN提供的ReactContextBaseJavaModule抽象类,然后实现其构造函数,其中的参数要为ReactApplicationContext reactContext。

public class CallPhoneModule extends ReactContextBaseJavaModule {
    public CallPhoneModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }
 }
CallPhoneModule

2.然后实现NativeModule中定义的getName()方法,返回一个String类型字符串,这个返回结果将要在JavaScript中使用,例如返回“CallPhoneModule”,则可以在JavaScript中通过React.NativeModules.CallPhoneModule调用。注意,如果返回的字符串中有RCT前缀,则会自动移除RCT前缀。例如返回“RCTCallPhoneModule”,则在JavaScript中依然可以通过React.NativeModules.CallPhoneModule调用。CallPhoneModule继承ReactContextBaseJavaModule,ReactContextBaseJavaModule继承BaseJavaModule,BaseJavaModule实现了NativeModule接口,这是CallPhoneModule与NativeModule的关系。

 @Override
    public String getName() {
        return "CallPhoneModule";
    }

3.然后在CallPhoneModule类中写一个方法,这个方法提供给JavaScript调用,例如方法名为callPhone,里面传递String类型参数(非必须),特别的这个方法要使用@ReactMethod注解,以及返回类型必须为void。然后在callPhone方法中写入要实现的功能,这里写入了拨号功能的实现。

 Intent intent = new Intent(Intent.ACTION_DIAL,  Uri.parse("tel:" + phoneString));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        this.reactContext.startActivity(intent);

CallPhoneModule.java文件的全部代码如下:

package com.zhuku02;

import android.content.Intent;
import android.net.Uri;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.lang.String;

public class CallPhoneModule extends ReactContextBaseJavaModule {

    public ReactApplicationContext reactContext;

    public CallPhoneModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
    }

    @ReactMethod
    public void callPhone(String phoneString) {
        Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneString));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        this.reactContext.startActivity(intent);
    }

    @Override
    public String getName() {
        return "CallPhoneModule";
    }
}
CallPhoneModule

4.新建一个java类文件,例如文件名为CallPhoneReactPackage.java,这个类必须实现ReactPackage接口,然后实现createViewManagers、createNativeModules两个方法,特别的要在createNativeModules方法的返回值中add进刚才新建的CallPhoneModule类。

CallPhoneReactPackage.java全部代码如下:

package com.zhuku02;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.zhuku02.CallPhoneModule;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CallPhoneReactPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new CallPhoneModule(reactContext));

        return modules;
    }
}
CallPhoneReactPackage

5.最后在MainApplication.java文件中的getPackages方法中加上刚才新建的CallPhoneReactPackage类。至此,原生Android模块书写完毕。关于JavaScript调用原生Android模块代码会在文末和调用原生iOS一起写出。

修改后的MainApplication.java文件代码如下:

package com.zhuku02;

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 com.zhuku02.CallPhoneReactPackage;

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

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}

MainApplication

五 实现原生iOS模块

1.在自己新建的Reacat Native项目中ios/xxx(项目包名)/ 目录下,需要新建一个后缀为m和一个后缀为h的文件。为了和其他文件分离以及和Android保持一致,笔者又在此目录下新建一个native文件夹。在Xcode的此文件夹上右键New File,然后在弹出的页面中Cocoa Touch Class选项输入文件名,这样会建立出相同文件名的m和h文件。如果New File时,分别选择Objective-C File和Header File,则这两个文件名要相同。例如文件名称为CallPhoneModuleIos。

2.在CallPhoneModuleIos.h文件中,类要实现RN提供的RCTBridgeModule协议。RCT是ReaCT的缩写,React Native中Object-C相关的命名均以RCT开头。RCTBridgeModule是定义好的protocol,实现该协议的类,会自动注册到Object-C对应的Bridge中。Object-C Bridge上层负责与Object-C通信,下层负责和JavaScript Bridge通信,而JavaScript Bridge负责和JavaScript通信。这样,通过Object-C Bridge和JavaScript Bridge就可以实现JavaScript和Object-C的相互调用。

CallPhoneModuleIos.h文件如下:


#import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>

@interface CallPhoneModuleIos : NSObject <RCTBridgeModule>

@end

CallPhoneModuleIos

3.CallPhoneModuleIos.m文件中,类需要包含RCT_EXPORT_MODULE()宏,作用是自动注册一个module。这个宏可以添加一个参数,用来指定在JavaScript调用这个模块的名字,类似于上文中说的getName()方法。如果不添加这个参数,则默认就是这个类的名字。

4.然后需要在此类中写一个方法,提供给RN调用,方法通过RCT_EXPORT_METHOD()宏来实现。

RCT_EXPORT_METHOD(callPhone: (NSString *)phone){
   NSLog(@"======%@",phone);
}

CallPhoneModuleIos.m完整代码:

#import "CallPhoneModuleIos.h"
#import <Foundation/Foundation.h>

@implementation CallPhoneModuleIos

RCT_EXPORT_MODULE(CallPhoneModuleIos);

RCT_EXPORT_METHOD(callPhone: (NSString *)phone){
  
   NSLog(@"======%@",phone);
  //去掉注释,下面代码就是实现拨号功能代码,但还未真机测试
//  NSMutableString * str = [[NSMutableString alloc] initWithFormat:@"telprompt://%@",phone];
//  [[UIApplication sharedApplication] openURL:[NSURL URLWithString:str]];
  
}

// -(dispath_queue_t)methodQueue{
//     return dispath_get_main_queue();
// }

@end

CallPhoneModuleIos

至此,则原生iOS代码书写完成,现在即将开始调用。

六 React Native调用Android、iOS原生模块

为了在JavaScript端同时访问Android、iOS原生模块更加方便,笔者把原生模块的调用封装在一个JavaScript文件中,例如文件名为CallPhone.js,这样在需要调用的地方直接调用此JavaScript文件既可,同时在此文件中,处理好Android、iOS、Web(若有)的分别调用。

import {Platform, NativeModules} from 'react-native';

var module = null;
if (Platform.OS == 'ios') {
    module = NativeModules.CallPhoneModuleIos;
} else if (Platform.OS == 'android') {  
    module = NativeModules.CallPhoneModule;
} else if (Platform.OS == 'web') {
    //暂未实现web功能  
}
export default module;
CallPhone

然后在JavaScript文件中这样调用:

import CallPhone from '../../native/CallPhone';
  CallPhone.callPhone('4007773177');

到这里,整篇文章就结束了,疑问、建议或者指教欢迎讨论。

七 参考资料

native-modules-ios
native-modules-android

原创文章,转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容