ARouter解析四:发现服务和Fragment

本来这期应该分享IoC思想和ARouter的自动注入这块内容,但是在自动注入这块涉及到服务的主动注入,而我们前面只说到Activity的发现,所以还是决定先做个服务和Fragment实例发现的分享。这也是ARouter的分享系列的第四篇,前面三篇分别是:
ARouter解析一:基本使用及页面注册源码解析
ARouter解析二:页面跳转源码分析
ARouter解析三:URL跳转本地页面源码分析

服务和Fragment的发现和Activity的发现会有很多重叠的地方,我们不会再重复说,建议小伙伴们在开始之前先看下解析系列的第二篇,再来看这篇会轻松很多。今天我们这次分享分成三部分。

1.服务的发现

2.Fragment的发现

3.服务发现的源码分析

4.Fragment实例获取的源码分析

Demo还是惯例使用官方的,我们看下效果图,点击BYNAME调用服务或者BYTYPE调用服务

服务.png

点击获取FRAGMENT实例可以获取BlackFragment实例。

Fragment.png

这期涉及到的内容Demo没有什么可观赏的,内容比较有意思。好了,开始进入正题~~~

1.发现服务

这里说到的服务不是Android四大组件中的Service,这里的服务是服务端开发的概念,就是将一部分功能和组件封装起来成为接口,以接口的形式对外提供能力,所以在这部分就可以将每个功能作为一个服务,而服务的实现就是具体的业务功能

ARouter发现服务主要有两种方式,ByName和ByType。先看看这两种方式分别是怎么使用。ByName就是需要传递path路径来进行发现,ByType就是通过服务class来进行查找。

case R.id.navByName:
    ((HelloService) ARouter.getInstance().build("/service/hello").navigation()).sayHello("mike");
    break;
case R.id.navByType:
    ARouter.getInstance().navigation(HelloService.class).sayHello("mike");
    break;

那么为什么需要区分两种类型?因为在Java中接口是可以有多个实现的,通过ByType的方式可能难以拿到想要的多种实现,这时候就可以通过ByName的方式获取真实想要的服务。所以其实大多数情况是通过ByType的,如果有多实现的时候就需要使用ByName。

服务发现的使用就是以上。

2.发现Fragment

Fragment获取实例的使用也是很简单,一行代码搞定,和跳转的写法很基本就是一样的。

Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();

Fragment发现的使用就是以上。

3.发现服务的源码分析

接下来我们分析下发现服务的逻辑过程,小伙伴们有没有疑问,在上面服务的使用时,HelloService.class是一个接口,怎么去获取到他的实现类的?

public interface HelloService extends IProvider {
    void sayHello(String name);
}

ByName的发现比较简单,很Activity的跳转很像。我们先来看看ByType的发现过程。

ARouter.getInstance().navigation(HelloService.class)

先跟到_ARouter的navigation(Class<? extends T> service)中。

protected <T> T navigation(Class<? extends T> service) {
        try {
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());

            // Compatible 1.0.5 compiler sdk.
            if (null == postcard) { // No service, or this service in old version.
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }

            LogisticsCenter.completion(postcard);
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
}

上面代码主要就是两步,第一构造postcard,第二LogisticsCenter跳转

1.构造postcard

首先还是熟悉的先构造postcard,只不过这里时直接在navigation中进行构造,之前activity或者url跳转都是通过build构造,意思都差不多。我们进去LogisticsCenter.buildProvider看看。看到LogisticsCenter就可以猜到这里逻辑是要和APT技术生成的Java类打交道,可以参考ARouter解析一:基本使用及页面注册源码解析

public static Postcard buildProvider(String serviceName) {
        RouteMeta meta = Warehouse.providersIndex.get(serviceName);

        if (null == meta) {
            return null;
        } else {
            return new Postcard(meta.getPath(), meta.getGroup());
        }
}

我们在源码中打个断点看看meta是什么东东。可以看到meta中有个destination属性可以拿到具体的实现类HelloServiceImpl

发现服务.png

上面的meta是直接从仓库Warehouse中获取服务,那么仓库的providersIndex 是从哪来的?其实就是在获取ARouter实例的时候加载进来的,在LogisticsCenter .init(),这里为了方便分析,对代码做了手脚。

for (String className : classFileNames) {
       if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // Load providerIndex
             ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
       }
}

