編輯:關於Android編程
有時候我們的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 當成文件,依次查找下面文件,只要其中有一個存在,就返回該文件,不再繼續執行。
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接口,該接口中有三個抽象函數待實現,分別是createNativeModules,createJSModules,createViewManagers,這三個函數中,我們需要實現的最關鍵的函數就是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) { Listmodules=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 Mapconstants = 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 Mapconstants = 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】段並沒有進行實踐實踐,基本上原理一致,等有空了再研究研究。
最近學到用AsyncTask來處理有關網絡的操作。雖然代碼看上去不是很復雜,但仍有很多地方有疑惑。所以研讀了一下API文檔,在這裡把我學到的和練習的代碼展示出來。如有錯誤
首先,定義一個服務,在這個服務裡面,完成土司的創建(小火箭布局創建)煙的效果屬於動畫播放,而且要依托一個activity。(這個activity要定義為透明狀態)定義煙的
從今天起傻蛋打算做一個系列文章,對最新的Android4.0系統中的Launcher,也就是Android4.0原生的桌面程序,進行一個深入淺出的分析,從而引領Andro
本文概述: 滑動解鎖九宮格的分析: 1、需要自定義控件; 2、需要重寫事件onTouchEvent(); 3、需要給九個點設置序號和坐標,這裡用Map類就行;