Android在Kotlin中更好地使用LitePal
Kotlin 是一個用于現(xiàn)代多平臺應(yīng)用的靜態(tài)編程語言,由 JetBrains 開發(fā)。
Kotlin可以編譯成Java字節(jié)碼,也可以編譯成JavaScript,方便在沒有JVM的設(shè)備上運行。
Kotlin已正式成為Android官方支持開發(fā)語言。
自從LitePal在2.0.0版本中全面支持了Kotlin之后,我也一直在思考如何讓LitePal更好地融入和適配Kotlin語言,而不僅僅停留在簡單的支持層面。
Kotlin確實是一門非常出色的語言,里面有許多優(yōu)秀的特性是在Java中無法實現(xiàn)的。因此,在LitePal全面支持了Kotlin之后,我覺得如果我還視這些優(yōu)秀特性而不見的話,就有些太暴殄天物了。所以在最新的LitePal 3.0.0版本里面,我準(zhǔn)備讓LitePal更加充分地利用Kotlin的一些語言特性,從而讓我們的開發(fā)更加輕松。
本篇文章除了介紹LitePal 3.0.0版本的升級內(nèi)容之外,還會講解一些Kotlin方面的高級知識。
升級到3.0.0
首先還是來看如何升級。
為什么這次的版本號跨度如此之大,直接從2.0升到了3.0呢?因為這次LitePal在結(jié)構(gòu)上面有了一個質(zhì)的變化。
為了更好地兼容Kotlin語言,LitePal現(xiàn)在不再只是一個庫了,而是變成了兩個庫,根據(jù)你使用的語言不同,需要引入的庫也不同。如果你使用的是Java,那么就在build.gradle中引入如下配置:
dependencies { implementation 'org.litepal.android:java:3.0.0' }
而如果你使用的是Kotlin,那么就在build.gradle中引入如下配置:
dependencies { implementation 'org.litepal.android:kotlin:3.0.0' }
好了,接下來我們就一起看一看LitePal 3.0.0版本到底變更了哪些東西。
泛型的優(yōu)化
不得不說,其實LitePal的泛型設(shè)計一直都不是很友好,尤其在異步查詢的時候格外難受,比如我們看下如下代碼:
在異步查詢的onFinish()
回調(diào)中,我們直接得到的并不是查詢的對象,而是一個泛型T對象,還需要再經(jīng)過一次強制轉(zhuǎn)型才能得到真正想要查詢的對象。
如果你覺得這還不算難受的話,那么再來看看下面這個例子:
可以看到,這次查詢返回的是一個List<T>,我們必須要對整個List進行強制轉(zhuǎn)型。不僅要多寫一行代碼,關(guān)鍵是開發(fā)工具還會給出一個很丑的警告。
這樣的設(shè)計無論如何都算不上友好。
這里非常感謝 xiazunyang 這位朋友在GitHub上提出的這個Issue(https://github.com/LitePalFramework/LitePal/issues/396),并且給出了建議的優(yōu)化方案,LitePal 3.0.0版本在泛型方面的優(yōu)化很大程度上是基于他的建議。
那么我們現(xiàn)在來看看,到了LitePal 3.0.0版本,同樣的功能可以怎么寫:
可以看到,這里在FindCallback接口上聲明了泛型類型為Song,那么在onFinish()方法回調(diào)中的參數(shù)就可以直接指定為Song類型了,從而避免了一次強制類型轉(zhuǎn)換。
那么同樣地,在查詢多條數(shù)據(jù)的時候就可以這樣寫:
LitePal.where("duration > ?", "100").findAsync(Song.class).listen(new FindMultiCallback<Song>() { @Override public void onFinish(List<Song> list) { } });
這次就清爽多了吧,在onFinish()回調(diào)方法中,我們直接拿到的就是一個List<Song>集合,而不會再出現(xiàn)那個丑丑的警告了。
而如果這段代碼使用Kotlin來編寫的話,將會更加的精簡:
LitePal.where("duration > ?", "100").findAsync(Song::class.java).listen { list ->}
得益于Kotlin出色的lambda機制,我們的代碼可以得到進一步精簡。在上述代碼中,行尾的list參數(shù)就是查詢出來的List<Song>集合了。
那么關(guān)于泛型優(yōu)化的講解就到這里,下面我們來看另一個主題,監(jiān)聽數(shù)據(jù)庫的創(chuàng)建和升級。
監(jiān)聽數(shù)據(jù)庫的創(chuàng)建和升級
沒錯,LitePal 3.0.0版本新增了監(jiān)聽數(shù)據(jù)庫的創(chuàng)建和升級功能。
加入這個功能是因為 JakeHao 這位朋友在GitHub上提了一個Issue(https://github.com/LitePalFramework/LitePal/issues/414),在他說明了應(yīng)用場景之后,我認(rèn)為監(jiān)聽數(shù)據(jù)庫創(chuàng)建和升級這個功能還是非常有意義的。
要實現(xiàn)這個功能肯定要添加新的接口了,而我對于添加新接口保持著一種比較謹(jǐn)慎的態(tài)度,因為要考慮到接口的易用性和對整體框架的影響。
LitePal的每一個接口我都要盡量將它設(shè)計得簡單好用,因此大家應(yīng)該也可以猜到了,監(jiān)聽數(shù)據(jù)庫創(chuàng)建和升級這個功能會非常容易,只需要簡單幾行代碼就可以了實現(xiàn)了:
需要注意的是,registerDatabaseListener()方法一定要確保在任何其他數(shù)據(jù)庫操作之前調(diào)用,然后當(dāng)數(shù)據(jù)庫創(chuàng)建的時候,onCreate()方法就會得到回調(diào),當(dāng)數(shù)據(jù)庫升級的時候onUpgrade()方法就會得到回調(diào),并且告訴通過參數(shù)告訴你之前的老版本號,以及升級之后的新版本號。
Kotlin版的代碼也是類似的,但是由于這個接口有兩個回調(diào)方法,因此用不了Kotlin的單抽象方法(SAM)這種語法糖,只能使用實現(xiàn)接口的匿名對象這種寫法:
這樣我們就將監(jiān)聽數(shù)據(jù)庫創(chuàng)建和升級這部分內(nèi)容也快速介紹完了,接下來即將進入到本篇文章的重頭戲內(nèi)容。
一次不可思議的升級
從上述文章中我們都可以看出,Kotlin版的代碼普遍都是比Java代碼要更簡約的,Google給出的官方統(tǒng)計是,使用Kotlin開發(fā)可以減少大約25%以上的代碼。
但是處處講究簡約的Kotlin,卻在有一處用法上讓我著實很難受。比如使用Java查詢song表中id為1的這條記錄是這樣寫的:
Song song = LitePal.find(Song.class, 1);
而同樣的功能在Kotlin中卻需要這樣寫:
val song = LitePal.find(Song::class.java, 1)
由于LitePal必須知道要查詢哪個表當(dāng)中的數(shù)據(jù),因此一定要傳遞一個Class參數(shù)給LitePal才行。在Java中我們只需要傳入Song.class即可,但是在Kotlin中的寫法卻變成了Song::class.java,反而比Java代碼更長了,有沒有覺得很難受?
當(dāng)然,很多人寫著寫著也就習(xí)慣了,這并不是什么大問題。但是隨著我深入學(xué)習(xí)Kotlin之后,我發(fā)現(xiàn)Kotlin提供了一個相當(dāng)強大的機制可以優(yōu)化這個問題,這個機制叫作泛型實化。接下來我會對泛型實化的概念和用法做個詳細(xì)的講解。
要理解泛型實化,首先你需要知道泛型擦除的概念。
不管是Java還是Kotlin,只要是基于JVM的語言,泛型基本都是通過類型擦除來實現(xiàn)的。也就是說泛型對于類型的約束只在編譯時期存在,運行時期是無法直接對泛型的類型進行檢查的。例如,我們創(chuàng)建一個List<String>集合,雖然在編譯時期只能向集合中添加字符串類型的元素,但是在運行時期JVM卻并不能知道它本來只打算包含哪種類型的元素,只能識別出來它是個List。
Java的泛型擦除機制,使得我們不可能使用if (a instanceof T),或者是T.class這樣的語法。
而Kotlin也是基于JVM的語言,因此Kotlin的泛型在運行時也是會被擦除的。但是Kotlin中提供了一個內(nèi)聯(lián)函數(shù)的概念,內(nèi)聯(lián)函數(shù)中的代碼會在編譯的時候自動被替換到調(diào)用它的地方,這就使得原有方法調(diào)用時的形參聲明和實參傳遞,在編譯之后直接變成了同一個方法內(nèi)的變量調(diào)用。這樣的話也就不存在什么泛型擦除的問題了,因為Kotlin在編譯之后會直接使用實參替代內(nèi)聯(lián)方法中泛型部分的代碼。
簡單點來說,就是Kotlin是允許將內(nèi)聯(lián)方法中的泛型進行實化的。
那么具體該怎么寫才能將泛型實化呢?首先,該方法必須是內(nèi)聯(lián)方法才行,也就是要用inline關(guān)鍵字來修飾該方法。其次,在聲明泛型的地方還必須加上reified關(guān)鍵字來表示該泛型要進行實化。示例代碼如下所示:
inline fun <reified T> instanceOf(value: Any) {}
上述方法中的泛型T就是一個被實化的泛型,因為它滿足了內(nèi)聯(lián)函數(shù)和reified關(guān)鍵字這兩個前提條件。那么借助泛型實化,我們到底可以實現(xiàn)什么樣的效果呢?從方法名上就可以看出來了,這里我們借助泛型來實現(xiàn)一個instanceOf的效果,代碼如下所示:
inline fun <reified T> instanceOf(value: Any) = value is T
雖然只有一行代碼,但是這里實現(xiàn)了一個Java中完全不可能實現(xiàn)的功能 —— 判斷參數(shù)的類型是不是屬于泛型的類型。這就是泛型實化不可思議的地方。
那么我們?nèi)绾问褂眠@個方法呢?在Kotlin中可以這么寫:
val result1 = instanceOf<String>("hello") val result2 = instanceOf<String>(123) // result1為true,result2為false
可以看到,第一行代碼指定的泛型是String,參數(shù)是字符串"hello",因此最后的結(jié)果是true。而第二行代碼指定泛型是String,參數(shù)卻是數(shù)字123,因此最后的結(jié)果是false。
除了可以做類型判斷之外,我們還可以直接獲取到泛型的Class類型。看一下下面的代碼:
inline fun <reified T> genericClass() = T::class.java
這段代碼就更加不可思議了,genericClass()方法直接返回了當(dāng)前指定泛型的class類型。T.class這樣的語法在Java中是不可能的,而在Kotlin中借助泛型實化功能就可以使用T::class.java這樣的語法了。
然后我們就可以這樣調(diào)用:
val result1 = genericClass<String>() val result2 = genericClass<Int>() // result1為java.lang.String,result2為java.lang.Integer
可以看到,我們?nèi)绻付朔盒蚐tring,那么最終就可以得到j(luò)ava.lang.String的Class,如果指定了泛型Int,最終就可以得到j(luò)ava.lang.Integer的Class。
關(guān)于Kotlin泛型實化這部分的講解就到這里,現(xiàn)在我們重新回到LitePal上面。講了這么多泛型實化方面的內(nèi)容,那么LitePal到底如何才能利用這個特性進行優(yōu)化呢?
回顧一下,剛才我們查詢song表中id為1的這條記錄是這樣寫的:
val song = LitePal.find(Song::class.java, 1)
這里需要傳入Song::class.java是因為要告知LitePal去查詢song這張表中的數(shù)據(jù)。而通過剛才泛型實化部分的講解,我們知道Kotlin中是可以使用T::class.java這樣的語法的,因此我在LitePal 3.0.0中擴展了這部分特性,允許通過指定泛型來聲明查詢哪張表中的內(nèi)容。于是代碼就可以優(yōu)化成這個樣子了:
val song = LitePal.find<Song>(1)
怎么樣,有沒有覺得代碼瞬間清爽了很多?看起來比Java版的查詢還要更加簡約。
另外得益于Kotlin出色的類型推導(dǎo)機制,我們還可以將代碼改為如下寫法:
val song: Song? = LitePal.find(1)
這兩種寫法效果是一模一樣的,因為如果我在song變量的后面聲明了Song?類型,那么find()方法就可以自動推導(dǎo)出泛型類型,從而不需要再手動進行<Song>的泛型指定了。
除了find()方法之外,我還對LitePal中幾乎全部的公有API都進行了優(yōu)化,只要是原來需要傳遞Class參數(shù)的接口,我都增加了一個通過指定泛型來替代Class參數(shù)的擴展方法。注意,這里我使用的是擴展方法,而不是修改了原有方法,這樣的話兩種寫法你都可以使用,全憑自己的喜好,如果是直接修改原有方法,那么項目升級之后就可能會造成大面積報錯了,這是誰都不想看到的。
那么這里我再向大家演示另外幾種CRUD操作優(yōu)化之后的用法吧,比如我想使用where條件查詢的時候就可以這樣寫:
val list = LitePal.where("duration > ?", "100").find<Song>()
這里在最后的find()方法中指定了泛型<Song>,得到的結(jié)果會是一個List<Song>集合。
想要刪除song表中id為1的這條數(shù)據(jù)可以這么寫:
LitePal.delete<Song>(1)
想要統(tǒng)計song表中的記錄數(shù)量可以這么寫:
val count = LitePal.count<Song>()
其他一些方法的優(yōu)化也都是類似的,相信大家完全可以舉一反三,就不再一一演示了。
這樣我們就將LitePal新版本中的主要功能都介紹完了。當(dāng)然,除了這些新功能之外,我還修復(fù)了一些已知的bug,提升了整體框架的穩(wěn)定性,如果這些正是你所需要的話,那就趕快升級吧。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
android ContentResolver獲取手機電話號碼和短信內(nèi)容
這篇文章主要為大家詳細(xì)介紹了android ContentResolver獲取手機電話號碼、短信內(nèi)容,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Android 實現(xiàn)自定義圓形listview功能的實例代碼
這篇文章主要介紹了Android 實現(xiàn)自定義圓形listview功能的實例代碼,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07Android中activity處理返回結(jié)果的實現(xiàn)方式
這篇文章主要介紹了Android中activity處理返回結(jié)果的實現(xiàn)方式,為了實現(xiàn)這個功能,Android提供了一個機制,跳轉(zhuǎn)到其他activity時,再返回,可以接受到其他activity返回的值,無需再start新的當(dāng)前activity。需要的朋友可以參考下2016-12-12Android學(xué)習(xí)系列一用按鈕實現(xiàn)顯示時間
這篇文章主要介紹了Android學(xué)習(xí)系列一用按鈕實現(xiàn)顯示時間的相關(guān)資料,需要的朋友可以參考下2016-05-05Android 中使用ContentObserver模式獲取短信用正則自動填充驗證碼
這篇文章主要介紹了Android 中使用ContentObserver模式獲取短信用正則自動填充驗證碼,首先使用了ContentObserver監(jiān)聽短信,然后從短信中用正則的分組去拿到驗證碼,具體實現(xiàn)代碼大家參考下本文2017-02-02