Android应用开发之(Location in Android-定位)

如何在Android上开发LBS(“基于位置的服务”),那么首先要明白如何获得位置。传统意义上的位置,就是指门牌号一类的描述,虽然可以被人理解,但是无法被计算机理解。为了让计算机能够理解“位置”,地理学上的位置,即经纬度被引入进来。

获取经纬度信息,一般都会想到GPS(Global Positioning System)。这个前身为美国军方卫星定位系统,在推出之后迅速发展成为最大的民用定位服务,现在市场上的车载导航仪、手机导航大都使用GPS。在 Android上,开发者可以利用系统提供的API方便获得位置信息(android.location.Location)。

没有GPS,移动设备,例如手机,如何获取位置信息?现在有一些应用,例如Google Map在没有GPS的情况上,也能标识位置,缺点是误差比较大。此类位置的获取有赖于手机无线通讯信号,当手机处在信号覆盖范围内,手机可以获得该区域(即通讯术语中的“小区”)的识别号。因为这些识别号是惟一的,因此可以将识别号和地理坐标对应起来,因此根据识别号就可以知道地理位置。

那么既没有GPS,也没有移动通讯网络接入怎么办?Google的粉丝应该留意到2010年Eric Schmidt一直在为收集私人WI-FI数据在纠结,Google为什么要收集WI-FI数据呢?显然不是为了像国内某些没品德的人那样去“蹭网”,原因之一就是WI-FI定位。它的原理是首先收集每个WIFI无线接入点的位置,对每个无线路由器进行唯一的标识,在数据库中注明这些接入点的具体位置。 使用时,但发现有WI-FI接入点,则进入到数据中查看匹配的记录,进而得到位置信息。

以上三种获取位置的技术在Android均得到了支持,通过系统自带的Setting应用, 进入到“Location & security”,可以看到在“My Locaiton”下,有“Use wireless networks”和“Use GPS satellites”。因此,开发者可以通过三种渠道获得使用者当前者的位置信息,不必担心没有GPS就无法使用的问题。

MCC(Mobile Country Code)、MNC(Mobile Network Code)、LAC(Location Aera Code)、CID(Cell Tower ID)是通讯业内的名词。MCC标识国家,MNC标识网络,两者组合起来则唯一标识一家通讯运营商。从维基百科上了解到,一个国家的MCC不唯一,例如中国有460和461,一家运营商也不只一个MNC,例如中国移动有00、02、07。LAC标识区域,类似于行政区域,运营商将大区域划分成若干小区域,每个区域分配一个LAC。CID标识基站,若手机处在工作状态,则必须要和一个通讯基站进行通讯,通过CID就可以确定手机所在的地理范围。

2006年Yahoo!曾经推出一项服务:ZoneTag,其功能之一就是拍照后将图片附上位置信息上传到Flickr。这样用户就不必对着几G,甚至几十G的图片,再费神回忆是在什么地方拍摄的了。 ZoneTag也正是利用了前文中的第二种方式获得位置信息。Yahoo!后期也曾经开放ZoneTag API,这样第三方开发者就可以将MMC、MNC、LAC、CID发给Yahoo!,然后获得位置信息,但现在似乎已经关闭了接口。

Google在昔日明星Gears中提供了一项Geolocation的功能,可以获得用户的地理位置,同时这项功能也是开放的,开发者可以依照Geolocation API Network Protocol调用相关功能。稍微不令人满意的是,其数据格式为JSON(JavaScript Object Notation),没有提供XML,不够RESTful,多少是个遗憾。同时需要注意的是,由于Gears已经被Google废弃(Deprecated),因此这项功能是否会被关闭还是未知。不过,Internet上还有其他提供类似功能的服务,例如OpenCellID。

在Android当中,大部分和通讯网络相关的信息都需要经过一项系统服务,即TelephoneManager来获得。

TelephonyManager mTelMan =(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);

Stringoperator =mTelMan.getNetworkOperator();Stringmcc =operator.substring(0, 3);

Stringmnc =operator.substring(3);

GsmCellLocation location =(GsmCellLocation)mTelMan.getCellLocation();

intcid =location.getCid();intlac =location.getLac();

通过上面的方法,即可获得MCC、MNC、CID、LAC,对照Geolocation API Network Protocol,剩下不多的参数也可以获得,发起请求后根据响应内容即可得到地理位置信息。

请求(Request)的信息如下:

{"cell_towers":[{"mobile_network_code":"00","location_area_code":9733,"mobile_country_code":"460","cell_id":17267},{"mobile_network_code":"00","location_area_code":9733,"mobile_country_code":"460","cell_id":27852},{"mobile_network_code":"00","location_area_code":9733,"mobile_country_code":"460","cell_id":27215},{"mobile_network_code":"00","location_area_code":9733,"mobile_country_code":"460","cell_id":27198},{"mobile_network_code":"00","location_area_code":9484,"mobile_country_code":"460","cell_id":27869},{"mobile_network_code":"00","location_area_code":9508,"mobile_country_code":"460","cell_id":37297},{"mobile_network_code":"00","location_area_code":9733,"mobile_country_code":"460","cell_id":27888}],"host":"maps.google.com","version":"1.1.0"}

