[Android組件化]- SPI加載

你們好,我係蒼王。
這個系列已經出到了第30章節了,已經開通了已經有一年半的時間了。
在一年半里,創建了千人的QQ大羣,很多編輯也找過我編輯圖書,也有同行找過我合做出公衆號。可是我的的時間是有限的,並不可能所有願望都實現。
那麼上一年就選了一件對這輩子很是有意義的事情,和電子工業出版社出版一本關於組件化技術的書。很是感謝陳曉猛編輯找到了我一同出書,也感謝在技術羣中不斷深討組件化技術的羣友們。
書中重點介紹了使用組件化思想去搭建一個Android項目,介紹了組件化的思想,組件化的編程技術,多人管理組件化,組件化的編譯優化,以及對項目演進的思想感悟。
此書並非只是介紹技術,也包含了我對一些生活的理解,技術思惟的理解。
京東淘寶噹噹都可以購買,有興趣能夠點擊連接就能夠跳轉了。php

Android組件化架構

如下是我這個系列的相關文章,有興趣能夠參考一下,能夠給個喜歡或者關注個人文章。html

[Android]如何作一個崩潰率少於千分之三噶應用app--章節列表java

關於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.比較適合於組件化中,模塊入口初始化的統一加載場景。android

SPI原理圖

如下借用一個Modular框架中的加載爲例
1.聲明接口編程

public interface IModule {
    /** * 模塊初始化,只有組建時才調用,用於開啓子線程輪訓消息 */
    void init();

    /** * 模塊ID * * @return 模塊ID */
    int getModuleId();

    /** * 模塊註冊並鏈接成功後,能夠作如下事情: * <p> * 一、註冊監聽事件 * 二、發送事件 * 三、註冊服務 * 四、調用服務 */
    void afterConnected();
}
複製代碼

2.使用@AutoService,將全路徑名寫到resources/META-INF/services目錄json

@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);
    }
複製代碼

能夠看到使用的是懶加載的迭代器,只有迭代器被使用的時候,纔會真正初始化每個繼承接口的實體類。app

//判斷是否有下一個對象
   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腳本動態更新。

相關文章
相關標籤/搜索