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(551010)
    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: ((fileFile, 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 最新更新信息,掃碼關注我吧!

發佈了78 篇原創文章 · 獲贊 6 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章