大家好,我係蒼王。
這個系列已經出到了第30章節了,已經開通了已經有一年半的時間了。
在一年半里,建立了千人的QQ大群,不少編輯也找過我編輯圖書,也有同行找過我合作出公眾號。但是個人的時間是有限的,並不可能全部願望都實現。
那麼上一年就選了一件對這輩子非常有意義的事情,和電子工業出版社出版一本關於元件化技術的書。非常感謝陳曉猛編輯找到了我一同出書,也感謝在技術群中不斷深討元件化技術的群友們。
書中重點介紹了使用元件化思想去搭建一個Android專案,介紹了元件化的思想,元件化的程式設計技術,多人管理元件化,元件化的編譯優化,以及對專案演進的思想感悟。
此書並不是只是介紹技術,也包含了我對一些生活的理解,技術思維的理解。
京東、淘寶和噹噹均可以購買,有興趣可以點選連結就可以跳轉了。
以下是我這個系列的相關文章,有興趣可以參考一下,可以給個喜歡或者關注我的文章。
[Android]如何做一個崩潰率少於千分之三噶應用app--章節列表
關於spi,其全名是Service Provider Interfaces。ServiceLoder用於動態載入介面實現類的載入器。
1.其可以動態載入一些繼承某個介面的實體類
2.需要將實體類名宣告到resources/META-INF/services目錄,可以取巧使用@AutoService
3.其載入的並不是單例,而且構造方法不帶任何引數,因為ServiceLoader底層是使用了反射的機制來載入。
4.載入檔案順序應該是按照resources/META-INF/services目錄中順序載入,所以如果使用@AutoService是不可控的。
5.ServiceLoader繼承iterator介面,可以像List一樣遍歷實體類。
6.其實際也是通過反射來實現初始化操作,使用介面的方式使模組,ServiceLoader裝載器、啟動器之間更加解耦。
7.比較適合於元件化中,模組入口初始化的統一載入場景。
以下借用一個Modular框架中的載入為例
1.宣告介面
public interface IModule {
/**
* 模組初始化,只有組建時才呼叫,用於開啟子執行緒輪訓訊息
*/
void init();
/**
* 模組ID
*
* @return 模組ID
*/
int getModuleId();
/**
* 模組註冊並連線成功後,可以做以下事情:
* <p>
* 1、註冊監聽事件
* 2、傳送事件
* 3、註冊服務
* 4、呼叫服務
*/
void afterConnected();
}
複製程式碼
2.使用@AutoService,將全路徑名寫到resources/META-INF/services目錄
@AutoService(IModule.class)
public class Module extends BaseModule {
@Override
public void afterConnected() {
}
@Override
public int getModuleId() {
return Constants.MODULE_B;
}
}
複製程式碼
3.使用ServiceLoder載入模組
@Override//只有當是組建單獨執行時,才當Application執行,才會走onCreate,最終打包時根本沒有這個類
public void onCreate() {
super.onCreate();
……
//自動註冊伺服器(如果是獨立模組內宣告只有一個IModule)
ServiceLoader<IModule> modules = ServiceLoader.load(IModule.class);
mBaseModule = (BaseModule) modules.iterator().next();
//模組初始化
mBaseModule.init();
……
}
複製程式碼
public void onCreate() {
super.onCreate();
……
//SPI自動註冊服務(主module裝載的時候,已經將全部META_INF檔案合併)
ServiceLoader<IModule> modules = ServiceLoader.load(IModule.class);
for (IModule module : modules) module.afterConnected();
}
複製程式碼
使用看起來非常簡單,我們研究一下ServiceLoader原始碼的特別之處。
//呼叫靜態load方法來初始化XXXInterface介面資訊。
public static <S> ServiceLoader<S> load(Class<S> service) {
//獲取當前執行緒ClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
//構建ServiceLoader物件
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//檢測介面是否否存在
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//檢測classloader是否為空,為空使用系統classloader載入器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// Android-changed: Do not use legacy security code.
// On Android, System.getSecurityManager() is always null.
// acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
//清理provides配置載入器
providers.clear();
//初始化懶載入迭代器
lookupIterator = new LazyIterator(service, loader);
}
複製程式碼
可以看到使用的是懶載入的迭代器,只有迭代器被使用的時候,才會真正初始化每一個繼承介面的實體類。
//判斷是否有下一個物件
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//PREFIX = "META-INF/services/"
//載入配置地址
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;
}
//解析config的節點
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
複製程式碼
通過反射完成介面類的初始化
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");
}
if (!service.isAssignableFrom(c)) {
// Android-changed: Let the ServiceConfigurationError have a cause.
ClassCastException cce = new ClassCastException(
service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
fail(service,
"Provider " + cn + " not a subtype", cce);
// fail(service,
// "Provider " + cn + " not a subtype");
}
try {
//反射初始化類,並轉化成介面
S p = service.cast(c.newInstance());
//記錄對映關係
providers.put(cn, p);
//返回介面實體
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
複製程式碼
ServiceLoader實際還是通過路徑名反射來完成,只是其通過配置到META_INF中的目錄檔案來完成解耦。
ServiceLoader使用場景是用於不需要區分module載入順序的情況,如果有載入順序,還需要重新排序後再初始化方法,這裡最後還是使用優先順序機制。
在開編的時候已經介紹了SPI的優點和侷限性,跳出SPI,依然能做一個更靈活更可控的載入機制,例如json指令碼,xml指令碼動態更新。