背景
前段时间在公司项目中引入了 kotlin,昨天用 kotlin 重写一个 Activity
,这个 Activity
需要调用相机,所以需要申请相机权限。
我的项目中使用了 PermissionsDispatcher 来处理权限请求,这是一个非常棒的使用注解来处理权限请求的库。
在使用 kotlin 以前,当我们用到的某个库(比如著名的 ButterKnife)需要使用注解时,我们需要引用库的注解库,引用方式是 annotationProcessor 'xxxx:version'
,
引入 kotlin 后,我们就需要使用 kapt (Kotlin Annotation Processing Tool)了,引用方式是 kapt 'xxxx:version'
。
引入和使用 kapt
引入 kapt 只需要在 moudule
级别的 build.gradle
文件的根节点加上 apply plugin: 'kotlin-kapt'
即可。
kapt 兼容以前的 annotationProcessor
,这意味着在引入 kapt 后,只需将以前的 annotationProcessor
直接替换为 kapt
即可。这在 kotlin 的文档里有所提及。
编译、报错
这么平滑地完成了变更,心里总是有点忐忑。
点击 gradle 配置界面的 “sync now”,很棒,没有报错。
点击 工具栏的“小锤子(Make Project)”按钮,好了,报错开始了:
More than one file was found with OS independent path 'META-INF/metadata.kotlin_module'.
Google 之后发现这是个很常见的问题,很多地方都给出了解决办法,即在 build.gradle
中找到 packagingOptions
(如果没有,就在 android
节点下创建),在里面添加 exclude 'META-INF/metadata.kotlin_module
:
android {
...
packagingOptions {
exclude 'META-INF/metadata.kotlin_module
}
}
再次编译项目,又报错了,不要慌,我们看看报的什么错:
More than one file was found with OS independent path 'META-INF/metadata.jvm.kotlin_module'.
呵呵,这次连 google 都不用了,完全如法炮制,修改后的内容如下:
android {
...
packagingOptions {
exclude 'META-INF/metadata.kotlin_module
exclude 'META-INF/metadata.jvm.kotlin_module'
}
}
再次编译项目,这次终于不报错了,看到 BUILD SUCCESSFUL 的字样,眉头都舒展了。
插曲
前文讲到,我这次改用 kapt 主要是为了在项目的 kotlin 代码中使用 PermissionsDispatcher
来处理动态权限申请。所以我去到 PermissionsDispatcher
的 GitHub 主页,看看 readme 中有没有提到什么该注意的地方。大概看了一遍,使用方法变化不大,只是利用了 kotlin 的扩展函数特性,让调用变得更自然。首先在 activity
加上 @RuntimePermissions
注解:
@RuntimePermissions
class CameraActivity : BaseActivity() {
...
}
再在需要相机权限的方法前加上 @NeedsPermission(Manifest.permission.CAMERA)
注解:
@RuntimePermissions
class CameraActivity : BaseActivity() {
...
@NeedsPermission(Manifest.permission.CAMERA)
private fun requestCamera() {
...
}
}
最后拦截权限请求的回调:
@RuntimePermissions
class CameraActivity : BaseActivity() {
...
@NeedsPermission(Manifest.permission.CAMERA)
private fun requestCamera() {
...
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
onRequestPermissionsResult(requestCode, grantResults)
}
}
这样就最低限度地完成了相机权限的请求,用户拒绝权限等行为的处理,这里不列出。
编译,报错了:
Execution failed for task ':app:kaptDebugKotlin'.
A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution
java.lang.reflect.InvocationTargetException (no error message)
就这个问题,我搜来搜去,也没能找到答案。根据报错内容,大概是什么反射方法没被找到。在一番搜索无果后,我想起来,PermissionsDispatcher
的文档中讲到被注解方法必须是非 private
的。检查代码,果然是犯了这个低级错误。修改代码,删除 private
并调用自动生成的集成了权限请求的方法:
@RuntimePermissions
class CameraActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
requestCameraWithPermissionCheck()
}
...
@NeedsPermission(Manifest.permission.CAMERA)
fun requestCamera() {
...
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
onRequestPermissionsResult(requestCode, grantResults)
}
}
编译成功,舒服了。
疑问
还记得前面的报错吗?
More than one file was found with OS independent path 'META-INF/metadata.kotlin_module'.
这个报错虽然根据网上的方法快速解决了问题,却没弄清楚原因。
在这篇文章中看到一个相对可信但比较模糊的说法:
大概意思就是工程生成了不止一个META-INF/DEPENDENCIES文件,看起来是因为多个 jar 包里包含了同样的文件(DEPENDENCIES.txt),导致打包时因为担心相互覆盖问题而提示出错
其他的地方我也没看到详细的解释。如果你有比较确定的看法,希望能在评论区指点。