天天看點

React Native建立原生子產品

我的部落格原文位址

在React Native開發過程中,有時候我們可能需要通路平台的API,但React Native還沒有相應的實作,或者是React Native還不支援一些原生的屬性,我們需要調用原生代碼來實作,或者是我們需要複用一些原來的Java代碼,這個時候我們就需要建立一個原生子產品來自己實作對我們需要功能的封裝。

可以參考官方文檔或中文文檔。

開發子產品

實作子產品

下面我們就通過實作一個自定義子產品,來熟悉編寫原生子產品需要用的一些知識。該子產品主要實作調用一些Android原生的功能,比如彈

Toast

,啟動

Activity

等。

我們首先來建立一個原生子產品。一個原生子產品是一個繼承了

ReactContextBaseJavaModule

的Java類,它有一個必須實作的方法

getName()

,它傳回一個字元串名字,在JS中我們就使用這個名字調用這個子產品;還有構造函數

NativeModule

然後在這個類裡面實作我們需要實作的方法:

public class MyNativeModule extends ReactContextBaseJavaModule {
    private final static String MODULE_NAME = "MyNativeModule";
    private static final  String TestEvent = "TestEvent";
    private ReactApplicationContext mContext;
    public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        mContext = reactContext;
    }

    @Override
    public String getName() {
        return MODULE_NAME;
    }

    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("SHORT", Toast.LENGTH_SHORT);
        constants.put("LONG", Toast.LENGTH_LONG);
        constants.put("NATIVE_MODULE_NAME", MODULE_NAME);
        constants.put(TestEvent, TestEvent);
        return constants;
    }

    @ReactMethod
    public void startActivity(){
        Intent intent = new Intent(mContext,SecondActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);
    }

    @ReactMethod
    public void showToast(String msg, int duration){
        Toast.makeText(mContext, msg, duration).show();
    }
}
           

React Native調用的方法需要使用@ReactMethod注解。

方法的傳回類型必須為void。

參數類型

原生Java資料類型和JS資料類型的映射關系:

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

詳情參考:ReadableMap和ReadableArray

導出常量

可以實作

getContants

方法導出需要給JavaScript使用的常量。

@Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("SHORT", Toast.LENGTH_SHORT);
        constants.put("LONG", Toast.LENGTH_LONG);
        constants.put("NATIVE_MODULE_NAME", MODULE_NAME);
        constants.put(TestEvent, TestEvent);
        return constants;
    }
           

注冊子產品

然後我還要注冊這個子產品,通過實作

ReactPackage

接口來實作:

public class MyReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new MyNativeModule(reactContext));
        return modules;
    }

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

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

添加子產品

Application

getPackages()

方法中添加子產品:

@Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(), new MyReactPackage()
            );
        }
           

或者是在

Activity

onCreate

中:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mReactRootView = new ReactRootView(this);
        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();
        mReactRootView.startReactApplication(mReactInstanceManager, "HelloWorld", null);
        setContentView(mReactRootView);
    }
           

封裝子產品

為了使JavaScript端通路起來更為友善,通常我們都會把原生子產品封裝成一個JavaScript子產品。在

index.android.js

檔案的同一目錄下面建立一個MyNativeModule.js。

'use strict';

import { NativeModules } from 'react-native'; 

// 這裡的MyNativeModule必須對應
// public String getName()中傳回的字元串

export default NativeModules.MyNativeModule;
           

調用子產品

現在,在别處的JavaScript代碼中可以這樣調用你的方法:

import MyNativeModule from './MyNativeModule'; 
class HelloWorld extends React.Component {
  startActivity(){
    console.log("MODULE NAME: ",MyNativeModule.NATIVE_MODULE_NAME);
    MyNativeModule.startActivity();
  }
  showToast(){
    console.log("MODULE NAME: ",MyNativeModule.NATIVE_MODULE_NAME);
    MyNativeModule.showToast("From JS", MyNativeModule.LONG);
  }
  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity onPress={this.startActivity}>  
          <Text style={styles.hello}>start Activity</Text>  
        </TouchableOpacity>
      </View>
    )
  }
}
           

