如下是我這個系列的相關文章,有興趣能夠參考一下,能夠給個喜歡或者關注個人文章。android
[Android]如何作一個崩潰率少於千分之三噶應用app--章節列表git
今天介紹的是大型app必備模塊-地圖模塊。 當今世界最大的地圖sdk應該是google地圖,可是因爲國內牆掉了google play service,國內是沒法使用google地圖的,然而國內比較熱門的地圖sdk是高德地圖和百度地圖。(若是你是IOS,還有自帶的地圖)github
近來項目中須要世界地圖,因此特此作了一個高德地圖和google地圖兼容的模塊了。web
1.google地圖,接入相對比較簡單,固然由於Android自己就是google親兒子的緣由。 須要引入google service的sdk,以及google map的sdk https://developers.google.com/places/android-api/start,獲取帳號須要gmail郵箱做爲管理json
2.高德地圖接入相對比較複雜一點,能夠選擇2d,3d,定位,搜索多種模塊去接入地圖。 而後須要申請帳號,隨便郵箱手機號就能夠了,經過keytools命令提出keystore的sha1值,包名和 sha1值相互綁定的,每次請求都會驗證。 而後配置AndroidManifest中的meta-data。api
預覽模塊 數組
1.高德地圖是經過sdk提供的com.amap.api.maps2d.MapView自定義地圖View來實現的。 google地圖是經過sdk提供一個Fragment空間來實現地圖獲取緩存
<fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/google_map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="match_parent"/> mapFragment = childFragmentManager.findFragmentById(R.id.google_map) as SupportMapFragment 複製代碼
2.地圖預覽 bash
Google地圖和高德地圖接口相關的名字都是差很少的,比較經常使用的接口 moveCamera 視窗轉移 縮放級別分爲1~17級,數值越大地圖越精準 addMarker 添加地圖標籤markdown
google地圖是使用getMapAysnc,會有onMapReady的接口回調
mapFragment?.getMapAsync(this) /** * 地圖就緒 */ override fun onMapReady(googleMap: GoogleMap?) { googleMap ?: return with(googleMap) { val latLng = LatLng(latitude, longitude) //視覺轉移 moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 16f)) //添加座標 addMarker(MarkerOptions().position(latLng)) } } 複製代碼
高德地圖使用setOnMapLoadedListener方法來設置回調
aMap?.setOnMapLoadedListener {
//視覺移動
aMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(latitude, longitude), 100f))
//添加座標
aMap?.addMarker(MarkerOptions().anchor(0.5f, 0.5f)
.icon(BitmapDescriptorFactory
.fromBitmap(BitmapFactory.decodeResource(
resources, R.drawable.common_drag_location)))
.position(LatLng(latitude, longitude)))
}
複製代碼
若是不想地圖的座標和視覺點顯示居中怎麼辦? 須要將佈局中margin上着手,若是想要往上移,就須要將marginTop設置爲負值,這樣地圖中心點就會上移動,而且視覺點也會和中心點同樣上移。
1.高德提供了AMapLocationListener做爲專爲提供高德座標的監聽
private fun setUpMap() { myLocationStyle = MyLocationStyle() myLocationStyle?.strokeColor(Color.argb(0, 0, 0, 0))// 設置圓形的邊框顏色 myLocationStyle?.radiusFillColor(Color.argb(0, 0, 0, 0))// 設置圓形的填充顏色 myLocationStyle?.myLocationIcon(BitmapDescriptorFactory.fromResource(R.drawable.common_self_location)) //顯示自身定位座標 aMap?.setMyLocationStyle(myLocationStyle) aMap?.isMyLocationEnabled = true// 設置爲true表示顯示定位層並可觸發定位,false表示隱藏定位層並不可觸發定位,默認是false val uriSettings = aMap?.uiSettings uriSettings?.isZoomControlsEnabled = false //關掉縮放鍵 } private fun initLoc() { //初始化定位 mLocationClient = AMapLocationClient(context!!.applicationContext) //設置定位回調監聽 mLocationClient?.setLocationListener(this) //初始化定位參數 mLocationOption = AMapLocationClientOption() //設置定位模式爲高精度模式,Battery_Saving爲低功耗模式,Device_Sensors是僅設備模式 mLocationOption?.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy //設置是否返回地址信息(默認返回地址信息) mLocationOption?.isNeedAddress = true //設置是否只定位一次,默認爲false mLocationOption?.isOnceLocation = false //設置是否強制刷新WIFI,默認爲強制刷新 mLocationOption?.isWifiActiveScan = false //設置是否容許模擬位置,默認爲false,不容許模擬位置 mLocationOption?.isMockEnable = false //設置定位間隔,單位毫秒,默認爲3000ms mLocationOption?.interval = (3000) //給定位客戶端對象設置定位參數 mLocationClient?.setLocationOption(mLocationOption) //啓動定位 mLocationClient?.startLocation() } override fun onLocationChanged(amapLocation: AMapLocation?) { //監聽實時定位 } 複製代碼
google的定位是使用Android原生的Location定位
locationManager = context!!.getSystemService(Context.LOCATION_SERVICE) as LocationManager locationManager?.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 10f, this) locationManager?.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 3000, 10f, this) /** *地圖就緒 */ override fun onMapReady(googleMap: GoogleMap?) { googleMap ?: return this.googleMap = googleMap with(googleMap.uiSettings) { isZoomGesturesEnabled = true isMyLocationButtonEnabled = true isScrollGesturesEnabled = true } try { googleMap.isMyLocationEnabled = true } catch (e: SecurityException) { ALog.e(TAG, e) } } /** * 定位更新 */ override fun onLocationChanged(location: Location?) { } 複製代碼
1.google 搜索有兩種方式,一種是經過webapi來搜索出附近相關的地點(這裏使用了RxVolley的框架),這個的好處關聯結果比較多。 這裏不用Uri.Builder的拼接方式是由於其指定了Utf-8的格式轉換將會出現「,」強轉爲「%」號
val googlePlaceUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" fun getGoogleNearByPlaces(latitude: Double, longitude: Double, radius: Int): Observable<GoogleLocation> { val builder = StringBuilder(googlePlaceUrl) builder.append("?location=").append(latitude.toString()).append(",").append(longitude.toString()) builder.append("&radius=").append(radius.toString()) builder.append("&key=").append(googlePlaceKey) return RxVolley.get<GoogleLocation>(builder.toString(), null, object : TypeToken<GoogleLocation>() {}.type) } 複製代碼
第二種是文字關聯搜索(地點自動完成),google提供了一個自定義的Fragment,可是若是你有高級定製,不用AutoCompleteTextView,那就須要經過定義一個Adapter來獲取相關內容。(搜索結果比較少) 這邊是使用了須要高級定義搜索,因此使用了Adapter的形式。
override fun doSearch(key: String, city: String?) { Observable.create(ObservableOnSubscribe<ArrayList<AutocompletePrediction>> { it.onNext(getAutocomplete(key)!!) //須要在次線程 }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ searchAdpater?.clearData() for (item in it) { val placeResult = mGeoDataClient!!.getPlaceById(item.placeId) placeResult.addOnCompleteListener(mUpdatePlaceDetailsCallback) //異步訪問單個placeId的詳細信息 } }, { ALog.e(TAG, it) }) } /** * 異步訪問單個placeId的詳細信息 */ val mUpdatePlaceDetailsCallback = object : OnCompleteListener<PlaceBufferResponse> { override fun onComplete(task: Task<PlaceBufferResponse>) { try { val place = task.result.get(0) searchAdpater?.addData(LocationItem(false, place.latLng.latitude, place.latLng.longitude, place.name.toString(), place.address.toString())) ALog.i(TAG, "Place details received: " + place.name) task.result.release() } catch (e: RuntimeRemoteException) { ALog.e(TAG, e) } } } private fun getAutocomplete(constraint: CharSequence): ArrayList<AutocompletePrediction>? { ALog.d(TAG, "Starting autocomplete query for: " + constraint) // Submit the query to the autocomplete API and retrieve a PendingResult that will // contain the results when the query completes. val results = mGeoDataClient?.getAutocompletePredictions(constraint.toString(), null, null) // This method should have been called off the main UI thread. Block and wait for at most // 60s for a result from the API. //收集文字關聯預測結果 try { Tasks.await<AutocompletePredictionBufferResponse>(results!!, 2, TimeUnit.SECONDS) } catch (e: ExecutionException) { e.printStackTrace() } catch (e: InterruptedException) { e.printStackTrace() } catch (e: TimeoutException) { e.printStackTrace() } try { val autocompletePredictions = results!!.result ALog.d(TAG, "Query completed. Received " + autocompletePredictions.count + " predictions.") // Freeze the results immutable representation that can be stored safely. return DataBufferUtils.freezeAndClose<AutocompletePrediction, AutocompletePrediction>(autocompletePredictions) } catch (e: RuntimeExecutionException) { // If the query did not complete successfully return null Toast.makeText(context, "Error contacting API: " + e.toString(), Toast.LENGTH_SHORT).show() ALog.e(TAG, "Error getting autocomplete prediction API call", e) return null } } 複製代碼
2.高德地圖中的PoiSearch是支持經過關鍵字搜索和經緯度地址附近搜索。
/** * 經緯度搜索 */ fun doSearchQuery(city: String, latitude: Double, longtitude: Double) { query = PoiSearch.Query("", "", city) // 第一個參數表示搜索字符串,第二個參數表示poi搜索類型,第三個參數表示poi搜索區域(空字符串表明全國) query?.pageSize = 20 // 設置每頁最多返回多少條poiitem query?.pageNum = 1 // 設置查第一頁 val poiSearch = PoiSearch(context, query) poiSearch.setOnPoiSearchListener(onPoiSearchListener) // 設置搜索區域爲以lp點爲圓心,其周圍5000米範圍 poiSearch.bound = PoiSearch.SearchBound(LatLonPoint(latitude, longtitude), 1000, true) poiSearch.searchPOIAsyn() // 異步搜索 } /** * 關鍵字搜索 */ fun doSearchQuery(keyWord: String, city: String?) { if (city != null) query = PoiSearch.Query(keyWord, "", city) else query = PoiSearch.Query(keyWord, "", "") query?.pageSize = 20 // 設置每頁最多返回多少條poiitem query?.pageNum = 1 // 設置查第一頁 val poiSearch = PoiSearch(context!!, query) poiSearch.setOnPoiSearchListener(onSearchListener) poiSearch.searchPOIAsyn() // 異步搜索 } 複製代碼
固然也支持異步返回
/** * 搜索結果 */ val onSearchListener = object : PoiSearch.OnPoiSearchListener { override fun onPoiSearched(result: PoiResult?, rCode: Int) { if (rCode == 1000) { if (result?.query!! == query) { //返回結果列表 result.pois } } } override fun onPoiItemSearched(p0: PoiItem?, p1: Int) { //返回再搜索 } } 複製代碼
1.高德地圖和google地圖都須要使用web api來獲取縮略圖
var builder: StringBuilder? = null if (type == GDMAP) { builder = StringBuilder(gdImgUrl) builder.append("?location=").append(longitude).append(",").append(latitude) builder.append("&zoom=").append(zoom) builder.append("&size=").append(dpToPx(context, width)).append("*").append(dpToPx(context, height)) builder.append("&markers=").append("mid").append(",").append(",A:").append(longitude).append(",").append(latitude) builder.append("&key=").append(gdMapKey) } else if (type == GOOGLEMAP) { builder = StringBuilder(googleImgeUrl) builder.append("?center=").append(latitude).append(",").append(longitude) builder.append("&zoom=").append(zoom) builder.append("&size=").append(dpToPx(context, width)).append("x").append(dpToPx(context, height)) builder.append("&markers=").append(latitude).append(",").append(longitude) builder.append("&key=").append(googlePlaceKey) } return builder.toString() 複製代碼
1.高德size的拼接,是用*號,而google size的拼接是使用「x」。 2.google須要使用的是placeKey,不是mapkey。 3.兩種地圖的縮略圖都不支持自定義標記(marker) 4.高德地圖沒法顯示到國外google的地址的詳細信息 [高德縮略圖說明](http://lbs.amap.com/api/webservice/guide/api/staticmaps/) google縮略圖說明
特殊轉換 1.高德地圖使用的是高德座標,並非標準的的GPS座標。而高德只提供了其餘地圖和GPS轉高德座標,並不沒有提供高德轉爲其餘座標。Google使用的標準的GPS座標,那麼若是須要座標互通就須要相互轉換了,這裏提供了座標轉換相互轉換的方式。
object GDConverter { fun fromGpsToLatLng(context: Context, latitude: Double, longitude: Double): LatLng? { val converter = CoordinateConverter(context) converter.from(CoordinateConverter.CoordType.GPS) try { converter.coord(DPoint(latitude, longitude)) val desLatLng = converter.convert() return LatLng(desLatLng.latitude, desLatLng.longitude) } catch (e: Exception) { e.printStackTrace() } return null } /** * GPS座標轉換成高德 */ fun toGDLatLng(latitude: Double, longitude: Double): LatLng { val latLng = LatLng(latitude, longitude) val converter = com.amap.api.maps2d.CoordinateConverter() converter.from(com.amap.api.maps2d.CoordinateConverter.CoordType.GPS) converter.coord(latLng) return converter.convert() } //圓周率 GCJ_02_To_WGS_84 var PI = 3.14159265358979324 /** * 方法描述:方法能夠將高德地圖SDK獲取到的GPS經緯度轉換爲真實的經緯度,能夠用於解決安卓系統使用高德SDK獲取經緯度的轉換問題。 * @param 須要轉換的經緯度 * @return 轉換爲真實GPS座標後的經緯度 * @throws <異常類型> {@inheritDoc} 異常描述 </異常類型> */ fun delta(lat: Double, lon: Double): HashMap<String, Double> { val a = 6378245.0//克拉索夫斯基橢球參數長半軸a val ee = 0.00669342162296594323//克拉索夫斯基橢球參數第一偏愛率平方 var dLat = this.transformLat(lon - 105.0, lat - 35.0) var dLon = this.transformLon(lon - 105.0, lat - 35.0) val radLat = lat / 180.0 * this.PI var magic = Math.sin(radLat) magic = 1 - ee * magic * magic val sqrtMagic = Math.sqrt(magic) dLat = dLat * 180.0 / (a * (1 - ee) / (magic * sqrtMagic) * this.PI) dLon = dLon * 180.0 / (a / sqrtMagic * Math.cos(radLat) * this.PI) val hm = HashMap<String, Double>() hm.put("lat", lat - dLat) hm.put("lon", lon - dLon) return hm } //轉換經度 fun transformLon(x: Double, y: Double): Double { var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)) ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0 ret += (20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0 ret += (150.0 * Math.sin(x / 12.0 * this.PI) + 300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0 return ret } //轉換緯度 fun transformLat(x: Double, y: Double): Double { var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)) ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0 ret += (20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0 ret += (160.0 * Math.sin(y / 12.0 * this.PI) + 320 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0 return ret } } 複製代碼
高德支持Google瓦片 1.高德地圖若是想要顯示國外地圖,能夠選擇使用google瓦片
/** * 加載在線瓦片數據 */ private fun useOMCMap() { // val url = "http://www.google.cn/maps/vt?lyrs=y&gl=cn&x=%d&s=&y=%d&z=%d" // val url = "http://mt1.google.cn/vt/lyrs=y&hl=zh-CN&gl=cn&x=%d&s=&y=%d&z=%d" //3D衛星地圖 // val url = "http://mt0.google.cn/vt/lyrs=y@198&hl=zh-CN&gl=cn&src=app&x=%d&y=%d&z=%d&s=" //衛星地圖 val url = "http://mt2.google.cn/vt/lyrs=m@167000000&hl=zh-CN&gl=cn&src=app&x=%d&y=%d&z=%d&s=Galil" //平面地圖 if (tileOverlayOptions == null) { tileOverlayOptions = TileOverlayOptions().tileProvider(object : UrlTileProvider(256, 256) { override fun getTileUrl(x: Int, y: Int, zoom: Int): URL? { try { //return new URL(String.format(url, zoom + 1, TileXYToQuadKey(x, y, zoom))); //return new URL(String.format(url, x, y, zoom)); val mFileDirName: String val mFileName: String mFileDirName = String.format("L%02d/", zoom + 1) mFileName = String.format("%s", TileXYToQuadKey(x, y, zoom))//爲了避免在手機的圖片中顯示,下載的圖片取消jpg後綴,文件名本身定義,寫入和讀取一致便可,因爲有本身的bingmap圖源服務,因此此處我用的bingmap的文件名 val LJ = FileApi.MAP_DIRECTORY + mFileDirName + mFileName if (MapImageCache.instance.isBitmapExit(mFileDirName + mFileName)) {//判斷本地是否有圖片文件,若是有返回本地url,若是沒有,緩存到本地並返回googleurl return URL("file://" + LJ) } else { val filePath = String.format(url, x, y, zoom) val mBitmap: Bitmap //mBitmap = BitmapFactory.decodeStream(getImageStream(filePath));//不知什麼緣由致使有大量的圖片存在壞圖,因此重寫InputStream寫到byte數組方法 val stream = getImageStream(filePath) if (stream != null) { mBitmap = getImageBitmap(stream) try { saveFile(mBitmap, mFileName, mFileDirName) } catch (e: IOException) { e.printStackTrace() } } return URL(filePath) } } catch (e: Exception) { e.printStackTrace() } return null } }) tileOverlayOptions!!.diskCacheEnabled(false) //因爲高德自帶的瓦片緩存在關閉程序後會自動清空,因此無心義,關閉本地緩存 .diskCacheDir(FileApi.MAP_DIRECTORY) .diskCacheSize(1024000) .memoryCacheEnabled(true) .memCacheSize(102400) .zIndex(-9999f) } //增長瓦片貼圖 mtileOverlay = aMap?.addTileOverlay(tileOverlayOptions) mtileOverlay?.isVisible = true } 複製代碼
若是須要在中國地域中中止添加瓦片,須要remove掉瓦片
fun stopUseOMCMap() { mtileOverlay?.remove() mtileOverlay?.clearTileCache() mtileOverlay?.isVisible = false aMap?.removecache() } 複製代碼
注意一點,請不要一直觸發重複addTileOverlay,調用remove的次數和add的次數是須要對應的。 這裏還有一部分的代碼沒有貼上,我將會開放一個module的demo供你們演示,有興趣的同窗也能夠在羣內聯繫我。
1.高德只支持國內座標詳情,若是在國外發送了國外地址到國內手機,國內手機會使用高德地圖,將會沒法顯示國外座標詳情。 2.google對國內座標信息詳情也是比較有限的。 3.地圖搜索涉及到異步返回,onDestroy請去掉監聽,否則會有內存泄露 4.暫時發現高德的mapview會持有Activity context對象不釋放的狀況,修復好會在這裏更新 5.高德搜索的時候,若是不加入city的參數,將是全國搜索,極可能搜索不到你所在城市的地點(獲取定位的時候能夠順帶獲取所在城市名字),請謹慎處理。
對於一些更加深刻的問題,例如交通地圖軌跡這樣的,暫時業務還沒涉及到,若是有更好的解決方案,會繼續上新篇,謝謝。