方法一、引导用户修改
此方法使用系统提供的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})
*/
@Deprecated
public 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())) {
@Override
public 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())) {
@Override
public 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 的方法列表,发现这么两个方法:
好家伙,在这儿等着呢!
@Deprecated
public static final void setLocationProviderEnabled(ContentResolver cr,
String provider, boolean enabled) {
setLocationProviderEnabledForUser(cr, provider, enabled, UserHandle.myUserId());
}
@Deprecated
public 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() {
@Override
public void onReceive(Context context, Intent intent) {
LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
boolean isEnabled = manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);//可以换成其它provider
if (isEnabled) {
//已开启
}
else {
//已关闭,可引导用户打开
}
}
},
new IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
);