This article is simultaneously published on my wechat public account. You can follow it by searching Guo Lin on wechat. The article is updated every weekday.

Good morning, everyone. I don’t know if you’re surprised at my speed, but my new open source library, PermissionX, was just released a few days ago.

Yes, in less than a month, PermissionX has received another major release update. If you think a month isn’t fast enough, keep in mind that two weeks ago I released a new version of LitePal. For me, this speed is pretty extreme.

PermissionX: PermissionX: PermissionX: PermissionX: PermissionX: PermissionX

As planned, in the next iteration, I was going to add the ability to customize the permission prompt dialog box style to PermissionX. However, with the release of the first version, and based on feedback, I realized that there was a more urgent need for Java language support.

It’s sad to see that even today Kotlin is still a language spoken by a small minority of developers in China, but that’s the reality. Therefore, if PermissionX only supports Kotlin, most developers will be shut out.

I originally made PermissionX Kotlin only because I really didn’t want to maintain two versions at the same time, which would have required a change in two places and cost too much to maintain.

However, I did some more thinking later and found that it is possible to have code that supports both Java and Kotlin with only a few syntactic costs, so in this article we will learn how to do it.

Compatible with Java and Kotlin

First, let’s review the basic usage of PermissionX, which we saw in the previous article:

PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .onExplainRequestReason { deniedList -> showRequestReasonDialog(deniedList, "To apply for permission to access the program must rely on", "I have learnt the")}. OnForwardToSettings {deniedList - > showForwardToSettingsDialog (deniedList, }. Request {allGranted, grantedList, DeniedList -> if (allGranted) {toast. makeText(this, "all permissions approved ", Toast.length_short).show()} else {toast.maketext (this, "you rejected the following permission: $deniedList", toast.length_short).show()}}Copy the code

Yes, it’s so easy to apply for permissions on Android apps.

The permissions() method is called to specify which permissions to apply for, and the onExplainRequestReason() method is used to explain and reapply for denied permissions. The onForwardToSettings() method explains to the user why permanently denied permissions are necessary, and automatically jumps to the application Settings to remind the user to manually enable permissions. Finally, the request() method is called to begin requesting permissions and receive the results of the request.

The entire usage is clear and concise, and PermissionX helps developers solve some of the most painful logical aspects of the permission application process, such as what happens when a permission is denied? What if access is permanently denied?

The use of PermissionX is simple and straightforward, thanks in large part to Kotlin’s higher-order functions. The onExplainRequestReason(), onForwardToSettings(), and Request () methods in the code examples above are all higher-order functions. For function type arguments received in higher-order functions, we can simply pass in a Lambda expression and process the callback logic in the Lambda expression.

The problem, however, is that since Java has no concept of higher-order functions, this convenient syntax does not work in the Java language, which leads to the fact that PermissionX does not support Java.

However, this problem can be solved!

In fact, in the Kotlin language, we can pass Lambda expressions not only to higher-order functions, but also to another SAM function. The full name of SAM is Single Abstract Method, also called Single Abstract Method. Specifically, we can pass Lambda expressions to interfaces defined in Java that have only one method to implement (so-called single abstract methods).

As a concrete example, all Android developers must have called the setOnClickListener() method, which can be used to register click events for a control.

In Java we would write:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        
    }
});

Copy the code

This code is so familiar to everyone that no explanation is necessary.

But as you can see, in the setOnClickListener() method, we create an anonymous class for view.onClickListener. What does the code for view.onClickListener look like? Click on the source code, as shown below:

public class View implements Callback, android.view.KeyEvent.Callback, AccessibilityEventSource { public interface OnClickListener { void onClick(View view); }}Copy the code

As you can see, OnClickListener is an interface, and there is only one onClick() method in the interface, so this is a single abstract method interface.

So according to the rules above, Kotlin allows us to pass Lambda expressions to a function that receives a single abstract method interface. So, in Kotlin, when we register a button click event, it usually reads something like this:

button.setOnClickListener {

}

Copy the code

Did you get a little inspiration when you saw this? I got it anyway. That said, if PermissionX wants to be compatible with both Java and Kotlin languages, it can make good use of the single Abstract method interface feature. If you change all the higher-order functions into this SAM function, then it is naturally compatible with both languages.

Yes, I did, but there was a bit of a problem with the implementation.

Because higher-order functions are very powerful, we can define a function type’s argument list and its return value, but also its class. Take a look at some sample code from PermissionX:

fun onExplainRequestReason(callback: ExplainScope.(deniedList: MutableList<String>) -> Unit): PermissionBuilder {
	explainReasonCallback = callback
	return this
}

Copy the code

The above code may seem a bit confusing to non-Kotlin readers, but if you have studied Kotlin, you know that this is just a simple function type parameter. Yes, here I would like to recommend my new book “The first Line of Code 3rd edition”, have not read friends can seriously consider, can help you easily use Kotlin language in a large program.

So in the above code, we defined the class of the function type in the ExplainScope. What does that mean? This means that in a Lambda expression, we automatically have the ExplainScope context, so we can call any method in the ExplainScope class directly. So, as you might have guessed, the showRequestReasonDialog() method called in the first sample code in this article is defined in the ExplainScope class.

However, this great feature in Kotlin, unfortunately, is not available in Java either, and is not available even through SAM functions.

So, at the expense of a few syntactic features, I’ve changed Kotlin’s feature of defining the context of a class to passing parameters. For this reason, the syntax of the new Version of PermissionX will not be fully compatible with the previous version, but will have to be changed slightly.

The new version of PermissionX implements the same functionality as before:

PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .onExplainRequestReason { scope, DeniedList - > scope. ShowRequestReasonDialog (deniedList, "is about to apply for permission to the program must rely on the permissions", "I have learnt the")}. OnForwardToSettings {the scope, DeniedList - > scope. ShowForwardToSettingsDialog (deniedList, "you need to manually open access of application Settings", "I have learnt the")}. Request {allGranted, GrantedList, deniedList -> if (allGranted) {toast.maketext (this, "all privileges have been granted ", Toast.length_short).show()} else {toast.maketext (this, "you rejected the following permission: $deniedList", toast.length_short).show()}}Copy the code

As you can see, simply adding a scope argument to the Lambda expression argument list of the onExplainRequestReason() and onForwardToSettings() methods, and then calling the Explain permission request reason dialog, The scope object is also added in front of it, with only minor changes, and the rest of the usage is exactly the same as before.

Kotlin’s little usage sacrifice resulted in full Support for the Java language. To do the same thing in Java, write:

PermissionX.init(this) .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE) .onExplainRequestReason(new ExplainReasonCallbackWithBeforeParam() { @Override public void onExplainReason(ExplainScope scope, List<String> deniedList, Boolean beforeRequest) {scope. ShowRequestReasonDialog (deniedList, "is about to apply for permission to access the program must rely on", "I have learnt the"); } }) .onForwardToSettings(new ForwardToSettingsCallback() { @Override public void onForwardToSettings(ForwardScope Scope, a List < String > deniedList) {scope. ShowForwardToSettingsDialog (deniedList, "you need to manually open access of application Settings", "I have learnt the"); } }) .request(new RequestCallback() { @Override public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {if (allGranted) {toast.maketext (mainActivity. this, "All permissions have been granted ", Toast.LENGTH_SHORT).show(); } else {toast.maketext (mainactivity.this, "you denied the following permissions:" + deniedList, toast.length_short).show(); }}});Copy the code

Purely from the comparison of the two languages, Kotlin version of the code is certainly far more concise than the Java version, but many friends may be more accustomed to Java syntax structure, it may look more friendly.

Support Android 11

The Beta version of Android 11 was officially released on Thursday, and I got ahead of the game by checking out the new features.

There have been major changes to the permission-related parts, but don’t worry, there aren’t many things we developers need to adapt, but you should be aware of the changes.

First, the “decline and ask no more” option that so many developers hate is gone. But don’t get too excited; Android 11 just changes it to another presentation. If an application is denied a permission twice, Android will automatically treat it as “denied and no questions asked.”

In addition, the permission request dialog box is now allowed to be cancelled. If the user cancels the permission request box, it will be treated as a refusal.

In Android 11, a permission expiration mechanism was introduced. Once a user granted a permission to an application, the permission would remain valid forever. Now, if an application is not started for a long time, the Android system will revoke the permission granted by the user automatically, and you need to request permission again next time.

In addition, Android 11 provides the option of single authorization for camera, microphone, and geolocation. Because these three permissions are privacy-sensitive permissions, if the user agrees once as in the past, it means permanent authorization, and some malicious applications may collect user information indiscriminately. Request camera permission in Android 11, and the interface is shown below.

As you can see, there is a “once only” option added to the figure. If the user chooses this option, camera data will be available for the entire life of the application. However, the next time you start the program, you need to request permission again.

These are some of the major permission-related changes in Android 11. As you can see, these changes didn’t really affect our coding or require any additional adaptation, so you just need to know about them.

But the next part is where we need to adapt.

Android system first introduced the Android: 10 foregroundServiceType attribute, if you want to be in the front desk Service for the user’s location information, then you must in AndroidManifest. In the following configuration in XML declaration:

<manifest>
    ...
    <service ... 
		android:foregroundServiceType="location" />
</manifest>

Copy the code

In Android 11, this requirement extends to camera and microphone access. That is, if you want to get camera and microphone data from your device in the foreground Service, you need to declare it in androidmanifest.xml as well:

<manifest>
    ...
    <service ...
		android:foregroundServiceType="location|camera|microphone" />
</manifest>

Copy the code

Now let’s look at another area that needs to be adapted.

A new permission, ACCESS_BACKGROUND_LOCATION, has been introduced in Android 10 to allow applications to request device location information in the background. This permission cannot be granted separately, but must be granted together with ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION. This is also easy to understand, how is it possible to request location information in the background without permission from the foreground.

In Android 10, if we apply for both foreground and background location permission, the following screen will appear:

As you can see, the options on the interface are somewhat different. Always Allow indicates that both foreground and background location permissions are allowed, Only when using this application indicates that only foreground location permissions are allowed, and Deny indicates that neither is allowed.

But what if we apply for both foreground and background location privileges in Android 11? I’m sorry to tell you, it will crash.

Because Android 11 requires that the ACCESS_BACKGROUND_LOCATION permission be requested separately, and prior to that, The application must also already have access to ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION.

PermissionX is not a rule to consider. If a developer has requested both foreground and background location permissions in Android 11, it makes sense for the system to throw an exception directly, as this violates the Android 11 rules.

However, in order to make it easier for developers to use PermissionX and reduce the number of scenarios for programming differentiation, I decided to adapt this new rule for Android 11.

The specific idea is also relatively simple, if the application has applied for the foreground and background positioning rights, then ignore the background positioning rights, only apply for the foreground positioning and other rights, and then apply for the background positioning rights alone after all the rights are applied for.

Looks simple, doesn’t it? It almost killed me when I tried to implement it, and it also revealed that PermissionX’s extensibility was poorly designed.

I always thought the PermissionX code was pretty good and encouraged people to read the source code. However, in order to be compatible with Android 11, I found that there were several places where the coupling was so high that it made it difficult to extend the functionality.

There are many places in PermissionX where you can register callback listening, whether it’s a callback when a permission is denied, a callback when a permission is permanently denied, or a callback when a permission request ends. There are more places in the code logic to notify these callbacks. Passing in an empty list of permissions does not request permission, and the callback ends. An incoming permission list is also called back if all permissions are already granted. Also, click the Cancel button on the Explain permission request dialog box to terminate subsequent permission requests.

This is just a few boundary cases, not a formal permission request flow, and there is more callback logic after a formal request.

So what’s the problem with this complicated callback logic? It is hard for me to find a point to determine that all permissions except background location permissions are handled (so many callback points need to be handled) and then apply for background location permissions separately. In addition, background location permissions also need to reuse the previous logic, so that at each callback I need to know whether I am currently requesting non-background location permissions or background location permissions (otherwise I will not know whether to request background location permissions next, or end the request and call back to the developer).

I probably tried two different if else designs to make Android 11 compatible, and both failed. After writing, the logic became more and more complicated. I changed this bug and found that bug. I really couldn’t continue.

Eventually I decided to tear down the entire architecture of PermissionX and start over. It wasn’t an easy decision, but knowing that PermissionX was poorly designed for extensibility, I had to fix it sooner or later.

The overall architecture of the new PermissionX is changed to a chained task execution mode, where requests are divided into two tasks based on different permission types, and the request for permission and the resulting callback are encapsulated within the task. When a task is completed, it determines whether there is another task to be executed. If so, the next task is executed. If not, the callback ends. The schematic diagram is as follows:

Part of the chain task implementation code is as follows:

public class RequestChain {

    
    private BaseTask headTask;

    
    private BaseTask tailTask;

    
    public void addTaskToChain(BaseTask task) {
        if (headTask == null) {
            headTask = task;
        }
        
        if (tailTask != null) {
            tailTask.next = task;
        }
        tailTask = task;
    }

    
    public void runTask() {
        headTask.request();
    }

}

Copy the code

I used a data structure called a linked list to do this, adding it to the end of the list every time a new task was added. When executing a task, it starts with the first task and then works backwards, before calling back to the developer when all tasks are completed.

Then in the request() method for permission, I build a chain of tasks like this:

public void request(RequestCallback callback) {
    requestCallback = callback;
    
    
    
    RequestChain requestChain = new RequestChain();
    requestChain.addTaskToChain(new RequestNormalPermissions(this));
    requestChain.addTaskToChain(new RequestBackgroundLocationPermission(this));
    requestChain.runTask();
}

Copy the code

As you can see, an instance of RequestChain is created and a RequestNormalPermissions task is added to the linked list to RequestNormalPermissions. Added a RequestBackgroundLocationPermission task is used to request the background location permissions, then call runTask () method can from the list in turn head backward mission.

Now, when you use PermissionX for permission processing, you can ignore the permissions mechanism differences on Android 11 completely, and PermissionX will handle all the judgment logic for you internally. If you request foreground and background location permissions at the same time, they will be requested together on Android 10, separately on Android 11, and not background location permissions on Android 9 or below, because they are not available at that time.

In addition, with this chain-task execution mode, PermissionX will scale very well in the future. Because in addition to the permissions we discussed above, the Android system has some more special permissions, such as hover window permissions. This permission can not be called code to apply, but to jump to a special setting interface, remind the user to manually open. Now, PermissionX just needs to add a new task to support this permission. Of course, this feature is a relatively late version of the plan, the focus of the next version is the custom permission prompt dialog box style function.

How to upgrade

PermissionX < span style = “box-sizing: border-box; color: RGB (51, 51, 51); line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;”

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

Especially for developers who still use the Java language, this release update is well worth a try.

If your project has not been upgraded to AndroidX, you can use the same version of permission-support.

dependencies { ... Implementation 'com. Permissionx. Guolindev: permission - support: 1.2.2'}Copy the code

Finally, the open source library for PermissionX is attached. Welcome to Star and fork.

Github.com/guolindev/P…

PermissionX is a new version of The Android operating system that has been updated for more than a few years. This article covers the new version of PermissionX, Kotlin’s knowledge, permissions changes for Android 11, and the architecture of PermissionX.

In the next release, PermissionX will add the ability to customize the permission notification dialog box. For details, see PermissionX’s major update to support custom permission notification dialog box.

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

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