在原生的開發(fā)中使用高德或者百度地圖實現(xiàn)定位等相關(guān)功能是每一個小伙伴必備的技能粘衬,那么現(xiàn)在我要在RN中實現(xiàn)定位的功能該怎么做呢项玛?一看到需要實現(xiàn)這樣的功能貌笨,第一反應(yīng)就是打開高德開放平臺看看是否有相關(guān)的SDK,可是翻來翻去也只看到了Android原生的實現(xiàn)方式稍计。但是躁绸,RN可以實現(xiàn)與原生的混合開發(fā),那么是不是可以嘗試下在RN頁面調(diào)用原生里面的定位功能呢臣嚣?
嘗試開始(內(nèi)心還是有點小激動...):
1.在Android工程中完成相關(guān)配置
此步驟跟原生開發(fā)沒有任何的區(qū)別净刮,可以參照高德開放平臺,此處不再介紹硅则。
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="you key" />
2.創(chuàng)建原生模塊
原生模塊是一個繼承于
ReactContextBaseJavaModule
的Java類
創(chuàng)建一個繼承ReactContextBaseJavaModule
的Java類淹父,并在此類中實現(xiàn)定位功能,先上代碼:
public class AMapLocationModule extends ReactContextBaseJavaModule {
private ReactApplicationContext mReactApplicationContext;
private AMapLocationClient mLocationClient = null;
// 定位回調(diào)監(jiān)聽器
private AMapLocationListener mAMapLocationListener = new AMapLocationListener() {
@Override
public void onLocationChanged(AMapLocation aMapLocation) {
// 實例化一個回調(diào)給RN端的map對象
WritableMap params = Arguments.createMap();
if (aMapLocation != null) {
if (aMapLocation.getErrorCode() == 0) {
// 定位成功
params.putString("address", aMapLocation.getAddress());
} else {
// 定位失敗
params.putBoolean("result", false);
}
}
// 發(fā)送給RN端
sendEvent("onLocationChanged", params);
}
};
private void sendEvent(String eventName, @Nullable WritableMap params) {
if (mReactApplicationContext != null) {
mReactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
public AMapLocationModule(ReactApplicationContext reactContext) {
super(reactContext);
// 在構(gòu)造函數(shù)中實例化定位功能的相關(guān)對象怎虫,并設(shè)置相關(guān)的定位配置暑认,跟原生實現(xiàn)定位功能一樣配置
mReactApplicationContext = reactContext;
mLocationClient = new AMapLocationClient(reactContext);
mLocationClient.setLocationListener(mAMapLocationListener);
AMapLocationClientOption clientOption = new AMapLocationClientOption();
clientOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Battery_Saving); // 低功耗模式, 只使用wifi
clientOption.setOnceLocation(true);
mLocationClient.setLocationOption(clientOption);
}
@Override
public String getName() {
return "AMapLocation"; //此Name在RN端標(biāo)記這個模塊,不能與已有的Name發(fā)生沖突
}
@ReactMethod // 可以被RN端調(diào)用的方法
public void destory() {
if (mLocationClient != null) {
mLocationClient.stopLocation(); // 停止定位
mLocationClient.onDestroy(); // 銷毀定位
}
}
@ReactMethod // 可以被RN端掉用的方法大审,開啟定位
public void startLocation() {
if (mLocationClient != null) {
mLocationClient.startLocation();
}
}
}
在上述的代碼中蘸际,必須注意以下幾個點:
-
getName()
方法
這是ReactContextBaseJavaModule
的派生類必須實現(xiàn)的方法。只要返回一個字符串名稱徒扶,在RN端才能找到這個模塊粮彤,需要防止命名沖突。還有一點姜骡,如果名稱中有RCT作為前綴导坟,會被自動忽略移除的,比如RCTAMapLocation
圈澈,那么在RN端通過React.NativeModules.AMapLocation
就能訪問這個模塊了惫周。 -
ReactMethod
注解
只有使用了此注解的public
void
方法才能被RN端訪問,RN端可以通過此方法的參數(shù)向原生端傳遞數(shù)據(jù)康栈。上面代碼中递递,RN端能夠訪問startLocation()
方法以及destory()
方法喷橙。還有一點需要注意的是,RN端訪問原生是異步進(jìn)行的漾狼。 -
sendEvent ()
方法
只要原生中的方法通過ReactMethod
注解重慢,RN端就能夠訪問得到,那么我們要在RN端啟動定位服務(wù)這個功能應(yīng)該已經(jīng)得到了實現(xiàn)逊躁,那么在RN端如何監(jiān)聽定位的結(jié)果呢似踱?如何將定位的結(jié)果傳遞給RN端呢?其實通過sendEvent()
方法就能夠得到實現(xiàn)稽煤。
在這肯定會有一個疑問核芽,為什么僅僅通過這么一個方法就能夠讓RN端監(jiān)聽并接收原生端的數(shù)據(jù)呢?
其實實現(xiàn)這個功能還是需要借助RCTDeviceEventEmitter
這個玩意的酵熙。通過代碼中的sendEvent()
方法可以看到轧简,通過reactContext
來獲得RCTDeviceEventEmitter
的引用,然后調(diào)用RCTDeviceEventEmitter
中的emit()
方法匾二。這樣在RN端就可以添加監(jiān)聽回調(diào)事件哮独。 -
WritableMap()
方法
顧名思義這是一個可寫的map類,通過它察藐,我們可以通過鍵值對的方式將需要發(fā)送給RN端的信息封裝起來皮璧,并通過emit ()
方法發(fā)送出去。其實還有一個ReadableMap用來存放RN傳遞給原生的數(shù)據(jù)分飞。
3. 注冊模塊
在第二個步驟中悴务,我們將可以被RN調(diào)用的原生模塊給創(chuàng)建好了,為了讓RN端可以識別此模塊譬猫,就少不了注冊這一個步驟讯檐。
注冊模塊相對簡單一點,創(chuàng)建一個實現(xiàn)ReactPackage
接口的Java類染服,實現(xiàn)相應(yīng)的方法就OK了别洪,下面看下代碼:
public class AMapLocationReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> list = new ArrayList<>();
list.add(new AMapLocationModule(reactContext));
return list;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
最后需要記住在Application里面ReactNativeHost
的getPackages()
方法里面添加上AMapLocationReactPackage
實例。
在AMapLocationReactPackage
這個類中柳刮,需要實現(xiàn)三個方法:
-
createNativeModules()
方法
此方法是向ReactPackage
中添加可以被RN端調(diào)用的原生模塊挖垛。在第二個步驟中創(chuàng)建AMapLocationModule
模塊就是為了讓RN端調(diào)用的,所以需要在此方法進(jìn)行注冊诚亚。 -
createJSModules ()
方法
此方法是添加可以讓原生調(diào)用的Js模塊,這邊沒有此需求午乓。所以就傳遞個空集合給ReactPackage
站宗。 -
createViewManagers()
方法
此方法是添加可以讓RN端調(diào)用的原生View組件,比如項目中原本就有一個寫的很nice的原生自定義View益愈,不想在RN端重新去寫梢灭,就可以通過實現(xiàn)一個ViewManager
派生類夷家,并通過createViewManagers
注冊,就能在RN進(jìn)行調(diào)用敏释。
到這Android原生端的代碼就告一段落了库快,來個中場休息吧(啦啦隊該上場...),接下來實現(xiàn)下RN端的代碼就能夠知道我這個嘗試是否能夠成功了钥顽。
4.在RN端獲取模塊
還記得在創(chuàng)建模塊的時候講過义屏,getName()
方法返回了一個字符串,那么在RN端通過此字符串就能獲得相對應(yīng)的模塊的引用了蜂大。所以此例子中闽铐,在RN端通過NativeModules.AMapLocation
就能獲取剛剛創(chuàng)建的模塊實例,這意味著現(xiàn)在已經(jīng)可以調(diào)用模塊中通過ReactMethod
注解的方法了奶浦,為了更好的使用兄墅,我做了個簡單的封裝。
import {NativeModules, DeviceEventEmitter} from 'react-native';
const location = NativeModules.AMapLocation;
export default class AMapLocation {
// 開啟定位
static startLocation() {
location.startLocation();
}
// 關(guān)閉定位
static destory() {
location.destory();
}
// 注冊定位回調(diào)監(jiān)聽
static addEventListener(handler) {
const listener = DeviceEventEmitter.addListener(
'onLocationChanged', // 與模塊中的eventName保持一致
handler // 回調(diào)函數(shù)澳叉,在此函數(shù)接收定位信息
);
return listener;
}
}
-
addEventListener()
方法
這是一個注冊回調(diào)監(jiān)聽方法隙咸,也就是為了接收從原生端傳遞過來的定位信息。在原生端通過RCTDeviceEventEmitter
的emit()
方法來發(fā)送事件的成洗,RN端同樣是通過DeviceEventEmitter (模塊名的RCT前綴會被自動移除)
來添加監(jiān)聽事件五督。需要傳遞一個與原生相同的eventName字符串以及一個回調(diào)方法。
5.在RN端開啟定位服務(wù)并接收定位數(shù)據(jù)
接下來就在RN端開啟個定位服務(wù)泌枪,看看能不能成功開啟并且接受到當(dāng)前的地址信息概荷。
import AMapLocation from '../modules/AMapLocation';
componentDidMount() {
this.listener = AMapLocation.addEventListener(this._onLocationChanged); // 注冊監(jiān)聽
AMapLocation.startLocation(); // 開啟定位
}
componentWillUnmount() {
AMapLocation.destory();
this.listener.clear();
}
_onLocationChanged = (data)=> {
if (data && data.result) {
console.log(data.address);
}
};
很常規(guī)的做法,在componentDidMount ()
方法中注冊監(jiān)聽并開啟定位碌燕;在componentWillUnmount()
方法中關(guān)閉定位误证,清除監(jiān)聽事件;
_onLocationChanged()
方法中接收數(shù)據(jù)修壕。
6.驗證結(jié)果(雞凍ing...)
歷經(jīng)千辛萬苦終于到了這一步了愈捅,那么就來看下log吧。
地址信息從logo中顯示出來了慈鸠,也就意味著這次嘗試成功了蓝谨!
6.總結(jié)
通過此次嘗試差不多也就學(xué)會了RN與原生的一種混合開發(fā)方式,那就來讓我們回憶一下混合開發(fā)大致的步驟吧青团。
- 在Android端繼承
ReactContextBaseJavaModule
創(chuàng)建一個原生模塊譬巫,并在原生模塊中通過ReactMethod
注解暴露出可以讓RN端調(diào)用的方法,并在getName()
方法中返回一個模塊名稱字符串督笆; - 在模塊中通過
RCTDeviceEventEmitter
的emit()
方法實現(xiàn)向RN端發(fā)送事件芦昔; - 通過實現(xiàn)
ReactPackage
接口注冊模塊,需要區(qū)別三個createXXX()
方法對應(yīng)的使用方式娃肿,同時不要忘記在Application中添加package實例咕缎; - 在RN端可以利用對應(yīng)的模塊名稱調(diào)用暴露的方法珠十,同時可以通過
DeviceEventEmitter
注冊回調(diào)的監(jiān)聽事件; - 需要記住RN中調(diào)用原生的方法都是異步進(jìn)行的凭豪;
- 混合開發(fā)中還可以通過
Promises
來實現(xiàn)回調(diào)函數(shù)焙蹭。
通過原生向RN發(fā)送事件的方法實現(xiàn)了高德地圖的定位功能,這是一次成功的嘗試嫂伞,但這才邁出了第一步孔厉。。末早。