前言
RN主要用於編寫UI,原生API的調用、網絡通信等等複雜的邏輯則大多是通過原生代碼去實現的。那麼RN和原生代碼是怎麼交互的呢?
官網在此
以下以安卓平臺原生爲例,我們按照官網的教程來實現這樣一個交互demo,藉此來了解互相調用的用法。不過我們的起點是一個剛剛創建的RN應用(通過
npx react-native init
來創建的),可能有些操作已經存在,不過這些步驟不會因此而被忽略。
思路
開始之前先來理清楚一下思路。
首先是RN調用原生接口:
- 第一步,很顯然的我們要在android裏實現一個方法,實現具體的邏輯供RN調用。
- 第二步,我們需要通過RN框架把這個方法“傳遞”給RN應用。
- 第三步,RN應用調用“傳遞”過來的方法,實現RN對原生的調用。
而原生對RN的調用則剛好相反。
那開始試試吧。
定義原生方法
假設我們要通過原生邏輯來顯示一個Toast(這種方式邏輯簡單、表現明顯),我們先定義一個ToastUtil類,並且爲其增加一個顯示Toast的方法show()。
public class ToastUtil {
public void show(Context context,String message){
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
}
將原生方法通過RN框架“傳給”RN應用
現在需要把我們的工具類交給RN並由其傳遞給RN應用了。分析官方示例,將工具類交給RN的路徑抽離如下圖所示:
- RN框架提供了一個ReactApplication接口,正如它名字想表達的一樣,這是一個要由android的Application實現的接口。
public interface ReactApplication {
/** Get the default {@link ReactNativeHost} for this app. */
ReactNativeHost getReactNativeHost();
}
其中要求實現方法getReactNativeHost。對於已經接入RN的android示例代碼來說,這些操作是現成的。
public class MainApplication extends Application implements ReactApplication {
@Override
public ReactNativeHost getReactNativeHost() {
return reactNativeHost;
}}
- 所以需要創建一個ReactNativeHost對象給ReactApplication 。這些在繼承RN框架的時候也是現成的。不過我們發現RN框架本身似乎還有一些其他的Package要返回,那麼我們自己的只要add在後面就可以了。
ReactNativeHost reactNativeHost =new ReactNativeHost(this){
@Override
public boolean getUseDeveloperSupport() {
return false;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
};
- ReactPackage是接口,我們想要返回自己的ReactPackage就需要實現下這個接口,需要實現的接口裏就包括了:createNativeModules
public interface ReactPackage {
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
@NonNull
List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext);
/** @return a list of view managers that should be registered with {@link UIManagerModule} */
@NonNull
List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext);
}
- 所以就需要一個ReactContextBaseJavaModule交給我們剛剛實現的ReactPackage了。到這終於到了我們要“傳遞”的工具類出場了。沒錯,我們的工具類都要定義成“ReactContextBaseJavaModule”以方便RN框架去“傳遞”。
邏輯基本就是這樣了,那麼現在開始動手實現吧。
首先,就是改造我們剛纔的工具類,因爲要RN認識的特殊類才能被“傳輸”,將剛纔的ToastUtil繼承下ReactContextBaseJavaModule。多了個需要實現的類:getName
public class ToastUtil extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactApplicationContext;
public ToastUtil(@NonNull ReactApplicationContext reactContext) {
super(reactContext);
reactApplicationContext=reactContext;
}
@ReactMethod
public void show(String message){
Toast.makeText(reactApplicationContext, message, Toast.LENGTH_SHORT).show();
}
@NonNull
@Override
public String getName() {
return "ToastUtil";
}
}
getName返回一個自定義的字符串,是當前這個工具類的標識符信息。RN應用就是通過這個字符串來找到它想要的工具類的。仔細觀察會發現show方法被註解了@ReactMethod,沒錯,希望在RN中調用的方法都要用這個註解來聲明下。這個時候突然發現了一個問題:RN中有Context這個上下文對象嗎?再看官網教程,果然直接用了一個傳進來的ReactApplicationContext,好吧,還是要傳進來並保存下這個ReactApplicationContext留作後用。(你可能會想直接從別的地方拿過來的Context不也行麼,結果是不傳ReactApplicationContext進來工具類無法正常使用,不僅是你,你實現的ReactContextBaseJavaModule也需要這麼一個Contex啊,所以就成了上面這個樣子了)
其次,需要實現一個ReactPackage,在它的createNativeModules方法中返回我們的工具類ToastUtil。
public class ToastPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ToastUtil(reactContext));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
然後需要把我們自己的ReactPackage加到實現了ReactApplication的ReactNativeHost對象的實現方法getPackages裏。
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new ToastPackage());
return packages;
}
剩下的實現ReactAplication並返回ReactNativeHost的操作一早就已經默認實現了。現在終於把自己定義的工具類交給RN了。
在RN中調用原生方法
我們已經按照RN的要求定義了自己的工具類,並把它交給RN了,RN會幫助我們把這個類“傳輸”到RN應用中,接下來我們就要嘗試在RN應用中來調用了。
import React from 'react';
import {NativeModules, Button, View} from 'react-native';
export default class App extends React.Component {
_onPress() {
NativeModules.ToastUtil.show('哈哈');
}
render(): React$Node {
return (
<View>
<Button onPress={this._onPress} title={'顯示Toast'} />
</View>
);
}
}
從原生傳過來的工具類都在NativeModules裏,所以我們需要先引入NativeModules。那麼怎麼定位到具體的我們的工具類呢?還記得實現工具類裏繼承了一個getName的方法麼,之前說它是那個工具類的標識符,現在派上用場了。之前返回的字符串是“ToastUtil”,那麼我們的工具類就是: NativeModules.ToastUtil了,可以直接在RN應用中通過
NativeModules.ToastUtil.show('哈哈');
來調到原生的ToastUtil.show(‘哈哈’)方法了。點擊按鈕就能看到熟悉的Toast了。
ok,到這入門就結束了。
總結
對於一個init出來的RN項目,快速實現RN對原生方法調用的操作如下:
- 創建一個類A繼承ReactContextBaseJavaModule,實現的getName返回字符串如“X”,可以使用構造方法獲取context,編寫自己的要提供給RN的方法y()並使用@ReactMethod註解自定義的方法。
- 新建一個類B實現ReactPackage接口,並把上面創建的工具類A在createNativeModules裏返回。
- 在已經實現了ReactApplication的Application裏,找到getReactNativeHost中返回的ReactNativeHost,並在其實現類的getPackages方法中,在返回的List上,增加上面創建的ReactPackage實現類B。
- 在RN中import NativeModules from ‘react-native’,並通過NativeModules.X.y()的方式調用原生方法。