AndroidUtilCodeKTX !是時候提升你的開發效率了 !(持續更新中...)

前言

第一次接觸 Kotlin 還是 2017 年,當時 Kotlin 還沒扶正,也不是 Android 的官方開發語言。至於我是怎麼被安利的,沒記錯的話,應該是 開源實驗室 的 Kotlin 教程。當時身邊幾乎沒有人在學 Kotlin,網上相關的資料也很少,我還翻譯了一部分官網文檔,寫了一本 GitBook 。 當然現在有更好的 Kotlin 語言中文站 了,對於英文基礎不是很好的同學,這是一個不錯的入門資料。

兩年半時間過來了,Kotlin 搖身一變,穩坐 Android 官方開發語言。儘管可能是爲了避免與 Oracle 的訴訟,但我相信這絕對不是主要原因。被定義爲 Better Java 的 Kotlin,的確做到了更好,也逐漸爲更多人所使用。

《全新 LeakCanary 2 ! 完全基於 Kotlin 重構升級 !》

Retrofit 2.6.0 ! 更快捷的協程體驗 !

越來越多的知名三方庫都已經開始提供對 Kotlin 的支持。Android 官方的新特性也將優先支持 Kotlin。除了 Android 領域,Spring 5 正式發佈時,也將 Kotlin 作爲其主打的新特性之一。肉眼可見的,在 JVM 語言領域,Kotlin 必將有一番作爲。

我在之前的一篇文章 真香!Kotlin+MVVM+LiveData+協程 打造 Wanandroid! 中開源了 Kotlin MVVM 版本的 Wanandroid 應用,到現在一共有 138 個 star 和 20 個 fork。數據並不是多亮眼,但算是我開源生涯中的一大步。這個項目我也一直在更新,歡迎大家持續關注。

目前的工作中,除非一些不可抗拒因素,我已經將 Kotlin 作爲第一選擇。在進行多個項目的開發工作之後,我發現我經常在各個項目之間拷貝代碼,base 類,擴展函數,工具類等等。甚至有的時候會翻回以前的 Java 代碼,或者 Blankj 的 AndroidUtilCode,拿過來直接使用。但是很多時候直接生搬硬套 Java 工具類,並不是那麼的優雅,也沒有很好的運用 Kotlin 語言特性。我迫切需要一個通用的 Kotlin 工具類庫。

基於此,AndroidUtilCodeKTX 誕生了。如果你用過 Blankj 的 AndroidUtilCode,它們的性質是一樣的,但絕不是其簡單的 Kotlin 翻譯版本,而是更多的糅合了 Kotlin 特性,一切從 “簡” ,代碼越少越好。Talk is easy,show me the code ! 話不多說,下面就代碼展示 AndroidUtilCodeKTX 中一些工具類的使用。

AndroidUtilCodeKTX

權限請求

request(Manifest.permission.READ_CALENDAR, Manifest.permission.RECORD_AUDIO) {
    onGranted { toast("onGranted") }
    onDenied { toast("onDenied") }
    onShowRationale { showRationale(it) }
    onNeverAskAgain { goToAppInfoPage() }
}

藉助擴展函數和 DSL ,可以輕鬆的在 Activity 中優雅的請求權限以及處理回調。這不是一個全新的輪子,主要代碼來自 PermissionsKt 。但是它有一個致命的缺點,需要開發者手動覆寫 onRequestPermissionsResult() 來處理權限請求結果,這顯得並不是那麼簡潔和優雅。我這裏借鑑了 RxPermissions 的處理方式,通過在當前 Activity 依附一個 Fragment 進行權限請求以及回調處理,這樣對用戶來說是無感的,且避免了額外的代碼。後續會單獨寫一篇文章,分析其中 Kotlin DSL 的應用。

SharedPreferences

putSpValue("int", 1)
putSpValue("float", 1f)
putSpValue("boolean", true)
putSpValue("string", "ktx")
putSpValue("serialize", Person("Man", 3))

getSpValue("int", 0)
getSpValue("float", 0f)
getSpValue(key = "boolean", default = false)
getSpValue("string", "null")
getSpValue("serialize", Person("default", 0))

基本的存儲和讀取操作都只需要一個函數即可完成,依賴泛型無需主動聲明值的類型。默認存儲文件名稱爲包名,如果你想自定義文件名稱,聲明 name 參數即可:

