Android学习笔记(2)----LocationManager的使用
今天使用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的网站一般都还在墙外,手机需要FQ才能获得Geocoding的功能。恰好自己之前购买了VPN,就先用笔记本电脑连接上VPN,在浏览器中确定可以通过http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true_or_false这样的地址获取位置服务,然后用笔记本开启WiFi,手机连接上这个开通了VPN的WiFi,就可以使用Geocoding服务了。