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车就开到这,小伙伴们可以下车喽,不要忘记点个赞哦!

谢谢!

欢迎关注公众号:JueCode

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,577评论 25 707
  • 怎么权衡影响力,有人总结出了一个公式: 影响力=你的新媒体资产+你的权威背书。 新媒体资产是你在某个具有社交属性的...
    周愚阅读 955评论 0 1
  • 我们平时买东西,可能真的很少去想这件商品的成本。只是在于自己喜不喜欢,自己认为这件商品值不值得我付费,即我们是给自...
    念美美阅读 439评论 0 3
  • 今天听得课是轻松父母养成术的第二讲--父母如何说孩子才肯听 这节课从三个方面阐述这个问题:1、看见自己;2、表达自...
    萌奶糖阅读 114评论 0 0