編輯:關於Android編程
前言:
在移動客戶端的開發中,地理位置定位是一個非常重要的環節,有些時候用戶可能會限制web app或者Android app的一些權限,或者由於信號不佳的原因無法獲得准確的GPS位置,甚至為了省電,用戶可能對開啟GPS開關可能會有抵觸情緒。但是不能因為GPS的種種限制就放棄了對用戶位置的追蹤。要通過一切能發送出信號的物體盡可能准確的獲取到用戶的位置,有時可以犧牲一些精度,對於大數據和用戶地區分布分析來說,有一個大體的位置已經夠分析人員使用,而且繞開GPS的重重壁壘,為數據的完整性提供可靠方案
開發背景:
有些項目經理反應目前版本的app耗電量過大,狀態欄的GPS標識一直在閃爍。於是為了降低軟件的耗電量,同時保證定位的經緯度,並且不能僅依賴GPS進行定位,在用戶不願意開啟GPS開關時,需要通過手機附近的基站,wifi熱點,甚至IP地址進行定位。對提高用戶體驗有非常重要的作用。(之前的獲取經緯度策略是使用Android提供的API,通過開啟一個IntentService,在Service中開始一個死循環的子線程不停的進行GPS位置獲取。
實現效果:
實現的效果可以參考高德地圖,高德地圖的使用體驗我覺得是不錯的,甚至完全不需要開啟GPS開關,就可以准確定位到道路街道,誤差一般不會超過50米,我猜測高德的實現方案無非也是wifi加基站。我做過一次測試,在一個收不到GPS信號的地下室中(但是附近有熱點和基站),在非聯網狀態下打開高德地圖,高德會把我定位到上次成功的位置,當我開啟了網絡開關,馬上位置就八九不離十了。
定位原理:
原理其實很簡單,在GPS定位沒有成功時(GPS定位一般是最准確的,具有最高的優先級),用手機掃描附近的基站,記錄下附近基站的mcc,lac,cid等參數,用於識別該基站,通過數據庫查詢到該基站的GPS位置,用收到該基站的信號強度當做相對基站的距離,如果搜索到附近的基站比較多,就可以以基站的位置為圓心,以信號強度為半徑進行畫圓,多個基站畫出圓圈重合的部分就是當前最可能的位置。同理wifi熱點定位也是一樣,不過用於識別熱點的換成了MAC地址,一樣用信號的強度作為距離的表示。
定位策略:
1、首先在開啟app的時候,取系統中記錄的最近一次GPS定位位置,如果取得的GPS精確度小於200m,那麼就棄用,如果取到了正常的結果,就不需要位置監聽和基站、wifi定位了,但是這在多數情況下是取不到的,尤其是剛剛打開GPS開關沒多久的時候,這時候從SharedPreference裡取出上次app定位成功的地點先頂上。
2、如果上一步沒有馬上取得當前的GPS位置,就會開啟一個監聽不斷監測位置的變化,然而這個監聽的要求是高靈敏度,高精度,高耗電的,此時狀態欄的GPS標識會開始閃爍,直到取得第一次精確的GPS位置,如果取得的GPS精確度小於200m,那麼就棄用。
3、在開啟監聽器的同時,開始掃描手機附近的基站信息和wifi熱點信息,獲取每個基站和熱點的信號強度,通過數據庫查詢出每個基站和熱點的GPS信息,然後通過相關數學算法求出手機的大致經緯度(在本文中這些是通過谷歌提供的API進行實現)但是如果取得的GPS精確度小於200m,那麼就棄用。
4、監聽器獲取到當前GPS定位成功後,就關掉當前的監聽器,然後開啟一個策略不同的監聽器,新監聽器的主要特點是低精度,低靈敏度,低耗電,此時狀態欄的GPS閃爍停止,但是定位的精度大幅降低,可能會跑到好幾公裡以外。
5、為了防止監聽器後台監聽耗電,被認為成後台偷跑電量的程序,需要在程序放到後台和關閉的時候關閉響應的監聽。
6、渠道優先級:GPS>基站>WIFI熱點>IP,這只是一個大體的安排,實際上在GPS信號不太好的地方,wifi加基站的定位結果可能要比GPS准確的多,所以具體采用哪種定位方式需要看他們的accuracy值
7、對於一些國產電信公司定制機,一般沒有安裝谷歌框架的,使用谷歌原生框架獲取GPS位置,基站定位,wifi熱點定位不變。
能正常定位的情景:
1、如果用戶在開闊的室外(GPS開,wifi可關可開,sim卡可以不插,可以不聯網),並且天氣晴朗,一般可以收到良好的GPS信號,此時的定位結果應是GPS定位信號
2、如果用戶在封閉的室內,且無法收到GPS信號,此時手機必須要聯網才能獲得定位信息,而且一下條件要二選一(1)手機要插sim卡且有信號(2)手機開啟wifi開關且附近有熱點。如果兩種信號都收不到,那麼會根據ip進行定位,且要在手機沒有開VPN網絡的時候
無法定位的情況:
1、用戶在封閉的室內,且沒有聯網。但此時會采用SharePreference中的記錄
2、用戶在室外,有大雨,大霧,陰天,且沒有聯網。或者聯網了但手機沒有信號,附近沒有wifi
運行環境:
1、首先需要引入Google Service gcm location SDK,引入這個SDK的原因是:省電。並且防止狀態欄上的GPS標識一直閃爍,谷歌的官方文檔說的很直白,不推薦使用Android原生的接口,原文如下
theGoogle Play services location APIsare preferred over the Android framework location APIs (android.location) as a way of adding location awareness to your app. If you are currently using the Android framework location APIs, you are strongly encouraged to switch to the Google Play services location APIs as soon as possible.
該SDK的官方文檔地址:https://developer.android.com/training/location/index.html
引入該SDK需要在gradle文件裡添加
compile 'com.google.android.gms:play-services-location:8.4.0'
注意:許多國產手機,尤其是運營商定制機裡往往沒有內置谷歌框架,此時的表現是一直無法connect GPS,這時候就不能用谷歌的SDK,而需要使用Android原生API。
2、開啟相關權限
注意:相關權限一旦用戶沒有授予或授予後駁回,可能會有crash的情況
主要的暴露方法:
void onCreateGPS(Application context)用於開啟一次gps位置獲取,程序會先獲取最近一次的GPS位置,如果失敗的話就開啟GPS追蹤和基站,wifi定位等
void restartGPS(Application context)在進入一些對GPS要求比較高的頁面時,重新獲取當前的准確位置的方法,本方法會先暫停掉當前的GPS追蹤,然後重新獲取一遍位置
void stopGPS()在退出app時,為了節省電力而徹底關閉GPS追蹤
void pauseGPS()當程序放到後台時為了防止後台耗電而停止GPS跟蹤
GPS的後台省電:
要監聽app是否被放到了後台,Android系統沒有給出相關接口或廣播,只能通過Activity的Stop和Start的關系來判斷,一般來說,一個Stop後如果緊跟著一個start那麼就可以說這是內部的轉換,但是如果只Stop沒有start那麼就很有可能是放到後台了。
第二種監聽方法是,在Activity stop的時候觀察一下本Activity是否還在棧頂,如果還在棧頂那麼可以說明被放到後台了,下面這段代碼就是這個思想,需要程序裡用到的Activity基本都以這個BaseActivity為父類
import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.util.Log; import com.imaginato.qravedconsumer.service.AlxLocationManager; import com.imaginato.qravedconsumer.utils.JLogUtils; import java.util.List; public class BaseActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected void onDestroy() { super.onDestroy(); } @Override protected void onPause() { super.onPause(); } @Override protected void onResume() { super.onResume(); } @Override protected void onStart() { super.onStart(); } @Override protected void onStop() { super.onStop(); if (!isAppOnForeground(this)) { //app 進入後台 JLogUtils.i("AlexLocation","程序進入後台運行"); //全局變量isActive = false 記錄當前已經進入後台 isRunningBackGround = true; AlxLocationManager.pauseGPS(); } } @Override protected void onRestart() { super.onRestart(); //從後台恢復,如果還沒有很好的獲得經緯度就繼續獲得 if(isRunningBackGround && AlxLocationManager.manager!=null && AlxLocationManager.manager.currentStatus!= AlxLocationManager.STATUS.TRYING_FIRST)AlxLocationManager.onCreateGPS(getApplication()); isRunningBackGround = false; } /** * 程序是否在前台運行 * * @return */ public static boolean isAppOnForeground(Activity activity) { // Returns a list of application processes that are running on the // device ActivityManager activityManager = (ActivityManager) activity.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); String packageName = activity.getApplicationContext().getPackageName(); List appProcesses = activityManager .getRunningAppProcesses(); if (appProcesses == null) return false; for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { // The name of the process that this object is associated with. if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { return true; } } return false; } }
谷歌API相關:
谷歌那裡有全世界幾乎所有基站的GPS位置,而且谷歌的數據庫裡還有全世界許多wifi熱點的GPS位置,還有IP的歸屬地,所以對於我現在的支援東南亞國家的app來說使用谷歌的數據庫再合適不過了。
谷歌API的主頁:https://developers.google.com/maps/documentation/geolocation/intro
1、首先先申請一個密鑰,申請非常簡單,只要有一個谷歌賬號,點一下“申請密鑰”,幾下就好了
2、使用postman等測試工具,使用谷歌給出的json示例看看好不好用
{ "homeMobileCountryCode": 310,//中國一般是460 "homeMobileNetworkCode": 260,//使用手機獲取 "radioType": "gsm",//這個填起來要特別小心,寧願不填,如果填錯了的話有可能無法獲得定位信息 "carrier": "T-Mobile",//運營商,作用不大 "cellTowers": [//基站列表 { "cellId": 39627456,//必填項,比較重要 "locationAreaCode": 40495, "mobileCountryCode": 310,//中國是460 "mobileNetworkCode": 260, "age": 0, "signalStrength": -95//相當於距離 } ], "wifiAccessPoints": [//wifi熱點 { "macAddress": "01:23:45:67:89:AB", "signalStrength": 8, "age": 0, "signalToNoiseRatio": -65, "channel": 8 }, { "macAddress": "01:23:45:67:89:AC", "signalStrength": 4, "age": 0 } ] }
post接口地址:
https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR_API_KEY
這裡要注意一下,以前google也提供了一個類似的接口,但是從2012年開始就棄用了,很多老博客裡還寫著這個過時的接口,在江湖上流傳已久,欺騙了不少無辜群眾:http://www.google.com/loc/json
postman測試如圖:(注意黃色的部分)
如果發送的數據格式有問題,谷歌會返回一些錯誤碼,如下:
dailyLimitExceeded
usageLimits
403
您已超出了您的每日限制。
keyInvalid
usageLimits
400
您的 API 密鑰對於 Google Maps Geolocation API 無效。請確保您已加入了完整的密鑰,而且您已購買該 API 或已啟用收費和激活 API以獲得免費配額。
userRateLimitExceeded
usageLimits
403
您已超出您在 API 控制台中配置的每個用戶每秒請求數限制。此限制應配置為防止單個或一小部分用戶耗盡您每天的配額,同時還允許所有用戶都能進行合理的訪問。
notFound
geolocation
404
請求有效,但未返回任何結果。
parseError
global
400
請求正文不是有效的 JSON。請參閱請求正文部分以了解每個字段的詳情。
開發中遇到的幾個大坑:
1、谷歌的API接口只接收json的數據,這對安卓這邊的發送造成了很多麻煩,一開始我用httpClient,經常碰到415異常,後來使用xUtils中的HttpUtil做json,經常收到404錯誤。後來知道,415異常應該是json放的位置不對,404異常不是post請求的問題,而是根據當前的基站,wifi信息沒有查詢到符合條件的地理位置,不是找不到服務器。
2、當我開開心心的把程序發給客戶看以後,客戶居然說定位太不准了,偏移了15公裡,後來經過一番檢查才發現,通過GPS獲取到位置的accuracy太大,經常是2000,我一直以為GPS信號准確無比,後來才知道這也要對比精確度的。最關鍵的是我在第一次獲取到精確的經緯度後開啟了一個省電策略,而這個省電策略獲得的經緯度精確度非常差,而我拿他當當前的經緯度來用,所以這種策略是不行的。省電模式要慎用!!
3、在測試位置過程中發現不管怎樣定位都會偏移一塊位置,我是在這個網站上根據經緯度查看地圖坐標:http://www.gpsspg.com/maps.htm
後來我發現換一下輸入的經緯度類型就好了,如下圖黃色的部分
後來我發現,GPS獲得的經緯度所用的坐標系和谷歌地圖、百度地圖上的坐標系是不同的,GPS硬件獲得的經緯度是WGS-84標准,國際通用,可以看成是正常的GPS位置,在google earth上全部坐標,谷歌地圖上除中國以外的坐標均為這個標准,但是谷歌地圖的中國坐標使用的是GCJ-02標准,是天朝測繪局在WGS-84的基礎上加入隨機偏差加密後的一種經緯度,會隨機偏移一公裡左右,所以導致無論怎樣定位總是顯示不對。而百度地圖在GCJ-02上又加入了自己的加密算法,變成了BD-09標准,另外其他很多的地圖都有自己的加密方法。這些都是硬件獲取經緯度在地圖上指示不對的原因,網上有免費做轉換的網站,在這裡推薦一個:http://map.yanue.net/gps.html,如果發現GPS定位結果總是有誤差,可以用這個轉換一下。
關於GPS隨意偏移,找到一篇比較好的文章,有興趣的童鞋可以看一下:http://yanue.net/post-121.html
關於這幾種坐標系的相關轉換,網上除了有免費api以外還有一些公開算法,鑒於法律原因這裡不再多講,有需要的童鞋可以百度一下
4、最大的一個坑是:google gcm service也就是com.google.android.gms:play-services-location:8.4.0這個library是谷歌推薦的替換Android原生的一個GPS方案,但是這個SDK真的是太爛了太爛了太爛了!!!本來Android原生API可以支持network passive gps定位,也就是沒開GPS開關也可以根據附近的網絡狀況定位,准確度很高,但是這個庫引進來之後只能根據GPS定位,而且很慢!!!很慢很慢,最坑的是:不准確!!!感覺這個SDK對它監測到的經緯度加上了偏移,不如Android原生的准確度高,總會偏移出去100m左右,真的讓人很頭疼,總之,這個庫還是不用為好。
主要代碼:
下面是定位主要的實現代碼,注釋已經比較詳盡,這裡就不多贅述。
import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.location.Location; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.telephony.CellIdentityCdma; import android.telephony.CellIdentityGsm; import android.telephony.CellIdentityLte; import android.telephony.CellIdentityWcdma; import android.telephony.CellInfo; import android.telephony.CellInfoCdma; import android.telephony.CellInfoGsm; import android.telephony.CellInfoLte; import android.telephony.CellInfoWcdma; import android.telephony.CellLocation; import android.telephony.NeighboringCellInfo; import android.telephony.TelephonyManager; import android.telephony.cdma.CdmaCellLocation; import android.telephony.gsm.GsmCellLocation; import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import com.imaginato.location.IMGLocationUpdateService; import com.imaginato.qravedconsumer.application.QravedApplication; import com.imaginato.qravedconsumer.utils.JViewUtils; import com.lidroid.xutils.HttpUtils; import com.lidroid.xutils.exception.HttpException; import com.lidroid.xutils.http.RequestParams; import com.lidroid.xutils.http.ResponseInfo; import com.lidroid.xutils.http.callback.RequestCallBack; import com.lidroid.xutils.http.client.HttpRequest; import com.qraved.app.R; import org.apache.http.entity.StringEntity; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; /** * Created by AlexLocation on 2016/6/6. */ public class AlxLocationManager implements GoogleApiClient.ConnectionCallbacks,GoogleApiClient.OnConnectionFailedListener,LocationListener { //下面這三個是沒拿到第一次經緯度的時候耗電抓取經緯度的策略 private static final int MAX_deviation = 100; private static final int FAST_UPDATE_INTERVAL = 10000; // 10 sec 平均更新時間 private static final int FATEST_INTERVAL = 5000; // 5 sec 最短更新時間 private static final int FAST_DISPLACEMENT = 1; // 1 meters 為最小偵聽距離 //下面這個是省電抓取經緯度的策略 private static final int SLOW_UPDATE_INTERVAL = 60000; // 60 sec 平均更新時間 private static final int SLOW_INTERVAL = 30000; // 30 sec 最短更新時間 private static final int SLOW_DISPLACEMENT = 500; // 500 meters 為最小偵聽距離 private Application context;//防止內存洩漏,不使用activity的引用 private GoogleApiClient mGoogleApiClient; public static AlxLocationManager manager;//單例模式 public STATUS currentStatus = STATUS.NOT_CONNECT; public float accuracy = 99999;//沒有獲得精度的時候是-1 public double lastLatitude; public double lastLongitude; public enum STATUS{ NOT_CONNECT,//沒有連接相關硬件成功 TRYING_FIRST,//第一次獲取地理位置,此時gps指示圖標閃爍,耗電量大 LOW_POWER,//開啟app拿到精確度的GPS之後,開啟省電模式 NOT_TRACK//當前沒有開啟跟蹤模式 } public final static boolean isDebugging = true;//調試模式開關 /** * 注冊gps監聽服務 * @param context */ public static void onCreateGPS(final Application context){ if(manager!=null && manager.mGoogleApiClient!=null)return; Log.i("AlexLocation","准備開啟gps"); manager = new AlxLocationManager(); manager.context = context; manager.mGoogleApiClient = new GoogleApiClient.Builder(context) .addConnectionCallbacks(manager) .addOnConnectionFailedListener(manager) .addApi(LocationServices.API) .build(); manager.mGoogleApiClient.connect(); new Handler().postDelayed(new Runnable() { @Override public void run() { if(manager!=null && manager.currentStatus!=STATUS.NOT_CONNECT)return;//如果連接GPS硬件到現在還沒成功 Log.i("AlexLocation","該手機沒有安裝谷歌框架服務,准備啟動舊版service"); manager.context.startService(new Intent(manager.context, IMGLocationUpdateService.class));//使用安卓原生API獲取地理位置 new AsyncTask(){//在子線程中獲取附近的基站和wifi信息 @Override protected String doInBackground(Void... params) { GeoLocationAPI geoLocationAPI = null; try { geoLocationAPI = getCellInfo(manager.context);//得到基站信息,通過基站進行定位 }catch (Exception e){ Log.i("AlexLocation","獲取附近基站信息出現異常",e); } if(geoLocationAPI ==null){ Log.i("AlexLocation","獲取基站信息失敗"); return "{}"; } getWifiInfo(manager.context, geoLocationAPI); String json = geoLocationAPI.toJson();//這裡使用gson.toJson()會被混淆,推薦使用手動拼json Log.i("AlexLocation","准備發給goggle的json是"+json); return json; } @Override protected void onPostExecute(String json) { super.onPostExecute(json); //開啟子線程請求網絡 if(manager!=null && context!=null)manager.sendJsonByPost(json,"https://www.googleapis.com/geolocation/v1/geolocate?key="+context.getString(R.string.google_location)); } }.execute(); } },9000); } /** * 進入某些頁面,重新刷GPS * @param context */ public static void restartGPS(Application context){ stopGPS();//先停止當前的GPS onCreateGPS(context);//重啟GPS } /** * 停止gps服務,用來省電 */ public static void stopGPS(){ if(manager==null)return; pauseGPS(); manager.mGoogleApiClient = null; manager = null; } /** * 當app被放到後台時,暫停GPS */ public static void pauseGPS(){ Log.i("Alex","准備暫停GPS"); if(manager==null || manager.mGoogleApiClient==null || manager.currentStatus==STATUS.NOT_CONNECT || manager.currentStatus == STATUS.NOT_TRACK)return; try { LocationServices.FusedLocationApi.removeLocationUpdates(manager.mGoogleApiClient, manager); manager.currentStatus = STATUS.NOT_CONNECT; if (manager.mGoogleApiClient.isConnected() || manager.mGoogleApiClient.isConnecting()) manager.mGoogleApiClient.disconnect(); manager.mGoogleApiClient = null; }catch (Exception e) { Log.i("AlexLocation","暫停GPS出現異常",e); } } @Override public void onConnected(@Nullable Bundle bundle) { Log.i("AlexLocation","connect gps成功"); if(currentStatus != STATUS.NOT_CONNECT)return;//有些手機會多次連接成功 currentStatus = STATUS.TRYING_FIRST; if(!getCurrentLocation()) {//得到當前gps並記錄 //如果沒有成功拿到當前經緯度,那麼就通過實時位置監聽去不斷的拿,直到拿到為止 //如果沒有拿到經緯度,就一直監聽 LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, createFastLocationRequest(), this);//創建位置監聽 new AsyncTask (){//在子線程中獲取附近的基站和wifi信息 @Override protected String doInBackground(Void... params) { GeoLocationAPI geoLocationAPI = null; try { geoLocationAPI = getCellInfo(context);//得到基站信息,通過基站進行定位 }catch (Exception e){ Log.i("AlexLocation","獲取附近基站信息出現異常",e); } if(geoLocationAPI ==null){ Log.i("AlexLocation","獲取基站信息失敗"); return "{}"; } getWifiInfo(context, geoLocationAPI); String json = geoLocationAPI.toJson();//這裡使用gson.toJson()會被混淆,推薦使用手動拼json Log.i("AlexLocation","准備發給goggle的json是"+json); return json; } @Override protected void onPostExecute(String json) { super.onPostExecute(json); //發送json數據到谷歌,等待谷歌返回結果 sendJsonByPost(json,"https://www.googleapis.com/geolocation/v1/geolocate?key="+context.getString(R.string.google_location)); } }.execute(); }else {//獲取了最後一次硬件記錄的經緯度,不進行追蹤 currentStatus = STATUS.NOT_TRACK; } } /** * 拿到最近一次的硬件經緯度記錄,只用精確度足夠高的時候才會采用這種定位 * @return */ public boolean getCurrentLocation(){ Location mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); Log.i("AlexLocation","得到last Location的gps是=="+mLastLocation); if (mLastLocation == null) return false;//在少數情況下這裡有可能是null double latitude = mLastLocation.getLatitude();//緯度 double longitude = mLastLocation.getLongitude();//經度 double altitude = mLastLocation.getAltitude();//海拔 float last_accuracy = mLastLocation.getAccuracy();//精度 Log.i("AlexLocation","last Location的精度是"+last_accuracy); String provider = mLastLocation.getProvider();//傳感器 float bearing = mLastLocation.getBearing(); float speed = mLastLocation.getSpeed();//速度 if(isDebugging)JViewUtils.showToast(context,"獲取到last location經緯度","緯度"+latitude+" 經度"+longitude+ "精確度"+last_accuracy); Log.i("AlexLocation","獲取last location成功,緯度=="+latitude+" 經度"+longitude+" 海拔"+altitude+" 傳感器"+provider+" 速度"+speed+ "精確度"+last_accuracy); if(last_accuracy 300)){//精度如果太小就放棄,如果精度提高或者移動的距離超過300m也更新 recordLocation(context,location.getLatitude(),location.getLongitude(),location.getAccuracy()); this.accuracy = location.getAccuracy(); }else { Log.i("Alex","精確度太低,准備放棄最新的位置"); } if(location.getAccuracy()>50 || currentStatus!=STATUS.TRYING_FIRST)return;//如果現在是高電量模式,那麼就停止當前監聽,采用低電量監聽 LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient,this);//成功獲取到之後就降低頻率來省電 Log.i("AlexLocation","准備開啟省電策略"); currentStatus = STATUS.LOW_POWER; LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, createLowPowerLocationRequest(), this);//創建位置監聽 } @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { } /** * 當手機移動後通過回調獲得移動後的經緯度,在這個函數裡配置相應的刷新頻率 * 建立一個耗電的,盡快的拿到當前經緯度的策略 */ private static LocationRequest createFastLocationRequest() { LocationRequest mLocationRequest = LocationRequest.create(); mLocationRequest.setInterval(FAST_UPDATE_INTERVAL); mLocationRequest.setFastestInterval(FATEST_INTERVAL); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);//耗電模式 mLocationRequest.setSmallestDisplacement(FAST_DISPLACEMENT); return mLocationRequest; } /** * 建立一個省電的跟蹤經緯度的策略 * 注意此方法要慎用,使用了低電量模式以後,精確度會大幅下降 * @return */ private static LocationRequest createLowPowerLocationRequest() { LocationRequest mLocationRequest = LocationRequest.create(); mLocationRequest.setInterval(SLOW_UPDATE_INTERVAL); mLocationRequest.setFastestInterval(SLOW_INTERVAL); mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);//省電模式,PRIORITY_LOW_POWER要慎用,會導致定位精確度大幅下降,能偏到好幾十公裡以外去 mLocationRequest.setSmallestDisplacement(SLOW_DISPLACEMENT); return mLocationRequest; } /** * 將獲取到的經緯度記錄在本地 * @param context */ public static void recordLocation(Context context, double latitude, double longitude,float accuracy){ SharedPreferences sharedPreferences = context.getSharedPreferences("QravedApplicationSetting", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("latitude", String.valueOf(latitude)); editor.putString("longitude", String.valueOf(longitude)); editor.putFloat("accuracy",accuracy); editor.apply(); QravedApplication.getPhoneConfiguration().getLocation().latitude = latitude; QravedApplication.getPhoneConfiguration().getLocation().longitude = longitude; if(manager==null)return; manager.lastLatitude = latitude; manager.lastLongitude = longitude; } /** * 從sharedPreference中獲取上次開啟app時候的地理位置,前面的是緯度,後面的是經度 * @return */ public static double[] getOldLocation(Context context){ SharedPreferences sharedPreferences = context.getSharedPreferences("QravedApplicationSetting", Context.MODE_PRIVATE); String latitudeStr = sharedPreferences.getString("latitude",""); String longitudeStr = sharedPreferences.getString("longitude",""); float accuracy = sharedPreferences.getFloat("accuracy",9999); Log.i("AlexLocation","SP裡的精確度"+accuracy); if(latitudeStr.length()==0 || longitudeStr.length()==0)return null; double[] latlng = {-1,-1}; try { latlng[0] = new Double(latitudeStr); latlng[1] = new Double(longitudeStr); }catch (Exception e){ Log.i("AlexLocation","解析經緯度出現異常",e); } return latlng; } /** * 得到附近的基站信息,准備傳給谷歌 */ public static GeoLocationAPI getCellInfo(Context context){ //通過TelephonyManager 獲取lac:mcc:mnc:cell-id GeoLocationAPI cellInfo = new GeoLocationAPI(); TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if(mTelephonyManager==null)return cellInfo; // 返回值MCC + MNC /*# MCC,Mobile Country Code,移動國家代碼(中國的為460); * # MNC,Mobile Network Code,移動網絡號碼(中國移動為0,中國聯通為1,中國電信為2); * # LAC,Location Area Code,位置區域碼; * # CID,Cell Identity,基站編號; * # BSSS,Base station signal strength,基站信號強度。 */ String operator = mTelephonyManager.getNetworkOperator(); Log.i("AlexLocation","獲取的基站信息是"+operator); if(operator==null || operator.length()<5){ Log.i("AlexLocation","獲取基站信息有問題,可能是手機沒插sim卡"); return cellInfo; } int mcc = Integer.parseInt(operator.substring(0, 3)); int mnc = Integer.parseInt(operator.substring(3)); int lac; int cellId; // 中國移動和中國聯通獲取LAC、CID的方式 CellLocation cellLocation = mTelephonyManager.getCellLocation(); if(cellLocation==null){ Log.i("AlexLocation","手機沒插sim卡吧"); return cellInfo; } if(mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) { Log.i("AlexLocation","當前是gsm基站"); GsmCellLocation location = (GsmCellLocation)cellLocation; lac = location.getLac(); cellId = location.getCid(); //這些東西非常重要,是根據基站獲得定位的重要依據 Log.i("AlexLocation", " MCC移動國家代碼 = " + mcc + "\t MNC移動網絡號碼 = " + mnc + "\t LAC位置區域碼 = " + lac + "\t CID基站編號 = " + cellId); }else if(mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { // 中國電信獲取LAC、CID的方式 Log.i("AlexLocation","現在是cdma基站"); CdmaCellLocation location1 = (CdmaCellLocation) mTelephonyManager.getCellLocation(); lac = location1.getNetworkId(); cellId = location1.getBaseStationId(); cellId /= 16; }else { Log.i("AlexLocation","現在不知道是什麼基站"); return cellInfo; } cellInfo.radioType = determine2g3g4g(context);//這裡填要慎重,要是填的不對就會報404 notFound cellInfo.homeMobileCountryCode = mcc; cellInfo.homeMobileNetworkCode = mnc; cellInfo.carrier = getCarrier(operator); cellInfo.considerIp = considerIP(context);//這裡要判斷是否采用了vpn,在wifi的時候可以用ip輔助定位,但是如果是用的2g3g4g信號那就只能用基站,ip會不准 ArrayList towers = new ArrayList<>(1); GoogleCellTower bigTower = new GoogleCellTower();//這個塔是當前連接的塔,只有一個,但是對定位有決定性的作用 bigTower.cellId = cellId; bigTower.mobileCountryCode = mcc; bigTower.mobileNetworkCode = mnc; bigTower.locationAreaCode = lac; bigTower.signalStrength = 0; towers.add(bigTower); cellInfo.cellTowers = towers; // 獲取鄰區基站信息 if(Build.VERSION.SDK_INT<17) {//低版的android系統使用getNeighboringCellInfo方法 List infos = mTelephonyManager.getNeighboringCellInfo(); if(infos==null){ Log.i("AlexLocation","手機型號不支持基站定位1"); return cellInfo; } if(infos.size()==0)return cellInfo;//附近沒有基站 towers = new ArrayList<>(infos.size()); StringBuffer sb = new StringBuffer("附近基站總數 : " + infos.size() + "\n"); for (NeighboringCellInfo info1 : infos) { // 根據鄰區總數進行循環 GoogleCellTower tower = new GoogleCellTower(); sb.append(" LAC : " + info1.getLac()); // 取出當前鄰區的LAC tower.locationAreaCode = info1.getLac(); tower.mobileCountryCode = mcc; tower.mobileNetworkCode = mnc; tower.signalStrength = info1.getRssi(); sb.append(" CID : " + info1.getCid()); // 取出當前鄰區的CID tower.cellId = info1.getCid(); sb.append(" BSSS : " + (-113 + 2 * info1.getRssi()) + "\n"); // 獲取鄰區基站信號強度 towers.add(tower); } Log.i("AlexLocation","基站信息是"+sb); }else {//高版android系統使用getAllCellInfo方法,並且對基站的類型加以區分 List infos = mTelephonyManager.getAllCellInfo(); if(infos!=null) { if(infos.size()==0)return cellInfo; towers = new ArrayList<>(infos.size()); for (CellInfo i : infos) { // 根據鄰區總數進行循環 Log.i("AlexLocation", "附近基站信息是" + i.toString()); GoogleCellTower tower = new GoogleCellTower(); if(i instanceof CellInfoGsm){//這裡的塔分為好幾種類型 Log.i("AlexLocation","現在是gsm基站"); CellIdentityGsm cellIdentityGsm = ((CellInfoGsm)i).getCellIdentity();//從這個類裡面可以取出好多有用的東西 if(cellIdentityGsm==null)continue; tower.locationAreaCode = cellIdentityGsm.getLac(); tower.mobileCountryCode = cellIdentityGsm.getMcc(); tower.mobileNetworkCode = cellIdentityGsm.getMnc(); tower.signalStrength = 0; tower.cellId = cellIdentityGsm.getCid(); }else if(i instanceof CellInfoCdma){ Log.i("AlexLocation","現在是cdma基站"); CellIdentityCdma cellIdentityCdma = ((CellInfoCdma)i).getCellIdentity(); if(cellIdentityCdma==null)continue; tower.locationAreaCode = lac; tower.mobileCountryCode = mcc; tower.mobileNetworkCode = cellIdentityCdma.getSystemId();//cdma用sid,是系統識別碼,每個地級市只有一個sid,是唯一的。 tower.signalStrength = 0; cellIdentityCdma.getNetworkId();//NID是網絡識別碼,由各本地網管理,也就是由地級分公司分配。每個地級市可能有1到3個nid。 tower.cellId = cellIdentityCdma.getBasestationId();//cdma用bid,表示的是網絡中的某一個小區,可以理解為基站。 }else if(i instanceof CellInfoLte) { Log.i("AlexLocation", "現在是lte基站"); CellIdentityLte cellIdentityLte = ((CellInfoLte) i).getCellIdentity(); if(cellIdentityLte==null)continue; tower.locationAreaCode = lac; tower.mobileCountryCode = cellIdentityLte.getMcc(); tower.mobileNetworkCode = cellIdentityLte.getMnc(); tower.cellId = cellIdentityLte.getCi(); tower.signalStrength = 0; }else if(i instanceof CellInfoWcdma && Build.VERSION.SDK_INT>=18){ Log.i("AlexLocation","現在是wcdma基站"); CellIdentityWcdma cellIdentityWcdma = ((CellInfoWcdma)i).getCellIdentity(); if(cellIdentityWcdma==null)continue; tower.locationAreaCode = cellIdentityWcdma.getLac(); tower.mobileCountryCode = cellIdentityWcdma.getMcc(); tower.mobileNetworkCode = cellIdentityWcdma.getMnc(); tower.cellId = cellIdentityWcdma.getCid(); tower.signalStrength = 0; }else { Log.i("AlexLocation","不知道現在是啥基站"); } towers.add(tower); } }else {//有些手機拿不到的話,就用廢棄的方法,有時候即使手機支持,getNeighboringCellInfo的返回結果也常常是null Log.i("AlexLocation","通過高版本SDK無法拿到基站信息,准備用低版本的方法"); List infos2 = mTelephonyManager.getNeighboringCellInfo(); if(infos2==null || infos2.size()==0){ Log.i("AlexLocation","該手機確實不支持基站定位,已經無能為力了"); return cellInfo; } towers = new ArrayList<>(infos2.size()); StringBuffer sb = new StringBuffer("附近基站總數 : " + infos2.size() + "\n"); for (NeighboringCellInfo i : infos2) { // 根據鄰區總數進行循環 GoogleCellTower tower = new GoogleCellTower(); sb.append(" LAC : " + i.getLac()); // 取出當前鄰區的LAC tower.age = 0; tower.locationAreaCode = i.getLac(); tower.mobileCountryCode = mcc; tower.mobileNetworkCode = mnc; sb.append(" CID : " + i.getCid()); // 取出當前鄰區的CID tower.cellId = i.getCid(); sb.append(" BSSS : " + (-113 + 2 * i.getRssi()) + "\n"); // 獲取鄰區基站信號強度 towers.add(tower); } Log.i("AlexLocation","基站信息是"+sb); } } cellInfo.cellTowers = towers; return cellInfo; } /** * 看看現在用wifi流量還是手機流量,如果是wifi返回true * @param context * @return */ public static boolean isWifiEnvironment(Context context){ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); if(networkInfo==null){ Log.i("AlexLocation","現在沒有聯網"); return false; } int netType = networkInfo.getType(); switch (netType){ case ConnectivityManager.TYPE_WIFI: Log.i("AlexLocation","現在是wifi網絡,可以用ip定位"); return true; case ConnectivityManager.TYPE_VPN://這個基本沒用 Log.i("AlexLocation","現在是VPN網絡"); break; case ConnectivityManager.TYPE_MOBILE: Log.i("AlexLocation","現在是移動網絡,不能用ip定位"); int subType = networkInfo.getSubtype(); Log.i("AlexLocation","移動網絡子類是"+subType+" "+networkInfo.getSubtypeName());//能判斷是2g/3g/4g網絡 break; default: Log.i("AlexLocation","不知道現在是什麼網絡"); break; } return false; } /** * 看看現在是wifi聯網還是用的流量,如果是wifi返回true,因為wifi的時候可以用ip定位,但如果這時候是vpn,那就不能用ip定位 * @param context */ public static boolean considerIP(Context context){ boolean considerIP = true;//默認是考慮 ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if(connectivityManager==null)return true; if(!isWifiEnvironment(context))return false;//如果現在不是wifi網絡,就不能用ip定位 if(Build.VERSION.SDK_INT<21) {//舊版本安卓獲取網絡狀態 NetworkInfo[] networkInfos = connectivityManager.getAllNetworkInfo(); if(networkInfos==null)return true; for(NetworkInfo i:networkInfos){ if(i==null)continue; Log.i("AlexLocation","現在的網絡是"+i.getTypeName()+i.getType()+" "+i.getSubtypeName());//WIFI,VPN,MOBILE+LTE if(i.getType()==ConnectivityManager.TYPE_VPN){ Log.i("AlexLocation","現在用的是VPN網絡,不能用ip定位"); considerIP = false; break; } } }else {//新版 Network[] networks = connectivityManager.getAllNetworks(); if(networks==null)return true; for(Network n:networks){ if(n==null)continue; NetworkInfo networkInfo = connectivityManager.getNetworkInfo(n); if(networkInfo==null)continue; Log.i("AlexLocation","現在的網絡是"+networkInfo.getTypeName()+networkInfo.getType()+" "+networkInfo.getSubtypeName());//WIFI,VPN,MOBILE+LTE if(networkInfo.getType()==ConnectivityManager.TYPE_VPN){ Log.i("AlexLocation","現在用的是VPN網絡,不能用ip定位"); considerIP = false; break; } } } return considerIP; } /** * 判斷當前手機在2g,3g,還是4g,用於發給谷歌 */ public static String determine2g3g4g(Context context){ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if(connectivityManager==null)return null; if(Build.VERSION.SDK_INT<21) {//舊版本安卓獲取網絡狀態 NetworkInfo[] networkInfos = connectivityManager.getAllNetworkInfo(); if(networkInfos==null)return null; for(NetworkInfo i:networkInfos){ if(i==null)continue; Log.i("AlexLocation","正在查看當前網絡的制式"+i.getTypeName()+i.getType()+" "+i.getSubtypeName());//WIFI,VPN,MOBILE+LTE if(i.getType()!=ConnectivityManager.TYPE_MOBILE)continue;//只看流量 else Log.i("AlexLocation","現在是移動網絡"); return determine2g3g4g(i); } }else {//新版 Network[] networks = connectivityManager.getAllNetworks(); if(networks==null)return null; for(Network n:networks){ if(n==null)continue; NetworkInfo networkInfo = connectivityManager.getNetworkInfo(n); if(networkInfo==null)continue; Log.i("AlexLocation","正在查看當前網絡的制式"+networkInfo.getTypeName()+networkInfo.getType()+" "+networkInfo.getSubtypeName());//WIFI,VPN,MOBILE+LTE if(networkInfo.getType()!=ConnectivityManager.TYPE_MOBILE) continue;//只看流量 return determine2g3g4g(networkInfo); } } return null; } /** * 看看現在用的是幾g,什麼網絡制式 * @param info * @return */ public static String determine2g3g4g(NetworkInfo info){ if(info==null)return null; switch (info.getSubtype()){ case TelephonyManager.NETWORK_TYPE_LTE: Log.i("AlexLocation","手機制式是lte"); return "lte"; case TelephonyManager.NETWORK_TYPE_EDGE: Log.i("AlexLocation","手機制式是edge"); break; case TelephonyManager.NETWORK_TYPE_CDMA: return "cdma"; case TelephonyManager.NETWORK_TYPE_GPRS: break; case TelephonyManager.NETWORK_TYPE_HSDPA: break; case TelephonyManager.NETWORK_TYPE_HSPA: break; case TelephonyManager.NETWORK_TYPE_HSPAP: break; case TelephonyManager.NETWORK_TYPE_HSUPA: break; case TelephonyManager.NETWORK_TYPE_EVDO_0: break; case TelephonyManager.NETWORK_TYPE_EVDO_A: break; case TelephonyManager.NETWORK_TYPE_EVDO_B: break; case TelephonyManager.NETWORK_TYPE_IDEN: break; case TelephonyManager.NETWORK_TYPE_UMTS: break; case TelephonyManager.NETWORK_TYPE_EHRPD: break; case TelephonyManager.NETWORK_TYPE_1xRTT: break; case TelephonyManager.NETWORK_TYPE_UNKNOWN: break; } return null; } /** * 得到附近的wifi信息,准備傳給谷歌 * @param context * @param geoLocationAPI * @return */ public static GeoLocationAPI getWifiInfo(Context context, GeoLocationAPI geoLocationAPI){ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); if(wifiManager == null)return geoLocationAPI; Log.i("AlexLocation","准備開始掃描附近wifi"); wifiManager.startScan(); //准備所有附近wifi放到wifi列表裡,包括現在正連著的wifi ArrayList lsAllWIFI = new ArrayList(); List lsScanResult = wifiManager.getScanResults();//記錄所有附近wifi的搜索結果 if(lsScanResult == null){ Log.i("AlexLocation","搜索附近wifi熱點失敗"); return geoLocationAPI; } for (ScanResult result : lsScanResult) { Log.i("AlexLocation","發現一個附近的wifi::"+result.SSID+" mac地址是"+result.BSSID+" 信號強度是"+result.level); if(result == null)continue; AlxScanWifi scanWIFI = new AlxScanWifi(result); lsAllWIFI.add(scanWIFI);//防止重復 } ArrayList wifiInfos = new ArrayList<>(lsAllWIFI.size()); for (AlxScanWifi w:lsAllWIFI){ if(w == null)continue; GoogleWifiInfo wifiInfo = new GoogleWifiInfo(); wifiInfo.macAddress = w.mac.toUpperCase();//記錄附近每個wifi路由器的mac地址 wifiInfo.signalStrength = w.dBm;//通過信號強度來判斷距離 wifiInfo.channel = w.channel;//通過信道來判斷ssid是否為同一個 wifiInfos.add(wifiInfo); } geoLocationAPI.wifiAccessPoints = wifiInfos; return geoLocationAPI; } /** * 掃描附近wifi之後,記錄wifi節點信息的類 */ public static class AlxScanWifi implements Comparable { public final int dBm; public final String ssid; public final String mac; public short channel; public AlxScanWifi(ScanResult scanresult) { dBm = scanresult.level; ssid = scanresult.SSID; mac = scanresult.BSSID;//BSSID就是傳說中的mac channel = getChannelByFrequency(scanresult.frequency); } public AlxScanWifi(String s, int i, String s1,String imac) { dBm = i; ssid = s1; mac = imac; } /** * 根據信號強度進行排序 * @param wifiinfo * @return */ public int compareTo(AlxScanWifi wifiinfo) { int i = wifiinfo.dBm; int j = dBm; return i - j; } /** * 為了防止添加wifi的列表重復,復寫equals方法 * @param obj * @return */ public boolean equals(Object obj) { boolean flag = false; if (obj == this) { flag = true; return flag; } else { if (obj instanceof AlxScanWifi) { AlxScanWifi wifiinfo = (AlxScanWifi) obj; int i = wifiinfo.dBm; int j = dBm; if (i == j) { String s = wifiinfo.mac; String s1 = this.mac; if (s.equals(s1)) { flag = true; return flag; } } flag = false; } else { flag = false; } } return flag; } public int hashCode() { int i = dBm; int j = mac.hashCode(); return i ^ j; } } /** * 根據頻率獲得信道 * * @param frequency * @return */ public static short getChannelByFrequency(int frequency) { short channel = -1; switch (frequency) { case 2412: channel = 1; break; case 2417: channel = 2; break; case 2422: channel = 3; break; case 2427: channel = 4; break; case 2432: channel = 5; break; case 2437: channel = 6; break; case 2442: channel = 7; break; case 2447: channel = 8; break; case 2452: channel = 9; break; case 2457: channel = 10; break; case 2462: channel = 11; break; case 2467: channel = 12; break; case 2472: channel = 13; break; case 2484: channel = 14; break; case 5745: channel = 149; break; case 5765: channel = 153; break; case 5785: channel = 157; break; case 5805: channel = 161; break; case 5825: channel = 165; break; } Log.i("AlexLocation","信道是"+channel); return channel; } /** * 根據國家代碼獲取通信運營商名字 * @param operatorString * @return */ public static String getCarrier(String operatorString){ if(operatorString == null) { return "0"; } if(operatorString.equals("46000") || operatorString.equals("46002")) { //中國移動 return "中國移動"; } else if(operatorString.equals("46001")) { //中國聯通 return "中國聯通"; } else if(operatorString.equals("46003")) { //中國電信 return "中國電信"; } //error return "未知"; } /** * 用於向谷歌根據基站請求經緯度的封裝基站信息的類 */ public static class GeoLocationAPI { /** * homeMobileCountryCode : 310 移動國家代碼(中國的為460); * homeMobileNetworkCode : 410 和基站有關 * radioType : gsm * carrier : Vodafone 運營商名稱 * considerIp : true * cellTowers : [] * wifiAccessPoints : [] */ public int homeMobileCountryCode;//設備的家庭網絡的移動國家代碼 (MCC) public int homeMobileNetworkCode;//設備的家庭網絡的移動網絡代碼 (MNC)。 public String radioType;//radioType:移動無線網絡類型。支持的值有 lte、gsm、cdma 和 wcdma。雖然此字段是可選的,但如果提供了相應的值,就應該將此字段包括在內,以獲得更精確的結果。 public String carrier;//運營商名稱。 public boolean considerIp;//指定當 Wi-Fi 和移動電話基站的信號不可用時,是否回退到 IP 地理位置。請注意,請求頭中的 IP 地址不能是設備的 IP 地址。默認為 true。將 considerIp 設置為 false 以禁用回退。 public List cellTowers; public List wifiAccessPoints; public String toJson(){ JSONObject jsonObject = new JSONObject(); try { jsonObject.put("homeMobileCountryCode",homeMobileCountryCode); jsonObject.put("homeMobileNetworkCode",homeMobileNetworkCode); jsonObject.put("radioType",radioType); jsonObject.put("carrier",carrier); jsonObject.put("considerIp",considerIp); if(cellTowers!=null){ JSONArray jsonArray = new JSONArray(); for (GoogleCellTower t:cellTowers) jsonArray.put(t.toJson()); jsonObject.put("cellTowers",jsonArray); } if(wifiAccessPoints!=null){ JSONArray jsonArray = new JSONArray(); for (GoogleWifiInfo w:wifiAccessPoints) jsonArray.put(w.toJson()); jsonObject.put("wifiAccessPoints",jsonArray); } } catch (JSONException e) { e.printStackTrace(); } return jsonObject.toString(); } } /** * 封裝和基站有關的數據,准備發給谷歌 */ public static class GoogleCellTower { /* GSM: { "cellTowers": [ { "cellId": 42, "locationAreaCode": 415, "mobileCountryCode": 310, "mobileNetworkCode": 410, "age": 0, "signalStrength": -60, "timingAdvance": 15 } ] } WCDMA { "cellTowers": [ { "cellId": 21532831, "locationAreaCode": 2862, "mobileCountryCode": 214, "mobileNetworkCode": 7 } ] } */ //下面的是必填 int cellId;//(必填):小區的唯一標識符。在 GSM 上,這就是小區 ID (CID);CDMA 網絡使用的是基站 ID (BID)。WCDMA 網絡使用 UTRAN/GERAN 小區標識 (UC-Id),這是一個 32 位的值,由無線網絡控制器 (RNC) 和小區 ID 連接而成。在 WCDMA 網絡中,如果只指定 16 位的小區 ID 值,返回的結果可能會不准確。 int locationAreaCode;//(必填):GSM 和 WCDMA 網絡的位置區域代碼 (LAC)。CDMA 網絡的網絡 ID (NID)。 int mobileCountryCode;//(必填):移動電話基站的移動國家代碼 (MCC)。 int mobileNetworkCode;//(必填):移動電話基站的移動網絡代碼。對於 GSM 和 WCDMA,這就是 MNC;CDMA 使用的是系統 ID (SID)。 int signalStrength;//測量到的無線信號強度(以 dBm 為單位)。 //下面的是選填 int age;//自從此小區成為主小區後經過的毫秒數。如果 age 為 0,cellId 就表示當前的測量值。 int timingAdvance;//時間提前值。 public JSONObject toJson(){ JSONObject jsonObject = new JSONObject(); try { jsonObject.put("cellId",cellId); jsonObject.put("locationAreaCode",locationAreaCode); jsonObject.put("mobileCountryCode",mobileCountryCode); jsonObject.put("mobileNetworkCode",mobileNetworkCode); jsonObject.put("signalStrength",signalStrength); jsonObject.put("age",age); jsonObject.put("timingAdvance",timingAdvance); } catch (JSONException e) { e.printStackTrace(); } return jsonObject; } } /** * 向谷歌服務器根據附近wifi請求位置的json */ public static class GoogleWifiInfo { /** * macAddress : 01:23:45:67:89:AB * signalStrength : -65 * age : 0 * channel : 11 * signalToNoiseRatio : 40 */ public String macAddress;//(必填)Wi-Fi 節點的 MAC 地址。分隔符必須是 :(冒號),並且十六進制數字必須使用大寫字母。 public int signalStrength;//測量到的當前信號強度(以 dBm 為單位)。 public int age;//自從檢測到此接入點後經過的毫秒數。 public short channel;//客戶端與接入點進行通信的信道 public int signalToNoiseRatio;//測量到的當前信噪比(以 dB 為單位)。 public JSONObject toJson(){ JSONObject jsonObject = new JSONObject(); try { jsonObject.put("signalStrength",signalStrength); jsonObject.put("age",age); jsonObject.put("macAddress",macAddress); jsonObject.put("channel",channel); jsonObject.put("signalToNoiseRatio",signalToNoiseRatio); } catch (JSONException e) { e.printStackTrace(); } return jsonObject; } } /** * 使用httpclient發送一個post的json請求 * @param url * @return */ public void sendJsonByPost(String json,String url){ final HttpUtils httpUtils=new HttpUtils(); RequestParams params = new RequestParams(); try { params.setBodyEntity(new StringEntity(json,"UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } params.setContentType("application/json"); httpUtils.send(HttpRequest.HttpMethod.POST, url, params, new RequestCallBack () { @Override public void onFailure(HttpException arg0, String arg1) { if(arg0==null)return; //這裡如果報404異常的話,一般是根據當前的基站cid無法查到相關信息 //如果返回值是 400 Bad Request,說明有的必填項沒有填 Log.i("AlexLocation","失敗了"+arg1+" "+arg0.getExceptionCode()+" "+arg0.getMessage(),arg0.getCause()); if(arg0.getExceptionCode()==0)Log.i("AlexLocation","谷歌沒有根據現有條件查詢到經緯度"); if(isDebugging)JViewUtils.showToast(context,"谷歌查詢失敗",arg1); } @Override public void onSuccess(ResponseInfo arg0) { /* 谷歌返回的json如下 { "location": { "lat": 1.3553794, "lng": 103.86774439999999 }, "accuracy": 16432.0 } */ if(arg0==null || context==null)return; String result = arg0.result; Log.i("AlexLocation","成功"+arg0.result+" "+arg0.statusCode); if(isDebugging)JViewUtils.showToast(context,"谷歌成功",arg0.result); if(result==null || result.length()<10 || !result.startsWith("{")){ Log.i("AlexLocation","返回格式不對"+result); } JSONObject returnJson = null; try { returnJson = new JSONObject(result); JSONObject location = returnJson.getJSONObject("location"); if(location==null){ Log.i("AlexLocation","條件不足,無法確定位置"); return; } double latitude = location.getDouble("lat"); double longitute = location.getDouble("lng"); double google_accuracy = returnJson.getDouble("accuracy"); Log.i("AlexLocation","谷歌返回的經緯度是"+latitude+" : "+longitute+" 精度是"+google_accuracy); if(isDebugging)JViewUtils.showToast(context,"","谷歌返回的經緯度是"+latitude+" : "+longitute+" 精度是"+google_accuracy); if(currentStatus==STATUS.NOT_CONNECT || google_accuracy
附錄資料:
移動設備網絡代碼(英語:Mobile Network Code,MNC)是與移動設備國家代碼(Mobile Country Code,MCC)(也稱為“MCC / MNC”)相結合,以用來表示唯一一個的移動設備的網絡運營商。這些運營商可以是使用的GSM/LTE、CDMA、iDEN、TETRA和通用移動通訊系統的公共陸基移動網亦或是衛星網絡。
在講正題之前我們講一段有關任務傳遞的小故事,拋磚迎玉下: 話說一家軟件公司,來一個任務,分派給了開發經理去完成: 開發經理拿到,看了一下,感覺好簡單,於是 開發經理:分派
繪圖資源也可以看看2D圖形可繪制資源是可以被繪制到屏幕上,哪些是你可以用的API,如getDrawable(INT)檢索或應用到另一個XML資源與屬性,比如Android
百度視頻播放器是百度公司推出的一款為Android用戶精心優化的免費視頻應用,集視頻搜索、視頻推薦、離線觀看、劇集提醒等等眾多創新優秀功能於一身。它不僅僅是
在web頁面中,有a標簽的超鏈接實現跳轉,同樣在Android當中,用TextView控件來顯示文字,實現它的事件來跳轉。用過微博Android手機端的朋友的都知道微博正