响应(Response)的信息如下:

{"location":{"latitude":23.12488,"longitude":113.271907,"accuracy":630.0},"
access_token":"2:61tEAW-rONCT1_W-:JVpp2_jq5a0L-5JK"}

与手机定位不同,WIFI定位主要取决于节点(node)的物理地址(mac address)。与提供TelephoneManager一样,Android也提供了获取WIFI信息的接口:WifiManager。

WifiManager wifiMan =(WifiManager)getSystemService(Context.WIFI_SERVICE);

WifiInfo info =wifiMan.getConnectionInfo();

Stringmac =info.getMacAddress();Stringssid =info.getSSID();

通过上面的方法,即可获得必要的请求参数。注意:根据Geolocation API Network Protocol,请求参数mac_address是指”The mac address of the WiFi node”,而根据WifiInfo.getMacAddress()得到的似乎是本机无线网卡地址,暂时没弄明白,留个悬疑。

暂时假定上面没有问题,发出的请求(Request)信息如下:

{"wifi_towers":[{"mac_address":"00:23:76:AC:41:5D","ssid":"Aspire-NETGEAR"}],"host":"maps.google.com","version":"1.1.0"}

响应(Response)的信息如下:

{"location":{"latitude":23.129075,"longitude":113.264423,"accuracy":140000.0},"access_token":"2:WRr36ynOz_d9mbw5:pRErDAmJXI8l76MU"}

比较两种响应结果,发现相差较小,尽管不如GPS定位精确,但是对于通常的LBS来说,已经基本可用了。

在Android官方文档《Obtaining User Location》中并入Network Location Provider一类,与GPS地位等同。前文中介绍的方法虽然可行,但是需要开发者处理比较多的数据。实际上,不必这么麻烦,还有更简单的方法,android.location中的LocationManager封装了地理位置信息的接口,提供了GPS_PROVIDER和 NETWORK_PROVIDER等。

如果开发的应用需要高精确性,那么可使用GPS_PROVIDER,但这也意味着应用无法在室内使用,待机时间缩短,响应时间稍长等问题;如果开发的应用需要快速反应,对精度要求不怎么高,并且要尽可能节省电量,那么使用NETWORK_PROVIDER是不错的选择。这里提一下,还有一个 PASSIVE_PROVIDER,在实际应用中较少使用。

提到GPS(Global Positioning System),那就顺便说说题外话,由于GPS前身来自于美国军方,后来“军转民”。尽管不收费,但是出于各自国家战略安全和商业利益考量,欧盟发起了伽利略定位系统(Gallileo Postionting System),俄罗斯建立了格洛纳斯(GLONASS),中国也开发了北斗卫星导航系统。尽管呈现出竞争格局,但是目前在非军用移动设备上,–例如手机、MID等,GPS占据了巨大的市场份额。目前来看,Android对GPS的支持还是相当给力,不过也希望未来能够在Android上收到来自北斗的信号。

言归正传,如下代码就是设置从Network中获取位置:

LocationManager mLocMan =(LocationManager)getSystemService(Context.LOCATION_SERVICE);mLocMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, mLocLis);

上面的代码需要对应的权限:android.permission.ACCESS_COARSE_LOCATION,同样,如果是 GPS_PROVIDER,则需要权限android.permission.ACCESS_FINE_LOCATION。如果代码里使用了两个 PROVIDER,则只需要一个权限即可:android.permission.ACCESS_FINE_LOCATION。

以下是整个过程的代码,由于目的只是技术验证,因此未做效率、健壮性等考虑,不过这并不妨碍我们对比四种获取locaiton方式的差异,其中的优劣由读者自行评判:

publicclassLocationActivity extendsActivity {

    

    privatestaticfinalString TAG= "DemoActivity";

 

    @Override

    publicvoidonCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

    }

 

    publicvoidonRequestLocation(View view) {

        switch(view.getId()){

        caseR.id.gpsBtn:

            Log.d(TAG, "GPS button is clicked");

            requestGPSLocation();

            break;

        caseR.id.telBtn:

            Log.d(TAG, "CellID button is clicked");

            requestTelLocation();

            break;

        caseR.id.wifiBtn:

            Log.d(TAG, "WI-FI button is clicked");

            requestWIFILocation();

            break;

        caseR.id.netBtn:

            Log.d(TAG, "Network button is clicked");

            requestNetworkLocation();

            break;

        }

    }

 

    privatevoidrequestTelLocation() {

        TelephonyManager mTelMan = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

        // MCC+MNC. Unreliable on CDMA networks

        String operator = mTelMan.getNetworkOperator();

        String mcc = operator.substring(0, 3);

        String mnc = operator.substring(3);

 

        GsmCellLocation location = (GsmCellLocation) mTelMan.getCellLocation();

        intcid = location.getCid();

        intlac = location.getLac();

 

        JSONObject tower = newJSONObject();

        try{

            tower.put("cell_id", cid);

            tower.put("location_area_code", lac);

            tower.put("mobile_country_code", mcc);

            tower.put("mobile_network_code", mnc);

        } catch(JSONException e) {

            Log.e(TAG, "call JSONObject's put failed", e);

        }

 

        JSONArray array = newJSONArray();

        array.put(tower);

 

        List<NeighboringCellInfo> list = mTelMan.getNeighboringCellInfo();

        Iterator<NeighboringCellInfo> iter = list.iterator();

        NeighboringCellInfo cellInfo;

        JSONObject tempTower;

        while(iter.hasNext()) {

            cellInfo = iter.next();

            tempTower = newJSONObject();

            try{

                tempTower.put("cell_id", cellInfo.getCid());

                tempTower.put("location_area_code", cellInfo.getLac());

                tempTower.put("mobile_country_code", mcc);

                tempTower.put("mobile_network_code", mnc);

            } catch(JSONException e) {

                Log.e(TAG, "call JSONObject's put failed", e);

            }

            array.put(tempTower);

        }

 

        JSONObject object = createJSONObject("cell_towers", array);

        requestLocation(object);

    }

 

    privatevoidrequestWIFILocation() {

        WifiManager wifiMan = (WifiManager) getSystemService(Context.WIFI_SERVICE);

        WifiInfo info = wifiMan.getConnectionInfo();

        String mac = info.getMacAddress();

        String ssid = info.getSSID();

 

        JSONObject wifi = newJSONObject();

        try{

            wifi.put("mac_address", mac);

            wifi.put("ssid", ssid);

        } catch(JSONException e) {

            e.printStackTrace();

        }

 

        JSONArray array = newJSONArray();

        array.put(wifi);

 

        JSONObject object = createJSONObject("wifi_towers", array);

        requestLocation(object);

    }

 

    privatevoidrequestLocation(JSONObject object) {

        Log.d(TAG, "requestLocation: "+ object.toString());

        HttpClient client = newDefaultHttpClient();

        HttpPost post = newHttpPost("http://www.google.com/loc/json");

        try{

            StringEntity entity = newStringEntity(object.toString());

            post.setEntity(entity);

        } catch(UnsupportedEncodingException e) {

            e.printStackTrace();

        }

 

        try{

            HttpResponse resp = client.execute(post);

            HttpEntity entity = resp.getEntity();

            BufferedReader br = newBufferedReader(newInputStreamReader(entity.getContent()));

            StringBuffer buffer = newStringBuffer();

            String result = br.readLine();

            while(result != null) {

                buffer.append(result);

                result = br.readLine();

            }

 

            Log.d(TAG, buffer.toString());

        } catch(ClientProtocolException e) {

            e.printStackTrace();

        } catch(IOException e) {

            e.printStackTrace();

        }

    }

 

    privateJSONObject createJSONObject(String arrayName, JSONArray array) {

        JSONObject object = newJSONObject();

        try{

            object.put("version", "1.1.0");

            object.put("host", "maps.google.com");

            object.put(arrayName, array);

        } catch(JSONException e) {

            Log.e(TAG, "call JSONObject's put failed", e);

        }

        returnobject;

    }

 

    privatevoidrequestGPSLocation() {

        LocationManager mLocMan = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        mLocMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000 * 60, 100, mLocLis);

    }

 

    privatevoidrequestNetworkLocation() {

        LocationManager mLocMan = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        mLocMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000 * 60, 100, mLocLis);

    }

 

    privateLocationListener mLocLis= newLocationListener() {

 

        @Override

        publicvoidonStatusChanged(String provider, intstatus, Bundle extras) {

            Log.d(TAG, "onStatusChanged, provider = "+ provider);

        }

 

        @Override

        publicvoidonProviderEnabled(String provider) {

            Log.d(TAG, "onProviderEnabled, provider = "+ provider);

        }

 

        @Override

        publicvoidonProviderDisabled(String provider) {

            Log.d(TAG, "onProviderDisabled, provider = "+ provider);

        }

 

        @Override

        publicvoidonLocationChanged(Location location) {

            doublelatitude = location.getLatitude();

            doublelongitude = location.getLongitude();

            Log.d(TAG, "latitude: "+ latitude + ", longitude: "+ longitude);

        }

    };

}

你可能感兴趣的:(Android应用开发之(Location in Android-定位))