putSpValue("another","from another sp file",name = "another")
getSpValue("another","null",name = "another")

Activity 相關

主要是優化了 startActivity 的使用,爭取做到任何情況下都可以一句代碼啓動 Activity。

普通跳轉:

startKtxActivity<AnotherActivity>()

帶 flag 跳轉:

startKtxActivity<AnotherActivity>(Intent.FLAG_ACTIVITY_NEW_TASK)

startActivityForResult :

startKtxActivityForResult<AnotherActivity>(requestCode = 1024)

帶值跳轉:

startKtxActivity<AnotherActivity>(value = "string" to "single value")

帶多個值跳轉:

startKtxActivity<AnotherActivity>(
                values = arrayListOf(
                    "int" to 1,
                    "boolean" to true,
                    "string" to "multi value"
                )
            )

帶 Bundle 跳轉:

startKtxActivity<AnotherActivity>(
                extra = Bundle().apply {
                    putInt("int", 2)
                    putBoolean("boolean", true)
                    putString("string", "from bundle")
                }
            )

基本涵蓋了所有的 Activity 跳轉情況。

Aes 加密相關

ByteArray.aesEncrypt(key: ByteArray, iv: ByteArray, cipherAlgotirhm: String = AES_CFB_NOPADDING): ByteArray
ByteArray.aesDecrypt(key: ByteArray, iv: ByteArray, cipherAlgotirhm: String = AES_CFB_NOPADDING): ByteArray 
File.aesEncrypt(key: ByteArray, iv: ByteArray, destFilePath: String): File?
File.aesDecrypt(key: ByteArray, iv: ByteArray, destFilePath: String): File?

封裝了 Aes 加密操作,提供了快捷的數據和文件加解密方法,無需關注內部細節。默認使用 AES/CFB/NoPadding 模式,你可以通過 cipherAlgotirhm 參數修改模式。

