尊重版權,未經受權不得轉載
本文出自:賈鵬輝的技術博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503)javascript告訴你們一個好消息。爲你們精心準備的React Native視頻教程公佈了,你們現可以看視頻學React Native了。html
一直想寫一下我在React Native原生模塊封裝方面的一些經驗和心得。來分享給你們,但實在抽不開身。今天看了一下日曆發現當即就春節了。因此就趕在春節以前將這篇博文寫好並公佈(事實上是兩篇:要看iOS篇的點這裏
《React Native iOS原生模塊開發》)。java
我平時在用React Native開發App時會用到一些原生模塊,比方:在作社會化分享、第三方登陸、掃描、通訊錄。日曆等等。想必你們也是同樣。react
關於在React Native中使用原生模塊。在這裏引用React Native官方文檔的一段話:android
有時候App需要訪問平臺API,但在React Native可能尚未對應的模塊。或者你需要複用一些Java代碼,而不想用JavaScript再又一次實現一遍;又或者你需要實現某些高性能的、多線程的代碼。譬如圖片處理、數據庫、或者一些高級擴展等等。
咱們把React Native設計爲可以在其基礎上編寫真正的原生代碼。而且可以訪問平臺所有的能力。這是一個相對高級的特性,咱們並不指望它應當在平常開發的過程當中經常出現,但它確實不可缺乏,而且是存在的。git假設React Native還不支持某個你需要的原生特性,你應當可以本身實現對該特性的封裝。github
上面是我翻譯React Native官方文檔上的一段話,你們假設想看英文版可以點這裏:Native Modules
在這篇文章中呢,我會帶着你們來開發一個從相冊獲取照片並裁切照片的項目,並結合這個項目來詳細解說一下怎樣一步步開發React Native Android原生模塊的。數據庫
提示:告訴你們一個好消息,React Native視頻教程公佈了。你們現可以看視頻學React Native了。react-native
首先。讓咱們先看一下,開發Android原生模塊的主要流程。promise
在這裏我把構建React Native Android原生模塊的流程歸納爲如下三大步:
接下來讓咱們一塊兒來看一下每一步所需要作的一些事情。
在這裏咱們就以開發一個從相冊獲取照片並裁切照片的實戰項目,來詳細解說一下怎樣開發React Native Android原生模塊的。
這一步咱們需要用到AndroidStudio。
首先咱們用AndroidStudio打開React Native項目根文件夾下的android文件夾。如圖:
用AndroidStudio第一次打開這個Android項目的時候,AndroidStudio會下載一些此項目所需要的依賴,比方項目所依賴的Gradle版本號等。這些依賴下載完畢以後呢。AndroidStudio會對項目進行初始化,初始化成功以後在AndroidStudio的工具欄中可以看到一個名爲「app」的一個可執行的模塊。如圖:
接下來呢,咱們就可以編寫Java代碼了。
首先呢。咱們先來實現一個Crop接口:
public interface Crop {
/** * 選擇並裁切照片 * @param outputX * @param outputY * @param promise */
void selectWithCrop(int outputX,int outputY,Promise promise);
}
咱們建立一個CropImpl.java。在這個類中呢。咱們實現了從相冊選擇照片以及裁切照片的功能:
/** * React Native Android原生模塊開發 * Author: CrazyCodeBoy * 技術博文:http://www.devio.org * GitHub:https://github.com/crazycodeboy * Email:crazycodeboy@gmail.com */
public class CropImpl implements ActivityEventListener,Crop{
private final int RC_PICK=50081;
private final int RC_CROP=50082;
private final String CODE_ERROR_PICK="用戶取消";
private final String CODE_ERROR_CROP="裁切失敗";
private Promise pickPromise;
private Uri outPutUri;
private int aspectX;
private int aspectY;
private Activity activity;
public static CropImpl of(Activity activity){
return new CropImpl(activity);
}
private CropImpl(Activity activity) {
this.activity = activity;
}
public void updateActivity(Activity activity){
this.activity=activity;
}
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if(requestCode==RC_PICK){
if (resultCode == Activity.RESULT_OK && data != null) {//從相冊選擇照片並裁剪
outPutUri= Uri.fromFile(Utils.getPhotoCacheDir(System.currentTimeMillis()+".jpg"));
onCrop(data.getData(),outPutUri);
} else {
pickPromise.reject(CODE_ERROR_PICK,"沒有獲取到結果");
}
}else if(requestCode==RC_CROP){
if (resultCode == Activity.RESULT_OK) {
pickPromise.resolve(outPutUri.getPath());
}else {
pickPromise.reject(CODE_ERROR_CROP,"裁剪失敗");
}
}
}
//...省略部分代碼
private void onCrop(Uri targetUri,Uri outputUri){
this.activity.startActivityForResult(IntentUtils.getCropIntentWith(targetUri,outputUri,aspectX,aspectY),RC_CROP);
}
}
關於Android拍照、從相冊或文件裏選擇照片。裁剪以及壓縮照片等更高級的功能實現,你們可以參考開源項目TakePhoto
實現了從相冊選擇照片以及裁切照片的功能以後呢,接下來咱們需要將public void selectWithCrop(int aspectX, int aspectY, Promise promise)
暴露給React Native,以供js調用。
接下了咱們就向React Native暴露接口以及作一些數據交互部分的操做。爲了暴露接口以及進行數據交互咱們需要藉助React Native的ReactContextBaseJavaModule
類,在這裏咱們建立一個ImageCropModule.java
類讓它繼承自ReactContextBaseJavaModule
。
/** * React Native Android原生模塊開發 * Author: CrazyCodeBoy * 技術博文:http://www.devio.org * GitHub:https://github.com/crazycodeboy * Email:crazycodeboy@gmail.com */
public class ImageCropModule extends ReactContextBaseJavaModule implements Crop{
private CropImpl cropImpl;
public ImageCropModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "ImageCrop";
}
//...省略部分代碼
@Override @ReactMethod
public void selectWithCrop(int aspectX, int aspectY, Promise promise) {
getCrop().selectWithCrop(aspectX,aspectY,promise);
}
private CropImpl getCrop(){
if(cropImpl==null){
cropImpl=CropImpl.of(getCurrentActivity());
getReactApplicationContext().addActivityEventListener(cropImpl);
}else {
cropImpl.updateActivity(getCurrentActivity());
}
return cropImpl;
}
}
在ImageCropModule.java
類中。咱們重寫了public String getName()
方法。來暴露咱們原生模塊的名字。
並在public void selectWithCrop(int aspectX, int aspectY, Promise promise)
上加入了@ReactMethod
註解來暴露接口。這樣以來咱們就可以在js文件裏經過ImageCrop.selectWithCrop
來調用咱們所暴露給React Native的接口了。
接下來呢,咱們來看一下原生模塊和js模塊是怎樣進行數據交互的?
在咱們要實現的從相冊選擇照片並裁切的項目中。js模塊需要告訴原生模塊照片裁切的比例,等照片裁切完畢後,原生模塊需要對js模塊進行回調來告訴js模塊照片裁切的結果,在這裏咱們需要將照片裁切後生成的圖片的路徑告訴js模塊。
提示:在所有的狀況下js和原生模塊以前進行通訊都是在異步的狀況下進行的。
接下來咱們就來看下一JS是怎樣向原生模塊傳遞數據的?
JS向原生模塊傳遞數據:
爲了實現JS向原生模塊進行傳遞數據。咱們可以直接經過調用原生模塊所暴露出來的接口。來爲接口方法設置參數。
這樣以來咱們就可以將數據經過接口參數傳遞到原生模塊中,如:
/** * 選擇並裁切照片 * @param outputX * @param outputY * @param promise */
void selectWithCrop(int outputX,int outputY,Promise promise);
經過上述代碼咱們可以看出,js模塊可以經過selectWithCrop
方法來告訴原生模塊要裁切照片的寬高比,最後一個參數是一個Promise
,照片裁剪完畢以後呢。原生模塊可以經過Promise
來對js模塊進行回調,來告訴裁切結果。
既然是js和Java進行數據傳遞。那麼他們二者之間是怎樣進行類型轉換的呢:
在上述樣例中咱們經過@ReactMethod
註解來暴露接口,被 @ReactMethod
標註的方法支持例如如下幾種數據類型。
被
@ReactMethod
標註的方法支持例如如下幾種數據類型的參數:
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
原生模塊向JS傳遞數據:
原生模塊向JS傳遞數據咱們可以藉助Callbacks與Promises。接下來就講一下怎樣經過他們兩個進行數據傳遞的。
Callbacks
原生模塊支持一個特殊類型的參數-Callbacks,咱們可以經過它來對js進行回調,以告訴js調用原生模塊方法的結果。
將咱們selectWithCrop
的參數改成Callbacks以後:
@Override
public void selectWithCrop(int aspectX, int aspectY, Callback errorCallback,Callback successCallback) {
this.errorCallback=errorCallback;
this.successCallback=successCallback;
this.aspectX=aspectX;
this.aspectY=aspectY;
this.activity.startActivityForResult(IntentUtils.getPickIntentWithGallery(),RC_PICK);
}
在回調的時候,咱們就可以這樣寫:
if (resultCode == Activity.RESULT_OK) {
successCallback.invoke(outPutUri.getPath());
}else {
errorCallback.invoke(CODE_ERROR_CROP,"裁剪失敗");
}
在上述代碼中咱們經過Callback
的invoke
方法來對js進行對調,如下咱們來看一下Callback.java
的源代碼:
public interface Callback {
/** * Schedule javascript function execution represented by this {@link Callback} instance * * @param args arguments passed to javascript callback method via bridge */
public void invoke(Object... args);
}
從Callback.java
的源代碼中咱們可以看出,它是一個僅僅有一個public void invoke(Object... args)
方法的接口,invoke
方法接受一個可變參數,因此咱們可以向js傳遞多個參數。
接下來呢,咱們在js中就可以這樣來調用咱們所暴露的接口:
ImageCrop.selectWithCrop(parseInt(x),parseInt(y),(error)=>{
console.log(error);
},(result)=>{
console.log(result);
})
提示:另外要告訴你們的是。不論是
Callback
仍是我接下來要講的Promise
,咱們僅僅能調用一次,也就是」you call me once,I can only call you once」。
Promises
除了上文所講的Callback
以外React Native還爲了咱們提供了第二種回調js的方式叫-Promise。
假設咱們暴露的接口方法的最後一個參數是Promise
時。如:
@Override @ReactMethod
public void selectWithCrop(int aspectX, int aspectY, Promise promise) {
getCrop().selectWithCrop(aspectX,aspectY,promise);
}
那麼當js調用它的時候將會返回一個Promsie:
ImageCrop.selectWithCrop(parseInt(x),parseInt(y)).then(result=> {
this.setState({
result: result
})
}).catch(e=> {
this.setState({
result: e
})
});
另外。咱們也可以使用ES2016的 async/await
語法,來簡化咱們的代碼:
async onSelectCrop() {
var result=await ImageCrop.selectWithCrop(parseInt(x),parseInt(y));
}
這樣以來代碼就簡化了很是多。
因爲。基於回調的數據傳遞不論是Callback仍是Promise。都僅僅能調用一次。但。在實際項目開發中咱們有時會向js屢次傳遞數據,比方二維碼掃描原生模塊,針對這樣的屢次數據傳遞的狀況咱們該怎麼實現呢?
接下來我就爲你們介紹一種原生模塊可以向js屢次傳遞數據的方式:
在原生模塊中咱們可以向js發送屢次事件,即便原生模塊沒有被直接的調用。
爲了向js傳遞事件咱們需要用到RCTDeviceEventEmitter,它是原生模塊和js之間的一個事件發射器。
private void sendEvent(ReactContext reactContext,String eventName, @Nullable WritableMap params) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
在上述方法中咱們可以向js模塊發送隨意次數的事件,當中eventName
是咱們要發送事件的事件名,params
是這次事件所攜帶的數據。接下來呢咱們就可以在js模塊中監聽這個事件了:
componentDidMount() {
//註冊掃描監聽
DeviceEventEmitter.addListener('onScanningResult',this.onScanningResult);
}
onScanningResult = (e)=> {
this.setState({
scanningResult: e.result,
});
}
另外。不要忘記在組件被卸載的時候移除監聽:
componentWillUnmount(){
DeviceEventEmitter.removeListener('onScanningResult',this.onScanningResult);//移除掃描監聽
}
到現在呢,暴露接口以及數據傳遞已經進行完了,接下來呢,咱們就需要註冊與導出React Native原生模塊了。
爲了向React Native註冊咱們剛纔建立的原生模塊,咱們需要實現ReactPackage
,ReactPackage
主要爲註冊原生模塊所存在,僅僅有已經向React Native註冊的模塊才幹在js模塊使用。
/** * React Native Android原生模塊開發 * Author: CrazyCodeBoy * 技術博文:http://www.devio.org * GitHub:https://github.com/crazycodeboy * Email:crazycodeboy@gmail.com */
public class ImageCropReactPackage implements ReactPackage {
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@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 ImageCropModule(reactContext));
return modules;
}
}
在上述代碼中,咱們實現一個ReactPackage
,接下來呢,咱們還需要在android/app/src/main/java/com/your-app-name/MainApplication.java
中註冊咱們的ImageCropReactPackage
:
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ImageCropReactPackage()//在這裏將咱們剛纔建立的ImageCropReactPackage加入進來
);
}
原生模塊註冊完畢以後呢。咱們接下來就需要爲咱們的原生模塊導出一個js模塊,以方便咱們使用它。
咱們建立一個ImageCrop.js文件。而後加入例如如下代碼:
import { NativeModules } from 'react-native';
export default NativeModules.ImageCrop;
這樣以來呢,咱們就可以在其它地方經過如下方式來使用咱們所導出的這個模塊了:
import ImageCrop from './ImageCrop' //導入ImageCrop.js
//...省略部分代碼
onSelectCrop() {
let x=this.aspectX?this.aspectX:ASPECT_X;
let y=this.aspectY?this.aspectY:ASPECT_Y; ImageCrop.selectWithCrop(parseInt(x),parseInt(y)).then(result=> { this.setState({ result: result }) }).catch(e=> { this.setState({ result: e }) }); } //...省略部分代碼 }
現在呢,咱們這個原生模塊就開發好了,而且咱們也使用了咱們的這個原生模塊。
關於Android拍照、從相冊或文件裏選擇照片,裁剪以及壓縮照片等更高級的功能實現,你們也可以參考開源項目TakePhoto
在React Native中,JS模塊執行在一個獨立的線程中。在咱們爲React Native開發原生模塊的時候,假設有耗時的操做比方:文件讀寫、網絡操做等,咱們需要新開闢一個線程。否則的話,這些耗時的操做會堵塞JS線程。
在Android中咱們可以藉助AsyncTask來實現多線程。另外。假設原生模塊中需要更新UI,咱們需要獲取主線程,而後在主線程中更新UI,如:
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (!activity.isFinishing()) {
mSplashDialog = new Dialog(activity,fullScreen?R.style.SplashScreen_Fullscreen:R.style.SplashScreen_SplashTheme); mSplashDialog.setContentView(R.layout.launch_screen); mSplashDialog.setCancelable(false); if (!mSplashDialog.isShowing()) { mSplashDialog.show(); } } } });
告訴你們一個好消息,爲你們精心準備的React Native視頻教程公佈了,你們現可以看視頻學React Native了。
假設,你們在開發原生模塊中遇到問題可以在本文的下方進行留言,我看到了後會及時回覆的哦。
另外也可以關注個人新浪微博
,或者關注個人Github
來獲取不少其它有關React Native開發的技術乾貨
。
推薦學習:視頻教程《React Native開發跨平臺GitHub App》