前言
作為 X-Library系列框架 的靈魂所在难述,XPage 開(kāi)源兩年以來(lái)题翻,一直致力于降低Fragment使用的難度蝶俱,努力實(shí)現(xiàn)一個(gè)Activity多Fragment的Android開(kāi)發(fā)模式帘腹。
就在前不久脚囊,我就整理了XPage開(kāi)源這幾年來(lái)的使用情況,寫(xiě)了一篇《史上最方便的Android頁(yè)面框架XPage使用指南》 ,并且還錄了幾期視頻單獨(dú)講解了XPage的使用 ,讓越來(lái)越多地人看到了XPage使用的便捷性静檬。
但就在前幾天,在交流群里突然有人問(wèn)我下面幾個(gè)問(wèn)題:
- 1.我如果想在多個(gè)module中使用XPage并级,我該怎么辦呀拂檩?
- 2.為什么我使用XPage之后,一直找不到
AppPageConfig
這個(gè)類俺氨獭稻励?
上面的問(wèn)題讓我突然認(rèn)識(shí)到一點(diǎn):并不是所有人都對(duì)APT技術(shù)有所了解的。
看來(lái)我之前參考ARouter
實(shí)現(xiàn)的自動(dòng)注冊(cè)功能可能并沒(méi)有完善愈涩,難怪ARouter
后來(lái)會(huì)寫(xiě)一個(gè)arouter-register
插件來(lái)實(shí)現(xiàn)自動(dòng)注冊(cè)的功能望抽。
于是乎,為了能夠讓XPage的自動(dòng)注冊(cè)功能更加完美履婉,我加班加點(diǎn)開(kāi)發(fā)煤篙,于是就有了XPage的3.1.1版本--徹底的自動(dòng)化注冊(cè) 。
升級(jí)后有什么變化
在感受全自動(dòng)化頁(yè)面注冊(cè)帶來(lái)的便利之前毁腿,讓我們先來(lái)感受一下之前版本的使用辑奈。
3.1.1之前版本
在3.1.1之前版本,在使用自動(dòng)注冊(cè)功能的時(shí)候已烤,還是需要實(shí)現(xiàn)PageConfiguration
接口鸠窗,并調(diào)用編譯時(shí)自動(dòng)生成的頁(yè)面配置類“moduleName”+PageConfig 的getPages()方法返回注冊(cè)的頁(yè)面。
PageConfig.getInstance()
.setPageConfiguration(new PageConfiguration() { //頁(yè)面注冊(cè)
@Override
public List<PageInfo> registerPages(Context context) {
//自動(dòng)注冊(cè)頁(yè)面,是編譯時(shí)自動(dòng)生成的胯究,build一下就出來(lái)了塌鸯。如果你還沒(méi)使用@Page的話,暫時(shí)是不會(huì)生成的唐片。
return AppPageConfig.getInstance().getPages(); //自動(dòng)注冊(cè)頁(yè)面
}
})
.debug("PageLog") //開(kāi)啟調(diào)試
.setContainActivityClazz(XPageActivity.class) //設(shè)置默認(rèn)的容器Activity
.enableWatcher(false) //設(shè)置是否開(kāi)啟內(nèi)存泄露監(jiān)測(cè)
.init(this);
可以看到丙猬,這里的自動(dòng)注冊(cè)還是需要一部分手動(dòng)配合才能完成的。如果說(shuō)你當(dāng)前只有一個(gè)module的話费韭,可能還好說(shuō)茧球。但是如果你使用了多個(gè)module之后,你就需要把多個(gè)module生成的配置類像上面那樣一個(gè)一個(gè)地加進(jìn)去星持,這樣用起來(lái)會(huì)讓人感覺(jué)非常的不方便抢埋,這明顯違背了我寫(xiě)XPage
框架的初衷!
不僅如此督暂,這樣寫(xiě)死還會(huì)帶來(lái)其他很多問(wèn)題:
- 1.如果module名變了揪垄,還需要對(duì)應(yīng)地去修改配置類的類名。
- 2.如果當(dāng)前module沒(méi)有使用
@Page
注解修飾Fragment的話逻翁,配置類也不會(huì)自動(dòng)生成饥努,這樣會(huì)讓很多初次使用者非常疑惑。 - 3.項(xiàng)目要是沒(méi)有編譯過(guò)的話八回,配置類是不會(huì)自動(dòng)生成的酷愧,這樣代碼就會(huì)報(bào)錯(cuò)說(shuō)類找不到,然后很多新手就懵逼了缠诅。
3.1.1之后版本
為了能夠解決以上的問(wèn)題溶浴,我實(shí)現(xiàn)了一個(gè)自動(dòng)注冊(cè)的配置類AutoPageConfiguration 。那么下面就看一下最新版本的XPage是如何注冊(cè)的吧:
PageConfig.getInstance()
.debug("PageLog") //開(kāi)啟調(diào)試
.setContainActivityClazz(XPageActivity.class) //設(shè)置默認(rèn)的容器Activity管引,按需設(shè)置(非必須)
.init(this); //初始化頁(yè)面配置
是的士败,你沒(méi)有看錯(cuò),這里沒(méi)有手動(dòng)實(shí)現(xiàn)PageConfiguration
接口的部分了褥伴,可以說(shuō)是真正實(shí)現(xiàn)了全自動(dòng)頁(yè)面注冊(cè)谅将,是不是非常方便呀?
如何實(shí)現(xiàn)注冊(cè)的自動(dòng)化
看到上面的變化噩翠,你是不是非常想知道我是如何實(shí)現(xiàn)徹底的自動(dòng)化注冊(cè)的戏自?
想要回答這個(gè)問(wèn)題,還是讓我們先看一看這個(gè)編譯時(shí)自動(dòng)生成的配置類是如何實(shí)現(xiàn)的伤锚。
APT技術(shù)實(shí)現(xiàn)頁(yè)面配置類的自動(dòng)生成
其實(shí)當(dāng)初實(shí)現(xiàn)頁(yè)面配置類的自動(dòng)生成的方案擅笔,也是我研讀了ARouter源碼之后,受到了APT技術(shù)的啟發(fā)后完成的屯援。
因?yàn)閄Page實(shí)現(xiàn)路由跳轉(zhuǎn)主要就是靠 PageInfo 和 Fragment 建立起來(lái)的映射關(guān)系猛们。當(dāng)時(shí)的思路就是采用APT技術(shù),利用@Page
注解去標(biāo)識(shí)需要注冊(cè)的Fragment狞洋,然后在編譯的時(shí)候通過(guò)APT技術(shù)去掃描出所有使用了@Page
注解標(biāo)識(shí)了的Fragment弯淘,將注解信息轉(zhuǎn)化為PageInfo
, 并按module生成對(duì)應(yīng)的頁(yè)面配置類,在這個(gè)配置類里面存放了該module下所有標(biāo)注了@Page
的頁(yè)面信息PageInfo
吉懊。
下面是自動(dòng)生成的一個(gè)簡(jiǎn)單的配置類例子:
可以看到庐橙,自動(dòng)生成的配置類都會(huì)存放在com.xuexiang.xpage.config
包下假勿,類名都是以PageConfig
作為結(jié)尾。注意這里非常關(guān)鍵态鳖,它是我后面實(shí)現(xiàn)自動(dòng)化注冊(cè)的關(guān)鍵转培。
詳細(xì)的實(shí)現(xiàn)細(xì)節(jié),可以參見(jiàn)XPage的頁(yè)面配置自動(dòng)生成器PageConfigProcessor的源碼 浆竭。
運(yùn)行時(shí)掃描指定包下的配置類反射實(shí)現(xiàn)自動(dòng)注冊(cè)
上面我們?cè)诹私忭?yè)面配置類是如何自動(dòng)生成的時(shí)候發(fā)現(xiàn)一個(gè)規(guī)律:
自動(dòng)生成的配置類都會(huì)存放在com.xuexiang.xpage.config
包下浸须,類名都是以PageConfig
作為結(jié)尾。
那么我們可不可以在運(yùn)行的時(shí)候邦泄,直接掃描com.xuexiang.xpage.config
包下的所有類删窒,然后找到以PageConfig
作為結(jié)尾的配置類,然后反射它的getPages
方法直接獲取到所有的配置信息顺囊,然后注冊(cè)進(jìn)去肌索?
下面是我根據(jù)上面的猜想,實(shí)現(xiàn)的AutoPageConfiguration類源碼:
public class AutoPageConfiguration implements PageConfiguration {
/**
* 頁(yè)面配置所在的包名
*/
private static final String PAGE_CONFIG_PACKAGE_NAME = "com.xuexiang.xpage.config";
/**
* 頁(yè)面配置生成類的類后綴名
*/
private static final String PAGE_CONFIG_CLASS_NAME_SUFFIX = "PageConfig";
@Override
public List<PageInfo> registerPages(Context context) {
List<PageInfo> pageInfos = new ArrayList<>();
Set<String> classSet = null;
try {
classSet = ClassUtils.getClassNames(context, PAGE_CONFIG_PACKAGE_NAME);
} catch (Exception e) {
e.printStackTrace();
}
if (classSet != null) {
for (String className : classSet) {
if (className != null && className.endsWith(PAGE_CONFIG_CLASS_NAME_SUFFIX)) {
try {
pageInfos.addAll(getPagesByClass(Class.forName(className)));
} catch (Exception e) {
PageLog.e(e);
}
}
}
}
return pageInfos;
}
private List<PageInfo> getPagesByClass(Class<?> clazz) throws Exception {
// 獲取單例對(duì)象
Method getInstanceMethod = clazz.getDeclaredMethod("getInstance");
getInstanceMethod.setAccessible(true);
Object instance = getInstanceMethod.invoke(null);
// 獲取頁(yè)面信息
Method getPagesMethod = clazz.getDeclaredMethod("getPages");
getPagesMethod.setAccessible(true);
return (List<PageInfo>) getPagesMethod.invoke(instance);
}
}
從源碼中我們可以看到包蓝,我是這樣做的:
- 1.使用
ClassUtils.getClassNames
獲取到com.xuexiang.xpage.config
包下的所有類的類名驶社。這里的ClassUtils
也是我借鑒了ARouter里面的源碼。 - 2.遍歷這個(gè)類名集合测萎,并根據(jù)類名結(jié)尾是否是
PageConfig
篩選出所有的配置類亡电。 - 3.調(diào)用
getPagesByClass
方法,反射獲取到配置類的所有頁(yè)面信息硅瞧,然后加入到頁(yè)面集合中份乒,最終返回所有module配置頁(yè)面的信息。
有了AutoPageConfiguration
之后腕唧,下面就非常簡(jiǎn)單啦或辖,我們只需要將mPageConfiguration默認(rèn)設(shè)置成AutoPageConfiguration
,這樣就可以實(shí)現(xiàn)自動(dòng)化注冊(cè)啦枣接!
/**
* 初始化頁(yè)面信息
*
* @param context 上下文
*/
private void initPages(Context context) {
if (mPageConfiguration == null) {
// 沒(méi)有設(shè)置的話颂暇,就使用自動(dòng)注冊(cè)配置
mPageConfiguration = new AutoPageConfiguration();
}
registerPageInfos(mPageConfiguration.registerPages(context));
CoreConfig.init(context, getPages());
}
增加混淆配置
你以為到這兒就結(jié)束了?沒(méi)那么簡(jiǎn)單但惶!可以發(fā)現(xiàn)耳鸯,上面的實(shí)現(xiàn)方案主要是依賴于掃描類并進(jìn)行反射注冊(cè)的。所以如果代碼做了混淆了之后膀曾,該方案就會(huì)失效了县爬,所以我們還需要在混淆配置清單中增加如下的配置來(lái)避免混淆:
-keep class com.xuexiang.xpage.config.** { *; }
我們要保證com.xuexiang.xpage.config
包下的類不能被混淆。
到這兒添谊,自動(dòng)注冊(cè)的實(shí)現(xiàn)算是真正的講完了财喳,下面讓我們來(lái)瞧瞧XPage的新版本還有那些地方更新了!
其他更新
去除LeakCanary依賴
在此之前,XPage一直依賴了LeakCanary
耳高,主要原因還是LeakCanary
在2.0版本之前的使用還是相當(dāng)不方便的扎瓶,于是我就做了一下默認(rèn)集成以方便使用。
但是當(dāng)LeakCanary
升級(jí)到2.0以上版本的時(shí)候祝高,這個(gè)問(wèn)題似乎就沒(méi)了栗弟。因?yàn)檫M(jìn)行了重新的設(shè)計(jì),LeakCanary
的使用變得沒(méi)那么具有侵入性工闺,因此我就考慮去除了LeakCanary
的依賴。
優(yōu)化了頁(yè)面點(diǎn)擊的鍵盤處理
之前在XPageActivity里面做了簡(jiǎn)單的頁(yè)面點(diǎn)擊處理:當(dāng)用戶點(diǎn)擊到非輸入框區(qū)域就隱藏鍵盤瓣蛀。
但是這樣做了之后發(fā)現(xiàn)效果并不是很好陆蟆,因?yàn)橛行╉?yè)面可能并不需要這個(gè)功能,如果把這個(gè)寫(xiě)到Activity里面的話惋增,那么在這個(gè)Activity下的所有Fragment都將擁有這個(gè)功能叠殷,這樣非常不靈活。
除此之外诈皿,使用者可能也想自定義屏幕的觸摸事件林束,因此我對(duì)此做了重新設(shè)計(jì),將觸摸事件的處理下放至每個(gè)Fragment之中稽亏,由Activity調(diào)用指定的方法進(jìn)行處理壶冒。
相關(guān)鏈接
- 史上最方便的Android頁(yè)面框架XPage使用指南
- Navigation和XPage框架相比誰(shuí)更香
- XPage項(xiàng)目地址:https://github.com/xuexiangjys/XPage
最后
非常感謝大家對(duì)XPage 的支持,喜歡的小伙伴可以到項(xiàng)目的Github主頁(yè):https://github.com/xuexiangjys/XPage 點(diǎn)擊star支持一下哦截歉!
更多資訊內(nèi)容胖腾,歡迎微信搜索公眾號(hào):「我的Android開(kāi)源之旅」