AndroidUtilCodeKTX !是时候提升你的开发效率了 !(更新啦 !)

AndroidUtilCodeKTX (以下简称 Ktx) 正式开源已经有一个月了。到目前为止,在 Github 上收获了 98 个 star 和 11 次 fork。期间上了一次 Github Trending Kotlin 分类的榜单,也收到了一些开发者的好评以及建议。经过这一个月的龟速更新,做了一些我想添加的功能,修复了一些开发者反馈的问题。

当前最新的版本是 0.0.6

implementation 'luyao.util.ktx:AndroidUtilKTX:0.0.6'

该版本的 Change log :

* 增加 log 开关
* 增加 TextView.notEmpty() 扩展方法
* 增加 ShellExt , 提供执行命令行相关函数
* BaseVMFragment、BaseVMActivity 中添加默认异常处理回调
* android 版本判断
* 判断无障碍服务是否开启
* RecyclerView.itemPadding
* 发送邮件
* 文件相关
* BaseActivity/BaseFragment 添加 CoroutineScope 实现
* 自动感知生命周期的 KtxHandler
* Activity 统一管理
* 应用前后台监听
* KtxSpan, 封装常见 Span 的使用

下面简单介绍一下 0.0.6 版本的主要修改内容。

一系列空判断

得益于高阶函数和 lambda 表达式的使用,我们可以充分发挥自己的想象力来封装一些通用逻辑。例如,对于一个可空对象,非空时执行指定操作,等于空时执行另一操作,我们的代码中可能充斥着大量这种结构的代码:

if (obj == null) {
    ...
} else {
    ...
}

看看 Ktx 中是如何书写这种逻辑的:

obj.notNull({
    ...    
}, {
    ...    
})

额,好像比原来优雅了那么一点(容许我欺骗一下自己)。实现也很简单,如下所示:

fun <T> Any?.notNull(f: () -> T, t: () -> T): T {
    return if (this != null) f() else t()
}

支持返回值。忽略上面那个不是那么明显的优化,顺着这个思路,我们可以在稍微复杂一点的情景下进行类似的优化。比如,TextView 中的文字是否为空,可以定义如下扩展函数 :

fun TextView.notEmpty(f: TextView.() -> Unit, t: TextView.() -> Unit) {
    if (text.toString().isNotEmpty()) f() else t()
}

如果你想到了更多的使用场景,欢迎来砸 issue

执行 shell 命令

这个没啥特殊的地方,来自我的 Box 项目中读取 linux 内核版本的需求。虽说最后也没读取成功,但也加了这么一个顶层函数:

fun executeCmd(command: String): String {
    val process = Runtime.getRuntime().exec(command)

    val resultReader = BufferedReader(InputStreamReader(process.inputStream))
    val errorReader = BufferedReader(InputStreamReader(process.errorStream))

    val resultBuilder = StringBuilder()
    var resultLine: String? = resultReader.readLine()

    val errorBuilder = StringBuilder()
    var errorLine = errorReader.readLine()

    while (resultLine != null) {
        resultBuilder.append(resultLine)
        resultLine = resultReader.readLine()
    }

    while (errorLine != null) {
        errorBuilder.append(errorLine)
        errorLine = errorReader.readLine()
    }

    return "$resultBuilder\n$errorBuilder"
}

BaseActivity 添加异常处理

这个来自我的 wanandroid 项目的 issue 7,主要是针对 Kotlin Coroutine 的异常处理。原来也有异常处理,只是 BaseViewModel 中用来接收异常的 mException 是私有的,无法直接获取。这个版本做了通用的异常处理,可以在 BaseVMActivityBaseVMFragment 中直接通过 onError() 回调得到异常。具体可以参考 MvvmActivity 中的异常处理演示。

Android 版本判断

fun fromM() = fromSpecificVersion(Build.VERSION_CODES.M)
fun beforeM() = beforeSpecificVersion(Build.VERSION_CODES.M)
fun fromN() = fromSpecificVersion(Build.VERSION_CODES.N)
fun beforeN() = beforeSpecificVersion(Build.VERSION_CODES.N)
fun fromO() = fromSpecificVersion(Build.VERSION_CODES.O)
fun beforeO() = beforeSpecificVersion(Build.VERSION_CODES.O)
fun fromP() = fromSpecificVersion(Build.VERSION_CODES.P)
fun beforeP() = beforeSpecificVersion(Build.VERSION_CODES.P)
fun fromSpecificVersion(version: Int): Boolean = Build.VERSION.SDK_INT >= version
fun beforeSpecificVersion(version: Int): Boolean = Build.VERSION.SDK_INT < version

