天天看點

react native使用原生子產品

有時候App需要通路平台API,但React Native可能還沒有相應的子產品包裝;或者你需要複用一些Java代碼,而不是用Javascript重新實作一遍;又或者你需要實作某些高性能的、多線程的代碼,譬如圖檔處理、資料庫、或者各種進階擴充等等。

使用步驟

在RN中使用系統原生子產品需要如下步驟:

  • 建立一個原生子產品。一個原生子產品是一個繼承了ReactContextBaseJavaModule的Java類,派生實作getName 傳回一個字元串名字,這個名字在JavaScript端标記這個子產品
  • 一個可選的方法getContants傳回了需要導出給JavaScript使用的常量。它并不一定需要實作,但在定義一些可以被JavaScript同步通路到的預定義的值時非常有用
  • 導出一個方法給JavaScript使用,Java方法需要使用注解@ReactMethod。方法的傳回類型必須為void
  • 注冊子產品。我們需要在應用的Package類的createNativeModules方法中添加這個子產品

##編寫子產品##

下面,我以官方的demo為栗子,來顯示一個在js中調用android原生toast的demo,首先需要編寫一個類繼承自ReactContextBaseJavaModule。

package com.secondproject;

import android.widget.Toast;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import java.util.Map;
import java.util.HashMap;

public class MyToastModule extends ReactContextBaseJavaModule {

    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

    public MyToastModule(ReactApplicationContext reactContext) {
      super(reactContext);
    }
    /**
     * getName方法。這個函數用于傳回一個字元串名字,就是js中的子產品名
     */
    @Override
    public String getName() {
      return "MyToast";
    }
    /**
     * 傳回了需要導出給JavaScript使用的常量
     */
    @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;
    }
    /**
     * 導出給js使用的方法,需要使用注解@ReactMethod。方法的傳回類型必須為void
     */
    @ReactMethod
    public void show(String message, int duration) {
      Toast.makeText(getReactApplicationContext(), message, duration).show();
    }
}
           

##注冊子產品##

上面已經編寫好了導出給js使用的子產品,下面還需要注冊目前子產品,如果子產品沒有被注冊,它也無法在JavaScript中被通路到。注冊子產品份兩步:

  • 編寫類實作ReactPackage接口
  • 在目前應用的MainActivity.java中添加目前子產品

    我們先來看下系統的ReactPackage接口:

    node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\ReactPackage.java

package com.facebook.react;

import java.util.List;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;

public interface ReactPackage {

  List<NativeModule> createNativeModules(ReactApplicationContext reactContext);


  List<Class<? extends JavaScriptModule>> createJSModules();


  List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}

           

可以看到這裡有三個方法需要實作,其中,目前最關心的就是createNativeModules方法的實作。

  • MyReactPackage.java
package com.secondproject;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;

public class MyReactPackage implements ReactPackage {


  @Override
  public List<NativeModule> createNativeModules(
                              ReactApplicationContext reactContext) {
    List<NativeModule> modules = new ArrayList<>();
    // 這裡的MyToastModule是之前他添加的module
    modules.add(new MyToastModule(reactContext));

    return modules;
  }

    @Override
	public List<Class<? extends JavaScriptModule>> createJSModules() {
		// TODO Auto-generated method stub
		return Collections.emptyList();
	}

	@Override
	public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
		// TODO Auto-generated method stub
		return Collections.emptyList();
	}

}
           
  • 在MainActivity中添加目前子產品

    可以看到在MainActivity的onCreate方法中,有這樣一段代碼:

mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
           

在目前代碼裡已經添加了一個package,.addPackage(new MainReactPackage()),這裡将自己之前定義的MyReactPackage 同樣加進去即可:

mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .addPackage(new MyReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
           

##在javascript中使用原生子產品##

使用也很簡單,隻需要這樣一句代碼就可以了:

// 這裡的MyToast就是在MyToastModule的getName中傳回的module名稱
React.NativeModules.MyToast.show('調用系統的toast啦', ToastAndroid.SHORT);
           
_onPress: function(duration,content) {
     React.NativeModules.MyToast.show('調用系統的toast啦', ToastAndroid.SHORT);
  },

  render: function() {
    return (
        <View style={{flexDirection: 'column'}}>
            <Text onPress={()=> this._onPress(ToastAndroid.SHORT,'this is short')} style={styles.button}>點選調用原生API</Text>
        </View>
    );
  }
           

現在效果如下:

react native使用原生子產品

##将原生子產品封裝##

為了在js裡使用友善,可以将原生子產品封裝成一個JavaScript子產品,在index.android.js同目錄下建立一個toast.js檔案:

react native使用原生子產品

toast.js

'use strict';

var { NativeModules } = require('react-native');
// 這裡的MyToast就是在MyToastModule 的getName傳回的子產品名
module.exports = NativeModules.MyToast;
           

此時使用起來就很友善了,如下:

// 引入目前的子產品,注意toast是檔案名
var MyToast = require('./toast'); 
// 這裡的MyToast是toast.js檔案通過module.exports給我們的
MyToast.show('調用系統的tdsaf', ToastAndroid.SHORT);
           

##參數類型##

下面的參數類型在@ReactMethod注明的方法中,會被直接映射到它們對應的JavaScript類型

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

回調函數

有時候,在js中調用android中自定義子產品中的方法,需要有傳回值,此時就需要使用到RN為我們封裝好的一個Callback接口。

package com.facebook.react.bridge;

public interface Callback {

  public void invoke(Object... args);

}

           

可以看到,通過我們在java中調用invoke方法,将結果傳回給js的,傳回的參數類型和個數都是不限的。

###建立CallbackModule###

package com.secondproject;

import android.widget.Toast;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;

import java.util.Map;
import java.util.HashMap;

public class CallbackModule extends ReactContextBaseJavaModule {


    public CallbackModule(ReactApplicationContext reactContext) {
      super(reactContext);
    }
    // 傳回js中使用目前子產品的名稱
    @Override
    public String getName() {
      return "MyCallback";
    }

    @ReactMethod
    public void getAddResult(int number1, int number2, Callback callback) {
        try {
             int result = number1 + number2;
             //通過invoke方法将結果傳遞給js
             callback.invoke("結果是:",result);
        } catch (Exception e) {
             e.printStackTrace();
        }

    }
}

           

###注冊CallbackModule 子產品###

記得在之前的MyReactPackage中注冊目前子產品:

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

    modules.add(new MyToastModule(reactContext));
    modules.add(new CallbackModule(reactContext));
    return modules;
  }
           

###建立callback.js###

  • 在與index.android.js同目錄下建立callback.js,用來導出目前CallbackModule 子產品。
'use strict';

var { NativeModules } = require('react-native');
module.exports = NativeModules.MyCallback;
           

###使用MyCallback###

MyCallback的使用和MyToast相同,這裡我将兩者結合起來使用,代碼如下:

var MyToast = require('./toast');
var MyCallback = require('./callback');

MyCallback.getAddResult(3,2,(code,result)=>{
        console.log("callback",code,result);
        MyToast.show(code + result,ToastAndroid.SHORT);
 });  
           

此時效果如下:

react native使用原生子產品

##發送事件到JavaScript##

另外,原生的代碼也可以在沒有被調用的情況下向js發送事件通知。通過RCTDeviceEventEmitter來實作。這裡依然以剛才的CallbakModule為例:

@ReactMethod
    public void getAddResult(int number1, int number2, Callback callback) {
        
        try {

              WritableMap params = Arguments.createMap();
              //通過WritableMap傳遞一個參數給js,也可以傳遞多個
              params.putString("first","i am first");
              getReactApplicationContext()
                      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                      .emit("toastMe", params);//對應的javascript層的事件名為toastMe,注冊該事件即可進行回調 

             int result = number1 + number2;
             callback.invoke("結果是:",result);
        } catch (Exception e) {
             e.printStackTrace();
        }

    }
           

###在componentDidMount方法中注冊事件###

componentDidMount:function(){
    //使用DeviceEventEmitter注冊事件
    DeviceEventEmitter.addListener('toastMe',(e)=>{
       MyToast.show(e.first,ToastAndroid.SHORT);
    });
  },
           

這裡注冊的事件後,一旦調用getAddResult方法,就會回到toastMe方法。此時效果如下:

react native使用原生子產品

ok,今天就到這裡了,由于代碼較大,這裡我将用到的代碼打包上傳,

歡 迎 關 注 我 的 公 衆 号 “編 程 大 全”

專注技術分享,包括Java,python,AI人工智能,Android分享,不定期更新學習視訊

react native使用原生子產品