Spi機(jī)制在Android開(kāi)發(fā)的應(yīng)用示例詳解
Spi機(jī)制介紹
SPI 全稱是 Service Provider Interface,是一種將服務(wù)接口與服務(wù)實(shí)現(xiàn)分離以達(dá)到解耦、可以提升程序可擴(kuò)展性的機(jī)制。嘿嘿,看到這個(gè)概念很多人肯定是一頭霧水了,沒(méi)事,我們直接就可以簡(jiǎn)單理解為是一種反射機(jī)制,即我們不需要知道具體的實(shí)現(xiàn)方,只要定義好接口,我們就能夠在運(yùn)行時(shí)找到一個(gè)實(shí)現(xiàn)接口的類,我們具體看一下官方定義。

舉個(gè)例子
加入我是一個(gè)庫(kù)設(shè)計(jì)者,我希望把一個(gè)接口暴露給使用者實(shí)現(xiàn)具體的邏輯,那么我肯定不能夠?qū)懰缹?shí)現(xiàn)類對(duì)吧,不然我們?cè)趺磾U(kuò)展嘛!比如我們有以下接口
package com.example.newtestproject
interface TestSpi {
fun getSpi();
}
如果我在使用的過(guò)程中,想不關(guān)心具體的實(shí)現(xiàn)類/又或者想兼容多個(gè)實(shí)現(xiàn),那么怎么辦呢?(比如日常開(kāi)發(fā)的gradle,如果我想兼容多個(gè)agp版本在自己庫(kù)的運(yùn)行,一個(gè)個(gè)寫死肯定是一個(gè)非常糟糕的實(shí)現(xiàn),如果出了新版本,那么我們還要更改代碼),我們能不能只定義一個(gè)規(guī)范,即上面的TestSpi就可以不關(guān)心以后的擴(kuò)展呢?很簡(jiǎn)單,我們只需要在resource/META-INF/services目錄下定義一個(gè)以該接口為名稱的文件,文件內(nèi)容是具體的接口實(shí)現(xiàn)類即可,如圖

內(nèi)容是實(shí)現(xiàn)類的全名稱,假如我們有以下實(shí)現(xiàn)類
class TheTestSpi:TestSpi {
override fun getSpi() {
Log.i("hello","i am the interface implementation from TheTestSpi")
}
}
那么文件只需要寫入com.example.newtestproject.TheTestSpi即可。
在我們想要用到TestSpi的功能的時(shí)候,就可以通過(guò)以下方式進(jìn)行使用,從而不用關(guān)心具體的實(shí)現(xiàn),達(dá)到了解耦合的目的
// spi test
val load = ServiceLoader.load(TestSpi::class.java)
load.forEach {
it.getSpi()
if(it is TheTestSpi){
Log.i("hello","theTestSpi")
}
}
ServiceLoader.load
從上面我們可以看到,最關(guān)鍵的是調(diào)用了ServiceLoader.load方法,這個(gè)就是Spi具體的實(shí)現(xiàn)了,本質(zhì)是什么呢?相信都能夠猜到了,其實(shí)就是反射,我們來(lái)跟一下源代碼
public static <S> ServiceLoader<S> load(Class<S> service) {
// 獲取了一個(gè)當(dāng)前類的classloader,做好了準(zhǔn)備
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
緊接著就會(huì)調(diào)用到
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
我們可以看到,執(zhí)行的時(shí)候有這么一句String fullName = PREFIX + service.getName();,很容易想到,如果我們要反射的話,是不是需要類全稱,PREFIX 其實(shí)就是
private static final String PREFIX = "META-INF/services/";
可以看到,之所以我們需要在resource下定一個(gè)文件路徑是META-INF/services,是因?yàn)樵赟erviceLoader中定義好了,所以ServiceLoader會(huì)按照約定的路徑,去該文件夾下查找service.getName()(TestSpi)的實(shí)現(xiàn)類,最后通過(guò)
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
// Android-changed: Let the ServiceConfigurationError have a cause.
"Provider " + cn + " not found", x);
// "Provider " + cn + " not found");
}
.....
Class.forName(cn, false, loader) 去進(jìn)行了類查找操作,因?yàn)槲覀兺ㄟ^(guò)foreach去遍歷,其實(shí)就是通過(guò)迭代器去獲取了每個(gè)實(shí)例,所以才能夠調(diào)用到了實(shí)現(xiàn)類TheTestSpi的getSpi() 方法。
在Android中的應(yīng)用
SPI機(jī)制在很多庫(kù)的設(shè)計(jì)上都有應(yīng)用,比如Coroutine(kotlin協(xié)程庫(kù))就有用到,比如我們經(jīng)常用到的 MainCoroutineDispatcher,如果我們希望一個(gè)任務(wù)運(yùn)行在主線程,在Android就可以通過(guò)handler的方式去向主線程post一個(gè)消息,那如果在其他環(huán)境呢?
kotlin不僅僅是想在android的世界立足,還有很多比如如果在native環(huán)境呢?在服務(wù)器環(huán)境呢?多平臺(tái)環(huán)境呢(如KMM),那就不一定有Handler這個(gè)概念對(duì)不對(duì)!但是都有一個(gè)主線程的概念,所以Coroutine把這部分就通過(guò)SPI的方式去實(shí)現(xiàn)了,如

定義了這個(gè)接口 MainDispatcherFactory::class.java 用于給具體環(huán)境的主線程實(shí)現(xiàn)類進(jìn)行實(shí)現(xiàn),具體的實(shí)現(xiàn)類就是
internal class AndroidDispatcherFactory : MainDispatcherFactory {
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
HandlerContext(Looper.getMainLooper().asHandler(async = true))
override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
override val loadPriority: Int
get() = Int.MAX_VALUE / 2
}
可以看到,在Android中就通過(guò)了Handler去實(shí)現(xiàn),最后我們可以在源碼中看到,SPI相關(guān)的注冊(cè)信息

總結(jié)
通過(guò)SPI技術(shù)去實(shí)現(xiàn)的解耦合工作的出色工程還有很多很多,比如我們用的APT,還有didi開(kāi)源的Booster,都有用到這方面的知識(shí)。
以上就是Spi機(jī)制在Android開(kāi)發(fā)的應(yīng)用示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Android開(kāi)發(fā)Spi機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android自定義View之酷炫數(shù)字圓環(huán)
這篇文章主要為大家詳細(xì)介紹了Android自定義View之酷炫數(shù)字圓環(huán),實(shí)現(xiàn)效果很酷,,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-01-01
Android TextView多文本折疊展開(kāi)效果
這篇文章主要為大家詳細(xì)介紹了Android TextView多文本折疊展開(kāi)效果的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Android自定義View實(shí)現(xiàn)打字機(jī)效果
最近在做Android開(kāi)發(fā)的時(shí)候,需要做類似于打字機(jī)打字的效果,字一個(gè)一個(gè)地蹦出來(lái),顯示每一個(gè)字都帶有打字的聲音?,F(xiàn)在分享給大家,有需要的可以參考借鑒。2016-08-08
flutter傳遞值到任意widget(當(dāng)需要widget嵌套使用需要傳遞值的時(shí)候)
這篇文章主要介紹了flutter傳遞值到任意widget(當(dāng)需要widget嵌套使用需要傳遞值的時(shí)候),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07
Android實(shí)現(xiàn)銀行卡號(hào)掃描識(shí)別功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)銀行卡號(hào)掃描識(shí)別功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09
android仿愛(ài)奇藝加載動(dòng)畫實(shí)例
這篇文章主要介紹了android仿愛(ài)奇藝加載動(dòng)畫實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。2016-10-10
深入Android Handler,MessageQueue與Looper關(guān)系
這篇文章主要介紹了深入Android Handler,MessageQueue與Looper關(guān)系,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08

