本来这期应该分享IoC思想和ARouter的自动注入这块内容,但是在自动注入这块涉及到服务的主动注入,而我们前面只说到Activity的发现,所以还是决定先做个服务和Fragment实例发现的分享。这也是ARouter的分享系列的第四篇,前面三篇分别是:
ARouter解析一:基本使用及页面注册源码解析
ARouter解析二:页面跳转源码分析
ARouter解析三:URL跳转本地页面源码分析
服务和Fragment的发现和Activity的发现会有很多重叠的地方,我们不会再重复说,建议小伙伴们在开始之前先看下解析系列的第二篇,再来看这篇会轻松很多。今天我们这次分享分成三部分。
1.服务的发现
2.Fragment的发现
3.服务发现的源码分析
4.Fragment实例获取的源码分析
Demo还是惯例使用官方的,我们看下效果图,点击BYNAME调用服务或者BYTYPE调用服务
点击获取FRAGMENT实例可以获取BlackFragment实例。
这期涉及到的内容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
。
上面的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
}
}
这里还是从仓库中去找服务,一开始肯定是没有因为还没有加载。
我们之前分享提到过ARouter是分组管理的,按需加载。所以这里到节点的map中找到service的分组,然后加载这个分组。
加载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