Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android React Native使用原生模塊

Android React Native使用原生模塊

編輯:關於android開發

Android React Native使用原生模塊


有時候我們的App需要訪問平台API,並且React Native可能還沒有相應的模塊包裝;或者你需要復用一些Java代碼,而不是用Javascript重新實現一遍;又或者你需要實現某些高性能的、多線程的代碼,譬如圖片處理、數據庫、或者各種高級擴展等等。
而用React Native可以在它的基礎上編寫真正原生的代碼,並且可以訪問平台所有的能力。如果React Native還不支持某個你需要的原生特性,你應當可以自己實現該特性的封裝。

不過在開始編寫代碼使用原生模塊前,有一個知識點需要掌握,免得又坑進去了。

在使用React Native的時候,經常會看到這麼一段代碼

var React = require('react-native');

那麼require這個語句的作用到底是什麼呢,下面的流程提取自require() 源碼解讀

當遇到 require(X) 時,按下面的順序處理。
(1)如果 X 是內置模塊(比如 require(‘http’))
  a. 返回該模塊。
  b. 不再繼續執行。
(2)如果 X 以 “./” 或者 “/” 或者 “../” 開頭
a. 根據 X 所在的父模塊,確定 X 的絕對路徑。
b. 將 X 當成文件,依次查找下面文件,只要其中有一個存在,就返回該文件,不再繼續執行。

X X.js X.json X.node

c. 將 X 當成目錄,依次查找下面文件,只要其中有一個存在,就返回該文件,不再繼續執行。

X/package.json(main字段) X/index.js X/index.json X/index.node

(3)如果 X 不帶路徑
  a. 根據 X 所在的父模塊,確定 X 可能的安裝目錄。
  b. 依次在每個目錄中,將 X 當成文件名或目錄名加載。
(4) 拋出 “not found”

以上就是require語句的整個執行過程。那麼require(‘react-native’);請求的到底是什麼呢,其實就是node_modules\react-native\Libraries\react-native\react-native.js這個文件,該文件中導出了一些常用的組件,其源碼如下

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */
'use strict';

// Export React, plus some native additions.
//
// The use of Object.create/assign is to work around a Flow bug (#6560135).
// Once that is fixed, change this back to
//
//   var ReactNative = {...require('React'), /* additions */}
//
var ReactNative = Object.assign(Object.create(require('React')), {
  // Components
  ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
  DatePickerIOS: require('DatePickerIOS'),
  DrawerLayoutAndroid: require('DrawerLayoutAndroid'),
  Image: require('Image'),
  ListView: require('ListView'),
  MapView: require('MapView'),
  Modal: require('Modal'),
  Navigator: require('Navigator'),
  NavigatorIOS: require('NavigatorIOS'),
  PickerIOS: require('PickerIOS'),
  ProgressBarAndroid: require('ProgressBarAndroid'),
  ProgressViewIOS: require('ProgressViewIOS'),
  ScrollView: require('ScrollView'),
  SegmentedControlIOS: require('SegmentedControlIOS'),
  SliderIOS: require('SliderIOS'),
  SnapshotViewIOS: require('SnapshotViewIOS'),
  Switch: require('Switch'),
  SwitchAndroid: require('SwitchAndroid'),
  SwitchIOS: require('SwitchIOS'),
  TabBarIOS: require('TabBarIOS'),
  Text: require('Text'),
  TextInput: require('TextInput'),
  ToastAndroid: require('ToastAndroid'),
  ToolbarAndroid: require('ToolbarAndroid'),
  TouchableHighlight: require('TouchableHighlight'),
  TouchableNativeFeedback: require('TouchableNativeFeedback'),
  TouchableOpacity: require('TouchableOpacity'),
  TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
  View: require('View'),
  ViewPagerAndroid: require('ViewPagerAndroid'),
  WebView: require('WebView'),

  // APIs
  ActionSheetIOS: require('ActionSheetIOS'),
  AdSupportIOS: require('AdSupportIOS'),
  AlertIOS: require('AlertIOS'),
  Animated: require('Animated'),
  AppRegistry: require('AppRegistry'),
  AppStateIOS: require('AppStateIOS'),
  AsyncStorage: require('AsyncStorage'),
  BackAndroid: require('BackAndroid'),
  CameraRoll: require('CameraRoll'),
  Dimensions: require('Dimensions'),
  Easing: require('Easing'),
  ImagePickerIOS: require('ImagePickerIOS'),
  InteractionManager: require('InteractionManager'),
  LayoutAnimation: require('LayoutAnimation'),
  LinkingIOS: require('LinkingIOS'),
  NetInfo: require('NetInfo'),
  PanResponder: require('PanResponder'),
  PixelRatio: require('PixelRatio'),
  PushNotificationIOS: require('PushNotificationIOS'),
  Settings: require('Settings'),
  StatusBarIOS: require('StatusBarIOS'),
  StyleSheet: require('StyleSheet'),
  VibrationIOS: require('VibrationIOS'),

  // Plugins
  DeviceEventEmitter: require('RCTDeviceEventEmitter'),
  NativeAppEventEmitter: require('RCTNativeAppEventEmitter'),
  NativeModules: require('NativeModules'),
  Platform: require('Platform'),
  processColor: require('processColor'),
  requireNativeComponent: require('requireNativeComponent'),

  // Prop Types
  EdgeInsetsPropType: require('EdgeInsetsPropType'),
  PointPropType: require('PointPropType'),

  // See http://facebook.github.io/react/docs/addons.html
  addons: {
    LinkedStateMixin: require('LinkedStateMixin'),
    Perf: undefined,
    PureRenderMixin: require('ReactComponentWithPureRenderMixin'),
    TestModule: require('NativeModules').TestModule,
    TestUtils: undefined,
    batchedUpdates: require('ReactUpdates').batchedUpdates,
    cloneWithProps: require('cloneWithProps'),
    createFragment: require('ReactFragment').create,
    update: require('update'),
  },
});

