今天使用Android的LocationManager制作了一款獲取當前經緯坐標位置的軟件。
LocationManager獲取的只是經緯坐標點,為了解析出當前經緯坐標點的實際位置,可以使用Google提供的 Geocoding API 服務。
谷歌提供了一套 Geocoding API,使用它的話可以完成反向地理編碼的工作,只不過它的用法稍微復雜了一些,但穩定性要比 GeoCoder 強得多。本小節中我們只是學習一下 GeocodingAPI 的簡單用法, 更詳細的用法請參考官方文檔: https://developers.google.com/maps/documentation/geocoding/。
GeocodingAPI 的工作原理並不神秘,其實就是利用了 HTTP 協議。
在手機端我們可以向谷歌的服務器發起一條 HTTP 請求,並將經緯度的值作為參數一同傳遞過去,然后服務器會幫我們將這個經緯值轉換成看得懂的位置信息,再將這些信息返回給手機端,最后手機端去解析服務器返回的信息,並進行處理就可以了。
Geocoding API 中規定了很多接口,其中反向地理編碼的接口如下:
http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true_or_false
我們來仔細看下這個接口的定義, 其中 http://maps.googleapis.com/maps/api/geocode/ 是固定的,表示接口的連接地址。json 表示希望服務器能夠返回 JSON 格式的數據,這里也可以指定成 xml。latlng=40.714224,-73.96145 表示傳遞給服務器去解碼的經緯值是北緯 40.714224度,西經 73.96145 度。 sensor=true_or_false 表示這條請求是否來自於某個設備的位置傳感器,通常指定成 false 即可。
如果發送
http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.96145&sensor=false
這樣一條請求給 服務器,我們將會得到一段非常長的 JSON 格式的數據,其中會包括如下部分內容:
"formatted_address" : "277 Bedford Avenue, 布魯克林紐約州 11211美國"
從這段內容中我們就可以看出北緯 40.714224 度,西經 73.96145 度對應的地理位置是在哪里了。如果你想查看服務器返回的完整數據,在瀏覽器中訪問上面的網址即可。
這樣的話,使用 Geocoding API 進行反向地理編碼的工作原理你就已經搞清楚了,那么難點其實就在於如何從服務器返回的數據中解析出我們想要的那部分信息了。因而軟件的一個關鍵點就在於JSON數據的解析。
代碼如下:
//activity_main.xml
//activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/position_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello" /> <TextView android:id="@+id/position_plain_text" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
界面如下:

//MainActivity.java
//MainActivity.java package com.example.location; import java.util.List; import org.json.JSONArray; import org.json.JSONObject; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private static final String TAG="POSITION"; public static final int SHOW_LOCATION=0;//更新文字式的位置信息 public static final int SHOW_LATLNG=1; //更新經緯坐標式的位置信息 private TextView positionTextView; private TextView positionLatLng; private LocationManager locationManager; private String provider; private Handler handler=new Handler(){ @SuppressLint("HandlerLeak") @Override public void handleMessage(Message message){ switch(message.what){ case SHOW_LOCATION: Log.d(TAG, "showing the positio>>>>>"); String currentPosition=(String)message.obj; positionTextView.setText(currentPosition); Log.d(TAG, "Has show the position...>>>>...."); break; case SHOW_LATLNG: String latlng=(String)message.obj; positionLatLng.setText(latlng); default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); positionTextView=(TextView)findViewById(R.id.position_text_view); positionLatLng=(TextView)findViewById(R.id.position_plain_text); locationManager=(LocationManager)getSystemService(Context.LOCATION_SERVICE); //獲取所有可用的位置提供器 List<String>providerList=locationManager.getProviders(true); if(providerList.contains(LocationManager.GPS_PROVIDER)){ provider=LocationManager.GPS_PROVIDER; } else if(providerList.contains(LocationManager.NETWORK_PROVIDER)){ provider=LocationManager.NETWORK_PROVIDER; } else{ //當沒有可用的位置提供器時,彈出Toast提示用戶 Toast.makeText(this, "No Location provider to use", Toast.LENGTH_SHORT).show(); return; } Location location=locationManager.getLastKnownLocation(provider); if(location!=null){ //顯示當前設備的位置信息 Log.d(TAG, "location!=null"); showLocation(location); } locationManager.requestLocationUpdates(provider, 1000, 1, locationListener); Log.d(TAG, "Running...."); } protected void onDestroy(){ super.onDestroy(); if(locationManager!=null){ //關閉程序時將監聽移除 locationManager.removeUpdates(locationListener); } } //LocationListener 用於當位置信息變化時由 locationManager 調用 LocationListener locationListener=new LocationListener(){ @Override public void onLocationChanged(Location location) { // TODO Auto-generated method stub //更新當前設備的位置信息 showLocation(location); } @Override public void onProviderDisabled(String provider) { // TODO Auto-generated method stub } @Override public void onProviderEnabled(String provider) { // TODO Auto-generated method stub } @Override public void onStatusChanged(String provider, int status, Bundle extras) { // TODO Auto-generated method stub } }; private void showLocation(final Location location){ //顯示實際地理位置 //開啟線程來發起網絡請求 new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub try{ String request="http://maps.googleapis.com/maps/api/geocode/json?latlng="; request+=location.getLatitude()+","+location.getLongitude()+"&sensor=false"; String response=HttpUtil.sendHttpRequest(MainActivity.this,request); parseJSONResponse(response); } catch(Exception e){ Log.d(TAG, "showLocation: the inptuStream is wrong!"); e.printStackTrace(); } } }).start(); //顯示經緯度坐標 new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub String position=""; position="Latitude="+location.getLatitude()+"\n" +"Longitude="+location.getLongitude(); Message msg=new Message(); msg.what=SHOW_LATLNG; msg.obj=position; handler.sendMessage(msg); } }).start(); } //解析JSON數據 private void parseJSONResponse(String response){ try{ Log.d(TAG, "parseJSONResponse: getting the jsonObject..."); JSONObject jsonObject=new JSONObject(response); //獲取results節點下的位置 Log.d(TAG, "parseJSONResponse: Getting the jsongArray..."); JSONArray resultArray=jsonObject.getJSONArray("results"); Log.d(TAG, "parseJSONResponse: Got the JSONArray..."); if(resultArray.length()>0){ JSONObject subObject=resultArray.getJSONObject(0); //取出格式化后的位置信息 String address=subObject.getString("formatted_address"); Message message=new Message(); message.what=SHOW_LOCATION; message.obj="您的位置:"+address; Log.d(TAG, "showLocation:Sending the inputStream..."); handler.sendMessage(message); } } catch(Exception e){ Log.d(TAG, "parseJSONResponse: something wrong"); e.printStackTrace(); } } }
通用類HttpUtil.java(提供獲取Http請求的結果的靜態方法 sendHttpRequest)
//HttpUtil.java
//HttpUtil.java package com.example.location; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import android.content.Context; import android.util.Log; public class HttpUtil { private static final String TAG="POSITION"; public static String sendHttpRequest(Context context,String address){ HttpURLConnection connection=null; try{ URL url=new URL(address); Log.d(TAG, "HttpUtil: request="+address); connection=(HttpURLConnection)url.openConnection(); connection.setRequestMethod("GET"); connection.setRequestProperty("Accept-Language", "zh-CN"); connection.setConnectTimeout(8000); connection.setReadTimeout(8000); Log.d(TAG, "HttpUtil: getting the inputStream..."); InputStream in=connection.getInputStream(); Log.d(TAG, "HttpUtil: got the inputStream..."); //下面對獲取到的流進行讀取 BufferedReader reader=new BufferedReader(new InputStreamReader(in)); StringBuilder response=new StringBuilder(); String line; while((line=reader.readLine())!=null){ response.append(line); } Log.d(TAG, "HttpUtil: Got the response..."); return response.toString(); } catch(Exception e){ e.printStackTrace(); Log.d(TAG, "HttpUtil: Some thing wrong...."); return e.getMessage(); } finally{ if(connection!=null){ connection.disconnect(); } } } }
因為軟件使用了位置服務,所以要在Manifest.xml文件中添加使用位置服務的 uses-permission:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
//Location Manifest.xml
//Location Manifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.location" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
但是調試軟件的時候,發現HttpUtil.sendHttpRequest() 方法總是在 getInputStream() 那一步出現 Exception。百思終得其解:原來忘記添加使用網絡功能的權限了。添加如下:
<uses-permission android:name="android.permission.INTERNET"/>
//Location Manifest.xml
//Location Manifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.location" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" /> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
現在軟件就可以正常運行了。
值得注意的是,這里需要使用Google提供的Geocoding服務,而目前Google的網站一般都還在牆外,手機需要翻牆才能獲得Geocoding的功能。恰好自己之前購買了VPN,就先用筆記本電腦連接上VPN,在瀏覽器中確定可以通過http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true_or_false這樣的地址獲取位置服務,然后用筆記本開啟WiFi,手機連接上這個開通了VPN的WiFi,就可以使用Geocoding服務了。
