Android AOP之Aspect-動態(tài)權(quán)限申請

PermissionJ

使用Aspect實現(xiàn)的面向切面進行Android動態(tài)權(quán)限申請
Github

使用:

  1. 根build.gradle
dependencies {
        classpath "com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10"
}
  1. 需要申請權(quán)限的module build.gradle
plugins {
    id 'kotlin-kapt'
}

// 選配
aspectjx {
    // 需要織入代碼的包名
    include 'com.xxx'
    // 不需要織入代碼的包名
    exclude 'com.xxx'
    // 關(guān)閉AspectJX功能 enabled默認為true,即默認AspectJX生效
    enabled true
}

dependencies {

    implementation 'com.github.Archer1347:PermissionJ:1.0.0'
}
  1. 申請權(quán)限
@PermissionRequest(
        permissions = [Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA],
        requestCode = 1
    )
fun test() {
    Toast.makeText(this, "申請權(quán)限成功", Toast.LENGTH_SHORT).show()
}
  1. 申請權(quán)限失敗(可選)
@PermissionRequestFailed
fun failed(permissionDetail: PermissionDetail) {
     Toast.makeText(this, "申請權(quán)限失敗", Toast.LENGTH_LONG).show()
}

@PermissionRequestFailed
fun failed(permissionDetail: PermissionDetail) {
        Toast.makeText(
            this, "申請權(quán)限失敗\n" +
                    "請求碼:${permissionDetail.requestCode}\n" +
                    "成功:${permissionDetail.grantedPermissions?.joinToString()}\n" +
                    "失敗:${permissionDetail.deniedPermissions?.joinToString()}\n" +
                    "是否勾選了不再提示:${permissionDetail.rejectRemind}", Toast.LENGTH_LONG
        ).show()
}

原理:

  1. 使用registerForActivityResult進行權(quán)限申請
  2. aspect織入代碼時境蔼,通過獲取上下文activity挥吵,添加一個空的Fragment用于權(quán)限請求
  3. 兜底:如果獲取不到activity捷沸,則打開一個透明Activity用于添加fragment
@Aspect
@DelicateCoroutinesApi
class PermissionAspect {

    @Pointcut("execution(@com.permission.core.annotation.PermissionRequest * *(..))" + " && @annotation(permissionRequest)")
    fun requestPermissionMethod(permissionRequest: PermissionRequest) {
    }

    @Around("requestPermissionMethod(permissionRequest)")
    fun aroundJoinPoint(joinPoint: ProceedingJoinPoint, permissionRequest: PermissionRequest) {
        if (hasSelfPermissions(application, *permissionRequest.permissions)) {
            try {
                joinPoint.proceed()
            } catch (throwable: Throwable) {
                Log.d(TAG, throwable.localizedMessage.orEmpty())
            }
            return
        }
        requestPermissions(joinPoint, permissionRequest)
    }

    /**
     * Desc: 申請權(quán)限
     */
    private fun requestPermissions(joinPoint: ProceedingJoinPoint, permissionRequest: PermissionRequest) {
        GlobalScope.launch(Dispatchers.Main.immediate) {
            // 優(yōu)先獲取當前activity
            var activity = findActivityFromContext(joinPoint.`this`)
            // 如果獲取不到當前activity哺徊,則啟動一個透明Activity
            if (activity == null) {
                // 啟動activity构资,onCreate之后返回activity實例
                activity = awaitStartActivityAndCreate(application, PermissionRequestActivity::class.java)
            }
            try {
                // 申請權(quán)限芜壁,返回申請結(jié)果
                val result = activity.requestPermissionsForResult(permissionRequest.permissions as Array<String>, permissionRequest.requestCode)
                if (activity is PermissionRequestActivity) {
                    activity.finish()
                }
                // 如果沒有權(quán)限被拒絕,則權(quán)限申請通過
                if (result.deniedPermissions.isEmpty()) {
                    joinPoint.proceed()
                } else {
                    onDenied(joinPoint, result)
                }
            } catch (throwable: Throwable) {
                Log.e(TAG, throwable.localizedMessage.orEmpty())
            }
        }
    }

    /**
     * Desc: 權(quán)限被拒絕庐冯,反射調(diào)用[PermissionRequestFailed]注解的方法孽亲,支持無參或只有一個[PermissionDetail]參數(shù)
     */
    private fun onDenied(joinPoint: ProceedingJoinPoint, permissionDetail: PermissionDetail) {
        val cls: Class<*> = joinPoint.`this`.javaClass
        val methods = cls.declaredMethods
        if (methods.isEmpty()) return
        methods.firstOrNull {
            it.isAnnotationPresent(PermissionRequestFailed::class.java)
        }?.apply {
            isAccessible = true
            val types = parameterTypes
            if (types.isEmpty()) {
                invoke(joinPoint.`this`)
            } else if (types.size == 1) {
                invoke(joinPoint.`this`, permissionDetail)
            }
        }
    }