if (__DEV__) {
  ReactNative.addons.Perf = require('ReactDefaultPerf');
  ReactNative.addons.TestUtils = require('ReactTestUtils');
}

module.exports = ReactNative;

了解了這個知識點後,我們來自定義一個模塊,去使用原生的模塊。假設有這麼一個需求,我們需要使用Andorid中的Log類,但是React Native並沒有為我們進行封裝,那麼我們自己動手實現一下吧。

我們需要繼承ReactContextBaseJavaModule這個抽象類,重寫getName()函數,用於返回一個字符串,這個字符串在JavaScript端標記這個模塊,暴露一個函數給javascript端,並且使用注解@ReactMethod進行標記,該函數的返回值必須為void,React Native的跨語言訪問是異步進行的,所以想要給JavaScript返回一個值的唯一辦法是使用回調函數或者發送事件。 我們需要實現一個類實現ReactPackage接口,該接口中有三個抽象函數待實現,分別是createNativeModulescreateJSModulescreateViewManagers,這三個函數中,我們需要實現的最關鍵的函數就是createNativeModules,在該函數中我們需要添加前一步創建的ReactContextBaseJavaModule子類 構建ReactInstanceManager的實例時,通過調用 addPackage()函數,將上一步實現的ReactPackage添加進去。

接下來我們來實現代碼。為了簡單方便,這裡只演示Log類中的d方法,即Log.d(String tag,String msg)

第一步,繼承ReactContextBaseJavaModule類,重寫getName()方法,因為是Log模塊,所以直接返回字符串Log,暴露一個d方法給javascript端,返回值為void,只用注解進行標記。最終的代碼如下。

public class LogModule extends ReactContextBaseJavaModule{
    private static final String MODULE_NAME="Log";

    public LogModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

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

    @ReactMethod
    public void d(String tag,String msg){
        Log.d(tag,msg);
    }
}

第二步,實現ReactPackage接口,在createNativeModules函數中添加我們的日志模塊。其余兩個函數返回空的List即可。

 createNativeModules(ReactApplicationContext reactContext) {
        List modules=new ArrayList<>();
        modules.add(new LogModule(reactContext));
        return modules;
    }

    @Override
    public List> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}
" data-snippet-id="ext.9f07b33a14eabf3cbd00c1fb0b5009f1" data-snippet-saved="false" data-csrftoken="4TcSqJMN-qwxVPXurbJ8DIBEzePIUnitFBt4" data-codota-status="done">public class AppReactPackage implements ReactPackage {
    @Override
    public List createNativeModules(ReactApplicationContext reactContext) {
        List modules=new ArrayList<>();
        modules.add(new LogModule(reactContext));
        return modules;
    }

    @Override
    public List> createJSModules() {
        return Collections.emptyList();
    }

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

第三步,添加AppReactPackage 到ReactInstanceManager的實例中去,在我們的MainActivity中可以看到這麼一段代碼

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

