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。填写后跳转至成功页。《第一行代码》第三版之编写开源库(十九)_1024程序员节

《第一行代码》第三版之编写开源库(十九)_android_02        

     基于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