android 初识SPI

android 初识SPI

SPI全名是Service Provide Interface
官方解释:为某个接口提供服务的机制。
ServiceLoader通过加载接口类或者抽象类,再通过模块的配置文件,以此扫描获取到多个实现类的全限定名后通过反射操作获取实例来跨模块通信。
官方文档:https://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html

使用

这里的使用仅仅对于android方向的
android组件化通信可以使用SPI接口模式来通信(这是对于进程来说)。
按照组件化思想,对于接口的定义应该属于公共base库或者新建一个接口base库来统一管理这些接口,也方便其他库类引用到。
先定义接口事件:
在base库或者接口库中
在这里插入图片描述
定义接口:

public interface ShowPlay {

    String display();

    void play();

}

然后在app主模块下实现接口:

package com.hujin.wan.spi;

import com.hujin.wan.commonbase.spi.ShowPlay;

public class AppShowPlay implements ShowPlay {
    @Override
    public String display() {
        return "this is app Module";
    }

    @Override
    public void play() {

    }


}

在主模块下 src - main 下创建 resources 文件夹,创建META-INF/services文件。再创建base库中接口的全路径。文件中写入实现接口类的全路径,一行一个路径。这是一个注册机制
在这里插入图片描述
其他模块下,同上操作。实现base库的接口:
在这里插入图片描述
代码如下:

package com.hujin.wan.news.spi;

import com.hujin.wan.commonbase.spi.ShowPlay;

public class NewsShowPlay implements ShowPlay {
    @Override
    public String display() {
        return "this is dispaly is module news";
    }

    @Override
    public void play() {

    }
    
}

同样也创建META-INF/services下的全路径文件:
在这里插入图片描述
调用:
由于组件隔离了,其他模块除了依赖基础base库是不能调用主模块和其他业务模块的。而SPI可以实现业务模块调用主模块的功能。
在其他模块调用:

  ServiceLoader<ShowPlay> load = ServiceLoader.load(ShowPlay.class);
        Iterator<ShowPlay> iterator = load.iterator();
        while (iterator.hasNext()){

            ShowPlay next = iterator.next();
            if(next.getClass().getSimpleName().equals("AppShowPlay")){
                String display = iterator.next().display();
                LogUtils.eTag(TAG,display);
            }
        }


这里走了AppShowPlay的display()方法了,实现了跨组件的通信了。

原理解析

主要还是看ServiceLoader类,官网定义为:一个简单的服务提供者加载工具 确实使用也很简单。代码不多,直接看,写了注释了。

//S 范型 就是指定的接口  实现了迭代器接口
public final class ServiceLoader<S>
    implements Iterable<S>
{
	//前面提到的 文件目录 所以创建名字一定要正确。
    private static final String PREFIX = "META-INF/services/";

    // 表示正在加载的服务的类或接口
    private final Class<S> service;

    //类加载器,来找加载类的 反射了接下
    private final ClassLoader loader;

    // 不同实现类的缓存,key值就是全路径类名 
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 懒加载的迭代器 LazyIterator是一个内部类
    private LazyIterator lookupIterator;
   
   //清除此加载程序的提供程序缓存,以便所有加载程序都将被重新加载。
   //LazyIterator 也重新创建了
   public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

	//由load调用 然后在调用reload 创建LazyIterator
	//load函数其实并没有创建service providers 
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        reload();
    }

 	//等会看
    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

  
    private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

    
    //内部类 
    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        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;
        }

        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
        }

        public boolean hasNext() {
            // Android-changed: do not use legacy security code
            /* if (acc == null) { */
                return hasNextService();
            /*
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
            */
        }

        public S next() {
            // Android-changed: do not use legacy security code
            /* if (acc == null) { */
                return nextService();
            /*
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
            */
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

	//返回一个迭代器
    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }


    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

   
    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

 
    public static <S> S loadFromSystemProperty(final Class<S> service) {
        try {
            final String className = System.getProperty(service.getName());
            if (className != null) {
                Class<?> c = ClassLoader.getSystemClassLoader().loadClass(className);
                return (S) c.newInstance();
            }
            return null;
        } catch (Exception e) {
            throw new Error(e);
        }
    }
    

}

load()

拆开看:
先看load()方法相关代码,开始的例子中ServiceLoader.load(ShowPlay.class);来获取获取ServiceLoader。

  public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

 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");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        reload();
    }

  public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

其实load方法就是创建了LazyIterator ServiceLoader ClassLoader,并没有加载service provider,懒加载模式
在看load.iterator()方法:

  public Iterator<S> iterator() {
        return new Iterator<S>() {
			//providers 就是开始定义的 不同接口实现类的缓存,获取到linkedhashmap的迭代器了
            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();
			//要实现三个方法 hasNext next remove
	
		
            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

发现上面hasNext next remove的实现主要依赖knownProviders的实现,但从定义就知道他只是个缓存工具,具体还是要看lookupIterator的实现。

hasNext()

先看hasNext方法:

 public boolean hasNext() {

                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

第一次knownProviders无节点数据,knownProviders.hasNext()会返回false的,走lookupIterator.hasNext()方法:

	**LazyIterator内部类**
  public boolean hasNext() {
           
                return hasNextService();

 private boolean hasNextService() {
			 //nextName 定义的k-v
            if (nextName != null) {
                return true;
            }
            //是一个URL集合
            if (configs == null) {
                try {
                	//service 就是传进来的接口 的Class对象
                	//fullName = META-INF/services/ + 接口全路径
                	//所以我们定义的时候,不就是按照上面格式定义的文件嘛
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                    	//getResources 返回的Enumeration<URL>对象
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            //这个循环条件 pending是待办的拦截器 第一次为null
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                //parse 函数会返回String类型集合的迭代器
                //实际操作就是BufferedReader读取 我们定义的fullName配置文件里面的内容,这个配置文件是合并后的,包含所有模块下的fullName里面的内容,就是我们实现接口类的实现类的全路径类名。
               	//配置文件千万别写错,因为后面要用到反射
                pending = parse(service, configs.nextElement());
            }
            //pending会返回当前fullName对应的全路径包名字符串
            //这里按上面例子会返回:
            //com.hujin.wan.spi.AppShowPlay
			//com.hujin.wan.news.spi.NewsShowPlay
            nextName = pending.next();
            return true;
        }

这里可能不理解,为啥能读到不同模块下配置的路径名,其实在app运行是会有合并操作的,各模块下根据路径合并的。这里分析apk文件可以看出:

在这里插入图片描述
在这里插入图片描述
总结下hasNext操作,就是根据全路径文件名,打开一个io流,读取里面配置,就是实现接口的实现类的全路径,返回一个包含全路径类名的String类型迭代器,迭代器存在值就返回true,并定义了nextName的值,方便nextService方法的调用。

next()

看next方法:

public S next() {
                return nextService();
        }
private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
                //拿到hasNextService找到的实现类路径
            String cn = nextName;
            //再清空,方便下次处理
            nextName = null;
            Class<?> c = null;
            try {
            //根据全路径反射获取Class
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found", x);
            }
            try {
            //调用newInstance创建实例
                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
        }

就是根据全路径类名反射创建实例,并返回。

总结

SPI一般用的还是少,有的组件库会用到这个。
另外大家可以关注下google推出的auto-service库,通过编译注解的方式帮你自动完成一些操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值