我們再build函數之前調用addPackage進行添加即可,最終代碼如下。

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

可以看到我們增加了一行.addPackage(new AppReactPackage())

這樣,在Java端我們要做的就做完了,接下來就是javascript端了,這時候編譯一下apk重新後運行,接下來我們來編寫javascript端。

如果你不嫌麻煩,每次都要從NativeModules來訪問我們的Log,那麼現在你可以直接在javascript中進行訪問了。就像這樣子。


var React = require('react-native');

var {
  NativeModules,
} = React;

var Log1= NativeModules.Log;

Log1.d("Log1","LOG");

但是,假如我再增加一個需求,就是當Log類在java層打印出一個日志的之後,希望在js端也輸出以下這個日志,那麼你會怎麼做呢,或許你會說,這個簡單,我再輸出一下js的日志就ok了。就像這樣子。


var React = require('react-native');

var {
  NativeModules,
} = React;

var Log1= NativeModules.Log;

Log1.d("Log1","LOG");
console("Log1","LOG");

沒錯是沒錯,就是看著蛋疼,不好維護不說,通樣的代碼你得寫多少遍。

這時候,我們就有必要封裝一下javascript端的代碼了,在index.android.js文件同目錄下新建一個log.js,輸入如下代碼。

'use strict';
var { NativeModules } = require('react-native');

var RCTLog= NativeModules.Log;

var Log = {
  d: function (
    tag: string,
    msg: string
  ): void {
    console.log(tag,msg);
    RCTLog.d(tag, msg);
  },
};

module.exports = Log;

代碼很簡單,我們通過NativeModules拿到我們的Log模塊在本地的實現,賦值給變量RCTLog,並且還聲明了一個Log變量,裡面有一個函數d,調用了RCTLog的d函數,並且在調用前輸出了javascript端的日志。最後使用module.exports=Log將Log變量導出

接下來就是引用log.js文件了,看過上面的require語句的解析,這對你應該不成問題了。

var Log=require('./log');
Log.d("TAG","111");

這還沒完,我們再提一個需求,就是我們希望這個Log模塊能夠提供一個常量,也就是TAG供我們使用,而這個常量定義在java層,以便以後我們使用的時候如果不想輸入TAG,可以直接使用這個默認的TAG,就像這樣子

var Log=require('./log');
Log.d(Log.TAG,"111");

那麼這個要怎麼實現呢,很顯然,我們需要在log.js中加入這個變量,就像這樣子

'use strict';


var { NativeModules } = require('react-native');

var RCTLog= NativeModules.Log;
var Log = {
  TAG: RCTLog.TAG,
  d: function (
    tag: string,
    msg: string
  ): void {
    console.log(tag,msg);
    RCTLog.d(tag, msg);
  },
};

module.exports = Log;

這樣雖然我們可以使用Log.TAG返回到這個值了,由於我們java層沒有定義TAG,所以這時候會報錯。因此我們需要在java層返回這個值,這又要怎麼做呢,別急。我們重新回過頭來看看我們實現的類LogModule,我們繼續在該類中定義兩個常量

private static final String TAG_KEY = "TAG";
private static final String TAG_VALUE = "LogModule";

什麼用呢,看常量名字就是到了,key和value,鍵值對,我們希望通過TAG_KEY拿到TAG_VALUE ,也就是我們日志要用到的TAG,怎麼實現呢。重寫getConstants函數即可。

 getConstants() {
        final Map constants = MapBuilder.newHashMap();
        constants.put(TAG_KEY, TAG_VALUE);
        return constants;
    }
" data-snippet-id="ext.0008550a573f1889c56ea9ffc26b61d8" data-snippet-saved="false" data-csrftoken="8CPAguR7-Ms31GPk3usjjppD11aB6bcq_vDo" data-codota-status="done">  @Override
    public Map getConstants() {
        final Map constants = MapBuilder.newHashMap();
        constants.put(TAG_KEY, TAG_VALUE);
        return constants;
    }

這時候重寫編譯運行一下,你就可以在javascript層通過Log.TAG就可以訪問到對應的值了,值為LogModule,而為什麼是Log.TAG而不是其他的值呢,因為我們constants中put進去的鍵就是TAG。