    /**
     * Desc: 從切面上下文中獲取當前Activity
     */
    private fun findActivityFromContext(any: Any): FragmentActivity? {
        if (any is Fragment) {
            val activity = any.activity
            if (activity != null && !activity.isDestroyed) {
                return activity
            }
        }
        if (any is FragmentActivity) {
            return any
        }
        // 獲取棧頂activity
        val curActivity = getTopActivity()
        if (curActivity is FragmentActivity) {
            return curActivity
        }
        return null
    }
}

利用Fragment進行權(quán)限申請

/**
 * Desc: 使用registerForActivityResult進行權(quán)限申請
 *
 * @param permissions 權(quán)限列表
 * @param requestCode 請求碼
 *
 * @return 權(quán)限請求結(jié)果詳情[PermissionDetail]
 */
internal suspend fun FragmentActivity.requestPermissionsForResult(permissions: Array<String>, requestCode: Int): PermissionDetail {
    return suspendCoroutine { continuation ->
        val fragment = Fragment()
        var launch: ActivityResultLauncher<Array<String>>? = null
        // 由于registerForActivityResult必須在fragment或者activity onCreate之前注冊,所以這里每次都添加一個空fragment進行注冊
        launch = fragment.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
            if (it.isEmpty()) {
                launch?.unregister()
                continuation.resumeWithException(IllegalArgumentException("權(quán)限${permissions.contentToString()}申請結(jié)果為空展父。注意:權(quán)限請求只有第一次有效返劲,請檢查是否有重復調(diào)用權(quán)限請求的地方"))
                return@registerForActivityResult
            }
            // 授予權(quán)限列表 
            val grantedPermissions = mutableListOf<String>()
            // 拒絕權(quán)限列表
            val deniedPermissions = mutableListOf<String>()
            // 是否拒絕且勾選了不再提示
            var rejectRemind = false
            it.forEach { entry ->
                val permission = entry.key
                if (!entry.value) {
                    deniedPermissions.add(permission)
                    if (!rejectRemind) {
                        rejectRemind = !ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
                    }
                } else {
                    grantedPermissions.add(permission)
                }
            }
            // 權(quán)限請求結(jié)果詳情
            val detail = PermissionDetail(
                requestCode = requestCode,
                grantedPermissions = grantedPermissions,
                deniedPermissions = deniedPermissions,
                rejectRemind = rejectRemind,
            )
            supportFragmentManager.beginTransaction().remove(fragment).commitAllowingStateLoss()
            continuation.resume(detail)
        }
        supportFragmentManager.beginTransaction().add(fragment, "PermissionRequestFragment").commitAllowingStateLoss()
        // 等待fragment創(chuàng)建完成 
        fragment.lifecycleScope.launchWhenResumed {
            launch.launch(permissions)
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市栖茉,隨后出現(xiàn)的幾起案子篮绿,更是在濱河造成了極大的恐慌,老刑警劉巖吕漂,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亲配,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機吼虎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門犬钢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人思灰,你說我怎么就攤上這事玷犹。” “怎么了官辈?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長遍坟。 經(jīng)常有香客問我拳亿,道長,這世上最難降的妖魔是什么愿伴? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任肺魁,我火速辦了婚禮,結(jié)果婚禮上隔节,老公的妹妹穿的比我還像新娘鹅经。我一直安慰自己,他們只是感情好怎诫,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布瘾晃。 她就那樣靜靜地躺著,像睡著了一般幻妓。 火紅的嫁衣襯著肌膚如雪蹦误。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天肉津,我揣著相機與錄音强胰,去河邊找鬼。 笑死妹沙,一個胖子當著我的面吹牛偶洋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播距糖,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼玄窝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了悍引?” 一聲冷哼從身側(cè)響起哆料,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吗铐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后典阵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫉鲸,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了岁钓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片微王。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡炕倘,死狀恐怖罩旋,靈堂內(nèi)的尸體忽然破棺而出瘸恼,到底是詐尸還是另有隱情东帅,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站拦键,受9級特大地震影響芬为,放射性物質(zhì)發(fā)生泄漏媚朦。R本人自食惡果不足惜询张,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一唯袄、第九天 我趴在偏房一處隱蔽的房頂上張望恋拷。 院中可真熱鬧梅掠,春花似錦、人聲如沸消痛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脸爱。三九已至簿废,卻和暖如春族檬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背看尼。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工躏结, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留媳拴,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親仪搔。 傳聞我的和親對象是個殘疾皇子烤咧,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容