Retrofit、Glide都是不错的开源库,我们只需要在build.gradle的dependices中添加一行库的引用地址就可以了。开发一个开源库提供给开发者使用应该是一件很有意思的事情。
-
开发前的准备工作
SDK以实现功能逻辑为主,对界面开发相关会少;产品形式是库文件或者库引用地址;面向用户群体是为开发者所调用的(稳定、简单和方便)。
记得chap8中有提到android运行时权限API的用法,需要判断用户是否授权拨打电话权限,若无权限申请,还需要在onRequestPermissionResult中处理权限申请的结果,最后执行拨打电话的操作,使用开源库简化运行时权限API的用法。就用PermissionX吧。
在Github创建代码仓库,AS创建PermissionX项目,git clone远程项目后,将reademe和.git复制到PermissionX中,基于git第一次提交。准备工作完成。
-
实现PermissionX开源库
开发应用模块是在app模块中,开发库的话是在新建的Module模块中。多模块之间相互引用,ModuleA编写某功能,ModuleB引用ModuleA,无缝衔接A中所有功能。
PermissonX顶层目录右击-> New -> Module -> Android Library,库名称library,包名尽量避免冲突,故起名com.permissionx.hzkdev。app和library两个模块目录图标不同,可以区分程序模块和库模块。打开两个模块的gradle。
##library的build.gradle。
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "30.0.0"
defaultConfig {
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
...
}
##app的build.gradle。
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "30.0.0"
defaultConfig {
applicationId "com.permisssionx.app"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
...
}
两个重要区别:1.头部引用插件不同。com.android.library和com.android.application分别是指库模块和应用程序模块;2.defaultConfig一个不必配置applicationId属性,一个必须要配置。用于程序的唯一标识。在library中实现PermissionX的具体功能吧。
在library模块中实现PermissionX的具体功能吧。对运行时权限API封装是需要依赖上下文的,一般需要接收onRequestPermissionResult回调才行,有一些方案譬如说将运行时权限封装至BaseActivity中,或者提供透明Activity处理运行时权限。笔者打算在Fragment中申请运行时权限。隐藏Fragment对运行时权限进行封装。轻量不会对性能有太大影响。
右击com.permissionx.hzkdev -> New -> Kotlin File/class。新建InvisibleFragment类继承自androidx.fragment.app.Fragment。
package com.permisssionx.hzkdev
import android.content.pm.PackageManager
import androidx.fragment.app.Fragment
//typealias关键字将任意类型指定一个别名。
typealias PermissionsCallback = (Boolean, List<String>) -> Unit
class InvisibleFragment : Fragment() {
//1.首先定义一个callback变量作为运行时权限申请结果的回调通知,该函数类型接收boolean和List<String>这两种类型的参数。
private var callback : PermissionsCallback ?= null
//2.requestNow接受一个与callback变量同类型的函数参数,同时使用vararg接受一个可变长度的permissions参数列表。
fun requestNow (cb: PermissionsCallback, vararg permissions: String) {
//将函数类型参数赋值给callback变量;
callback = cb
//立即申请运行时权限,并将permissions参数列表传递进行来。
requestPermissions(permissions, 1)
}
//3.处理运行时权限的申请结果。
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == 1) {
//deniedList用来存储运行时申请结果,遍历grantResults数组,若未授权,则添加进列表中,
val deniedList = ArrayList<String>()
for ((index, result) in grantResults.withIndex()) {
if (result != PackageManager.PERMISSION_GRANTED) {
deniedList.add(permissions[index])
}
}
//遍历结束后使用allGranted标识所有申请权限是否被授权。判断依据是deniedList是否为空,最后将运行时申请结果进行回调。
val allGranted = deniedList.isEmpty()
callback?.let {
it(allGranted, deniedList)
}
}
}
}
编写对外接口部分的代码。
package com.permisssionx.hzkdev
import androidx.fragment.app.FragmentActivity
//对外接口的代码,将PermissionX指定为单例类,是为了PermissionX接口更方便被调用
object PermissionX {
private const val TAG = "InvisibleFragment"
//request方法接受一个FragmentActivity参数、一个可变长度的permissions参数列表以及一个callback回调。FragmentActivity是AppCompatActivity的父类
fun request(activity: FragmentActivity, vararg permissions: String, callback: PermissionsCallback) {
//获取FragmentManager实例
val fragmentManager = activity.supportFragmentManager
//再调用findFragmentByTag来判断传入的Activity参数是否包含指定TAG的Fragment,
val existFragment = fragmentManager.findFragmentByTag(TAG)
val fragment = if (existFragment != null) {
//如果是,直接使用
existFragment as InvisibleFragment
} else {
//如果不是,创建新InvisibleFragment实例,将其添加进Activity中去,并指定一个TAG,添加结束后调用commitNow方法,commit不会立即执行添加操作。
val invisibleFragment = InvisibleFragment()
fragmentManager.beginTransaction().add(invisibleFragment, TAG).commitNow()
invisibleFragment
}
//requestNow目的是申请运行时权限,申请结果会自动回调至callback参数中,permissions是一个数组,加*的目的是将数组转换为可变长度参数传递过去。
fragment.requestNow(callback, *permissions)
}
}
-
对开源库进行测试
自己先测试下。app模块中引用library模块,app/build.gradle的dependices中添加如下代码,这样app模块可以无缝使用library模块里提供的所有功能了。
dependencies {
...
implementation project(':library')
...
}
编辑布局文件activity_main.xml,在里面加入打电话按钮。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/makeCallBtn"
android:text="Make Call"/>
</LinearLayout>
MainActivity中添加申请权限及拨打电话功能:
package com.permisssionx.app
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.permisssionx.hzkdev.PermissionX
import kotlinx.android.synthetic.main.activity_main.*
import java.security.Permission
import java.util.jar.Manifest
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCallBtn.setOnClickListener {
//只需调用request方法,传入当前Activity和所需要的权限名,然后再Lambda表达式中处理权限的申请结果即可。
//与此同时,PermissionX支持一次性申请多个权限,将所需要的权限名传入Request方法即可。
// PermissionX.request(this,
// android.Manifest.permission.CALL_PHONE,
// android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
// android.Manifest.permission.READ_CONTACTS) { allGranted, deniedList ->
// ...
// }
PermissionX.request(this,
android.Manifest.permission.CALL_PHONE) { allGranted, deniedList ->
if (allGranted) {
call()
} else {
Toast.makeText(this, "you denied $deniedList", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun call() {
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
最后在AndroidManifest.xml中添加打电话权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.permisssionx.app">
<uses-permission android:name="android.permission.CALL_PHONE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
-
将开源库发布到jcenter仓库
只需要一行代码就可以引用第三方仓库,开发中的第三方库是如何被引用的呢?最外层目录的gradle中默认配置了jcenter仓库中。谷歌是自家扩展依赖库,jcenter是第三方开源依赖库,譬如Retrofit、Glide。我们需要把PermissionX发布至jcenter仓库中。
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
}
...
}
allprojects {
repositories {
google()
jcenter()
}
}
注册Bintray(专门提供软件分发服务、jcenter的发布与下载都由它提供,用gmail注册更简单), 注册成功了长这样。点击Add New Respository。填写后跳转至成功页。
基于bintray-release(https://github.com/novoda/bintray-release)将代码发布至jcenter仓库中,打开library/build.gradle文件。
apply plugin: 'com.novoda.bintray-release'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.novoda:bintray-release:0.9.2'
}
}
publish {
userOrg = 'Bintary用户名'
groupId = 'com.permisssionx.hzkdev'
artifactId = 'permissionx'
publishVersion = '1.0.0'
desc = 'Make Android runtime permission request easy'
website = 'https://github.com/hzka/PermissonX'
}
依赖库的引用地址是
com.permisssionx.hzkdev:permissionx:1.0.0
执行下面命令上传
./gradlew clean build bintrayUpload -PbintrayUser=bintary用户名 -PbintrayKey=API Key -PdryRun=false
我这块报错了,因为我的AS是4.0.2,gradle必须要使用6.0.0以上,而bintray-release必须要在5.6.4上编译,因而报错。必须要降低AS版本才能适配bintray-release,或者bintray-release更新其到支持6.0.0以上,都太麻烦。不过一个SDK的大致流程算是明白了,也算是达到目的了。最后是Add to JCenter发布到jcenter仓库中。
A problem occurred configuring project ':library'.
> Could not create an instance of type com.novoda.release.internal.compat.gradle5_3.AndroidSoftwareComponentCompat_Gradle_5_3.
> org/gradle/api/internal/java/usagecontext/LazyConfigurationUsageContext