React-Native桥接Android原生百度定位地图SDK-MapView

最近有个React-native项目需要添加定位功能,看了网上很多相关得帖子,基本都是桥接的3方的SDK ,而且有些功能不能定制,考虑到以后百度SDK版本更新的问题,以及方便后期维护,决定自己桥接一下百度原生定位功能


1.React-Native桥接Android原生UI 组件和原生模块

  • 1.1桥接原生UI组件

具体步骤

  • 创建一个继承React-Native ViewGroupManager 的 java 类
//MapViewManager.java
public class MapViewManager extends ViewGroupManager<MapView> {

}
  • 重写 getName 方法,返回一个字符串,对应JavaScript 中requireNativeComponent 的对象
//MapViewManager.java
@Override
public String getName() {
    return "MSMapView";
}
  • 重写 createViewInstance 方法,返回自定义的原生组件
//MapViewManager.java
@Override
protected MapView createViewInstance(ThemedReactContext themedReactContext) {
   //创建自定义的MapView
}
  • 导出视图的属性设置器:通过@ReactProp 注解
//MapViewManager.java
@ReactProp(name = "posLatitude")
public void setPosLatitude(MapView mapView, double posLatitude) {
    mStopLatitude = posLatitude;
}
  • 对应JavaScript 模块调用
//MapView.js
MapView.propTypes = {
  posLatitude: PropTypes.number.isRequired
  }
  • 把视图管理类注册到应用程序包的creatViewManagers里
//MapViewPackage.java
public class MapViewPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new GeolocationModule(reactContext));
        return modules;
    }
  • 在JavaScript 中使用
//MapView.js
import React from 'react'

import { requireNativeComponent } from 'react-native'
import PropTypes from 'prop-types'

const MSMapView = requireNativeComponent('MSMapView', null)
export default function MapView(props) {
  return <MSMapView {...props} />
}

MapView.propTypes = {
  posLatitude: PropTypes.number.isRequired,
  posLongitude: PropTypes.number.isRequired,
  posName: PropTypes.string.isRequired,
  posAddress: PropTypes.string.isRequired
}

具体参考官方文档原生UI组件

  • 1.2 桥接原生模块

  • 首先来创建一个原生模块(GeolocationModule) 。一个原生模块是一个继承了ReactContextBaseJavaModule的Java类,它可以实现一些JavaScript所需的功能
//GeolocationModule.java 
public class GeolocationModule extends ReactContextBaseJavaModule {
	public GeolocationModule(ReactApplicationContext reactContext) {
    		super(reactContext);
	}
}
  • 实现getName方法。这个函数用于返回一个字符串名字,这个名字在JavaScript端标记这个模块
//GeolocationModule.java 
public String getName() {
    return "MSLocationManager";
}
  • 导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod。方法的返回类型必须为void
//GeolocationModule.java 
@ReactMethod
public void startLocationWithCallback(Callback locationCallback) {
    //添加回调
    Logger.t(TAG).i("startLocationWithCallback");
    this.locationCallback = locationCallback;
    initLocationClient();
}
  • 注册模块,在应用的Package类的createNativeModules方法中添加这个模块
//MapViewPackage .java
public class MapViewPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new GeolocationModule(reactContext));
        return modules;
    }
}
  • 对应JavaScript 模块调用
//JS
import {  NativeModules } from 'react-native'
const LocationManager = NativeModules.MSLocationManager

LocationManager.startLocationWithCallback((error, location) => {
  if (error) {
    logger.error(`get location error:${JSON.stringify(error)}`)
    ...
  } else {
   ...
  }
})

2.Android 百度地图SDK配置

注意应用混淆

集成地图SDK的应用,在打包混淆的时候,需要注意与地图SDK相关的方法不可被混淆。混淆方法如下:

-keep class com.baidu.** {*;}
-keep class vi.com.** {*;}    
-dontwarn com.baidu.**
保证百度类不能被混淆,否则会出现网络不可用等运行时异常

  • Android manifest.xml 配置如下

