React Native 與android原生交互
有時(shí)候App需要訪(fǎng)問(wèn)平臺(tái)API,但React Native可能還沒(méi)有相應(yīng)的模塊包裝;或者你需要復(fù)用一些Java代碼,而不是用Javascript重新實(shí)現(xiàn)一遍;又或者你需要實(shí)現(xiàn)某些高性能的、多線(xiàn)程的代碼,譬如圖片處理、數(shù)據(jù)庫(kù)、或者各種高級(jí)擴(kuò)展等等。
React Native設(shè)計(jì)為可以在其基礎(chǔ)上編寫(xiě)真正的原生代碼,并且可以訪(fǎng)問(wèn)平臺(tái)所有的能力。這是一個(gè)相對(duì)高級(jí)的特性,React Native并不認(rèn)為它應(yīng)當(dāng)在日常開(kāi)發(fā)的過(guò)程中經(jīng)常出現(xiàn),但具備這樣的能力是很重要的。如果React Native還不支持某個(gè)你需要的原生特性,你應(yīng)當(dāng)可以自己實(shí)現(xiàn)該特性的封裝。
Toast模塊
本向?qū)?huì)用Toast作為例子。假設(shè)我們希望可以從Javascript發(fā)起一個(gè)Toast消息(Android中的一種會(huì)在屏幕下方彈出、保持一段時(shí)間的消息通知)
我們首先來(lái)創(chuàng)建一個(gè)原生模塊。一個(gè)原生模塊是一個(gè)繼承了ReactContextBaseJavaModule
的Java類(lèi),它可以實(shí)現(xiàn)一些JavaScript所需的功能。我們這里的目標(biāo)是可以在JavaScript里寫(xiě)ToastExample.show('Awesome', ToastExample.SHORT);
,來(lái)調(diào)起一個(gè)Toast通知。
ReactContextBaseJavaModule要求派生類(lèi)實(shí)現(xiàn)getName方法。這個(gè)函數(shù)用于返回一個(gè)字符串名字,這個(gè)名字在JavaScript端標(biāo)記這個(gè)模塊。這里我們把這個(gè)模塊叫做ToastExample,這樣就可以在JavaScript中通過(guò)React.NativeModules.ToastExample訪(fǎng)問(wèn)到這個(gè)模塊。譯注:RN已經(jīng)內(nèi)置了一個(gè)名為T(mén)oastAndroid的模塊,所以在練習(xí)時(shí)請(qǐng)勿使用ToastAndroid的名字,否則運(yùn)行時(shí)會(huì)報(bào)錯(cuò)名字沖突!
@Override
public String getName() {
return "ToastExample";
}
要導(dǎo)出一個(gè)方法給JavaScript使用,Java方法需要使用注解@ReactMethod。方法的返回類(lèi)型必須為void。React Native的跨語(yǔ)言訪(fǎng)問(wèn)是異步進(jìn)行的,所以想要給JavaScript返回一個(gè)值的唯一辦法是使用回調(diào)函數(shù)或者發(fā)送事件(參見(jiàn)下文的描述)。
@ReactMethod
public void show(String message) {
Toast.makeText(getReactApplicationContext(), message, Toast.LENGTH_LONG).show();
}
參數(shù)類(lèi)型
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
注冊(cè)模塊
在Java這邊要做的最后一件事就是注冊(cè)這個(gè)模塊。我們需要在應(yīng)用的Package類(lèi)的createNativeModules方法中添加這個(gè)模塊。如果模塊沒(méi)有被注冊(cè),它也無(wú)法在JavaScript中被訪(fǎng)問(wèn)到。
package com.awesomeproject;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AnExampleReactPackage 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 ToastModule(reactContext));
return modules;
}
}
這個(gè)package需要在MainApplication.java文件的getPackages方法中提供。這個(gè)文件位于你的react-native應(yīng)用文件夾的android目錄中。具體路徑是: android/app/src/main/java/com/your-app-name/MainApplication.java.
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new AnExampleReactPackage()); // <-- 添加這一行,類(lèi)名替換成你的Package類(lèi)的名字.
}
完整代碼
- ReactContextBaseJavaModule 創(chuàng)建Module
package com.awesomeproject;
import android.widget.Toast;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.IllegalViewOperationException;
/**
* @author xiongxiang
* @time 2018/5/8.
* @e-mail 276186694@qq.com
*/
public class ToastModule extends ReactContextBaseJavaModule {
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}
// NativeModules.ToastExample.show("????????");
@Override
public String getName() {
return "ToastExample";
}
//提供給RN調(diào)用 原生土司
@ReactMethod
public void show(String message) {
Toast.makeText(getReactApplicationContext(), message, Toast.LENGTH_LONG).show();
}
//React Naitve 回傳數(shù)據(jù)
@ReactMethod
public void measureLayout(
int tag,
int ancestorTag,
Callback errorCallback,
Callback successCallback) {
sendEvent("noticeName",100);
try {
successCallback.invoke(1f, 2f, 3f, 4f);
} catch (IllegalViewOperationException e) {
errorCallback.invoke(e.getMessage());
}
}
//主動(dòng)發(fā)送數(shù)據(jù)
public void sendEvent(String eventName, int status) {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, status);
}
}
- ReactPackage 注冊(cè)模塊
package com.awesomeproject;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AnExampleReactPackage 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 ToastModule(reactContext));
return modules;
}
}
package com.awesomeproject;
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 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 AnExampleReactPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
代碼調(diào)用
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component} from 'react';
import {Button, DeviceEventEmitter, NativeModules, View} from 'react-native';
export default class NativeInteractionDemo extends Component {
//注冊(cè)監(jiān)聽(tīng)
componentDidMount() {
this.subscription = DeviceEventEmitter.addListener('noticeName', function (msg) {
alert("接收到安卓端主動(dòng)發(fā)送的數(shù)據(jù)" + msg)
});
}
componentWillUnmount() {
this.subscription.remove();
}
render() {
return <View>
<Button
onPress={() => {
this.measureLayout();
this.showToast();
}
}
title="登陸"
/>
</View>
}
//調(diào)用安卓原生代碼并回傳數(shù)據(jù)
measureLayout() {
NativeModules.ToastExample.measureLayout(100, 100, (msg) => {
console.log(msg);
}, (x, y, width, height) => {
console.log(x + ':' + y + ':' + width + ':' + height);
}
);
}
//調(diào)用原生安卓土司
showToast() {
NativeModules.ToastExample.show("土司")
}
}