在以前,我們項目框架通常使用Rxjava+Retrofit+okhttp,所以使用RxPermissions進行動態權限申請就順理成章了。隨着Google新技術的推出,kotlin、jetpack成爲大勢所趨,如果項目中沒有使用Rxjava,那麼依賴於Rxjava的RxPermissions也就無法適用了。
RxPermissions 利用了Rxjava的觀察者模式,而jetpack也提供了一種可觀察的數據存儲器類——LiveData,所以可以利用LiveData完成類似RxPermissions的動態權限申請框架,而且LiveData能夠感知組件的生命週期,當 Activity 和 Fragment 的生命週期被銷燬時,系統會立即退訂它們,避免內存泄漏。
如果觀察者(由 Observer 類表示)的生命週期處於 STARTED 或 RESUMED 狀態,則 LiveData 會認爲該觀察者處於活躍狀態。LiveData 只會將更新通知給活躍的觀察者。爲觀察 LiveData 對象而註冊的非活躍觀察者不會收到更改通知。
代碼實現
我們需要新建一個Fragment,用這個Fragment來進行權限的申請處理(新建Fragment的用途很廣泛,比如Glide就是通過創建一個Fragment,然後通過這個Fragment感知生命週期),需要注意的是如果當前在Fragment裏申請權限,需要用fragment.childFragmentManager
,
class LivePermissions {
......
constructor(activity: AppCompatActivity) {
liveFragment = getInstance(activity.supportFragmentManager)
}
constructor(fragment: Fragment) {
liveFragment = getInstance(fragment.childFragmentManager)
}
@Volatile
private var liveFragment: LiveFragment? = null
private fun getInstance(fragmentManager: FragmentManager) =
liveFragment ?: synchronized(this) {
liveFragment ?: if (fragmentManager.findFragmentByTag(TAG) == null) LiveFragment().run {
fragmentManager.beginTransaction().add(this, TAG).commitNow()
this
} else fragmentManager.findFragmentByTag(TAG) as LiveFragment
}
......
}
權限申請方法,在這裏調用Fragment的申請申請方法並返回MutableLiveData
,MutableLiveData
是LiveData
的子類,使用setValue
或者postValue
傳入數據,通過LiveData.observe(LifecycleOwner,Observer)
傳入觀察者觀察數據更新,這樣在Fragment裏通過MutableLiveData
將權限申請的結果回調回去。
fun request(vararg permissions: String): MutableLiveData<PermissionResult> {
return this.requestArray(permissions)
}
fun requestArray(permissions: Array<out String>): MutableLiveData<PermissionResult> {
liveFragment!!.requestPermissions(permissions)
return liveFragment!!.liveData
}
創建LiveFragment繼承Fragment。在requestPermissions
方法裏創建MutableLiveData
並調用Android申請權限的方法,這裏在onRequestPermissionsResult
裏處理返回結果。
返回結果這裏創建了2個ArrayList,分別用來臨時保持“拒絕”和“拒絕並不再詢問”的權限,最終調用LiveData
的setValue
方法,利用LiveData觀察者模式的特性,數據改變時會自動通知觀察者。
internal class LiveFragment : Fragment() {
lateinit var liveData :MutableLiveData<PermissionResult>
private val PERMISSIONS_REQUEST_CODE = 100
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true //旋轉屏幕時保存Fragment狀態
}
@TargetApi(Build.VERSION_CODES.M)
fun requestPermissions(permissions: Array<out String>) {
liveData = MutableLiveData()
requestPermissions(permissions, PERMISSIONS_REQUEST_CODE)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == PERMISSIONS_REQUEST_CODE) {
val denyPermission = ArrayList<String>()
val rationalePermission = ArrayList<String>()
for ((index, value) in grantResults.withIndex()) {
if (value == PackageManager.PERMISSION_DENIED) {
if (shouldShowRequestPermissionRationale(permissions[index])) {
rationalePermission.add(permissions[index])
} else {
denyPermission.add(permissions[index])
}
}
}
if (denyPermission.isEmpty() && rationalePermission.isEmpty()) {
liveData.value = PermissionResult.Grant
} else {
if (rationalePermission.isNotEmpty()) {
liveData.value = PermissionResult.Rationale(rationalePermission.toTypedArray())
} else if (denyPermission.isNotEmpty()) {
liveData.value = PermissionResult.Deny(denyPermission.toTypedArray())
}
}
}
}
}
在最後返回權限的權限處理選項時,使用kotlin的sealed
關鍵字,來確保在項目裏使用when
時能安全的判斷每個權限每種可能的情況。
sealed class PermissionResult {
object Grant : PermissionResult() //全部同意
class Deny(val permissions: Array<String>) : PermissionResult() //拒絕且勾選了不再詢問,permissions——被拒絕的權限
class Rationale(val permissions: Array<String>) : PermissionResult() //只是拒絕,沒有勾選不再詢問,permissions——被拒絕的權限
}
代碼已託管到github,歡迎star,地址:https://github.com/LGD2009/LivePermissions
項目已發佈帶jcenter,可直接引用。
使用方法
1.添加依賴
implementation 'com.ftd.livepermissions:livepermissions:1.0.0'
2.添加代碼
//申請權限
LivePermissions(this).request(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
).observe(this, Observer {
when (it) {
is PermissionResult.Grant -> { //權限允許
Toast.makeText(this, "Grant", Toast.LENGTH_SHORT).show()
}
is PermissionResult.Rationale -> { //權限拒絕
it.permissions.forEach {s->
println("Rationale:${s}")//被拒絕的權限
}
Toast.makeText(this, "Rationale", Toast.LENGTH_SHORT)
.show()
}
is PermissionResult.Deny -> { //權限拒絕,且勾選了不再詢問
it.permissions.forEach {s->
println("deny:${s}")//被拒絕的權限
}
Toast.makeText(this, "deny", Toast.LENGTH_SHORT).show()
}
}
})