判断无障碍服务是否开启

工作中遇到的一个小需求,根据无障碍服务名称判断服务是否开启。

fun Context.checkAccessbilityServiceEnabled(serviceName: String): Boolean {
    val settingValue =
        Settings.Secure.getString(applicationContext.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
    return settingValue.notNull({
        var result = false
        val splitter = TextUtils.SimpleStringSplitter(':')
        while (splitter.hasNext()) {
            if (splitter.next().equals(serviceName, true)) {
                result = true
                break
            }
        }
        result
    }, { false })
}

RecyclerView.itemPadding

fun RecyclerView.itemPadding(top: Int, bottom: Int, left: Int = 0, right: Int = 0) {
    addItemDecoration(PaddingItemDecoration(top, bottom, left, right))
}

方便快捷添加 itemPadding ,如下所示:

commonRecycleView.run {
    itemPadding(5, 5, 10, 10)
    layoutManager = LinearLayoutManager(this@CommonListActivity)
    adapter = commonAdapter
}

单位是 dp,内部处理了 dp2px 的逻辑。

发送邮件

IntentExt 中添加了发送邮件的函数,支持最基本的 subjecttext

fun Context.sendEmail(email: String, subject: String?, text: String?) {
    Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:$email")).run {
        subject?.let { putExtra(Intent.EXTRA_SUBJECT, subject) }
        text?.let { putExtra(Intent.EXTRA_TEXT, text) }
        startActivity(this)
    }
}

文件相关

文件相关的部分,本来是准备花大把功夫来整合的,可是 Kotlin 标准库关于文件操作的支持实在是太完善了。我有对照 AndroidUtilCode 中的文件工具类,标准库基本就可以满足其中的大部分功能。为了能更全面的封装文件管理相关的工具类,我在 Box 项目中添加了简单的文件管理功能,具体可见最新 commit。

通过这个简单的文件管理器模块,在标准库的基础上,主要添加了以下扩展属性和函数。

val File.canListFiles: Boolean :是否可以获取子文件夹
val File.totalSize: Long       :总大小,包括所有子文件夹
val File.formatSize: String    :格式化文件总大小,包括所有子文件夹
val File.mimeType: String      :获取文件 mimeType

/**
 * [isRecursive] 是否获取所有子文件夹
 * [filter] 文件过滤器
 * /
fun File.listFiles(isRecursive: Boolean = false, filter: ((file: File) -> Boolean)? = null): Array<out File> {}

/**
 * [append] 是否追加
 * [text] 要 write 的内容
 * [charset] 字符编码
 * /
fun File.writeText(append: Boolean = false, text: String, charset: Charset = Charsets.UTF_8)
fun File.writeBytes(append: Boolean = false, bytes: ByteArray)

/**
 *   [destFile]  目标文件/文件夹
 *   [overwrite] 是否覆盖
 *   [reserve] 是否保留源文件
 */
fun File.moveTo(destFile: File, overwrite: Boolean = true, reserve: Boolean = true)

/**
 *   [destFolder] 目标文件/文件夹
 *   [overwrite] 是否覆盖
 *   [func] 单个文件的进度回调 (from 0 to 100)
 */
fun File.moveToWithProgress(
    destFolder: File,
    overwrite: Boolean = true,
    reserve: Boolean = true,
    func: ((file: File, i: Int) -> Unit)? = null
)

fun File.rename(newName: String)
fun File.rename(newFile: File)

除此之外的一些常见操作大多已经包含在 Kotlin Stdlib 中,读者可以阅读一下 /kotlin/io/Utils.kt 文件。

CoroutineScope

BaseActivity/BaseFragment 中添加了 CoroutineScope 实现,其子类中可以直接通过 launch {} 使用主线程的协程作用域,并在 onDestroy() 中会自动取消在该作用域中启动的协程。有过协程使用经验的同学应该不难理解,实现也很简单。

abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() {

    ...
    ...
    
    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}

没使用过协程的也没有关系,后续我会写一篇文章简单介绍如何正确的在 Android 上使用协程。

自动感知生命周期的 KtxHandler

这是一个自动感知组件生命周期的 Handler,给它注册一个 LifecycleOwner,它就能在组件 onDestroy() 时自动清理资源。这个想法来自我在网上看到的代码,恕我实在想不起来出处了,下次会提前记录出处并标注出来。