Java文件名就是ARouter$$Providers$$app,这个类就是在编译器使用APT技术自动生成的。有木有很激动???其实就是一个map,其中就有我们上面使用到的HelloService,对应的注册类就是HelloServiceImpl .class

public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));
  }
}

所以我们在仓库中就可以根据Name-HelloService发现服务的具体实现。之后就很好理解了,可以从meta中拿到path-/service/hello,group-service构造postcard。

2.LogisticsCenter跳转

拿到postcard之后就是跳转到目标服务了,这个和activity跳转是一样的。我们到老朋友LogisticsCenter.completion(postcard)中看下。

public synchronized static void completion(Postcard postcard) {
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }

                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }

                completion(postcard);   // Reload
            }
}

这里还是从仓库中去找服务,一开始肯定是没有因为还没有加载。

仓库发现服务.png

我们之前分享提到过ARouter是分组管理的,按需加载。所以这里到节点的map中找到service的分组,然后加载这个分组。

 

service分组.png

HelloService.png

加载service分组后,就可以拿到我们需要的HelloService了。

public class ARouter$$Group$$service implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/service/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    atlas.put("/service/json", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
    atlas.put("/service/single", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));
  }
}

看到这里小伙伴们有没有感到疑惑?其实也算是框架的一个需要改进的地方。前面在provider中已经拿到HelloService具体实现类的路径,这里又加载service分组,然后再找到具体实现类,做了反复没必要的工作了。不过这并不影响框架的牛逼性哈。

再接下来其实就是通过反射构造HelloService实例。

switch (routeMeta.getType()) {
    case PROVIDER:  // if the route is provider, should find its instance
            // Its provider, so it must be implememt IProvider
            Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
            IProvider instance = Warehouse.providers.get(providerMeta);
            if (null == instance) { // There's no instance of this provider
                IProvider provider;
                try {
                    provider = providerMeta.getConstructor().newInstance();
                    provider.init(mContext);
                    Warehouse.providers.put(providerMeta, provider);
                    instance = provider;
                } catch (Exception e) {
                    throw new HandlerException("Init provider failed! " + e.getMessage());
                }
            }
            postcard.setProvider(instance);
            postcard.greenChannel();    // Provider should skip all of interceptors
            break;
}

将服务实例设置给postcard,之后可以在navigation中通过postcard的方法getProvider()得到。
发现服务的源码就是以上,和activity的发现差别就是服务需要通过反射构造实例返回。

4.发现Fragment的源码分析

接着看下发现Fragment的源码,使用上和activity是一样的。build也是构造postcard实例。

ARouter.getInstance().build("/test/fragment")

我们看下build的操作,也activity也是一致的,代码比较简单,这里就不多做解释。

protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return build(path, extractGroup(path));
        }
}

有了postcard接着就是到LogisticsCenter.completion(postcard)中进行具体的跳转了。首先还是找到映射关系的类,可以看到倒数第二个就是我们需要的fragment,接着就是加载这个节点,将信息补充到postcard中。

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test",
            new java.util.HashMap<String, Integer>(){{put("pac", 9); put("obj", 10); put("name", 8); put("boy", 0);
              put("age", 3); put("url", 8); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2",
            "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

有了完整信息的postcard就可以拿到fragment了,跳转逻辑在_ARouter的_navigation中,也是通过反射拿到Fragment的实例,注意Fragment实例中需要有默认的构造函数。通过setArguments给fragment参数。

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        switch (postcard.getType()) {
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
}

从上面也可以看出,目前ARouter还没实现另外三个组件 broadcast,content provider,service的路由功能,期待后续更新哈。

发现Fragment的源码就是以上。

5.总结

在activity跳转的基础上我们今天分享了service(这里不是指四大组件的Service,和后端开发的接口有点类似),fragment路由的使用和源码分析,大部分逻辑是类似的,主要区别就是这里需要通过反射拿到实例,activity则是拿到路径后进行跳转。再有就是前面加载service时会有反复加载的过程,这应该是没有必要的。

今天的发现服务和Fragment车就开到这,小伙伴们可以下车喽,不要忘记点个赞哦!

谢谢!



作者:juexingzhe
链接:https://www.jianshu.com/p/d4ef1f60bd84
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载于:https://my.oschina.net/JiangTun/blog/1612720

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值