当前位置:网站首页>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 .

边栏推荐
- 论文笔记: 多标签学习 DM2L
- Export MySQL database to xxx SQL, set xxx The SQL file is imported into MySQL on the server. Project deployment.
- ZUCC_编译语言原理与编译_实验04 语言与文法
- 12--合并两个有序链表
- Promise的使用场景
- 独立站运营中如何提升客户留存率?客户细分很重要!
- os.path.join()使用过程中遇到的坑
- Nodejs redlock notes
- [ACNOI2022]做过也不会
- Take my brother to do the project. It's cold
猜你喜欢

Utilisation de la fermeture / bloc de base SWIFT (source)

ZUCC_ Principles of compiling language and compilation_ Experiment 05 regular expression, finite automata, lexical analysis

疫情下更合适的开发模式

JUC个人简单笔记

More appropriate development mode under epidemic situation

李白最经典的20首诗排行榜

【微服务~Nacos】Nacos服务提供者和服务消费者

longhorn安装与使用

2021-03-11 COMP9021第八节课笔记

FPGA的虚拟时钟如何使用?
随机推荐
ZUCC_ Principles of compiling language and compilation_ Experiment 05 regular expression, finite automata, lexical analysis
jwt(json web token)
Promise的使用场景
11-- longest substring without repeated characters
The JS macro of WPS implements the separation method of picture text in the same paragraph
【微服务~Nacos】Nacos服务提供者和服务消费者
VsCode主题推荐
复习SGI STL二级空间配置器(内存池) | 笔记自用
2021-06-24: find the length of the longest non repeating character substring in a string.
51单片机_外部中断 与 定时/计数器中断
Getting started with ffmpeg
Fundamentals of 3D mathematics [17] inverse square theorem
487. 最大连续1的个数 II ●●
How to implement approval function in Tekton
13 -- 移除无效的括号
2021-06-25: a batch of strings consisting only of lowercase letters (a~z) are put
[acnoi2022] not a structure, more like a structure
Live broadcast review | detailed explanation of koordinator architecture of cloud native hybrid system (complete ppt attached)
Pat 1157: school anniversary
05-ubuntu安装mysql8