如何在Android中以编程方式启用或禁用GPS?

173
我知道在安卓上编程实现GPS的开关已经 讨论 很多 ,答案总是一样的:
“出于安全/隐私原因,你不能这样做,你必须转到位置首选项屏幕并让用户启用/禁用它。”
我理解这一点,然而我最近从市场购买了Tasker,除了许多其他可以完成的事情外,您还可以设置规则,在进入预定应用程序时自动启用GPS并在退出时禁用它(请参见此处的教程,它只是起作用!)并且该应用程序无法使用固件签名密钥进行签名,因为它适用于许多不同的Android版本和设备,甚至不需要root。
我希望在我的应用程序中实现这个功能。当然,我不想侵犯用户的隐私,所以我会先询问用户是否想要通过典型的“记住我的决定”复选框来自动启用它,如果回答是,则启用它。
有人知道Tasker如何实现这一点吗?

我想要关闭和打开GPS,以便我可以编写需要其开启的代码测试。不使用mocks,因为Android使它们变得很麻烦。 - Phlip
17个回答

160

通过利用电源管理小部件中的漏洞,可以切换GPS。请参阅xda线程进行讨论。

这是我使用的一些示例代码

private void turnGPSOn(){
    String provider = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);

    if(!provider.contains("gps")){ //if gps is disabled
        final Intent poke = new Intent();
        poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); 
        poke.addCategory(Intent.CATEGORY_ALTERNATIVE);
        poke.setData(Uri.parse("3")); 
        sendBroadcast(poke);
    }
}

private void turnGPSOff(){
    String provider = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);

    if(provider.contains("gps")){ //if gps is enabled
        final Intent poke = new Intent();
        poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider");
        poke.addCategory(Intent.CATEGORY_ALTERNATIVE);
        poke.setData(Uri.parse("3")); 
        sendBroadcast(poke);
    }
}

使用以下内容来测试现有版本的电源控制小部件是否允许您切换GPS。
private boolean canToggleGPS() {
    PackageManager pacman = getPackageManager();
    PackageInfo pacInfo = null;

    try {
        pacInfo = pacman.getPackageInfo("com.android.settings", PackageManager.GET_RECEIVERS);
    } catch (NameNotFoundException e) {
        return false; //package not found
    }

    if(pacInfo != null){
        for(ActivityInfo actInfo : pacInfo.receivers){
            //test if recevier is exported. if so, we can toggle GPS.
            if(actInfo.name.equals("com.android.settings.widget.SettingsAppWidgetProvider") && actInfo.exported){
                return true;
            }
        }
    }

    return false; //default
}

4
在我发表这条评论时,该回答中的链接似乎表明此漏洞已被修复。我只想指出,在我的测试环境中,这种攻击仍然可以正常工作,所以你不应该放弃尝试……只要确保你的代码能够处理任何错误,如果它不能按照预期工作的话! - SilithCrowe
52
这真是个糟糕的想法。一旦漏洞被修复,你的攻击将不再奏效。最好直接将用户发送到设置应用程序。 - Edward Falk
5
哈哈哈……这个漏洞在4.2.2中再次出现,看到它我感到很惊讶。天哪! - amithgc
1
这不再是可行的解决方案了。似乎安卓已经修复了这个问题,所以它看起来在状态栏中工作,但它的硬件仍然关闭,并且除了纬度和经度的值为0.0之外,不会传递任何位置信息。 - Harpreet
1
Settings.Secure.LOCATION_PROVIDERS_ALLOWEDKITKAT 上已经被弃用。这是这个技巧不再起作用的时候了。 - Shnkc
显示剩余14条评论

77

现在不允许使用所有这些答案。以下是正确的答案:

对于那些仍在寻找答案的人:

以下是 OLA Cabs 和其他类似应用程序如何实现它的方法。

将以下内容添加到您的 onCreate 中:

if (googleApiClient == null) {
    googleApiClient = new GoogleApiClient.Builder(this)
            .addApi(LocationServices.API).addConnectionCallbacks(this)
            .addOnConnectionFailedListener(Login.this).build();
    googleApiClient.connect();
            LocationRequest locationRequest = LocationRequest.create();
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    locationRequest.setInterval(30 * 1000);
    locationRequest.setFastestInterval(5 * 1000);
    LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
            .addLocationRequest(locationRequest);

    // **************************
    builder.setAlwaysShow(true); // this is the key ingredient
    // **************************

    PendingResult<LocationSettingsResult> result = LocationServices.SettingsApi
            .checkLocationSettings(googleApiClient, builder.build());
    result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
        @Override
        public void onResult(LocationSettingsResult result) {
            final Status status = result.getStatus();
            final LocationSettingsStates state = result
                    .getLocationSettingsStates();
            switch (status.getStatusCode()) {
            case LocationSettingsStatusCodes.SUCCESS:
                // All location settings are satisfied. The client can
                // initialize location
                // requests here.
                break;
            case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                // Location settings are not satisfied. But could be
                // fixed by showing the user
                // a dialog.
                try {
                    // Show the dialog by calling
                    // startResolutionForResult(),
                    // and check the result in onActivityResult().
                    status.startResolutionForResult(Login.this, 1000);
                } catch (IntentSender.SendIntentException e) {
                    // Ignore the error.
                }
                break;
            case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                // Location settings are not satisfied. However, we have
                // no way to fix the
                // settings so we won't show the dialog.
                break;
            }
        }
    });
}

这些是已实现的方法:

@Override
public void onConnected(Bundle arg0) {
    // TODO Auto-generated method stub

}

@Override
public void onConnectionSuspended(int arg0) {
    // TODO Auto-generated method stub

}

@Override
public void onConnectionFailed(ConnectionResult arg0) {
    // TODO Auto-generated method stub

}

这是关于同一主题的Android文档
以下内容旨在帮助其他人解决问题: 编辑:添加Irfan Raza的评论以获取更多帮助。
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     if (requestCode == 1000) {
         if(resultCode == Activity.RESULT_OK){
             String result=data.getStringExtra("result"); 
         } if (resultCode == Activity.RESULT_CANCELED) {
             //Write your code if there's no result 
         } 
    } 
} 

2
需要Google API客户端集成,因此只适用于特定用例的解决方案,不适合通用解决方案。 - Cik
1
我们能否在不显示构建器的情况下实现这一点?因为我需要在不显示任何警报的情况下打开GPS。 - Punithapriya
3
不可能。用户必须同意,因此必须展示那位建筑商。 - Aishvarya Jaiswal
是的,我知道,但我想在不知道用户身份的情况下跟踪他。 - Punithapriya
此答案现已过时。请查看此处:https://developers.google.com/android/reference/com/google/android/gms/location/SettingsClient - Vaibhav Jani
显示剩余7条评论

50

启用GPS:

Intent intent=new Intent("android.location.GPS_ENABLED_CHANGE");
intent.putExtra("enabled", true);
sendBroadcast(intent);

禁用 GPS:

Intent intent = new Intent("android.location.GPS_ENABLED_CHANGE");
intent.putExtra("enabled", false);
sendBroadcast(intent);

1
自动开启/关闭GPS。 - mob_web_dev
1
这也有助于启用。private void turnGPSOn(){ String provider = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);if(!provider.contains("gps")){ //如果 GPS 被禁用 final Intent poke = new Intent(); poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); poke.addCategory(Intent.CATEGORY_ALTERNATIVE); poke.setData(Uri.parse("3")); sendBroadcast(poke); }} - mob_web_dev
25
仅启用了 Android 4.0.4 版本中的 GPS 通知,而非 GPS 本身。因此它看起来像是开启了 GPS,但实际上并没有开启。 - alex
@alex:出于安全考虑,我希望相反的情况不可能发生,即查询位置(GPS或A-GPS),但抑制其UI通知。那么这是否可能? - porg
17
Java.lang.SecurityException: 权限拒绝:不允许发送广播 android.location.GPS_ENABLED_CHANGE。 - Abhi
显示剩余2条评论

28

如果应用程序被移动到/system/aps并且在清单中具有以下权限,则此代码适用于已ROOT的手机:

<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>

代码

private void turnGpsOn (Context context) {
    beforeEnable = Settings.Secure.getString (context.getContentResolver(),
                                              Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
    String newSet = String.format ("%s,%s",
                                   beforeEnable,
                                   LocationManager.GPS_PROVIDER);
    try {
        Settings.Secure.putString (context.getContentResolver(),
                                   Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                                   newSet); 
    } catch(Exception e) {}
}


private void turnGpsOff (Context context) {
    if (null == beforeEnable) {
        String str = Settings.Secure.getString (context.getContentResolver(),
                                                Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
        if (null == str) {
            str = "";
        } else {                
            String[] list = str.split (",");
            str = "";
            int j = 0;
            for (int i = 0; i < list.length; i++) {
                if (!list[i].equals (LocationManager.GPS_PROVIDER)) {
                    if (j > 0) {
                        str += ",";
                    }
                    str += list[i];
                    j++;
                }
            }
            beforeEnable = str;
        }
    }
    try {
        Settings.Secure.putString (context.getContentResolver(),
                                   Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                                   beforeEnable);
    } catch(Exception e) {}
}

5
感谢提到这种方法,它也适用于非Root设备上的系统应用程序。+1 - AlexS
这是正确的方法。适用于所有版本的Android,无需任何技巧! - BQuadra
关闭GPS无法正常工作!!您能告诉我原因和可能的解决方案吗? - Shivansh
现在GPS开关正常,但是GPS不工作,即无法提供位置的经纬度0.0。 - Shivansh
它只是触发了Wifi切换动画,但实际上并没有触发Google位置服务。 - Yohanim
显示剩余2条评论

27

不必使用 intent Settings.ACTION_LOCATION_SOURCE_SETTINGS,您可以直接在您的应用程序中显示弹出窗口,例如Google Map,并在单击“确定”按钮时启用GPS,无需重定向到设置,只需使用以下代码:

注意:如果位置未开启,此代码行将自动打开对话框框。 Google地图中也使用了这个代码片段。

 public class MainActivity extends AppCompatActivity
    implements GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener {


LocationRequest mLocationRequest;
GoogleApiClient mGoogleApiClient;
PendingResult<LocationSettingsResult> result;
final static int REQUEST_LOCATION = 199;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addApi(LocationServices.API)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this).build();
    mGoogleApiClient.connect();

}

@Override
public void onConnected(Bundle bundle) {

    mLocationRequest = LocationRequest.create();
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    mLocationRequest.setInterval(30 * 1000);
    mLocationRequest.setFastestInterval(5 * 1000);

    LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
            .addLocationRequest(mLocationRequest);
    builder.setAlwaysShow(true);

    result = LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());

    result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
        @Override
        public void onResult(LocationSettingsResult result) {
            final Status status = result.getStatus();
            //final LocationSettingsStates state = result.getLocationSettingsStates();
            switch (status.getStatusCode()) {
                case LocationSettingsStatusCodes.SUCCESS:
                    // All location settings are satisfied. The client can initialize location
                    // requests here.
                    //...
                    break;
                case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                    // Location settings are not satisfied. But could be fixed by showing the user
                    // a dialog.
                    try {
                        // Show the dialog by calling startResolutionForResult(),
                        // and check the result in onActivityResult().
                        status.startResolutionForResult(
                                MainActivity.this,
                                REQUEST_LOCATION);
                    } catch (SendIntentException e) {
                        // Ignore the error.
                    }
                    break;
                case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                    // Location settings are not satisfied. However, we have no way to fix the
                    // settings so we won't show the dialog.
                    //...
                    break;
            }
        }
    });

}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
    Log.d("onActivityResult()", Integer.toString(resultCode));

    //final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);
    switch (requestCode)
    {
        case REQUEST_LOCATION:
            switch (resultCode)
            {
                case Activity.RESULT_OK:
                {
                    // All required changes were successfully made
                    Toast.makeText(MainActivity.this, "Location enabled by user!", Toast.LENGTH_LONG).show();
                    break;
                }
                case Activity.RESULT_CANCELED:
                {
                    // The user was asked to change settings, but chose not to
                    Toast.makeText(MainActivity.this, "Location not enabled, user cancelled.", Toast.LENGTH_LONG).show();
                    break;
                }
                default:
                {
                    break;
                }
            }
            break;
    }
}

@Override
public void onConnectionSuspended(int i) {

}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {

}
} 

注意:如果位置服务未开启,此代码将自动打开对话框。这行代码也在Google地图中使用。

1
这段代码运行良好,但不要忘记在Gradle文件中添加位置权限和PlayService jar。 - Akash pasupathi
在没有访问onActivityResult的情况下,如何在Compose应用程序中实现这一点? - YaMiN

23

自安卓4.4版本开始,您无法通过编程的方式启用/禁用GPS。如果您尝试这个答案提出的代码,将会触发异常。

java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.location.GPS_ENABLED_CHANGE

2
那么这是一条注释还是有其他解决方案? - Shailendra Madda
@Shylendra Madda,目前还没有启用GPS的解决方案。您只能调用相应的系统对话框。 - The incredible Jan

11

上面的正确回答已经很老了,需要一些更新,所以这里是新的回答。

在最近的更新中,我们拥有了androidx支持,因此首先要在您的应用级build.gradle文件中包含依赖项。

implementation 'com.google.android.gms:play-services-location:17.0.0'
然后在你的清单文件中添加:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

如果您要发布应用程序,请不要忘记获取用户同意这些权限。

现在这里有代码,只需使用它即可。

 protected void createLocationRequest() {
    LocationRequest locationRequest = LocationRequest.create();
    locationRequest.setInterval(10000);
    locationRequest.setFastestInterval(5000);
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

    LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
            .addLocationRequest(locationRequest);

    SettingsClient client = LocationServices.getSettingsClient(this);
    Task<LocationSettingsResponse> task = client.checkLocationSettings(builder.build());



    task.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
        @Override
        public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
            // All location settings are satisfied. The client can initialize
            // location requests here.
            // ...

            Toast.makeText(MainActivity.this, "Gps already open", 
                                          Toast.LENGTH_LONG).show();
            Log.d("location settings",locationSettingsResponse.toString());
        }
    });

    task.addOnFailureListener(this, new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            if (e instanceof ResolvableApiException) {
                // Location settings are not satisfied, but this can be fixed
                // by showing the user a dialog.
                try {
                    // Show the dialog by calling startResolutionForResult(),
                    // and check the result in onActivityResult().
                    ResolvableApiException resolvable = (ResolvableApiException) e;
                    resolvable.startResolutionForResult(MainActivity.this,
                            REQUEST_CHECK_SETTINGS);
                } catch (IntentSender.SendIntentException sendEx) {
                    // Ignore the error.
                }
            }
        }
    });
}


@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if(requestCode==REQUEST_CHECK_SETTINGS){

        if(resultCode==RESULT_OK){

            Toast.makeText(this, "Gps opened", Toast.LENGTH_SHORT).show();
            //if user allows to open gps
            Log.d("result ok",data.toString());

        }else if(resultCode==RESULT_CANCELED){

            Toast.makeText(this, "refused to open gps", 
                                         Toast.LENGTH_SHORT).show();
            // in case user back press or refuses to open gps
            Log.d("result cancelled",data.toString());
        }
    }
}

如果出了问题,请@我


7

使用最新的API,简短易懂的解决方案,来自https://developer.android.com/training/location/change-location-settings.html

您将获得漂亮的Google AlertDialog,带有ok按钮,无需访问设置。

直接核心。我的片段代码:

override fun onResume() {
        super.onResume()
        checkGPSEnabled()
    }

private fun checkGPSEnabled() {
        val manager = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
        if (manager.isProviderEnabled(LocationManager.GPS_PROVIDER).not()) {
            turnOnGPS()
        }
    }

private fun turnOnGPS() {
        val request = LocationRequest.create().apply {
            interval = 2000
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }
        val builder = LocationSettingsRequest.Builder().addLocationRequest(request)
        val client: SettingsClient = LocationServices.getSettingsClient(requireActivity())
        val task: Task<LocationSettingsResponse> = client.checkLocationSettings(builder.build())
        task.addOnFailureListener {
            if (it is ResolvableApiException) {
                try {
                    it.startResolutionForResult(requireActivity(), 12345)
                } catch (sendEx: IntentSender.SendIntentException) {
                }
            }
        }.addOnSuccessListener {
            //here GPS is On
        }
    }

就是这样。只需复制并粘贴。你还需要:implementation 'com.google.android.gms:play-services-location:18.0.0',以及在清单文件中添加<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 权限。


3
这段代码仅适用于已经root过的手机:
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String[] cmds = {"cd /system/bin" ,"settings put secure location_providers_allowed +gps"};
        try {
            Process p = Runtime.getRuntime().exec("su");
            DataOutputStream os = new DataOutputStream(p.getOutputStream());
            for (String tmpCmd : cmds) {
                os.writeBytes(tmpCmd + "\n");
            }
            os.writeBytes("exit\n");
            os.flush();
        }
        catch (IOException e){
            e.printStackTrace();
        }
    }
}

关闭GPS的命令如下:
settings put secure location_providers_allowed -gps

您可以使用以下命令切换网络精度: 要打开,请使用:
settings put secure location_providers_allowed +network

关闭的话,可以使用以下方法:

settings put secure location_providers_allowed -network

2

3
这个Settings.Secure类看起来很有前途,然而我遇到了一个安全异常,说我需要android.permission.WRITE_SECURE_SETTINGS权限,即使我在清单文件中添加了这个权限(还有WRITE_SETTINGS)。但这似乎是一个不错的继续探索的方法。谢谢:) - maid450
WRITE_SECURE_SETTINGS 具有 systemOrSignature 的保护级别,您需要将该应用程序变成系统应用程序才能使其正常工作,这也在此答案中提到 - Flow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接