plainText.toByteArray().aesEncrypt(key, iv, "AES/CBC/PKCS5Padding"
plainText.toByteArray().aesEncrypt(key, iv, "AES/ECB/PKCS5Padding"
plainText.toByteArray().aesEncrypt(key, iv, "AES/CTR/PKCS5Padding"

Hash 相關

StringByteArray 提供了擴展方法,可以快捷的進行哈希。

ByteArray.hash(algorithm: Hash): String
String.hash(algorithm: Hash, charset: Charset = Charset.forName("utf-8")): String
ByteArray.md5Bytes(): ByteArray
ByteArray.md5(): String
String.md5(charset: Charset = Charset.forName("utf-8")): String
ByteArray.sha1Bytes(): ByteArray
ByteArray.sha1(): String
String.sha1(charset: Charset = Charset.forName("utf-8")): String
ByteArray.sha224Bytes(): ByteArray
ByteArray.sha224(): String
String.sha224(charset: Charset = Charset.forName("utf-8")): String
ByteArray.sha256Bytes(): ByteArray
ByteArray.sha256(): String
String.sha256(charset: Charset = Charset.forName("utf-8")): String
ByteArray.sha384Bytes(): ByteArray
ByteArray.sha384(): String
String.sha384(charset: Charset = Charset.forName("utf-8")): String
ByteArray.sha512Bytes(): ByteArray
ByteArray.sha512(): String
String.sha512(charset: Charset = Charset.forName("utf-8")): String
File.hash(algorithm: Hash = Hash.SHA1): String

支持 MD5, sha1, sha224, sha256, sha384, sha512MD5 已經不再安全,密碼學上來說已經不再推薦使用。

val origin = "hello"
val md5 = origin.hash(Hash.MD5)
val sha1 = origin.hash(Hash.SHA1)

除了字符串取哈希,還提供了對文件取哈希值操作。

val file = File("xxx")
val md5 = file.hash(Hash.MD5)
val sha1 = file.hash(Hash.SHA1)

Intent 相關

跳轉到應用信息界面:

Context.goToAppInfoPage(packageName: String = this.packageName)

跳轉到日期和時間頁面:

Context.goToDateAndTimePage()

跳轉到語言設置頁面:

Context.goToLanguagePage()

跳轉到無障礙服務設置頁面:

Context.goToAccessibilitySetting()

瀏覽器打開指定網頁:

Context.openBrowser(url: String)

安裝 apk :

// need android.permission.REQUEST_INSTALL_PACKAGES after N
Context.installApk(apkFile: File)

在應用商店中打開應用:

Context.openInAppStore(packageName: String = this.packageName)

啓動 App :

Context.openApp(packageName: String)

卸載 App :(好像並沒有什麼用)

Context.uninstallApp(packageName: String)

Log 相關

fun String.logv(tag: String = TAG) = log(LEVEL.V, tag, this)
fun String.logd(tag: String = TAG) = log(LEVEL.D, tag, this)
fun String.logi(tag: String = TAG) = log(LEVEL.I, tag, this)
fun String.logw(tag: String = TAG) = log(LEVEL.W, tag, this)
fun String.loge(tag: String = TAG) = log(LEVEL.E, tag, this)

private fun log(level: LEVEL, tag: String, message: String) {
    when (level) {
        LEVEL.V -> Log.v(tag, message)
        LEVEL.D -> Log.d(tag, message)
        LEVEL.I -> Log.i(tag, message)
        LEVEL.W -> Log.w(tag, message)
        LEVEL.E -> Log.e(tag, message)
    }
}

tag 默認爲 ktx,你也可以在參數中自己指定。

"abc".logv()
"def".loge(tag = "xxx")

SystemService 相關

原來我們是這樣獲取系統服務的:

val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager

其實也挺簡潔的,但是我們忘記了 Context.WINDOW_SERVICE 咋辦?通過擴展屬性可以讓它更優雅。

val Context.powerManager get() = getSystemService<PowerManager>()

inline fun <reified T> Context.getSystemService(): T? =
    ContextCompat.getSystemService(this, T::class.java)

對於用戶來說,直接使用 powerManager 即可,無需任何額外工作。

已作爲擴展屬性的系統服務如下:

val Context.windowManager
val Context.clipboardManager
val Context.layoutInflater
val Context.activityManager
val Context.powerManager
val Context.alarmManager
val Context.notificationManager
val Context.keyguardManager
val Context.locationManager
val Context.searchManager
val Context.storageManager
val Context.vibrator
val Context.connectivityManager
val Context.wifiManager
val Context.audioManager
val Context.mediaRouter
val Context.telephonyManager
val Context.sensorManager
val Context.subscriptionManager
val Context.carrierConfigManager
val Context.inputMethodManager
val Context.uiModeManager
val Context.downloadManager
val Context.batteryManager
val Context.jobScheduler

App 相關

Context.versionName: String
Context.versionCode: Long
Context.getAppInfo(apkPath: String): AppInfo
Context.getAppInfos(apkFolderPath: String): List<AppInfo>
Context.getAppSignature(packageName: String = this.packageName): ByteArray?

這一塊內容暫時還比較少,主要是整理了我在項目中遇到的一些常見需求。獲取版本號,版本名稱,根據 apk 文件獲取應用信息,獲取應用簽名等等。App 相關的其實會有很多工具類,後續會慢慢補充。

View 相關

View.visible()
View.invisible()
View.gone()
var View.isVisible: Boolean
var View.isInvisible: Boolean
var View.isGone: Boolean
View.setPadding(@Px size: Int)
View.postDelayed(delayInMillis: Long, crossinline action: () -> Unit): Runnable
View.toBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap

也都是一些比較常用的函數。

Common

Context.dp2px(dp: Float): Int
Context.px2dp(px: Float): Int
View.dp2px(dp: Float): Int
View.px2dp(px: Float): Int
Context.screenWidth
Context.screenHeight
Context.copyToClipboard(label: String, text: String)

其他

RecyclerView.itemPadding(top: Int, bottom: Int, left: Int = 0, right: Int = 0)
ByteArray.toHexString(): String
...... 

使用

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

總結

以上都是我在項目中提煉出來的,也正因如此,現在的 AndroidUtilCodeKTX 還只是個孩子,做不到那麼的盡善盡美,面面俱到。我會持續更新完善這個庫,也希望大家可以多多提 issuepr,提供您寶貴的意見!

在這裏也十分感謝 Jie Smart 昨天的郵件反饋,提供了一些修改意見,也讓我更有動力去繼續維護。另外,最新版本的 AndroidUtilCodeKTX 我都會第一時間用在我的開源項目 wanandroid 上,歡迎大家 star ,fork !

文章首發微信公衆號: 秉心說 , 專注 Java 、 Android 原創知識分享,LeetCode 題解。

更多相關知識,掃碼關注我吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章