This article by Jian Yue SimpRead transcoding, original address guolin.blog.csdn.net

Write an original article about my own technical experience in writing Android permission request code.

As the title of this article describes, requesting permissions on Android has never been easy. Why is that? I think Google designed runtime permissions with the user experience in mind, but not the developer’s coding experience.

one

I think the Runtime permissions API provided by Android is very easy to use, and I don’t think it is difficult to use it.

Is it really so? Let’s look at a specific example.

Suppose I am developing a photo function, which usually requires camera permission and location permission. In other words, these two permissions are the prerequisites for me to realize the photo function, and I can continue to take photos only after the user agrees with these two permissions.

So how to apply for these two permissions? The Runtime permissions API provided by Android should be familiar to everyone, so we can write the following code naturally:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION), 1) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { 1 -> { var allGranted = true for (result in grantResults) { if (result ! = PackageManager.PERMISSION_GRANTED) { allGranted = false } } if (allGranted) { takePicture() } else { Toast.makeText(this, "You have rejected a permission, Toast.length_short). Show ()}}}} fun takePicture() {toast.maketext (this, "start taking pictures ", Toast.LENGTH_SHORT).show() } }Copy the code

Here you can see, the first by calling requestPermissions () method request permissions and camera positioning permissions, and then in onRequestPermissionsResult authorized () method in monitoring results. If the user agrees to the two permissions, we can take photos. If the user refuses any of the permissions, a Toast prompt will pop up, telling the user that certain permissions have been rejected and that the user cannot take photos.

two

Is this a troublesome way to write it? This is a matter of opinion, some friends may feel that this is not many lines of code ah, what trouble. But I personally think it’s a hassle, and EVERY time I need to request runtime permissions, I feel tired and don’t want to write such verbose code.

But let’s not think about it from the point of view of simplicity, but from the point of view of correctness, is this right? I think there is a problem, because we just play a Toast to remind the user when the permission is denied, and there is no follow-up action plan. If the user really refuses a permission, the application cannot continue to be used.

Therefore, we also need to provide a mechanism to request permission again when it is denied by the user.

Now I make the following changes to the code:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) requestPermissions() } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { 1 -> { var allGranted = true for (result in grantResults) { if (result ! = PackageManager.PERMISSION_GRANTED) { allGranted = false } } if (allGranted) { takePicture() } else { Alertdialog.builder (this).apply {setMessage(" Camera and location permission required ") setCancelable(false) setPositiveButton(" ok ") {_, _ -> requestPermissions() } }.show() } } } } fun requestPermissions() { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION), 1)} fun takePicture() {toast.maketext (this, "start taking pictures ", toast.length_short).show()}Copy the code

Here I will request permissions code extraction to a requestPermissions () method, and then in onRequestPermissionsResult () in the judgment, if the user has rejected a permissions, then pop up a dialog box, tell the user cameras and location permissions are needed, The requestPermissions() method is then called in the setPositiveButton click event to re-request the permissions.

Let’s take a look at the results now:

As you can see, we are now more fully considering the scenario where permissions are denied.

three

Does this take into account the various scenarios in which runtime permissions are requested? Not yet, because the Android permissions system also provides a very “disgusting” mechanism called reject and ask no more.

When a permission is rejected by the user once, the next time we apply for the permission again, there will be an option to reject and not ask again. As long as the user selects this item, then that’s it, we can’t request this permission again, because the system will directly return that our permission was rejected.

This mechanism is very user friendly, because it prevents malicious software from harassing users by applying for permissions indefinitely. But for developers, it’s a pain in the ass. What if I have a feature that relies on this permission to run, and now the user rejects it and doesn’t ask for it anymore?

Of course, the vast majority of users are not stupid X, of course, know that the camera function needs to use the camera permission, I believe that 99% of users will click to agree to authorize. But can we ignore the other 1%? No, because your company’s testing is for the 1% of users who do this stupid thing.

In other words, even for the 1% of users, we still need to take this scenario into account in our programs for this unlikely operation.

So what do we do if permission is denied and no longer asked? A common way to do this is to remind the user to manually open the permissions in the Settings. If you want to do better, you can provide a function that automatically jumps to the current application Settings screen.

Let’s perfect this scenario, as follows:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) requestPermissions() } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { 1 -> { val denied = ArrayList<String>() val deniedAndNeverAskAgain = ArrayList<String>() grantResults.forEachIndexed { index, result -> if (result ! = PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[index])) { denied.add(permissions[index]) } else { deniedAndNeverAskAgain.add(permissions[index]) } } } if (denied.isEmpty() && deniedAndNeverAskAgain.isEmpty()) { takePicture() } else { if (denied.isNotEmpty()) { Alertdialog.builder (this).apply {setMessage(" Photo function requires you to approve album and location permissions ") setCancelable(false) setPositiveButton(" OK ") {_, _ -> requestPermissions()}}.show()} else {alertdialok.builder (this).apply {setMessage() {requestPermissions() {requestPermissions()}.show()} else {alertdialok.builder (this). SetCancelable (false) setPositiveButton(" Confirm ") {_, _ -> val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) val uri = Uri.fromParts("package", packageName, null) intent.data = uri startActivityForResult(intent, 1) } }.show() } } } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { 1 -> { requestPermissions() } } } fun requestPermissions() { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION), 1)} fun takePicture() {toast.maketext (this, "start taking pictures ", toast.length_short).show()}Copy the code

Now that the code is getting longer, let me just walk you through it.

Here I am onRequestPermissionsResult () method adds denied and deniedAndNeverAskAgain two sets, were used to record refused and refused to stop asking permission. If both sets are empty, then all permissions are granted and the photo can be taken directly.

If the denied set is not empty, it means that the user has denied permission. In this case, we will pop up a dialog box to remind the user and apply for permission again. If deniedAndNeverAskAgain is not null, the privilege has been denied by the user and is not asked again. In this case, the user is prompted to manually enable the privilege in the Settings. That is, when the user comes back from the Settings, they re-apply for permission.

Now run the program and it will look like this:

As you can see, when we first deny permissions, we remind the user that camera and location permissions are required. If the user continues to ignore it and chooses not to ask again, we will remind the user that he must manually register these permissions in order to continue running the program.

four

So far, we have managed to complete a “simple” permission request process. But is the code really that simple to write here? Can you really stand having to write such a long piece of code every time you apply for runtime permission?

That’s why I wrote PermissionX, an open source library. Requesting permissions on Android has never been easy, but it shouldn’t be.

PermissionX encapsulates all the complex logic that should be considered when requesting runtime permissions, exposing only the simplest interfaces to developers and removing the need to consider many of the scenarios I’ve discussed above.

We use PermissionX to do exactly the same thing, just write:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION) .onExplainRequestReason { scope, DeniedList - > val message = "camera positioning need you agree to photo albums and permissions" val ok = "sure" scope. ShowRequestReasonDialog (deniedList, message, ok) } .onForwardToSettings { scope, DeniedList - > val message = "you need to set permissions agreed to photo albums and positioning of" val ok = "sure" scope. ShowForwardToSettingsDialog (deniedList, message, Request {_, _, _ -> takePicture()}} fun takePicture() {toast.maketext (this, "start taking pictures ", Toast.LENGTH_SHORT).show() } }Copy the code

As you can see, the code for requesting permissions has suddenly become extremely lean.

We just need to pass in the permissions() method the name of the permission to request, fill in the prompt on the dialog in the onExplainRequestReason() and onForwardToSettings() callbacks, Then in the request() callback to ensure that all requested permissions have been granted, call the takePicture() method to start taking pictures.

So just to give you a sense of what PermissionX is all about, right? I really wrote that long permission request code for demonstration purposes, and I don’t want to write it again.

In addition, this article mainly demonstrates the ease of use of PermissionX, and does not cover many of its specific uses, such as Android 11 compatibility, custom dialog box styles, and so on. If you’re interested, see the links below for more usage.

The ultimate Solution to Android runtime permissions is PermissionX

PermissionX now supports Java! Android 11 permission changes are also explained

PermissionX has been updated to support custom permission notification dialogs

Introducing PermissionX into a project is also very simple, just add the following dependencies:

dependencies { ... Implementation 'com. Permissionx. Guolindev: permissionx: 1.3.1'}Copy the code

PermissionX open Source library: github.com/guolindev/P…

If you want to learn about Kotlin and the latest on Android, check out my new book, Line 1, Version 3.

Pay attention to my technical public account “Guo Lin”, there are high-quality technical articles pushed every day.