手機應用中最酷的可能就是位置服務相關的了,如何讀取GPS信息,在官方文檔上有相當詳細的說明,後面如果有機會,我也會專門寫例子來介紹(教程已完成,請參見:教程:實現Android的不同精度的定位(基於網絡和GPS))。但今天,我們先來看下如何以編程的方式來開啓或關閉GPS。
官方的API中,android.provider.Settings.Secure類有2個靜態方法:
public static final void setLocationProviderEnabled (ContentResolver cr, String provider, boolean enabled)
和
public static final boolean isLocationProviderEnabled (ContentResolver cr, String provider)
不過遺憾的是,這2個方法都註明了從API Level 8(即Android 2.2)纔開始提供,那麼在2.2之前又該如何編程實現GPS的開關呢?
山重水複疑無路
首先,我們要知道,Android系統的設置畫面中就可以進行GPS的開關,那麼它是如何實現的呢?由於我的機器上的android source是2.3版本的,所以直接啓動了一個2.1的模擬器,用adb pull將Settings.apk抓下來,反編譯之後,在SecuritySettings類中找到如下代碼:
package,com.android.settings.SecuritySettings.java
- CheckBoxPreference localCheckBoxPreference3 = this.mGps;
- if (paramPreference == localCheckBoxPreference3)
- {
- ContentResolver localContentResolver3 = getContentResolver();
- boolean bool6 = this.mGps.isChecked();
- Settings.Secure.setLocationProviderEnabled(localContentResolver3, "gps", bool6);
- continue;
- }
可以看到2.1系統中已經存在有Settings.Secure.setLocationProviderEnabled方法了,只是該方法沒有開放而已,事實上讀過Android源碼的人都對/*hide*/很反感吧,看得到,摸不到!
既然Setting畫面中的用法,我們不能使用,那麼再換1種方法,我們去看一下Settings.Secure.setLocationProviderEnabled的寫法,然後直接套用。這次,我們直接去看Android 2.3的源碼,找到Setting.java之後,找到相關的方法,代碼如下:
core, android.provider.Setting.java
- public static final void setLocationProviderEnabled(ContentResolver cr,
- String provider, boolean enabled) {
- // to ensure thread safety, we write the provider name with a '+' or '-'
- // and let the SettingsProvider handle it rather than reading and modifying
- // the list of enabled providers.
- if (enabled) {
- provider = "+" + provider;
- } else {
- provider = "-" + provider;
- }
- putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
- }
原來這個方法只是1個包裝,事實上調用的還是Settings.Secure中的putString方法,我們直接借用過來:
在自己的onClick事件中寫上
Settings.Secure.putString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, “network,gps”);
然後執行,WOW,發生了什麼,需要android.permission.WRITE_SETTINGS權限?在Manifest文件中加上,再運行,還是出錯,不過這次需要的是android.permission.WRITE_SECURE_SETTINGS,再次加上。
滿懷希望的再次運行,結果還是一樣的問題:
java.lang.SecurityException: Permission denial: writing to secure settings requires android.permission.WRITE_SECURE_SETTINGS
看來,Google封死了直接調用Settings的路了,事實上我又試着使用反射來直接調用setLocationProviderEnabled方法,結果也是一樣的告訴我需要權限。
柳暗花明又一村
難道沒有別的辦法了嗎?那些2.1中可以運行的App Widget是如何做到的呢?再次檢視2.1的Settings.apk中的代碼,發現有1個widget包,裏面有1個類叫SettingsAppWidgetProvider,這就是用來提供App Widget的類,繼續研究這裏的代碼,發現構造RemoteView的代碼中用到如下方法:
- private static PendingIntent getLaunchPendingIntent(Context paramContext, int paramInt1, int paramInt2)
- {
- Intent localIntent1 = new Intent();
- Intent localIntent2 = localIntent1.setClass(paramContext, SettingsAppWidgetProvider.class);
- Intent localIntent3 = localIntent1.addCategory("android.intent.category.ALTERNATIVE");
- Uri localUri = Uri.parse("custom:" + paramInt2);
- Intent localIntent4 = localIntent1.setData(localUri);
- return PendingIntent.getBroadcast(paramContext, 0, localIntent1, 0);
- }
由於這是反編譯的結果,略微有點混亂,但還是可以看出思路,目的是通過PendingIntent來扔出1個Intent,接受者是SettingsAppWidgetProvider.class,接受的參數有2個,1個是Category:SettingsAppWidgetProvider.class(正是這個類自身),另1個是Data:Uri.parse(“custom:” + paramInt2),這個paramInt2是Widget中的圖標按鈕的序號。所有的序號在類的首部都有定義:
- private static final int BUTTON_BLUETOOTH = 4;
- private static final int BUTTON_BRIGHTNESS = 1;
- private static final int BUTTON_GPS = 3;
- private static final int BUTTON_SYNC = 2;
- private static final int BUTTON_WIFI = 0;
那麼我們只要送出custom:3的Uri給SettingsAppWidgetProvider.class,就應該可以由SettingsAppWidgetProvider類來幫我們調用Settings.Secure.setLocationProviderEnabled方法了。
說到做到,在Activity中添加如下方法:
- private void toggleGPS() {
- Intent gpsIntent = new Intent();
- gpsIntent.setClassName("com.android.settings",
- "com.android.settings.widget.SettingsAppWidgetProvider");
- gpsIntent.addCategory("android.intent.category.ALTERNATIVE");
- gpsIntent.setData(Uri.parse("custom:3"));
- try {
- PendingIntent.getBroadcast(this, 0, gpsIntent, 0).send();
- }
- catch (CanceledException e) {
- e.printStackTrace();
- }
- }
這個方法是1個純開關,如果當前是開啓的,那麼就會關閉它,反之亦然。
檢查GPS開關狀態
- secureClass = cl.loadClass("android.provider.Settings$Secure");
- isMethod = secureClass.getMethod("isLocationProviderEnabled",
- ContentResolver.class, String.class);
- Boolean ret = (Boolean) isMethod.invoke(secureClass, this
- .getContentResolver(), "gps");
也可以直接用下面的方法:
- private void isGPSEnable() {
- /* 用Setting.System來讀取也可以,只是這是更舊的用法
- String str = Settings.System.getString(getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
- */
- String str = Settings.Secure.getString(getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
- Log.v("GPS", str);
- if (str != null) {
- return str.contains("gps");
- }
- else{
- return false;
- }
- }
這2種方法的原理都是一樣的,方法2其實也就是isLocationProviderEnabled實際調用的代碼,只是Google未對讀取操作進行權限限制。
總結
如果目標手機是運行Android 2.2的話,那麼最好還是使用2.2開放的Settings.Secure類中的2個方法來操作。但如果目標手機運行的版本是2.1或以下的話,那麼就只能使用變通的方法來實現了。這1方法在Android官方的Wiki上已經有人提出了,詳情請見:Issue 7890。但可能是2.1版本已經古舊不再維護的原因,官方並未進行任何的Fix。
轉自:http://www.itivy.com/android/archive/2011/6/17/android-open-or-close-gps.html