Android數據庫高手祕籍(十),如何在Kotlin中更好地使用LitePal

轉載請註明出處:https://blog.csdn.net/guolin_blog/article/details/82714414

本文同步發表於我的微信公衆號,掃一掃文章底部的二維碼或在微信搜索 郭霖 即可關注,每個工作日都有文章更新。

自從LitePal在2.0.0版本中全面支持了Kotlin之後,我也一直在思考如何讓LitePal更好地融入和適配Kotlin語言,而不僅僅停留在簡單的支持層面。

Kotlin確實是一門非常出色的語言,裏面有許多優秀的特性是在Java中無法實現的。因此,在LitePal全面支持了Kotlin之後,我覺得如果我還視這些優秀特性而不見的話,就有些太暴殄天物了。所以在最新的LitePal 3.0.0版本里面,我準備讓LitePal更加充分地利用Kotlin的一些語言特性,從而讓我們的開發更加輕鬆。

本篇文章除了介紹LitePal 3.0.0版本的升級內容之外,還會講解一些Kotlin方面的高級知識。

升級到3.0.0

首先還是來看如何升級。

爲什麼這次的版本號跨度如此之大,直接從2.0升到了3.0呢?因爲這次LitePal在結構上面有了一個質的變化。

爲了更好地兼容Kotlin語言,LitePal現在不再只是一個庫了,而是變成了兩個庫,根據你使用的語言不同,需要引入的庫也不同。如果你使用的是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版本到底變更了哪些東西。

泛型的優化

不得不說,其實LitePal的泛型設計一直都不是很友好,尤其在異步查詢的時候格外難受,比如我們看下如下代碼:

在異步查詢的onFinish()回調中,我們直接得到的並不是查詢的對象,而是一個泛型T對象,還需要再經過一次強制轉型才能得到真正想要查詢的對象。

如果你覺得這還不算難受的話,那麼再來看看下面這個例子:

可以看到,這次查詢返回的是一個List<T>,我們必須要對整個List進行強制轉型。不僅要多寫一行代碼,關鍵是開發工具還會給出一個很醜的警告。

這樣的設計無論如何都算不上友好。