其他特性

React Native的跨語言通路是異步進行的,是以想要給JavaScript傳回一個值的唯一辦法是使用回調函數或者發送事件。

回調函數

原生子產品還支援一種特殊的參數——回調函數。它提供了一個函數來把傳回值傳回給JS。

@ReactMethod
    public void testCallback(int para1, int para2, Callback resultCallback){
        int result = para1 + para2;
        resultCallback.invoke(result);
    }
           

可以在JS中調用:

testCallback(){
    MyNativeModule.testCallback(,,(result) => {
    console.log("result: ",result); //'result: ', 
    });
  }
           

原生子產品通常隻應調用回調函數一次。但是,它可以儲存callback并在将來調用。

callback并非在對應的原生函數傳回後立即被執行——注意跨語言通訊是異步的,這個執行過程會通過消息循環來進行。

發送事件到JavaScript

原生子產品可以在沒有被調用的情況下往JavaScript發送事件通知。最簡單的辦法就是通過

RCTDeviceEventEmitter

,這可以通過

ReactContext

來獲得對應的引用:

public void sendEvent(){
        WritableMap params = Arguments.createMap();
        params.putString("module", "MyNativeModule");
        mContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(TestEvent, params);
    }
           

在JS中這樣調用:

import { DeviceEventEmitter } from 'react-native';
......
  componentWillMount() {
    console.log("componentWillMount");
    //接收事件
    DeviceEventEmitter.addListener(MyNativeModule.TestEvent, info => {
      console.log(info);
    });
  }
           

Promise

如果對ES6的

Promise

對象不太熟悉的話,可以點這裡進行了解。

原生子產品還可以使用

promise

來簡化代碼,搭配ES2016(ES7)标準的

async/await

文法則效果更佳。如果橋接原生方法的最後一個參數是一個

Promise

,則對應的JS方法就會傳回一個

Promise

對象。

Promise

是異步程式設計的一種解決方案,比傳統的解決方案(回調函數和事件)更合理和更強大。

@ReactMethod
    public void testPromise(Boolean isResolve, Promise promise) {
        if(isResolve) {
            promise.resolve(isResolve.toString());
        }
        else {
            promise.reject(isResolve.toString());
        }
    }
           

在JS中調用:

testPromise(){
    MyNativeModule.testPromise(true)
    .then(result => {
      console.log("result1 is ", result);
    })
    .catch(result => {
      console.log("result2 is ", result);
    });
  }
           

這裡可以用

then

方法分别指定

Resolved

狀态和

Reject

狀态的回調函數。第一個回調函數是

Promise

對象的狀态變為

Resolved

時調用,第二個回調函數是

Promise

對象的狀态變為

Reject

時調用。其中,第二個函數是可選的,不一定要提供。

catch

方法用于指定發生錯誤時的回調函數。

結果:

'result1 is ', 'true'

從startActivityForResult中擷取結果

參考官方文檔

監聽生命周期

監聽activity的生命周期事件(比如

onResume

,

onPause

等等),子產品必須實作

LifecycleEventListener

,然後需要在構造函數中注冊一個監聽函數:

public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        mContext = reactContext;
        //添加監聽
        reactContext.addLifecycleEventListener(this);
    }
           

實作

LifecycleEventListener

的幾個接口:

@Override
    public void onHostResume() {
        Log.e(MODULE_NAME, "onHostResume");
    }

    @Override
    public void onHostPause() {
        Log.e(MODULE_NAME, "onHostPause");
    }

    @Override
    public void onHostDestroy() {
        Log.e(MODULE_NAME, "onHostDestroy");
    }
           

然後就可以監聽ReactNative應用的生命周期了。

繼續閱讀