class KtxHandler(lifecycleOwner: LifecycleOwner, callback: Callback) : Handler(callback), LifecycleObserver {

    private val mLifecycleOwner: LifecycleOwner = lifecycleOwner

    init {
        lifecycleOwner.lifecycle.addObserver(this)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    private fun onDestroy() {
        removeCallbacksAndMessages(null)
        mLifecycleOwner.lifecycle.removeObserver(this)
    }
}

其简单使用可以参见 LifeCycleActivity.kt

Activity 统一管理/应用前后台监听

这个来源于 issue 区的需求。开发中的需求无非两个,关闭指定的 Activity,退出应用时关闭所有 Activity ,这两个函数定义在了单例类 KtxManager 中。

fun  finishActivity(clazz: Class<*>){
    for (activity in mActivityList)
        if (activity.javaClass == clazz)
            activity.finish()
}

fun finishAllActivity() {
    for (activity in mActivityList)
        activity.finish()
}

同样在 LifeCycleActivity.kt 中有这两个函数的使用例子。

这块的使用还是比较巧妙的。通过 ActivityLifecycleCallbacks 来监听 Activity 的生命周期,而 ActivityLifecycleCallbacks 的一般使用方式就是在 Application 中进行注册。但是在 Ktx 中,并没有 Application 类,也无需开发者在集成时书写任何代码。它自动完成了生命周期的注册监听。可能有的同学在其他地方也见识过这个小技巧,如果不知道的话,阅读一下 Ktx.kt,相信你就明白了。

顺带也通过 ProcessLifecycleOwner 监听了应用进入前台和后台,相信你也看到了对应的 Toast 提示。

KtxSpan

最后是一个 Span 工具类,先看看效果。

对这个图很熟悉的话,说明你是 Blankji 的 AndroidUtilCode 的忠实用户。这里我没有再重新造轮子了,当然也不是完全照抄 AndroidUtilCode 。我一直是 material-dialogs 的使用者,非常喜欢它的 API 形式,所以仿照它的 API 结构重构了 KtxSpan

KtxSpan().with(spanTv).show {
            text(
                "SpanUtils",
                foregroundColor = Color.YELLOW,
                backgroundColor = Color.LTGRAY,
                alignment = Layout.Alignment.ALIGN_CENTER
            )
            text("前景色", foregroundColor = Color.GREEN)
            text("背景色", backgroundColor = Color.LTGRAY)
            blockLine(spanTv.dp2px(6), true)
            text("行高", lineHeight = 2 * spanTv.lineHeight, backgroundColor = Color.LTGRAY)
            text(
                "测试段落缩进,首行缩进两字,其他行不缩进,其他行不缩进",
                first = (spanTv.textSize * 2).toInt(),
                rest = 0,
                backgroundColor = Color.GREEN
            )
            text(
                "测试引用,后面的字是为了凑到两行的效果",
                quoteColor = Color.GREEN,
                quoteStripeWidth = 10,
                quoteGapWidth = 10,
                backgroundColor = Color.LTGRAY
            )
            image(
                R.drawable.small,
                verticalAlignment = ImageSpan.ALIGN_BOTTOM,
                marginLeft = dp2px(30),
                marginRight = dp2px(40)
            )
}

KtxSpan 只有三个方法,但已经足以覆盖大部分使用场景。

第一个方法 text() ,可以表示文字的大部分效果。得益于 Kotlin 的方法参数支持默认值的特性,消灭了大部分方法重载的情况。text() 方法的参数也相当之多,共有 31 个参数,除了 text 为必须的之外,其余均可以根据需求赋值。默认每个 text() 方法都是新的一行,你也可以传入 isNewLine: Boolean = false 来使得下一行不再换行。

第二个方法是 image() ,可以表示图片显示,支持属性有对齐方式和左右间距。

第三个方法是 blockLine(),表示段落间距。有两个参数,一个是段落间距的值,一个是是否为以后的段落都添加间距。

关于 KtxSpan 的具体使用,可见项目中的示例代码 : KtxSpanActivity

Last

以上就是这次更新的全部内容了,AndroidUtilCodeKTX 还是个孩子,欢迎来撩 !

文章首发微信公众号: 秉心说 , 专注 Java 、 Android 原创知识分享,LeetCode 题解。

AndroidUtilCodeKTX 最新更新信息,扫码关注我吧!

green.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容