這裏非常感謝 xiazunyang 這位朋友在GitHub上提出的這個Issue(https://github.com/LitePalFramework/LitePal/issues/396),並且給出了建議的優化方案,LitePal 3.0.0版本在泛型方面的優化很大程度上是基於他的建議。

那麼我們現在來看看,到了LitePal 3.0.0版本,同樣的功能可以怎麼寫:

LitePal.findAsync(Song.class, 1).listen(new FindCallback<Song>() {
    @Override
    public void onFinish(Song song) {

    }
});

可以看到,這裏在FindCallback接口上聲明瞭泛型類型爲Song,那麼在onFinish()方法回調中的參數就可以直接指定爲Song類型了,從而避免了一次強制類型轉換。

那麼同樣地,在查詢多條數據的時候就可以這樣寫:

LitePal.where("duration > ?", "100").findAsync(Song.class).listen(new FindMultiCallback<Song>() {
    @Override
    public void onFinish(List<Song> list) {

    }
});

這次就清爽多了吧,在onFinish()回調方法中,我們直接拿到的就是一個List<Song>集合,而不會再出現那個醜醜的警告了。

而如果這段代碼使用Kotlin來編寫的話,將會更加的精簡:

LitePal.where("duration > ?", "100").findAsync(Song::class.java).listen { list ->
    
}

得益於Kotlin出色的lambda機制,我們的代碼可以得到進一步精簡。在上述代碼中,行尾的list參數就是查詢出來的List<Song>集合了。

那麼關於泛型優化的講解就到這裏,下面我們來看另一個主題,監聽數據庫的創建和升級。

監聽數據庫的創建和升級

沒錯,LitePal 3.0.0版本新增了監聽數據庫的創建和升級功能。

加入這個功能是因爲 JakeHao 這位朋友在GitHub上提了一個Issue(https://github.com/LitePalFramework/LitePal/issues/414),在他說明了應用場景之後,我認爲監聽數據庫創建和升級這個功能還是非常有意義的。

)

要實現這個功能肯定要添加新的接口了,而我對於添加新接口保持着一種比較謹慎的態度,因爲要考慮到接口的易用性和對整體框架的影響。

LitePal的每一個接口我都要儘量將它設計得簡單好用,因此大家應該也可以猜到了,監聽數據庫創建和升級這個功能會非常容易,只需要簡單幾行代碼就可以了實現了:

LitePal.registerDatabaseListener(new DatabaseListener() {
    @Override
    public void onCreate() {
    }

    @Override
    public void onUpgrade(int oldVersion, int newVersion) {
    }
});

需要注意的是,registerDatabaseListener()方法一定要確保在任何其他數據庫操作之前調用,然後當數據庫創建的時候,onCreate()方法就會得到回調,當數據庫升級的時候onUpgrade()方法就會得到回調,並且告訴通過參數告訴你之前的老版本號,以及升級之後的新版本號。

Kotlin版的代碼也是類似的,但是由於這個接口有兩個回調方法,因此用不了Kotlin的單抽象方法(SAM)這種語法糖,只能使用實現接口的匿名對象這種寫法:

LitePal.registerDatabaseListener(object : DatabaseListener {
    override fun onCreate() {
    }

    override fun onUpgrade(oldVersion: Int, newVersion: Int) {
    }
})

這樣我們就將監聽數據庫創建和升級這部分內容也快速介紹完了,接下來即將進入到本篇文章的重頭戲內容。

一次不可思議的升級

從上述文章中我們都可以看出,Kotlin版的代碼普遍都是比Java代碼要更簡約的,Google給出的官方統計是,使用Kotlin開發可以減少大約25%以上的代碼。

但是處處講究簡約的Kotlin,卻在有一處用法上讓我着實很難受。比如使用Java查詢song表中id爲1的這條記錄是這樣寫的:

Song song = LitePal.find(Song.class, 1);

而同樣的功能在Kotlin中卻需要這樣寫:

val song = LitePal.find(Song::class.java, 1)

由於LitePal必須知道要查詢哪個表當中的數據,因此一定要傳遞一個Class參數給LitePal纔行。在Java中我們只需要傳入Song.class即可,但是在Kotlin中的寫法卻變成了Song::class.java,反而比Java代碼更長了,有沒有覺得很難受?

當然,很多人寫着寫着也就習慣了,這並不是什麼大問題。但是隨着我深入學習Kotlin之後,我發現Kotlin提供了一個相當強大的機制可以優化這個問題,這個機制叫作泛型實化。接下來我會對泛型實化的概念和用法做個詳細的講解。

要理解泛型實化,首先你需要知道泛型擦除的概念。

不管是Java還是Kotlin,只要是基於JVM的語言,泛型基本都是通過類型擦除來實現的。也就是說泛型對於類型的約束只在編譯時期存在,運行時期是無法直接對泛型的類型進行檢查的。例如,我們創建一個List<String>集合,雖然在編譯時期只能向集合中添加字符串類型的元素,但是在運行時期JVM卻並不能知道它本來只打算包含哪種類型的元素,只能識別出來它是個List

Java的泛型擦除機制,使得我們不可能使用if (a instanceof T),或者是T.class這樣的語法。

而Kotlin也是基於JVM的語言,因此Kotlin的泛型在運行時也是會被擦除的。但是Kotlin中提供了一個內聯函數的概念,內聯函數中的代碼會在編譯的時候自動被替換到調用它的地方,這就使得原有方法調用時的形參聲明和實參傳遞,在編譯之後直接變成了同一個方法內的變量調用。這樣的話也就不存在什麼泛型擦除的問題了,因爲Kotlin在編譯之後會直接使用實參替代內聯方法中泛型部分的代碼。

簡單點來說,就是Kotlin是允許將內聯方法中的泛型進行實化的。

那麼具體該怎麼寫才能將泛型實化呢?首先,該方法必須是內聯方法纔行,也就是要用inline關鍵字來修飾該方法。其次,在聲明泛型的地方還必須加上reified關鍵字來表示該泛型要進行實化。示例代碼如下所示:

inline fun <reified T> instanceOf(value: Any) {

}

上述方法中的泛型T就是一個被實化的泛型,因爲它滿足了內聯函數和reified關鍵字這兩個前提條件。那麼藉助泛型實化,我們到底可以實現什麼樣的效果呢?從方法名上就可以看出來了,這裏我們藉助泛型來實現一個instanceOf的效果,代碼如下所示:

inline fun <reified T> instanceOf(value: Any) = value is T

雖然只有一行代碼,但是這裏實現了一個Java中完全不可能實現的功能 —— 判斷參數的類型是不是屬於泛型的類型。這就是泛型實化不可思議的地方。

那麼我們如何使用這個方法呢?在Kotlin中可以這麼寫:

val result1 = instanceOf<String>("hello")
val result2 = instanceOf<String>(123)
// result1爲true,result2爲false

可以看到,第一行代碼指定的泛型是String,參數是字符串"hello",因此最後的結果是true。而第二行代碼指定泛型是String,參數卻是數字123,因此最後的結果是false

除了可以做類型判斷之外,我們還可以直接獲取到泛型的Class類型。看一下下面的代碼:

inline fun <reified T> genericClass() = T::class.java

這段代碼就更加不可思議了,genericClass()方法直接返回了當前指定泛型的class類型。T.class這樣的語法在Java中是不可能的,而在Kotlin中藉助泛型實化功能就可以使用T::class.java這樣的語法了。

然後我們就可以這樣調用:

val result1 = genericClass<String>()
val result2 = genericClass<Int>()
// result1爲java.lang.String,result2爲java.lang.Integer

可以看到,我們如果指定了泛型String,那麼最終就可以得到java.lang.String的Class,如果指定了泛型Int,最終就可以得到java.lang.Integer的Class。

關於Kotlin泛型實化這部分的講解就到這裏,現在我們重新回到LitePal上面。講了這麼多泛型實化方面的內容,那麼LitePal到底如何才能利用這個特性進行優化呢?

回顧一下,剛纔我們查詢song表中id爲1的這條記錄是這樣寫的:

val song = LitePal.find(Song::class.java, 1)

這裏需要傳入Song::class.java是因爲要告知LitePal去查詢song這張表中的數據。而通過剛纔泛型實化部分的講解,我們知道Kotlin中是可以使用T::class.java這樣的語法的,因此我在LitePal 3.0.0中擴展了這部分特性,允許通過指定泛型來聲明查詢哪張表中的內容。於是代碼就可以優化成這個樣子了:

val song = LitePal.find<Song>(1)

怎麼樣,有沒有覺得代碼瞬間清爽了很多?看起來比Java版的查詢還要更加簡約。

另外得益於Kotlin出色的類型推導機制,我們還可以將代碼改爲如下寫法:

val song: Song? = LitePal.find(1)

這兩種寫法效果是一模一樣的,因爲如果我在song變量的後面聲明瞭Song?類型,那麼find()方法就可以自動推導出泛型類型,從而不需要再手動進行<Song>的泛型指定了。

除了find()方法之外,我還對LitePal中幾乎全部的公有API都進行了優化,只要是原來需要傳遞Class參數的接口,我都增加了一個通過指定泛型來替代Class參數的擴展方法。注意,這裏我使用的是擴展方法,而不是修改了原有方法,這樣的話兩種寫法你都可以使用,全憑自己的喜好,如果是直接修改原有方法,那麼項目升級之後就可能會造成大面積報錯了,這是誰都不想看到的。

那麼這裏我再向大家演示另外幾種CRUD操作優化之後的用法吧,比如我想使用where條件查詢的時候就可以這樣寫:

val list = LitePal.where("duration > ?", "100").find<Song>()

這裏在最後的find()方法中指定了泛型<Song>,得到的結果會是一個List<Song>集合。

想要刪除song表中id爲1的這條數據可以這麼寫:

LitePal.delete<Song>(1)

想要統計song表中的記錄數量可以這麼寫:

val count = LitePal.count<Song>()

其他一些方法的優化也都是類似的,相信大家完全可以舉一反三,就不再一一演示了。

這樣我們就將LitePal新版本中的主要功能都介紹完了。當然,除了這些新功能之外,我還修復了一些已知的bug,提升了整體框架的穩定性,如果這些正是你所需要的話,那就趕快升級吧。

我沒學過LitePal怎麼辦?

本篇文章是寫給已經有LitePal基礎的人看的,幫助他們快速地升級到LitePal 3.0.0。如果你之前並沒有學過LitePal,可以參考《第一行代碼 第2版》第6章中的內容,裏面有非常詳盡的LitePal使用講解。

另外也可以閱讀我寫的專欄《Android數據庫高手祕籍》,同樣對LitePal的各種使用方法進行了詳細地剖析。


關注我的技術公衆號,每天都有優質技術文章推送。關注我的娛樂公衆號,工作、學習累了的時候放鬆一下自己。

微信掃一掃下方二維碼即可關注:

        

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