<!-- 获取运营商信息,用于支持提供运营商信息相关的接口 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <!-- 访问网络,网络定位需要上网 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 用于读取手机当前的状态 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 这个权限用于进行网络定位 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 这个权限用于访问GPS定位 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 写入扩展存储,向扩展卡写入数据,用于写入离线定位数据 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

       <service
            android:name="com.baidu.location.f"
            android:enabled="true"
            android:process=":remote" />
        <meta-data
            android:name="com.baidu.lbsapi.API_KEY"
            android:value="你申请的AK码" />
  • app build.gradle 配置如下

android {
.....
  defaultConfig {
  .....
 ndk {
    // 设置支持的SO库架构
    abiFilters 'armeabi' , 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
 .....
  }
 sourceSets {
    main {
        jniLibs.srcDir 'libs'
        jni.srcDirs = []
     }
   }
   .....
}


3.MapView 加载

  • 3.1 根据React-Native桥接原生UI(前面已经讲过,具体参考步骤1.1),需要在createViewInstance 创建 View(BaiduMapView) ,代码如下
//MapViewPackage.java
@Override
protected MapView createViewInstance(ThemedReactContext themedReactContext) {
    //在使用SDK各组件之前初始化context信息,传入ApplicationContext
    SDKInitializer.initialize(themedReactContext.getApplicationContext());
    //创建MapView对象
    mapView = new MapView(themedReactContext);
    mIconMarker = BitmapDescriptorFactory.fromResource(R.drawable.map_maker);
    this.themedReactContext = themedReactContext;
    initMap();//初始化mapView参数
    return mapView;
}

具体参考创建BaiduView文档

  • 3.2 在 MapView 上添加 Location(定位) 和 InfoWindow 布局,注意要在 MapLoaded后添加
//MapViewPackage.java
private void initMap() {
    mBaiduMap = mapView.getMap();
    mBaiduMap.setMapType(BaiduMap.MAP_TYPE_NORMAL);
    mapView.showZoomControls(true);
    //定义BaiduMapOptions对象
    BaiduMapOptions options = new BaiduMapOptions();
    options.scaleControlEnabled(false); // 不显示比例尺
    mBaiduMap.setMyLocationEnabled(true);
    mBaiduMap.setMaxAndMinZoomLevel(18, 7);//地图的最大最小缩放比例7-18
    mBaiduMap.setOnMapLoadedCallback(new BaiduMap.OnMapLoadedCallback() {
        @Override
        public void onMapLoaded() {
            setLocation();
            setInfoWindow();
        }
    });
    mBaiduMap.setOnMapClickListener(new BaiduMap.OnMapClickListener() {
        @Override
        public boolean onMapPoiClick(MapPoi arg0) {
            return false;
        }

        @Override
        public void onMapClick(LatLng arg0) {
            mBaiduMap.hideInfoWindow();
        }
    });
}

根据已经获取的经纬度,设置定位布局

//MapViewPackage.java
private void setLocation() {
    //获取定位
    LatLng latLng = new LatLng(mStopLatitude, mStopLongitude);
    //添加定位图标
    OverlayOptions overlayOptions = new MarkerOptions().position(latLng)
            .icon(mIconMarker).zIndex(3);
    Marker marker = (Marker) mBaiduMap.addOverlay(overlayOptions);
    Bundle bundle = new Bundle();
    bundle.putDoubleArray("location", new double[]{mStopLatitude, mStopLongitude});
    marker.setExtraInfo(bundle);
    MapStatus.Builder builder = new MapStatus.Builder();//地图状态构造器
    //设置地图中心点,设置地图缩放级别并创建地图状态对象
    MapStatus mapStatus = builder.target(latLng).zoom(14f).build()
    MapStatusUpdate mapStatusUpdate = MapStatusUpdateFactory.newMapStatus(mapStatus);
    //以动画方式更新地图状态,动画耗时 300 ms
    mBaiduMap.animateMapStatus(mapStatusUpdate);
}

设置 InfoWindow 布局,并监听布局点击状态

//MapViewPackage.java
void setInfoWindow() {
    Logger.t(TAG).i("setInfoWindow");
    //显示信息窗
    mBaiduMap.setOnMarkerClickListener(new BaiduMap.OnMarkerClickListener() {
        InfoWindow.OnInfoWindowClickListener infoWindow_ClickListener = new InfoWindow.OnInfoWindowClickListener() {
            @Override
            public void onInfoWindowClick() { // TODO Auto-generated method stub
                mBaiduMap.hideInfoWindow();
            }
        };
        @Override
        public boolean onMarkerClick(Marker marker) {
            LatLng point = new LatLng(mStopLatitude, mStopLongitude);
            View infoWindowView = LayoutInflater.from(themedReactContext).inflate(R.layout.info_window, null);
            TextView posName = infoWindowView.findViewById(R.id.infoWindow_posName);
            TextView posAddress = infoWindowView.findViewById(R.id.infoWindow_posAddress);
            posName.setText(mPosName);
            posAddress.setText(mPosAddress);
            //根据一个 View 创建 Bitmap 描述信息, 当 view 为 null 时返回 null
            BitmapDescriptor infoWindow_bitmap = BitmapDescriptorFactory.fromView(infoWindowView);
            mInfoWindow = new InfoWindow(infoWindow_bitmap, point, -marker.getIcon().getBitmap().getHeight()/2, infoWindow_ClickListener);
            //显示信息窗
            Logger.t(TAG).i("showInfoWindow");
            mBaiduMap.showInfoWindow(mInfoWindow);
            return true;
        }
    });
}

infowwindow.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/popup"
    android:paddingTop="@dimen/map_info_window_padding_top"
    android:paddingBottom="@dimen/map_info_window_padding_bottom"
    android:paddingLeft="@dimen/map_info_window_padding_left"
    android:paddingRight="@dimen/map_info_window_padding_right"
    android:orientation="vertical">

    <TextView
        android:id="@+id/infoWindow_posName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal" />

    <TextView
        android:id="@+id/infoWindow_posAddress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="@dimen/map_info_window_pos_name_text_size"/>

</LinearLayout>
  • 3.3 桥接Android 原生模块(前面已经讲过,参考1.2),获取经纬度定位信息,通过 LocationClient 发起定位,通过 LocationClientOption 设置LocationClient相关参数
//GeolocationModule.java
private void initLocationClient() {
    LocationClientOption option = new LocationClientOption();//声明LocationClient类实例并配置定位参数
    option.setLocationMode(LocationMode.Hight_Accuracy);//可选,默认高精度,设置定位模式,高精度,低功耗,仅设备
    option.setCoorType("bd09ll");//可选,默认gcj02,设置返回的定位结果坐标系
    option.setIsNeedAddress(true);//可选,设置是否需要地址信息,默认不需要
    option.setIsNeedAltitude(true);//可选,默认false,设置定位时是否需要海拔信息,默认不需要,除基础定位版本都可用
    option.setIsNeedLocationDescribe(true);//可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”
    option.setOpenGps(true);//可选,默认false,设置是否使用gps
    option.setScanSpan(1000);//可选,默认0,即仅定位一次,设置发起连续定位请求的间隔需要大于等于1000ms才是有效的
    //定位初始化
    locationClient = new LocationClient(getReactApplicationContext()/*context.getApplicationContext()*/);
    locationClient.setLocOption(option);
    //开启地图定位图层
    locationClient.start();

    Log.i("locationClient", "locationClient");
    //注册LocationListener监听器
    MyLocationListener myLocationListener = new MyLocationListener();
    locationClient.registerLocationListener(myListener);
}

public class MyLocationListener extends BDAbstractLocationListener {
    private double latitude, longitude;

    /**
     * 实现定位监听 位置一旦有所改变就会调用这个方法
     * 可以在这个方法里面获取到定位之后获取到的一系列数据
     */
    @Override
    public void onReceiveLocation(BDLocation bdLocation) {
        latitude = bdLocation.getLatitude();
        longitude = bdLocation.getLongitude();
        Logger.t(TAG).i("onReceiveLocation");
        if (bdLocation != null) {
            StringBuffer buffer = new StringBuffer(256);
            buffer.append(latitude);
            buffer.append(",");
            buffer.append(longitude);
            location = buffer.toString();
            locationClient.stop();
            try {
                GeolocationModule.this.locationCallback.invoke(null, location);
            } catch (IllegalViewOperationException e) {
                locationCallback.invoke(e.getMessage(), null);
            }
        }
        Logger.t(TAG).i("onReceiveLocation:" + location
                + "latitude:" + latitude
                + "longitude:" + longitude);
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值