那麼這個有什麼作用呢,還記得android中我們的Toast的使用,顯示時間有兩個值嗎,一個是Toast.LENGTH_SHORT,另一個是Toast.LENGTH_LONG,我們希望在javascript層通用有這麼兩個常量可以使用,那麼就可以使用這種方法。我們可以看看系統的ToastAndroid的實現。

首先看java層

 getConstants() {
    final Map constants = MapBuilder.newHashMap();
    constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
    constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
    return constants;
  }

  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }
}
" data-snippet-id="ext.b6ffc6da30953242ea41fd1abab455a3" data-snippet-saved="false" data-csrftoken="Ws1sIYY7-0-swsiDcqXaHz8nq3x0PdVnFdw4" data-codota-status="done">public class ToastModule extends ReactContextBaseJavaModule {

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

  public ToastModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  public String getName() {
    return "ToastAndroid";
  }

  @Override
  public Map getConstants() {
    final Map constants = MapBuilder.newHashMap();
    constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
    constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
    return constants;
  }

  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }
}

看到沒有,在getConstants函數中暴露了兩個值給javascript層,SHORT對應java層的Toast.LENGTH_SHORT,LONG對應java層的Toast.LENGTH_LONG。接著看javascript層的代碼

'use strict';

var RCTToastAndroid = require('NativeModules').ToastAndroid;

var ToastAndroid = {

  SHORT: RCTToastAndroid.SHORT,
  LONG: RCTToastAndroid.LONG,

  show: function (
    message: string,
    duration: number
  ): void {
    RCTToastAndroid.show(message+"lizhangqu", duration);
  },

};

module.exports = ToastAndroid;

直接可以通過定義的變量SHORT或者LONG訪問到,最終我們的使用就是這樣子的。

var React = require('react-native');
var {
  ToastAndroid
} = React;
ToastAndroid.show("toast",ToastAndroid.SHORT);

這還沒完,這僅僅是沒有返回值的情況,假如有返回值情況又是怎麼樣呢,比如javascript調用java層的方法,但是java層需要將結果返回javascript,沒錯,答案就是回調!,最典型的一個場景就是javascript層調用java層的網絡請求方法,java層拿到網絡數據後需要將結果返回給javascript層。通用的,我們用最快的速度實現一下這個模塊。

繼承ReactContextBaseJavaModule,實現getName方法,返回值為Net,暴露一個getResult方法給javascript,並進行注解,注意這個函數有一個Callback類型的入參,返回結果就是通過這個進行回調

public class NetModule extends ReactContextBaseJavaModule {
    private static final String MODULE_NAME="Net";
    public NetModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

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

    @ReactMethod
    public void getResult(String url,final Callback callback){
        Log.e("TAG","正在請求數據");
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {

                    String result="這是結果";
                    Thread.sleep(1000);//模擬網絡請求
                    callback.invoke(true,result);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }).start();


    }
}

Callback的定義如下,它是一個接口,invoke函數的入參是個數是任意的。

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);

}

在前面的AppReactPackage類createNativeModules函數中注冊該模塊

modules.add(new NetModule(reactContext));

之後新建一個net.js文件,實現javascript層

'use strict';
var { NativeModules } = require('react-native');

var RCTNet= NativeModules.Net;

var Net = {

  getResult: function (
    url: string,
    callback:Function,
  ): void {
    RCTNet.getResult(url,callback);
  },
};

module.exports = Net;

進行使用

{
        console.log("callback",code,result);
     }
);" data-snippet-id="ext.107e880f3149ba75eadfdf8a2d0708f1" data-snippet-saved="false" data-csrftoken="j4Ei9vvD-5rn_ieDw5bD-7eRkjmp4yKJgSW8" data-codota-status="done">var Net=require('./net');
Net.getResult(
    "http://baidu.com",
     (code,result)=>{
        console.log("callback",code,result);
     }
);

如果不出意外,在java層將輸出日志

11-20 22:30:53.598 25323-1478/com.awesomeproject E/TAG: 正在請求數據

在javascript層,控制台將輸出

callback true 這是結果

以上就是回調的一個示例,你可以簡單想象成java層的網絡請求模型,主線程開啟子線程請求數據,子線程拿到數據後回調對應的方法使用handler通知主線程返回結果。

基本上,掌握了上面的內容,對原始模塊的使用也差不多了,本篇文章是基於官方文檔的最佳實踐Native Modules,但是該文檔坑太多,還需謹慎參考。該文檔中【發送事件給javascript】段並沒有進行實踐實踐,基本上原理一致,等有空了再研究研究。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved