当前位置:网站首页>New technology practice, encapsulating the permission application library step by step with the activity results API

New technology practice, encapsulating the permission application library step by step with the activity results API

2022-06-24 08:31:00 Sharp surge

The author of this article :CoderPig, The original is published in : Pick the boy .

1

introduction

Last section 《【Jetpack】 Learn to wear :Activity Results API》 Said , In restructuring the company's project BaseFragment when , encounter onRequestPermissionResult() Deprecated The problem of , By the way, I learned a wave systematically Activity Results API Related postures .

https://juejin.cn/post/7094904353667940388

In the project , Put all the operations related to applying for permission into BaseActivity/BaseFragment in , Sure , But not very elegant , Many subclasses Activity/Fragment Forced to inherit this unused function . After all, only newcomers APP、 Take pictures and videos 、 Only when the map is located will you apply for permission , It's true. It's not necessary .

So what I want to do in this section is : Brush out the common sense related to authority + use Activity Results API Encapsulate a permission request library to play . It's no use saying more , I'll just start !

2

Previously applied for permission

① AndroidManifest.xml State the rights in

<manifest ...>
    <!--  Access camera  -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <application ...>
        ...
    </application>
</manifest>

If App Hardware is used in , Like a camera , Advice and Optional declaration , If not ,Android The system will think your App You can only run with this hardware , If you don't have this hardware, it will stop you directly App Installation . But most of the time , our App It can also run on devices without this hardware , So it is suggested to add :

<manifest ...>
    <application>
        ...
    </application>
    <uses-feature android:name="android.hardware.camera"
                  android:required="false" />
<manifest>

When using this hardware, perform the next judgment , If there is, execute the normal logic , No other logic is executed :

//  Determine whether there is a front camera 
if (applicationContext.packageManager.hasSystemFeature(
        PackageManager.FEATURE_CAMERA_FRONT)) {
    //  Yes ,XXX
} else {
    //  No, ,XXX
}

② Application authority

•  call ContextCompat.checkSelfPermission() Judge whether it has the corresponding authority , This method returns :PERMISSION_GRANTED( Authorized ) or PERMISSION_DENIED( unauthorized );

•  Call... If not authorized ActivityCompat.requestPermissions() Application authority ;

•  rewrite onRequestPermissionsResult() The callback method , Determine the authorization result , Follow up .

The code example is as follows :(OldRequestPermissionActivity.kt)

Apply for a permission randomly every time you click , Print the authorization result in the callback , The operation effect is as follows : 

3

Now apply for permission

The permission statement is the same , Permission to apply for API Different : Use Activity Results API Provided RequestPermission() or RequestMultiplePermissions() To apply for permission . An example of a single permission application code is as follows :(NewRequestPermissionActivity.kt)

The operation effect is as follows : 

 

Then try multiple permission applications , Use another protocol RequestMultiplePermissions(), The code example is as follows : 

The operation effect is as follows : 

The code doesn't seem to be much streamlined , Of course , It's not complete , Just show the most basic API Call it , Next, learn about 100 million common sense of authority . 

4

Billion points of authority common sense  

For detailed learning materials, see :《 Official documents :Android The permissions 》.

https://developer.android.com/guide/topics/permissions/overview?hl=zh-cn

① Reasons for setting application permissions

Protect user privacy , It includes two aspects : data ( Such as system status 、 User contact information ) And operation ( Such as audio recording ).

② Authority classification

Android Divide permissions into different types , Different levels of protection are assigned , All permissions can be viewed in the following ways API:

•  Official documents : jurisdiction API Reference documents ,Ctrl + F search Protection level: normal( Replace... As needed ) Find the corresponding level of permissions .

https://developer.android.com/reference/android/Manifest.permission?hl=zh-cn

•  System source code :/frameworks/base/core/res/AndroidManifest.xml, This file defines system permissions and other information .

1) Permissions during installation

The system will automatically grant corresponding permissions when the user allows the installation of the application ( Need to declare in the application ), It is divided into two subtypes :

•  General jurisdiction (normal)→ Permissions that do not threaten user security and privacy , Just in AndroidManifest.xml Can be used directly under the statement in .

•  Signature authority (signature)→ When the application declares the signature permission defined by other applications , If two applications use the same signature file for signature , The system will grant this permission to the former at the time of installation . otherwise , The system cannot grant this permission to the former .

2) Runtime permissions (dangerous)

Android 6.0 (M) New features , Also known as dangerous authority , refer to Permissions that may touch user privacy or affect device security , If you get contact information 、 Access location, etc . Such permission needs to be applied in the code , The license dialog box pops up , Authorization will only be obtained after the user manually agrees .

3) Special privileges

Relatively rare ,Google It is considered that such authority is more sensitive than dangerous authority , Therefore, users need to go to a special setting page to manually authorize an application . Such as : Hover box permissions 、 Modify setting permissions 、 Manage external storage, etc . Special permissions require special handling !!!

Tips:( Two permissions are related adb command )

#  View the permissions of the application 
adb shell dumpsys package  App package name 

#  Get permission level 
adb shell dumpsys package permision |grep -i prot

③ Application process of ordinary authority

In the example API demonstration , Only check permission and request permission are included , In fact, there is also an explanation of authority API:

ActivityCompat.shouldShowRequestPermissionRationale()

It can be judged after requesting permission callback : Whether the user rejected the request again , Examples of interpretation are as follows :

•  I haven't applied for permission , return false, Just apply ;

•  Applied for , But the user refused , return true, You can pop up a window to remind users , Then apply for permission here ;

•  The user chooses to reject and no longer display , return false, You can pop up a window to remind you of the necessary permissions , Boot jump setting manual Authorization ;

•  User allowed , return false, There is no need to apply or prompt ;

This part of processing logic can be arranged according to your actual situation , This is just an example ~

④ Permission group

Android To improve the user experience , Organize different permissions into groups according to device capabilities or functions , such as READ_CONTACTS And WRITE_CONTACTS Belong to the same group . Users do not have to understand the specific definition of each permission , When a permission in the group is granted , When requesting other permissions within the group , The system will not pop up a window asking for authorization , But directly Grant , The system automatically completes , Don't do anything .

⑤ Compatibility adaptation problem

Permission application must also be compatible , such as : The location authority has undergone several major changes , Normal location permissions 、 The background location permission acquisition rules are constantly changing . And such as : Apply for permission to suspend the box ,Android 10 You can jump directly to the suspension box setting page before ,Android 11 After that, you can only skip the management list of setting the suspension box .

Sum up , What the permission request library needs to do is :

Request runtime permissions + Special processing of applying for special permission + Handling compatibility issues

Common sense is almost understood , Then common sense uses new API Encapsulate a permission request Library ~

5

Packaging exploration journey  

① It seems that the process of requesting permission for official documents is not very good ?

Follow the user request permission suggestion process given in the official document :

The whole wave of code :

class TestCpPermissionActivity: AppCompatActivity() {
    private lateinit var mBinding: ActivityTestCpPermissionBinding
    private var mTipsDialog: AlertDialog? = null
    private var mGoSettingDialog: AlertDialog? = null
    private val mTakePhoto = takePhoto { " Whether there are photo results :${it == null}".logD() }

    private val mPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            if (isGranted) {
                " Permission granted ".logD()
            } else {
                " No permission granted ".logD()
                showGoSettingDialog()
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_test_cp_permission)
    }

    private fun requestPermission(permission: String) {
        val selfPermission = ContextCompat.checkSelfPermission(this, permission)
        when {
            //  Authorized 
            PackageManager.PERMISSION_GRANTED == selfPermission -> {
                shortToast(" Permission granted :{$permission}")
                mTakePhoto.launch(null)
            }
            //  Refuse , But not selected  " No more reminder ";  A window may pop up to prompt the user , Why this permission is required 
            ActivityCompat.shouldShowRequestPermissionRationale(this, permission) -> {
                showTipsDialog(permission)
            }
            //  Refuse , And no more reminders 
            else -> {
                mPermissionLauncher.launch(permission)
            }
        }
    }

    fun requestPermissionClick(v: View) {
        requestPermission(Manifest.permission.CAMERA)
    }

    private fun showTipsDialog(permission: String) {
        if(mTipsDialog == null) {
            mTipsDialog = AlertDialog.Builder(this).apply {
                setMessage(" The current app lacks the necessary permissions , It will cause the function to be temporarily unavailable , Please re authorize ")
                setPositiveButton(" determine ") { _, _ -> mPermissionLauncher.launch(permission) }
            }.create()
        }
        mTipsDialog!!.show()
    }

    private fun showGoSettingDialog() {
        if(mGoSettingDialog == null) {
            mGoSettingDialog = AlertDialog.Builder(this).apply {
                setTitle(" Prompt information ")
                setMessage(" The current app lacks the necessary permissions , This function is temporarily unavailable . If necessary , Please click on the 【 determine 】 Button to go to the setting Center for permission authorization .")
                setNegativeButton(" Cancel ") { _, _ -> }
                setPositiveButton(" determine ") { _, _ ->
                    //  Jump to settings page 
                    startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
                        Uri.parse("package:" + [email protected])
                    ))
                }
            }.create()
        }
        mGoSettingDialog!!.show()
    }
}

The operation results are as follows :

 

??? It doesn't seem right , It should be playing the general prompt first , Then ask again , Refuse again , The last thing I did was pop it up .shouldShowRequestPermissionRationale(Context, String) It shouldn't be here , It should be put in the permission callback .

Then came the question : How to get the requested permission in the permission callback ?ActivityResultContract.RequestPermission This thing only returns one Authorized or not boolean value . You can define a global variable externally , Assign a value to it every time you apply for permission , And then take it directly , Yes, yes , It's just cumbersome to use , Every time you apply for permission, let the developer define another variable to save ?

In fact, custom agreements , Add a variable to store permissions , Then the output type is changed to Pair Just fine .

② Customize ActivityResultContract

Just copy a wave RequestPermission Just change something , The code is as follows :

//  notes :Output The output type becomes Pair<String, Boolean>
class RequestPermissionContract : ActivityResultContract<String, Pair<String, Boolean>>() {
    //  Variables that store permissions 
    private lateinit var mPermission: String

    override fun createIntent(context: Context, input: String): Intent {
        //  establish Intent Pre assignment 
        mPermission = input
        return Intent(ACTION_REQUEST_PERMISSIONS).putExtra(EXTRA_PERMISSIONS, arrayOf(input))
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Pair<String, Boolean> {
        if (intent == null || resultCode != Activity.RESULT_OK) return mPermission to false
        val grantResults =
            intent.getIntArrayExtra(ActivityResultContracts.RequestMultiplePermissions.EXTRA_PERMISSION_GRANT_RESULTS)
        return mPermission to
            if (grantResults == null || grantResults.isEmpty()) false
            else grantResults[0] == PackageManager.PERMISSION_GRANTED
    }

    override fun getSynchronousResult(
        context: Context,
        input: String?
    ): SynchronousResult<Pair<String, Boolean>>? =
        when {
            null == input -> SynchronousResult("" to false)
            ContextCompat.checkSelfPermission(context, input) == PackageManager.PERMISSION_GRANTED -> {
                SynchronousResult(input to true)
            }
            else -> null
        }
}

Then adjust it , Use this protocol , And then in the callback shouldShowRequestPermissionRationale(), Now you can get the permission ~

private val mPermissionLauncher =
    registerForActivityResult(RequestPermissionContract()) { result ->
        if (result.second) {
            " Permission granted ".logD()
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, result.first)) {
                showTipsDialog(result.first)
            } else {
                showGoSettingDialog()
            }
        }
    }


private fun requestPermission(permission: String) {
    if(ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
        shortToast(" Permission granted :{$permission}")
        mTakePhoto.launch(null)
    } else {
        mPermissionLauncher.launch(permission)
    }
}

③ Write a quick build extension

It's obviously not easy to write such a big pile of code every time you apply for permission , Sure ActivityResultCaller Write fast build ActivityResultLauncher Extension method of , Pass in three handler functions , Authorized 、 Authorization tips 、 Deny authorization , Users can pass in as needed , So it is defined as nullable type :

fun ActivityResultCaller.registerForPermissionResult(
    onGranted: (() -> Unit)? = null,
    onDenied: (() -> Unit)? = null,
    onShowRequestRationale: (() -> Unit)? = null
): ActivityResultLauncher<String> {
    return registerForActivityResult(RequestPermissionContract()) { result ->
        val permission = result.first
        when {
            //  Authorized 
            result.second -> onGranted?.let { it -> it() }
            //  Prompt Authorization 
            permission.isNotEmpty() && ActivityCompat.shouldShowRequestPermissionRationale(
                this as Activity,
                permission
            ) -> onShowRequestRationale?.let { it -> it() }
            //  Deny authorization 
            else -> onDenied?.let { it -> it() }
        }
    }
}

call :

//  Definition 
private val mPermissionLauncher = registerForPermissionResult(
    onGranted = { " Permission granted ".logD(); mTakePhoto.launch(null) },
    onDenied = {  showGoSettingDialog()  },
    onShowRequestRationale = { showTipsDialog(it) })

//  Application authority 
mPermissionLauncher.launch("xxx")

It's refreshing all of a sudden ~


Just when I was a little complacent , Metacognition reminds me again :

In the absence of guidance and reading from excellent source code , It's easy to have the illusion that your code has been written well enough .

therefore , I have to borrow some libraries (chao) Jian (xi) Next , see , I just saw DylanCai It's packaged by the boss ActivityResult.kt.

https://github.com/DylanCaiCoding/Longan/blob/11826ad8a3108babc38c86334181121f8677a043/longan/src/main/java/com/dylanc/longan/ActivityResult.kt

④ borrow (chao) Jian (xi) Other people's excellent code

The big man's package is as follows :

 

The writing is similar to me , But the message is AppSettingsScope.() -> Unit→ Higher order extension function , Why do you do this , And the definition of the interface :


I'm a little confused here , What's the advantage of this over direct transfer function ? 

 

Add a friend of the author brother Lang directly , Consult a wave , Under the guidance of his patience + Read a large amount of data by yourself and fill in the blind area , Finally, it opened up ~

1) What is? SAM

Java8 after , Will be only Interface to a single abstract method be called SAM Interface or Functional interface , adopt Lambda It can be greatly simplified SAM Interface call .

such as Java In the code SAM Before and after the conversion , The code is really streamlined :

 

And in the Kotlin in , Support Java Of SAM transformation , But it doesn't directly support Kotlin Interface SAM transformation : 

 

Search the Internet and don't support Kotlin interface SAM The reason for the conversion :

Kotlin It already has function types and higher-order functions , You don't have to go SAM conversion . The intention is to encourage developers to try to replace the object-oriented thinking with the idea of functional programming , Therefore, it is recommended to use function type instead of SamType.

Say so ,Kotlin 1.4 After that, I still supported , Just in Kotlin Interface Definition time , add fun Just fine :


2) Higher order extension function  

 

You may not understand it for a while , But change it to the following , You will understand :

onShowRequestRationale: (PermissionScope) -> Unit

Is to initialize a PermissionScope example ( General function implementation , For example, jump to the setting page ), You can get this instance at the call :


Here we need to go through it Get an example , If change to :PermissionScope.() -> Unit: 

 

Directly through this You can get examples . This wave of birds eat cattle !!!


The code is very Fine, Next second My, Directly define a method to jump to the application settings page Launcher:

Modify the code that requests a single permission : 

 

Change the use : 

perfect ! At this time, I suddenly think of a question ,Kotlin It's really cool to call like this in , but Java How to pass the parameters in the ? After all, the company's projects use a pile of pages Java Written :


It's not hard ,lambda Just take a wave of expression : 

 

next PermissionScope The granularity of interface definition can be weighed by itself , Lange is a business type ( Such as jump setting ) Define a functional interface , You can also plug all businesses into one interface , And then implement it all , The advantage is that all businesses can be called in each function .

⑤ Special permission processing : Background location permissions

Reference material :《android Locating permission adaptation is enough to read this article 》

https://juejin.cn/post/6917975056026959885

Three location permissions :

<!-- Allows an app to access approximate location.   Approximate location permissions ,api1, Such as : Network location  -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- Allows an app to access precise location  Precise positioning authority ,api1, Such as :GPS location -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- Allows an app to access location in the background.  Background location permissions ,api29,android10 newly added  -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

The third permission is Android 10 New permissions , If you don't get background location permission , When APP In the background , Getting the location will fail . Compatible processing is as follows :

• Android 10.0 following , There is no such authority , Just apply  ACCESS_FINE_LOCATION  jurisdiction .

• Android 10.0, You can apply at the same time ACCESS_FINE_LOCATION and ACCESS_BACKGROUND_LOCATION jurisdiction .

 Android 10.0 above , You must apply for ACCESS_FINE_LOCATION After permission and Authorization , To apply for ACCESS_BACKGROUND_LOCATION jurisdiction , If the application is notified , Can't pop up the window , Direct authorization failed .

Write a launch() Extension method , Then delete the permission data group , Then traverse , Special handling of background permissions :


It looks like there's nothing wrong , The background location permission application is passed in at the call :


Just when I thought I could finish work and everything was fine , The result is this :


Refused and kept popping up the window , Is dubious , What's the matter ? Read the log output : 

 

You can only apply for one set of permissions at a time , You can't apply for the next group until one group is finished ? Follow a wave of source code Activity#requestPermissions:


Is dubious , Straight back to , No wonder the failure callback is triggered directly , It's hard . Difficult to engage in , It seems that you can't apply for multiple permissions like this , Only use ActivityResultContracts#RequestMultiplePermissions, Then the request result is processed .

⑥ To write registerForPermissionsResult


That's basically it , Then we need to add the authorization of foreground and background positioning Launcher: 

 

Then put the authority application part registerForPermissionsResult() Change to registerForPermissionsResult() It's so rich .


That's all about the record of encapsulation exploration , The code is very messy , Not elegant ,BUG Many , There must be repeated iterations and improvements later , Library second , Just get something from the tossing process . Subsequent code will be lost Github: CpPermission On , Those who are interested can first Star Occupy the pit , In addition to reference DylanCai Of ActivityResult Outside the package , You can also learn from some mature open source permission Libraries ( Adaptation strategies , Code design - For example, Guo Shen used the design mode of responsibility chain to deal with special permissions ), It can also be used directly :

https://github.com/coder-pig/CpPermission

https://github.com/DylanCaiCoding/Longan/blob/11826ad8a3108babc38c86334181121f8677a043/longan/src/main/java/com/dylanc/longan/ActivityResult.kt

https://github.com/guolindev/PermissionX

https://github.com/getActivity/XXPermissions

  • Guo Lin :PermissionX

  • The wheel elder brother :XXPermissions

Thanks to the dedication of the open source boss , Let's avoid many detours , More time to fish ~

6

Summary

This section first compares the old and new permission applications API The difference of , Then I learned the common sense related to 100 million authority , Then try to use new Activity Results API Encapsulate the permission application , Then, it uses the packaging of a wave of bosses for reference to modify , Finally, the compatibility processing of background location permission is also done . If the reader patiently reads , Want to encapsulate a permission request Library , It should be a small matter that can be caught easily .


from : New technology practice , Step by step Activity Results API Encapsulate the permission application library  

原网站

版权声明
本文为[Sharp surge]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/175/202206240553204469.html