本來這期應該分享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