React Native填坑之旅--與Android模塊通信

使用Toast作為例子。實現的功能是可以在JavaScript里寫ToastAndroid.show('Awesome', ToastAndroid.SHORT)來顯示一個Toast通知。

代碼:https://github.com/future-challenger/react-native-gaode-map

創建一個原生模塊

創建一個類,繼承ReactContextBaseJavaModule

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

之后需要實現一個方法getName

  @Override
  public String getName() {
    return "AnotherToastAndroid"; // 不能返回ToastAndroid,這個會報錯,或者需要手動指定覆蓋RN已有的實現。
  }

這個方法必須實現。它的返回值是React Native的js部分調用模塊時的名稱。另外,如果這個方法返回的字符串包含RCT的話,那么RCT會被去掉。也就是,如果getName返回的是RCTToastAndroid的話,在js調用的時候還是使用ToastAndroid

接下來實現show方法。

  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }

注意:模塊要導出方法給js使用,那么這個方法上必須使用@ReactMethod注解!并且返回值必須為void。如果要返回值的話,需要使用回調方法或者注冊事件。這些下文會講到。

方法的參數類型

在導出給js的方法中添加參數的時候,只能使用部分類型(java -> javascript):

Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array

注冊模塊

注冊模塊之后就可以使用。如果你的App里沒有Package類,那就自己創建一個。比如本例,就可以創建名為ToastReactPackage的Package類,該類實現ReactPackage接口。

public class ToastReactPackage implements ReactPackage {
  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    return null;
  }

  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    return null;
  }

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

類中每個方法的名稱已經明確的表明了其本身的作用。我們這里導出的是一個模塊,所以需要實現createNativeModules方法。其他的方法只要返回一個空列表就可以。最后的ToastReactPackage類的實現是:

public class ToastReactPackage implements ReactPackage {
  @Override
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    List<NativeModule> modules = new ArrayList<>();
    modules.add(new ToastModule(reactContext));

    return modules;
  }

  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    return Collections.emptyList();
  }

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

最后在MainApplicationgetPackages方法里注冊Package。

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 ToastReactPackage() // 這一句用來注冊我們的AnotherToastAndroid模塊
      );
    }
  };

在React Native中使用模塊。

import {
  //...
  NativeModules,
  PixelRatio,
} from 'react-native';

let AnotherToastAndroid = NativeModules.AnotherToastAndroid;

export default class mobike extends Component {
  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity style={styles.button} onPress={() => {
          AnotherToastAndroid.show('Another Toast', AnotherToastAndroid.LONG);
        }}>
          <Text style={{ textAlign: 'center', }}>
            Show Toast
          </Text>
        </TouchableOpacity>
      </View>
    );
  }
}

不直接相關的內容就隱藏掉了。使用的時候只要在import中引入NativeModules,之后在let AnotherToastAndroid = NativeModules.AnotherToastAndroid;提取我們的原生模塊。這個模塊的名字就是在Android模塊getName方法里返回的名稱AnotherToastAndroid

之后在TouchableOpacity的onPress事件中調用AnotherToastAndroidshow方法。

至此,我們之前說的功能就都實現了。

返回常量

前面的內容要運行起來還差這個一環。返回常量,你看到js代碼里有這樣的調用:

AnotherToastAndroid.show('Another Toast', AnotherToastAndroid.LONG);

有這么一句:AnotherToastAndroid.LONG。要使用LONG,還有沒有用到的SHORT常量,需要原生模塊返回這樣的常量。

  @Nullable
  @Override
  public Map<String, Object> getConstants() {
//    return super.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;
  }

方法getConstants是類ReactContextBaseJavaModule的一個可選方法,專門用來返回常量。返回的內容就是字典Map<String, Object>

現在Demo可以運行起來了。

回調方法

前文說道,要返回值給js就需要用回調方法。現在看看在原生里如何實現這一點:

  @ReactMethod
  public void currentThreadName(Callback errorCallback, Callback successCallback) {
    try {
      String tn = Thread.currentThread().getName();
      successCallback.invoke(tn);
    } catch(Exception e) {
      errorCallback.invoke(e.getMessage());
    }
  }

在Toast模塊里加了一個獲取當前線程的方法。Android的這個導出回調方法看起來還是有點奇怪。本來應該是一個回調返回兩個參數:一個error,一個結果。這里用了兩個Callback,可能也是條件限制吧。

看看js如何使用:

  <Button
    style={{ marginTop: 10, }}
    title='use callback'
    pressHandler={
      () => {
        AnotherToastAndroid.currentThreadName((msg) => console.log(`error message ${msg}`)
          , (threadName) => {
            Alert.alert('Thread Name', `thread nane: ${threadName}`, null);
          });
      }}
  />

Promise

回調缺點很明顯。所以多數的時候都會選擇使用Promise。再加上現在流行的async-await就更多的人使用Promise了。

  @ReactMethod
  public void currentThreadNameByPromise(Promise promise) {
    try {
      String tn = Thread.currentThread().getName();
      promise.resolve(tn);
    } catch (Exception e) {
      promise.reject("Thread Error", e);
    }
  }

來看看如何使用Promise的:

  <Button
    style={{ marginTop: 10, }}
    title='use Promise'
    pressHandler={
      () => {
        AnotherToastAndroid.currentThreadNameByPromise().then((threadName) =>
          Alert.alert('Thread Name', `thread nane: ${threadName}`, null)
        ).catch(err => Alert.alert('Thread Name', `get thread nane error: ${err.message}`, null));
      }}
  />

這些知識在一般的使用中就足夠了,如果需要更復雜的內容可以查看官方文檔。我也會在之后補齊這部分的內容。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容