React Native 调用 iOS / Android (Toast) 原生模块学习笔记

WanVillarre 7年前
   <h2>参考官方文档初始化一个react-native项目</h2>    <p><a href="/misc/goto?guid=4959643958079565672" rel="nofollow,noindex">初始化项目</a></p>    <pre>  <code class="language-java">react-native init androidToast</code></pre>    <p>生成如下目录:</p>    <p><img src="https://simg.open-open.com/show/541be29691322e49f77515ee6902ab07.jpg"></p>    <h2>运行命令查看项目</h2>    <pre>  <code class="language-java">react-native run-android</code></pre>    <h3>如图:</h3>    <p><img src="https://simg.open-open.com/show/b86508b5e310f87dafa06ad81079d723.jpg"></p>    <h2>接入Android原生模块</h2>    <p>按照官方的说法,第一步需要创建一个java类 本例中为:ToastModule ,并继承 ReactContextBaseJavaModule ,然后复写 getName() 方法,其返回值,就是在 react-native 中引用的 组件名称</p>    <p>复写 getConstants() 方法可以返回一些 常量 用于react-native中调用,官方文档中 return "ToastAndroid" 这个名称在原生的组件中已经存在,返回相同的名称将会冲突,so:改个名字吧!!</p>    <p>@ReactMethod 注解:用于java返回一个 react-native 中可调用的 方法 ,其不能有返回值所以使用 void</p>    <p>注册模块:创建java类 本例中为:ExampleReactPackage ,实现 ReactPackage 接口</p>    <p>复写createJSModules() , createViewManagers() 方法,返回 Collections.emptyList() 空集合</p>    <p>createNativeModules() 方法中添加我们需注册的模块对象, new ToastModule() ,并返回模块集合</p>    <p>添加已注册模块对象到返回集合中,向react-native抛出模块,如:第三步</p>    <p>在react-native中调用,如:第四步</p>    <h3>android目录结构</h3>    <p><img src="https://simg.open-open.com/show/ab6d8392cb37a56f1acd156a8ac968fe.jpg"></p>    <h3>注意:引入包的名称不要弄错了</h3>    <h3>Java React-native基本类型对照</h3>    <table>     <thead>      <tr>       <th>Java</th>       <th>RN</th>      </tr>     </thead>     <tbody>      <tr>       <td>Boolean</td>       <td>Bool</td>      </tr>      <tr>       <td>Integer</td>       <td>Number</td>      </tr>      <tr>       <td>Double</td>       <td>Number</td>      </tr>      <tr>       <td>Float</td>       <td>Number</td>      </tr>      <tr>       <td>String</td>       <td>String</td>      </tr>      <tr>       <td>Callback</td>       <td>function</td>      </tr>      <tr>       <td>ReadableMap</td>       <td>Object</td>      </tr>      <tr>       <td>ReadableArray</td>       <td>Array</td>      </tr>     </tbody>    </table>    <h3>第一步 创建模块类</h3>    <p>在androidtoast目录下,创建一个ToastModule.java的类</p>    <pre>  <code class="language-java">package com.androidtoast; //包名    import android.widget.Toast; //引入调用的类    import com.非死book.react.bridge.ReactApplicationContext;  import com.非死book.react.bridge.ReactContextBaseJavaModule;  import com.非死book.react.bridge.ReactMethod;  import com.非死book.react.uimanager.IllegalViewOperationException;    import java.util.Map;  import java.util.HashMap;    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);      }        // 复写方法,返回react-native中调用的 组件名      @Override      public String getName() {          return "ToastNative";      }      // 复写方法,返回常量      @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;      }      // 使用 @ReactMethod注解返回react-native中可调用的 方法      @ReactMethod      public void show(String message, int duration) {          Toast.makeText(getReactApplicationContext(), message, duration).show();      }  }</code></pre>    <h3>第二步 注册模块</h3>    <p>在androidtoast目录下,创建一个ExampleReactPackage.java的类</p>    <pre>  <code class="language-java">package com.androidtoast;    import android.widget.Toast;    import com.非死book.react.bridge.NativeModule;  import com.非死book.react.bridge.ReactApplicationContext;  import com.非死book.react.bridge.ReactMethod;  import com.非死book.react.ReactPackage;  import com.非死book.react.bridge.JavaScriptModule;  import com.非死book.react.uimanager.ViewManager;    import java.util.ArrayList;  import java.util.Collections;  import java.util.List;    public class ExampleReactPackage implements ReactPackage {        @Override      public List<Class<? extends JavaScriptModule>> createJSModules() {          return Collections.emptyList();      }        @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 ToastModule(reactContext));            return modules;      }  }</code></pre>    <h3>第三步 添加注册类</h3>    <p>添加到 MainApplication.java 中的 getPackages() 方法中</p>    <pre>  <code class="language-java">@Override  protected List<ReactPackage> getPackages() {    return Arrays.<ReactPackage>asList(        new MainReactPackage(), // 这个是自动创建        new ExampleReactPackage() // 这个类是我们创建的    );  }</code></pre>    <p>项目结构如下:</p>    <p><img src="https://simg.open-open.com/show/844f4a2e618e9bedae73100d32bd5a8c.jpg"></p>    <p>Java部分的代码就结束了,再次提醒下:包名啊!!不要弄错了!!!</p>    <h3>第四步 修改react-native代码引入原生模块</h3>    <p>修改index.android.js</p>    <ul>     <li>引入react-native所需模块 NativeModules</li>     <li>获取导出组件 NativeModules.ToastNative</li>     <li>调用方法 show()</li>    </ul>    <p>修改了下index.android.js文件,代码如下:</p>    <pre>  <code class="language-java">/**   * Sample React Native App   * https://github.com/非死book/react-native   * @flow   */    import React, { Component } from 'react';  import {    AppRegistry,    StyleSheet,    Text,    View,    TouchableOpacity,    NativeModules  } from 'react-native';    let toast = NativeModules.ToastNative;    export default class androidToast extends Component {    render() {      return (        <View style={styles.container}>          <Text style={styles.title}>react-native 调用android原生模块</Text>          <TouchableOpacity onPress={()=>{            toast.show('Toast message',toast.SHORT);          }}>            <Text style={styles.btn}>Click Me</Text>          </TouchableOpacity>        </View>      );    }  }    const styles = StyleSheet.create({    container: {      flex: 1,      justifyContent: 'center',      alignItems: 'center',      backgroundColor: '#F5FCFF',    },    title:{      fontSize:16,    },    btn:{      fontSize:16,      paddingVertical:7,      paddingHorizontal:10,      borderColor:'#f00',      borderWidth:1,      borderRadius:5,      marginTop:10,      color:'#f00'    }  });    AppRegistry.registerComponent('androidToast', () => androidToast);</code></pre>    <h2>运行程序</h2>    <pre>  <code class="language-java">react-native run-android</code></pre>    <h3>效果如下:</h3>    <p><img src="https://simg.open-open.com/show/93bd7d9f6f6035404ab1fb26a3300b89.jpg"></p>    <h2>react-native回调函数</h2>    <p>*java中提供了一个 Callback 的数据类型对应了react-native中的 function *</p>    <p>具体操作就是在@ReactMethod注解的返回函数中 添加 类型 为 Callback 的参数,并通过 invoke(...params) 调用</p>    <p>RN中通过调用show方法时提供对应的回调函数就可以了, :smile:</p>    <ul>     <li>修改 ToastModule.java 代码中 show() 方法,添加回调</li>    </ul>    <p>注意引包!! import com.非死book.react.bridge.Callback;</p>    <pre>  <code class="language-java">// 说明下:count,flag是我自定义的变量    @ReactMethod  public void show(String message, int duration ,Callback successCallback, Callback errorCallback) {      Toast.makeText(getReactApplicationContext(), message, duration).show();      // 通过invoke调用,随便你传参      if(flag) successCallback.invoke("success", ++count);      else errorCallback.invoke("error", ++count);      flag = !flag;  }</code></pre>    <ul>     <li>修改 index.android.js 中调用函数</li>    </ul>    <pre>  <code class="language-java"><TouchableOpacity onPress={()=>{    toast.show('Toast message',toast.SHORT,(message,count)=>{console.log("==",message,count)},(message,count)=>{console.log("++",message,count)});  }}></code></pre>    <p>:ok_hand: ,试试看吧~~</p>    <h2>触发事件</h2>    <p>首先我们定义一个发送事件的方法</p>    <pre>  <code class="language-java">private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params){      reactContext      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)      .emit(eventName, params);  }</code></pre>    <p>引包</p>    <pre>  <code class="language-java">import javax.annotation.Nullable;    import com.非死book.react.bridge.Arguments;  import com.非死book.react.bridge.WritableMap;  import com.非死book.react.bridge.ReactContext;    import com.非死book.react.modules.core.DeviceEventManagerModule;</code></pre>    <p>继续改造 show 方法,添加参数,并调用预先定义的方法</p>    <pre>  <code class="language-java">// 静态方法  WritableMap map = Arguments.createMap();  map.putBoolean("boolean",true);  map.putDouble("double",0.003);  map.putString("string","string");  sendEvent(this.reactContext, "eventName",map);</code></pre>    <p>改造 index.android.js 啦 ,添加事件监听,这里的 eventName 就是我们 sendEvent 中定义的事件名称</p>    <pre>  <code class="language-java">componentWillMount(){    DeviceEventEmitter.addListener('eventName',(e)=>{      console.log(e)    });  }</code></pre>    <h3>效果如下:</h3>    <p><img src="https://simg.open-open.com/show/f6c8aff93494946bdb4c4d8ddab7b8a3.gif"></p>    <h2>接入IOS原生模块</h2>    <p>IOS 日历模块</p>    <p>创建一个名为 CalendarManager.h 的OC的接口 interface 文件,以及一个 CalendarManager.m 的实现类 implementation</p>    <p>CalendarManager.h 需要继承 NSObject 并实现 RCTBridgeModule 接口</p>    <p>CalendarManager.m 需要添加 RCT_EXPORT_MODULE() 宏,导出的方法需要通过 RCT_EXPORT_METHOD() 宏来实现</p>    <p>react-native中通过 NativeModules.类名.方法 调用 (本例中为:NativeModules.CalendarManager 获取iOS抛出模块,并通过模块调用抛出方法)</p>    <h3>IOS React-native 类型对照</h3>    <table>     <thead>      <tr>       <th>IOS</th>       <th>React-native</th>      </tr>     </thead>     <tbody>      <tr>       <td>NSString</td>       <td>string</td>      </tr>      <tr>       <td>NSInteger, float, double, CGFloat, NSNumber</td>       <td>number</td>      </tr>      <tr>       <td>BOOL, NSNumber</td>       <td>boolean</td>      </tr>      <tr>       <td>NSArray</td>       <td>array</td>      </tr>      <tr>       <td>NSDictionary</td>       <td>object</td>      </tr>      <tr>       <td>RCTResponseSenderBlock</td>       <td>function</td>      </tr>     </tbody>    </table>    <p>除此以外,任何RCTConvert类支持的的类型也都可以使用(参见RCTConvert了解更多信息)。RCTConvert还提供了一系列辅助函数,用来接收一个JSON值并转换到原生Objective-C类型或类</p>    <h3>创建IOS模块</h3>    <p><img src="https://simg.open-open.com/show/952562326fdfb42de14e392091458f79.gif"></p>    <ul>     <li>CalendarManager.h</li>    </ul>    <pre>  <code class="language-java">// CalendarManager.h  #import "RCTBridgeModule.h"    @interface CalendarManager : NSObject <RCTBridgeModule>  @end</code></pre>    <ul>     <li>CalendarManager.m</li>    </ul>    <pre>  <code class="language-java">// CalendarManager.m  @implementation CalendarManager    RCT_EXPORT_MODULE();    RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)  {    RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);  }    @end</code></pre>    <h3>react-native 调用</h3>    <pre>  <code class="language-java">import { NativeModules } from 'react-native';  let CalendarManager = NativeModules.CalendarManager;  CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');</code></pre>    <h3>调用效果</h3>    <p><img src="https://simg.open-open.com/show/2ef2a5c22c4e3eb2aac569cbeca265ea.jpg"></p>    <ul>     <li>官方文档中还给出了一些类型转换和词典运用的例子,请自行查看!</li>    </ul>    <h3>回调函数</h3>    <p>通过 RCTResponseSenderBlock 声明回调函数类型,但RCTResponseSenderBlock只接受一个参数——传递给JavaScript回调函数的参数数组</p>    <pre>  <code class="language-java">RCT_EXPORT_METHOD(addEvents:(RCTResponseSenderBlock)callback)  {    NSString *message = @"callback message!!!";    callback(@[[NSNull null], message]);  }</code></pre>    <pre>  <code class="language-java">CalendarManager.addEvents((error, message) => {    if (error) {      console.error(error);    } else {      console.log("message:",message)    }  })</code></pre>    <h3>导出常量</h3>    <pre>  <code class="language-java">- (NSDictionary *)constantsToExport  {    return @{ @"YEAR": @"2016" };  }</code></pre>    <pre>  <code class="language-java">CalendarManager.YEAR</code></pre>    <h3>发送事件</h3>    <pre>  <code class="language-java">#import "RCTBridge.h"  #import "RCTEventDispatcher.h"    [self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"                                                 body:@{@"name": @"xing.he"}];}</code></pre>    <pre>  <code class="language-objectivec">import { NativeAppEventEmitter } from 'react-native';    var subscription = NativeAppEventEmitter.addListener(    'EventReminder',    (reminder) => console.log(reminder.name)  );    // 千万不要忘记忘记取消订阅, 通常在componentWillUnmount函数中实现。  subscription.remove();</code></pre>    <h2>曾走过的路</h2>    <p>曾想在返回的方法中定义一个 Object 类型的变量,但pa! 报错了!!不支持滴,请查看类型对应表格</p>    <pre>  <code class="language-java">cloudn't find argument class : Object</code></pre>    <p>参照官方文档时,各种类找不到,瞬间醉了!</p>    <pre>  <code class="language-java"># 百度吧,一般不管用  # stackoverflow,Google 有时可以搜到,尼玛!英文。。。  # github  # react-native 源码 !!!这里面有个`ReactAndroid`的目录就是各种`Java`类啦    react-native/ReactAndroid/src/main/java/com/非死book/      https://github.com/非死book/react-native</code></pre>    <p><img src="https://simg.open-open.com/show/b7f093ae418390c0e78bbef5c279e567.jpg"></p>    <p>RCTBridgeModule.h file not found</p>    <p><img src="https://simg.open-open.com/show/a2e70150d3e2e571997a718294e911f6.jpg"></p>    <pre>  <code class="language-java">npm install</code></pre>    <p>npm WARN jest-react-native@17.0.0 requires a peer of whatwg-fetch@^1.0.0 but none was installed</p>    <pre>  <code class="language-java">npm install whatwg-fetch@^1.0.0</code></pre>    <p>implicit declaration of function'RCTLogInfo' in invalid</p>    <pre>  <code class="language-java">#import "RCTLog.h"    http://bbs.reactnative.cn/topic/1429/rctloginfo-%E6%8A%A5%E9%94%99%E7%9A%84%E9%97%AE%E9%A2%98/2</code></pre>    <h2>参考文档</h2>    <p><a href="/misc/goto?guid=4959724795430352569" rel="nofollow,noindex">江清清 ModulesDemo</a></p>    <p><a href="/misc/goto?guid=4959724795519042519" rel="nofollow,noindex">react-native Android 中文</a></p>    <p><a href="/misc/goto?guid=4959715830126579166" rel="nofollow,noindex">react-native Android 官方英文</a></p>    <p><a href="/misc/goto?guid=4959725666647323482" rel="nofollow,noindex">react-native IOS 中文</a></p>    <p><a href="/misc/goto?guid=4959724795616073256" rel="nofollow,noindex">react-native IOS 官方英文</a></p>    <p><a href="/misc/goto?guid=4959724795700249509" rel="nofollow,noindex">RN-Resource-ipk github</a></p>    <p> </p>    <p>来自:https://github.com/Xing-He/react-native-nativeModule/blob/master/README.md</p>    <p> </p>