方法一、引导用户修改
此方法使用系统提供的Action,跳转到系统界面,不涉及敏感权限,所有APP均可通用。方法如下:
Intent intent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);startActivityForResult(intent, 0);
方法二、代码修改
权限
这种方法需要修改SettingsProvider的值,需要使用的权限也很高:
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
但是,这个权限不是什么APP都能申请的,它的限定如下:
Permission is only granted to system apps. Permissions with the protection level signature, privileged or signatureOrSystem are only granted to system apps. If an app is a regular non-system app, it will never be able to use these permissions.
意思就是说,要使用这个权限,必须是系统APP(可以没有 platform 签名)。如果是系统APP,或者有platform权限,就继续往下看;否则,就放弃吧,苦海无涯回头是岸……
SettingsProvider
一般 SettingsProvider 的值,都能通过 android.provider.Settings 的内部类 Global、Secure、System 的各种 put 方法进行修改,修改也是即时生效的。比如修改WiFi开关状态:
Settings.Global.putInt(getContentResolver(),Settings.Global.WIFI_ON,isChecked ? 1 : 0 //1开,0关);
而位置信息的KEY值是:Settings.Secure.LOCATION_PROVIDERS_ALLOWED,具体说明如下:
/*** Comma-separated list of location providers that activities may access. Do not rely on* this value being present in settings.db or on ContentObserver notifications on the* corresponding Uri.** @deprecated use {@link #LOCATION_MODE} and* {@link LocationManager#MODE_CHANGED_ACTION} (or* {@link LocationManager#PROVIDERS_CHANGED_ACTION})*/@Deprecatedpublic static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";/*** The degree of location access enabled by the user.* <p>* When used with {@link #putInt(ContentResolver, String, int)}, must be one of {@link* #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, {@link* #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. When used with {@link* #getInt(ContentResolver, String)}, the caller must gracefully handle additional location* modes that might be added in the future.* <p>* Note: do not rely on this value being present in settings.db or on ContentObserver* notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION}* to receive changes in this value.*/public static final String LOCATION_MODE = "location_mode";
可以看到,LOCATION_PROVIDERS_ALLOWED 已经废弃(API19)了,取而代之的是 LOCATION_MODE,这个值的取值范围是如下这几个 int 值之一:
/*** Location access disabled.*/public static final int LOCATION_MODE_OFF = 0;/*** Network Location Provider disabled, but GPS and other sensors enabled.*/public static final int LOCATION_MODE_SENSORS_ONLY = 1;/*** Reduced power usage, such as limiting the number of GPS updates per hour. Requests* with {@link android.location.Criteria#POWER_HIGH} may be downgraded to* {@link android.location.Criteria#POWER_MEDIUM}.*/public static final int LOCATION_MODE_BATTERY_SAVING = 2;/*** Best-effort location computation allowed.*/public static final int LOCATION_MODE_HIGH_ACCURACY = 3;
这些啥意思就不解释了,不懂的copy一下,出门右转百度翻译……
踩坑(M0,API22)
这里我的需求是,当检测到用户关闭位置信息开关时,自动将其重置为“网络定位”,即 BATTERY_SAVING 模式。
于是基于上面的分析,还有自己的经验积累,就有了一个方案:
对 LOCATION_MODE 的值进行监听,检测到变化后,判断是不是关闭操作;如果是,则重新设置为 BATTERY_SAVING 模式。样例代码如下:
getContentResolver().registerContentObserver(Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE),false,new ContentObserver(new Handler(Looper.getMainLooper())) {@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);int mode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);if (Settings.Secure.LOCATION_MODE_OFF == mode) {Settings.Secure.putInt(getContentResolver(), Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_BATTERY_SAVING);}}});
看起来没毛病,结果却发现怎么也监听不到 LOCATION_MODE 的变化,WTF???
因为时间关系,就没有研究为什么监听不到了,后面有时间再细看一下。
因为我本职工作是做ROM定制开发的,在位置信息开关这个点上,经常有客需要默认开或者关,每次我们改的都是 xml 里面的字符串值,而这个修改方法到 O1 都是可行的。
字符串值说明:
“”,关闭“gps”,仅GPS传感器定位“network”,仅网络定位,即省电模式“gps,network”,高精度模式
这写字符串值是存储在LOCATION_PROVIDERS_ALLOWED里的,于是稍微调整了一下上面的代码:
getContentResolver().registerContentObserver(Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED),false,new ContentObserver(new Handler(Looper.getMainLooper())) {@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);String mode = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);if (null == mode || "".equals(mode)) {Settings.Secure.putString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "network");}}});
本以为这样就可以了,结果却发现能监听到 LOCATION_PROVIDERS_ALLOWED 的变化,但是重置为“network”却不能成功……
不对呀,string 值就是这么 put 的呀!就在一脸懵逼的时候,仔细看了下 Settings.Secure 的方法列表,发现这么两个方法:![[app]Android 修改位置信息(GPS)开关状态 - 图1](/uploads/projects/xshawn@aosp/4de2b5997339e6259105240458a3da68.png)
好家伙,在这儿等着呢!
@Deprecatedpublic static final void setLocationProviderEnabled(ContentResolver cr,String provider, boolean enabled) {setLocationProviderEnabledForUser(cr, provider, enabled, UserHandle.myUserId());}@Deprecatedpublic static final boolean setLocationProviderEnabledForUser(ContentResolver cr,String provider, boolean enabled, int userId) {synchronized (mLocationSettingsLock) {// 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;}return putStringForUser(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider,userId);}}
看到这就明白了,这货虽然看起来存的是"gps,network"的值,实际上是两个字段拼接的,每次通过 "+/- provider"去改变的。
因此,要想在用户关闭位置信息开关后,能重置为"network",只需要将前面的代码稍作修改即可:
String mode = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);if (null == mode || "".equals(mode)) {Settings.Secure.setLocationProviderEnabled(getContentResolver(), LocationManager.NETWORK_PROVIDER, true);//或者用下面的方式//Settings.Secure.putString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+network");}
补充:三方APP如何监听位置开关变化?
上面介绍的接听方法是系统APP适用的,三方APP没有权限这么做,但并不是没有办法,只不过实现不一样而已。
而方法在前面介绍源码时就已在注释信息中出现:
/*** The degree of location access enabled by the user.* <p>* When used with {@link #putInt(ContentResolver, String, int)}, must be one of {@link* #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, {@link* #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. When used with {@link* #getInt(ContentResolver, String)}, the caller must gracefully handle additional location* modes that might be added in the future.* <p>* Note: do not rely on this value being present in settings.db or on ContentObserver* notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION}* to receive changes in this value.*/public static final String LOCATION_MODE = "location_mode";
三方APP可以通过监听如下广播来监听开关变化:
/*** Broadcast intent action when {@link android.provider.Settings.Secure#LOCATION_MODE} changes.* For use with the {@link android.provider.Settings.Secure#LOCATION_MODE} API.* If you're interacting with {@link #isProviderEnabled(String)}, use* {@link #PROVIDERS_CHANGED_ACTION} instead.** In the future, there may be mode changes that do not result in* {@link #PROVIDERS_CHANGED_ACTION} broadcasts.*/public static final String MODE_CHANGED_ACTION = "android.location.MODE_CHANGED";
示例如下:
registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);boolean isEnabled = manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);//可以换成其它providerif (isEnabled) {//已开启}else {//已关闭,可引导用户打开}}},new IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION));
