美文网首页Android技术知识Android开发Android开发经验谈
android自定义serviceloader接口隔离及获取自定

android自定义serviceloader接口隔离及获取自定

作者: 君莫醉 | 来源:发表于2018-04-16 17:50 被阅读142次
    1.简述:

      之前看过大神的美团组件化方案,其中提到了通过servicelaoder进行解耦的思路,主要是通过配置接口及其实现类的方式坐到接口隔离作用,本文主要是实现此思路并延伸出通过加载自定义properties文件获取参数配置信息

    2.系统ServiceLoader简介

      通过查看ServiceLoader源码可知,ServiceLoader是通过加载META-INF/services/路径下的接口实现类,加载方式是通过读取配置文件并通过反射的方式获取类的实例
    1.配置文件读取,获取文件流

           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;
                    }
                    //通过流获取实现类的class全路径String集合
                    pending = parse(service, configs.nextElement());
                }
                nextName = pending.next();
                return true;
            }
    

    其中PREFIX = "META-INF/services/";
    由此可见加载路径是META-INF文件夹下面的文件

    2.通过流获取实现类全路径

    private Iterator<String> parse(Class<?> service, URL u)
                throws ServiceConfigurationError
        {
            InputStream in = null;
            BufferedReader r = null;
            //存储获取的类全路径
            ArrayList<String> names = new ArrayList<>();
            try {
                //通过URL获取流
                in = u.openStream();
                r = new BufferedReader(new InputStreamReader(in, "utf-8"));
                int lc = 1;
                //通过流逐行读取文件并存入names
                while ((lc = parseLine(service, u, r, lc, names)) >= 0);
            } catch (IOException x) {
               ...
            } finally {
                ...
            }
            return names.iterator();
        }
    

    其中parseLine方法里面是做了类全路径名校验

    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                              List<String> names)
                throws IOException, ServiceConfigurationError
        {
            ...
            if (n != 0) {
                //判断是否有空格
                if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                    fail(service, u, lc, "Illegal configuration-file syntax");
                int cp = ln.codePointAt(0);
                //确定是否允许将字符(Unicode 代码点)作为 Java 标识符中的首字符
                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);
                    //确定字符(Unicode 代码点)是否可以是 Java 标识符中首字符以外的部分
                    if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                        fail(service, u, lc, "Illegal provider-class name: " + ln);
                }
                ...
            }
            return lc + 1;
        }
    
    3.自定义ServiceLoader

    思路:
      1.读取配置文件
      2.获取配置的类全名
      3.通过反射获取类的实例

      我们的配置文件将写在assets文件夹下


    image.png

      通过查看apk包结构可以发现assets文件夹位置是与META-INF平级的,由此我们可以将系统的ServiceLoader加载文件路径改为assets路径

    1.配置文件读取,获取文件流

    class Load {
    
        private ClassLoader loader;
        private Enumeration<URL> configs = null;
    
        Load() {
            //初始化加载器
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            if (null != cl) {
                loader = cl;
            } else {
                loader = ClassLoader.getSystemClassLoader();
            }
        }
        //获取URL
        URL initLoad(String location) {
            if (configs == null) {
                try {
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(location);
                    else
                        configs = loader.getResources(location);
                } catch (IOException x) {
                    x.printStackTrace();
                }
            }
            return configs.nextElement();
        }
    
        ClassLoader getLoader() {
            return loader;
        }
    }
    

    2.通过流获取接口类与实现类的对应集合
      由于接口类与实现类是一对一关系,所以通过Map以键值对的方式存储接口类与实现类,在系统ServiceLoader做简单修改:

    private static Map<String,String> parse(URL u)
                throws ServiceConfigurationError
        {
           ...
            Map<String,String> names = new HashMap<>();
            try {
              ...
                while ((lc = parseLine(r, lc, names)) >= 0);
            } catch (IOException x) {
    //            fail(service, "Error reading configuration file", x);
            } finally {
               ...
            }
            return names;
        }
    
     private static int parseLine(BufferedReader r, int lc,
                                     Map<String,String> names)
                throws IOException, ServiceConfigurationError
        {
            ...
            if(lns.length == 2){
                if(!isJavaIdentifier(lns[0]) || !isJavaIdentifier(lns[1])){
                    return -1;
                }
                names.put(lns[0],lns[1]);
            }else {
                return -1;
            }
            return lc + 1;
        }
    

    3.获取实现类
      在上一步已经获取了所有接口类和实现类的集合,在此通过接口类全名来获取实现类全名,并通过反射的方式获取实现类实例:

    <T> T load(Context context,Class<T> server){
    
                String cn = pending.get(server.getName());
                Class<?> c = null;
                try {
                    c = Class.forName(cn, false, dzmLoad.getLoader());
                } catch (ClassNotFoundException x) {
                   ...
                }
                ...
                try {
                    T p = server.cast(c.newInstance());
                    servers.put(server.getName(),p);
                    return p;
                } catch (Throwable x) {
                    ...
                }
                throw new Error();          // This cannot happen
            }
    

    到此我们自定义ServiceLoader已经初步实现,在实际开发中,我们一般只需要一个实例及单利,在此我们可以用Map将类的实例与接口类名绑定起来即可。

    配置文件 image.png

    使用

    MyServicesLoader.getService(TestService.class).test()
    
    4.延伸---加载properties配置参数

      加载properties配置参数的思路与ServiceLoader基本一致,只是获取配置参数可以通过java类Properties获取
    1.获取流
      和自定义ServiceLoader获取流一致

    2.获取Properties实例

    private Properties loadProperties(String... resourcesPaths) {
            Properties props = new Properties();
    
            for (String location : resourcesPaths) {
                InputStream is = null;
                try {
                    //获取流
                    URL url = new Load().initLoad(location);
                    URLConnection con = url.openConnection();
                    is = con.getInputStream();
                    //加载流配置
                    props.load(is);
                } catch (IOException ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        if(null != is)
                            is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return props;
        }
    

    3.获取value

        private String getValue(String key) {
            String systemProperty = System.getProperty(key);
            if (systemProperty != null) {
                return systemProperty;
            }
            if (properties.containsKey(key)) {
                return properties.getProperty(key);
            }
            return "";
        }
    

    4.使用

    PropertiesLoader propertiesLoader = new PropertiesLoader("assets/services/data.properties");
    propertiesLoader.getProperty("ip")
    
    配置:
    image.png
    image.png
    结果
    image.png
    image.png
    注:

    1.在查看Iterable 接口时无意中发现了default关键字,经查看资料显示为java8新加的,用于在接口中写默认的方法函数体

    有兴趣的可以去https://github.com/dengzhi00/deployloader看看

    相关文章

      网友评论

        本文标题:android自定义serviceloader接口隔离及获取自定

        本文链接:https://www.haomeiwen.com/subject/hadekftx.html