Android動(dòng)態(tài)權(quán)限申請?jiān)斀?/h1>
更新時(shí)間:2023年04月25日 10:41:23 作者:dreamgyf
大家是否還在為動(dòng)態(tài)權(quán)限申請感到苦惱呢?傳統(tǒng)的動(dòng)態(tài)權(quán)限申請需要在Activity中重寫onRequestPermissionsResult方法來接收用戶權(quán)限授予的結(jié)果,本文介紹了如何簡單的實(shí)現(xiàn)Android動(dòng)態(tài)權(quán)限申請
前言
注:只想看實(shí)現(xiàn)的朋友們可以直接跳到最后面的最終實(shí)現(xiàn)
大家是否還在為動(dòng)態(tài)權(quán)限申請感到苦惱呢?傳統(tǒng)的動(dòng)態(tài)權(quán)限申請需要在Activity
中重寫onRequestPermissionsResult
方法來接收用戶權(quán)限授予的結(jié)果。試想一下,你需要在一個(gè)子模塊中申請權(quán)限,那得從這個(gè)模塊所在的Activity
的onRequestPermissionsResult
中將結(jié)果一層層再傳回到這個(gè)模塊中,相當(dāng)?shù)穆闊?,代碼也相當(dāng)冗余和不干凈,逼死強(qiáng)迫癥。
使用
為了解決這個(gè)痛點(diǎn),我封裝出了兩個(gè)方法,用于隨時(shí)隨地快速的動(dòng)態(tài)申請權(quán)限,我們先來看看我們的封裝方法是如何調(diào)用的:
activity.requestPermission(Manifest.permission.CAMERA, onPermit = {
//申請權(quán)限成功 Do something
}, onDeny = { shouldShowCustomRequest ->
//申請權(quán)限失敗 Do something
if (shouldShowCustomRequest) {
//用戶選擇了拒絕并且不在詢問,此時(shí)應(yīng)該使用自定義彈窗提醒用戶授權(quán)(可選)
}
})
這樣是不是非常的簡單便捷?申請和結(jié)果回調(diào)都在一個(gè)方法內(nèi)處理,并且支持隨用隨調(diào)。
方案
那么,這么方便好用的方法是怎么實(shí)現(xiàn)的呢?不知道小伙伴們在平時(shí)開發(fā)中有沒有注意到過,當(dāng)你調(diào)用startActivityForResult
時(shí),AS會(huì)提示你該方法已被棄用,點(diǎn)進(jìn)去看會(huì)告訴你應(yīng)該使用registerForActivityResult
方法替代。沒錯(cuò),這就是androidx
給我們提供的ActivityResult
功能,并且這個(gè)功能不僅支持ActivityResult
回調(diào),還支持打開文檔,拍攝照片,選擇文件等各種各樣的回調(diào),同樣也包括我們今天要說的權(quán)限申請
其實(shí)Android在官方文檔 請求運(yùn)行時(shí)權(quán)限 中就已經(jīng)將其作為動(dòng)態(tài)權(quán)限申請的推薦方法了,如下示例代碼所示:
val requestPermissionLauncher =
registerForActivityResult(RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// feature requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
}
when {
ContextCompat.checkSelfPermission(
CONTEXT,
Manifest.permission.REQUESTED_PERMISSION
) == PackageManager.PERMISSION_GRANTED -> {
// You can use the API that requires the permission.
}
shouldShowRequestPermissionRationale(...) -> {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected, and what
// features are disabled if it's declined. In this UI, include a
// "cancel" or "no thanks" button that lets the user continue
// using your app without granting the permission.
showInContextUI(...)
}
else -> {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
requestPermissionLauncher.launch(
Manifest.permission.REQUESTED_PERMISSION)
}
}
說到這里,可能有小伙伴要質(zhì)疑我了:“官方文檔里都寫明了的東西,你還特地寫一遍,還起了這么個(gè)標(biāo)題,是不是在水文章?!”
莫急,如果你遵照以上方法這么寫的話,在實(shí)際調(diào)用的時(shí)候會(huì)直接發(fā)生崩潰:
java.lang.IllegalStateException:
LifecycleOwner Activity is attempting to register while current state is RESUMED.
LifecycleOwners must call register before they are STARTED.
這段報(bào)錯(cuò)很明顯的告訴我們,我們的注冊工作必須要在Activity
聲明周期STARTED
之前進(jìn)行(也就是onCreate
時(shí)和onStart
完成前),但這樣我們就必須要事先注冊好所有可能會(huì)用到的權(quán)限,沒辦法做到隨時(shí)隨地有需要時(shí)再申請權(quán)限了,有辦法解決這個(gè)問題嗎?答案是肯定的。
繞過生命周期檢測
想解決這個(gè)問題,我們必須要知道問題的成因,讓我們帶著問題進(jìn)到源碼中一探究竟:
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback) {
return registerForActivityResult(contract, mActivityResultRegistry, callback);
}
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultRegistry registry,
@NonNull final ActivityResultCallback<O> callback) {
return registry.register(
"activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
}
public final <I, O> ActivityResultLauncher<I> register(
@NonNull final String key,
@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback) {
Lifecycle lifecycle = lifecycleOwner.getLifecycle();
if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is "
+ "attempting to register while current state is "
+ lifecycle.getCurrentState() + ". LifecycleOwners must call register before "
+ "they are STARTED.");
}
registerKey(key);
LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
if (lifecycleContainer == null) {
lifecycleContainer = new LifecycleContainer(lifecycle);
}
LifecycleEventObserver observer = new LifecycleEventObserver() { ... };
lifecycleContainer.addObserver(observer);
mKeyToLifecycleContainers.put(key, lifecycleContainer);
return new ActivityResultLauncher<I>() { ... };
}
我們可以發(fā)現(xiàn),registerForActivityResult
實(shí)際上就是調(diào)用了ComponentActivity
內(nèi)部成員變量的mActivityResultRegistry.register
方法,而在這個(gè)方法的一開頭就檢查了當(dāng)前Activity
的生命周期,如果生命周期位于STARTED
后則直接拋出異常,那我們該如何繞過這個(gè)限制呢?
其實(shí)在register
方法的下面就有一個(gè)同名重載方法,這個(gè)方法并沒有做生命周期的檢測:
public final <I, O> ActivityResultLauncher<I> register(
@NonNull final String key,
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback) {
registerKey(key);
mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
if (mParsedPendingResults.containsKey(key)) {
@SuppressWarnings("unchecked")
final O parsedPendingResult = (O) mParsedPendingResults.get(key);
mParsedPendingResults.remove(key);
callback.onActivityResult(parsedPendingResult);
}
final ActivityResult pendingResult = mPendingResults.getParcelable(key);
if (pendingResult != null) {
mPendingResults.remove(key);
callback.onActivityResult(contract.parseResult(
pendingResult.getResultCode(),
pendingResult.getData()));
}
return new ActivityResultLauncher<I>() { ... };
}
找到這個(gè)方法就簡單了,我們將registerForActivityResult
方法調(diào)用替換成activityResultRegistry.register
調(diào)用就可以了
當(dāng)然,我們還需要注意一些小細(xì)節(jié),檢查生命周期的register
方法同時(shí)也會(huì)注冊生命周期回調(diào),當(dāng)Activity
被銷毀時(shí)會(huì)將我們注冊的ActivityResult
回調(diào)移除,我們也需要給我們封裝的方法加上這個(gè)邏輯,最終實(shí)現(xiàn)就如下所示。
最終實(shí)現(xiàn)
private val nextLocalRequestCode = AtomicInteger()
private val nextKey: String
get() = "activity_rq#${nextLocalRequestCode.getAndIncrement()}"
fun ComponentActivity.requestPermission(
permission: String,
onPermit: () -> Unit,
onDeny: (shouldShowCustomRequest: Boolean) -> Unit
) {
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
onPermit()
return
}
var launcher by Delegates.notNull<ActivityResultLauncher<String>>()
launcher = activityResultRegistry.register(
nextKey,
ActivityResultContracts.RequestPermission()
) { result ->
if (result) {
onPermit()
} else {
onDeny(!ActivityCompat.shouldShowRequestPermissionRationale(this, permission))
}
launcher.unregister()
}
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
launcher.unregister()
lifecycle.removeObserver(this)
}
}
})
launcher.launch(permission)
}
fun ComponentActivity.requestPermissions(
permissions: Array<String>,
onPermit: () -> Unit,
onDeny: (shouldShowCustomRequest: Boolean) -> Unit
) {
var hasPermissions = true
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(
this,
permission
) != PackageManager.PERMISSION_GRANTED
) {
hasPermissions = false
break
}
}
if (hasPermissions) {
onPermit()
return
}
var launcher by Delegates.notNull<ActivityResultLauncher<Array<String>>>()
launcher = activityResultRegistry.register(
nextKey,
ActivityResultContracts.RequestMultiplePermissions()
) { result ->
var allAllow = true
for (allow in result.values) {
if (!allow) {
allAllow = false
break
}
}
if (allAllow) {
onPermit()
} else {
var shouldShowCustomRequest = false
for (permission in permissions) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
shouldShowCustomRequest = true
break
}
}
onDeny(shouldShowCustomRequest)
}
launcher.unregister()
}
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
launcher.unregister()
lifecycle.removeObserver(this)
}
}
})
launcher.launch(permissions)
}
總結(jié)
其實(shí)很多實(shí)用技巧本質(zhì)上都是很簡單的,但沒有接觸過就很難想到,我將我的開發(fā)經(jīng)驗(yàn)分享給大家,希望能幫助到大家。
到此這篇關(guān)于Android動(dòng)態(tài)權(quán)限申請?jiān)斀獾奈恼戮徒榻B到這了,更多相關(guān)Android動(dòng)態(tài)權(quán)限申請內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
-
Android自定義View實(shí)現(xiàn)五子棋游戲
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下 2019-11-11
-
詳解Android中使用Notification實(shí)現(xiàn)進(jìn)度通知欄(示例三)
這篇文章主要介紹了詳解Android中使用Notification實(shí)現(xiàn)進(jìn)度通知欄(示例三),具有一定的參考價(jià)值,有興趣的可以了解一下。 2016-12-12
-
Android中實(shí)現(xiàn)在矩形框中輸入文字顯示剩余字?jǐn)?shù)的功能
在矩形輸入框框中輸入文字顯示剩余字?jǐn)?shù)的功能在app開發(fā)中經(jīng)常會(huì)見到,今天小編就通過實(shí)例代碼給大家分享android實(shí)現(xiàn)輸入框提示剩余字?jǐn)?shù)功能,代碼簡單易懂,需要的朋友參考下吧 2017-04-04
-
Android實(shí)現(xiàn)excel/pdf/word/odt/圖片相互轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了Android如何實(shí)現(xiàn)excel/pdf/word/odt/圖片之間的相互轉(zhuǎn)換,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下 2023-04-04
-
Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開屏布局
這篇文章主要介紹了Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開屏布局,需要的朋友可以參考下 2014-07-07
-
Android自定義相機(jī)、預(yù)覽區(qū)域裁剪
這篇文章主要為大家詳細(xì)介紹了Android自定義相機(jī)、預(yù)覽區(qū)域裁剪,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下 2022-05-05
-
Android Xutils3網(wǎng)絡(luò)請求的封裝詳解及實(shí)例代碼
這篇文章主要介紹了Android Xutils3網(wǎng)絡(luò)請求的封裝詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下 2016-12-12
-
Android布局之GridLayout網(wǎng)格布局
網(wǎng)格布局標(biāo)簽是GridLayout。這個(gè)布局是android4.0新增的布局。這個(gè)布局只有4.0之后的版本才能使用。本文給大家介紹Android布局之GridLayout網(wǎng)格布局相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧 2015-12-12
最新評論
前言
注:只想看實(shí)現(xiàn)的朋友們可以直接跳到最后面的最終實(shí)現(xiàn)
大家是否還在為動(dòng)態(tài)權(quán)限申請感到苦惱呢?傳統(tǒng)的動(dòng)態(tài)權(quán)限申請需要在Activity
中重寫onRequestPermissionsResult
方法來接收用戶權(quán)限授予的結(jié)果。試想一下,你需要在一個(gè)子模塊中申請權(quán)限,那得從這個(gè)模塊所在的Activity
的onRequestPermissionsResult
中將結(jié)果一層層再傳回到這個(gè)模塊中,相當(dāng)?shù)穆闊?,代碼也相當(dāng)冗余和不干凈,逼死強(qiáng)迫癥。
使用
為了解決這個(gè)痛點(diǎn),我封裝出了兩個(gè)方法,用于隨時(shí)隨地快速的動(dòng)態(tài)申請權(quán)限,我們先來看看我們的封裝方法是如何調(diào)用的:
activity.requestPermission(Manifest.permission.CAMERA, onPermit = { //申請權(quán)限成功 Do something }, onDeny = { shouldShowCustomRequest -> //申請權(quán)限失敗 Do something if (shouldShowCustomRequest) { //用戶選擇了拒絕并且不在詢問,此時(shí)應(yīng)該使用自定義彈窗提醒用戶授權(quán)(可選) } })
這樣是不是非常的簡單便捷?申請和結(jié)果回調(diào)都在一個(gè)方法內(nèi)處理,并且支持隨用隨調(diào)。
方案
那么,這么方便好用的方法是怎么實(shí)現(xiàn)的呢?不知道小伙伴們在平時(shí)開發(fā)中有沒有注意到過,當(dāng)你調(diào)用startActivityForResult
時(shí),AS會(huì)提示你該方法已被棄用,點(diǎn)進(jìn)去看會(huì)告訴你應(yīng)該使用registerForActivityResult
方法替代。沒錯(cuò),這就是androidx
給我們提供的ActivityResult
功能,并且這個(gè)功能不僅支持ActivityResult
回調(diào),還支持打開文檔,拍攝照片,選擇文件等各種各樣的回調(diào),同樣也包括我們今天要說的權(quán)限申請
其實(shí)Android在官方文檔 請求運(yùn)行時(shí)權(quán)限 中就已經(jīng)將其作為動(dòng)態(tài)權(quán)限申請的推薦方法了,如下示例代碼所示:
val requestPermissionLauncher = registerForActivityResult(RequestPermission() ) { isGranted: Boolean -> if (isGranted) { // Permission is granted. Continue the action or workflow in your // app. } else { // Explain to the user that the feature is unavailable because the // feature requires a permission that the user has denied. At the // same time, respect the user's decision. Don't link to system // settings in an effort to convince the user to change their // decision. } } when { ContextCompat.checkSelfPermission( CONTEXT, Manifest.permission.REQUESTED_PERMISSION ) == PackageManager.PERMISSION_GRANTED -> { // You can use the API that requires the permission. } shouldShowRequestPermissionRationale(...) -> { // In an educational UI, explain to the user why your app requires this // permission for a specific feature to behave as expected, and what // features are disabled if it's declined. In this UI, include a // "cancel" or "no thanks" button that lets the user continue // using your app without granting the permission. showInContextUI(...) } else -> { // You can directly ask for the permission. // The registered ActivityResultCallback gets the result of this request. requestPermissionLauncher.launch( Manifest.permission.REQUESTED_PERMISSION) } }
說到這里,可能有小伙伴要質(zhì)疑我了:“官方文檔里都寫明了的東西,你還特地寫一遍,還起了這么個(gè)標(biāo)題,是不是在水文章?!”
莫急,如果你遵照以上方法這么寫的話,在實(shí)際調(diào)用的時(shí)候會(huì)直接發(fā)生崩潰:
java.lang.IllegalStateException: LifecycleOwner Activity is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
這段報(bào)錯(cuò)很明顯的告訴我們,我們的注冊工作必須要在Activity
聲明周期STARTED
之前進(jìn)行(也就是onCreate
時(shí)和onStart
完成前),但這樣我們就必須要事先注冊好所有可能會(huì)用到的權(quán)限,沒辦法做到隨時(shí)隨地有需要時(shí)再申請權(quán)限了,有辦法解決這個(gè)問題嗎?答案是肯定的。
繞過生命周期檢測
想解決這個(gè)問題,我們必須要知道問題的成因,讓我們帶著問題進(jìn)到源碼中一探究竟:
public final <I, O> ActivityResultLauncher<I> registerForActivityResult( @NonNull ActivityResultContract<I, O> contract, @NonNull ActivityResultCallback<O> callback) { return registerForActivityResult(contract, mActivityResultRegistry, callback); } public final <I, O> ActivityResultLauncher<I> registerForActivityResult( @NonNull final ActivityResultContract<I, O> contract, @NonNull final ActivityResultRegistry registry, @NonNull final ActivityResultCallback<O> callback) { return registry.register( "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback); } public final <I, O> ActivityResultLauncher<I> register( @NonNull final String key, @NonNull final LifecycleOwner lifecycleOwner, @NonNull final ActivityResultContract<I, O> contract, @NonNull final ActivityResultCallback<O> callback) { Lifecycle lifecycle = lifecycleOwner.getLifecycle(); if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is " + "attempting to register while current state is " + lifecycle.getCurrentState() + ". LifecycleOwners must call register before " + "they are STARTED."); } registerKey(key); LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key); if (lifecycleContainer == null) { lifecycleContainer = new LifecycleContainer(lifecycle); } LifecycleEventObserver observer = new LifecycleEventObserver() { ... }; lifecycleContainer.addObserver(observer); mKeyToLifecycleContainers.put(key, lifecycleContainer); return new ActivityResultLauncher<I>() { ... }; }
我們可以發(fā)現(xiàn),registerForActivityResult
實(shí)際上就是調(diào)用了ComponentActivity
內(nèi)部成員變量的mActivityResultRegistry.register
方法,而在這個(gè)方法的一開頭就檢查了當(dāng)前Activity
的生命周期,如果生命周期位于STARTED
后則直接拋出異常,那我們該如何繞過這個(gè)限制呢?
其實(shí)在register
方法的下面就有一個(gè)同名重載方法,這個(gè)方法并沒有做生命周期的檢測:
public final <I, O> ActivityResultLauncher<I> register( @NonNull final String key, @NonNull final ActivityResultContract<I, O> contract, @NonNull final ActivityResultCallback<O> callback) { registerKey(key); mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract)); if (mParsedPendingResults.containsKey(key)) { @SuppressWarnings("unchecked") final O parsedPendingResult = (O) mParsedPendingResults.get(key); mParsedPendingResults.remove(key); callback.onActivityResult(parsedPendingResult); } final ActivityResult pendingResult = mPendingResults.getParcelable(key); if (pendingResult != null) { mPendingResults.remove(key); callback.onActivityResult(contract.parseResult( pendingResult.getResultCode(), pendingResult.getData())); } return new ActivityResultLauncher<I>() { ... }; }
找到這個(gè)方法就簡單了,我們將registerForActivityResult
方法調(diào)用替換成activityResultRegistry.register
調(diào)用就可以了
當(dāng)然,我們還需要注意一些小細(xì)節(jié),檢查生命周期的register
方法同時(shí)也會(huì)注冊生命周期回調(diào),當(dāng)Activity
被銷毀時(shí)會(huì)將我們注冊的ActivityResult
回調(diào)移除,我們也需要給我們封裝的方法加上這個(gè)邏輯,最終實(shí)現(xiàn)就如下所示。
最終實(shí)現(xiàn)
private val nextLocalRequestCode = AtomicInteger() private val nextKey: String get() = "activity_rq#${nextLocalRequestCode.getAndIncrement()}" fun ComponentActivity.requestPermission( permission: String, onPermit: () -> Unit, onDeny: (shouldShowCustomRequest: Boolean) -> Unit ) { if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) { onPermit() return } var launcher by Delegates.notNull<ActivityResultLauncher<String>>() launcher = activityResultRegistry.register( nextKey, ActivityResultContracts.RequestPermission() ) { result -> if (result) { onPermit() } else { onDeny(!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) } launcher.unregister() } lifecycle.addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (event == Lifecycle.Event.ON_DESTROY) { launcher.unregister() lifecycle.removeObserver(this) } } }) launcher.launch(permission) } fun ComponentActivity.requestPermissions( permissions: Array<String>, onPermit: () -> Unit, onDeny: (shouldShowCustomRequest: Boolean) -> Unit ) { var hasPermissions = true for (permission in permissions) { if (ContextCompat.checkSelfPermission( this, permission ) != PackageManager.PERMISSION_GRANTED ) { hasPermissions = false break } } if (hasPermissions) { onPermit() return } var launcher by Delegates.notNull<ActivityResultLauncher<Array<String>>>() launcher = activityResultRegistry.register( nextKey, ActivityResultContracts.RequestMultiplePermissions() ) { result -> var allAllow = true for (allow in result.values) { if (!allow) { allAllow = false break } } if (allAllow) { onPermit() } else { var shouldShowCustomRequest = false for (permission in permissions) { if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) { shouldShowCustomRequest = true break } } onDeny(shouldShowCustomRequest) } launcher.unregister() } lifecycle.addObserver(object : LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (event == Lifecycle.Event.ON_DESTROY) { launcher.unregister() lifecycle.removeObserver(this) } } }) launcher.launch(permissions) }
總結(jié)
其實(shí)很多實(shí)用技巧本質(zhì)上都是很簡單的,但沒有接觸過就很難想到,我將我的開發(fā)經(jīng)驗(yàn)分享給大家,希望能幫助到大家。
到此這篇關(guān)于Android動(dòng)態(tài)權(quán)限申請?jiān)斀獾奈恼戮徒榻B到這了,更多相關(guān)Android動(dòng)態(tài)權(quán)限申請內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android自定義View實(shí)現(xiàn)五子棋游戲
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11詳解Android中使用Notification實(shí)現(xiàn)進(jìn)度通知欄(示例三)
這篇文章主要介紹了詳解Android中使用Notification實(shí)現(xiàn)進(jìn)度通知欄(示例三),具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12Android中實(shí)現(xiàn)在矩形框中輸入文字顯示剩余字?jǐn)?shù)的功能
在矩形輸入框框中輸入文字顯示剩余字?jǐn)?shù)的功能在app開發(fā)中經(jīng)常會(huì)見到,今天小編就通過實(shí)例代碼給大家分享android實(shí)現(xiàn)輸入框提示剩余字?jǐn)?shù)功能,代碼簡單易懂,需要的朋友參考下吧2017-04-04Android實(shí)現(xiàn)excel/pdf/word/odt/圖片相互轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了Android如何實(shí)現(xiàn)excel/pdf/word/odt/圖片之間的相互轉(zhuǎn)換,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-04-04Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開屏布局
這篇文章主要介紹了Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開屏布局,需要的朋友可以參考下2014-07-07Android自定義相機(jī)、預(yù)覽區(qū)域裁剪
這篇文章主要為大家詳細(xì)介紹了Android自定義相機(jī)、預(yù)覽區(qū)域裁剪,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Android Xutils3網(wǎng)絡(luò)請求的封裝詳解及實(shí)例代碼
這篇文章主要介紹了Android Xutils3網(wǎng)絡(luò)請求的封裝詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-12-12Android布局之GridLayout網(wǎng)格布局
網(wǎng)格布局標(biāo)簽是GridLayout。這個(gè)布局是android4.0新增的布局。這個(gè)布局只有4.0之后的版本才能使用。本文給大家介紹Android布局之GridLayout網(wǎng)格布局相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧2015-12-12