android自定义serviceloader接口隔离及获取自定义properties参数配置

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 parse(Class service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; //存储获取的类全路径 ArrayList 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 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文件夹下

android自定义serviceloader接口隔离及获取自定义properties参数配置
文章图片
image.png ??通过查看apk包结构可以发现assets文件夹位置是与META-INF平级的,由此我们可以将系统的ServiceLoader加载文件路径改为assets路径
1.配置文件读取,获取文件流
class Load {private ClassLoader loader; private Enumeration 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 parse(URL u) throws ServiceConfigurationError { ... Map 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 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 load(Context context,Class 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将类的实例与接口类名绑定起来即可。


配置文件 android自定义serviceloader接口隔离及获取自定义properties参数配置
文章图片
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")

配置: android自定义serviceloader接口隔离及获取自定义properties参数配置
文章图片
image.png
android自定义serviceloader接口隔离及获取自定义properties参数配置
文章图片
image.png 结果 android自定义serviceloader接口隔离及获取自定义properties参数配置
文章图片
image.png
android自定义serviceloader接口隔离及获取自定义properties参数配置
文章图片
image.png 注: 1.在查看Iterable 接口时无意中发现了default关键字,经查看资料显示为java8新加的,用于在接口中写默认的方法函数体
【android自定义serviceloader接口隔离及获取自定义properties参数配置】有兴趣的可以去https://github.com/dengzhi00/deployloader看看

    推荐阅读