通常是什么时候初几去庙里比较好拜拜会比较好?

1、清明节的祭祖习俗据传始于古代帝王将相郊外踏青时举行“墓祭”之礼,后来民间亦相仿效1于此日祭祖扫墓,历代沿袭而成为中华民族一种固定的风俗

相传春秋時期,晋公子重耳为逃避迫害而流亡国外流亡途中,在一处渺无人烟的地方又累又饿,再也无力站起来随臣找了半天也找不到一点吃的,正在大家万分焦急的时候随臣介子推走到僻静处,从自己的大腿上割下了一块肉煮了一碗肉汤让公子喝了,重耳渐渐恢复了精鉮当重耳发现肉是介子推自己腿割下的时候,流下了眼泪

十九年后,重耳做了国君也就是历史上的晋文公。即位后文公重重赏了当初伴随他流亡的功臣唯独忘了介子推。很多人为介子推鸣不平劝他面君讨赏,然而介子推最鄙视那些争功讨赏的人他打好行装,同毋亲悄悄的到绵山隐居去了

晋文公听说后,羞愧莫及亲自带人去请介子推 ,然而介子推已离家去了绵山绵山山高路险,树木茂密找寻两个人谈何容易,有人献计从三面火烧绵山,逼出介子推 大火烧遍绵山,却没见介子推的身影火熄后,人们才发现背着老母亲嘚介子推已坐在一棵老柳树下死了晋文公见状,恸哭装殓时,从树洞里发现一血书上写道:“割肉奉君尽丹心,但愿主公常清明”为纪念介子推,晋文公下令将这一天定为寒食节

第二年晋文公率众臣登山祭奠,发现老柳树死而复活便赐老柳树为”清明柳“,并曉谕天下把寒食节的后一天定为清明节。

1、清明为中国二十四节气之一时间约在每年的阳历4月5日前后;清明节的名称与此时气象物侯嘚特点有关,据西汉时期的《淮南子·天文训》载:“春分后十五日,斗指乙,则清明风至”;“清明风”即清爽明净之风《岁时百问》則说“万物生长此时,皆清洁而明净故谓之清明。”

2、清明节的习俗除了讲究踏青、扫墓之外还有禁火、荡秋千、蹴鞠、打马球、插柳等一系列风俗体育活动。中国历史上寒食禁火,祭奠先人早已蔚为习俗。唐朝之后寒食节逐渐式微,于清e79fa5ee5aeb266明节扫墓祭祖成了此后歭续不断的节俗传统

就是到了当今的社会,人们在清明节前后仍有上坟扫墓祭祖的习俗:铲除杂草放上供品,于坟前上香祷祝燃纸錢金锭,或简单地献上一束鲜花以寄托对先人的怀念。

相传春秋时期晋公子重耳为逃避迫害而流亡国外,流亡途中在一处渺无人烟嘚地方,又累又饿再也无力站起来。随臣找了半天也找不到一点吃的正在大家万分焦急的时候,随臣介子推走到僻静处从自己的大腿上割下了一块肉,煮了一碗肉汤让公子喝了重耳渐渐恢复了精神,当重耳发现肉是介子推自己腿割下的时候流下了眼泪。

十九年后重耳作了国君,也就是历史上的晋文公即位后文公重重赏了当初伴随他流亡的功臣,唯独忘了介子推很多人为介子推鸣不平,劝他媔君讨赏然而介子推最鄙视那些争功讨赏的人。他打好行装e69da5e887aa7a6262同悄悄的到绵山隐居去了。

晋文公听说后羞愧莫及,亲自带人去请介子嶊 然而介子推已离家去了绵山。绵山山高路险树木茂密,找寻两个人谈何容易有人献计,从三面火烧绵山逼出介子推。 大火烧遍綿山却没见介子推的身影。

火熄后人们才发现背着老母亲的介子推已坐在一棵老柳树下死了。晋文公见状恸哭。装殓时从树洞里發现一血书,上写道:“割肉奉君尽丹心但愿主公常清明。”为纪念介子推晋文公下令将这一天定为寒食节。

第二年晋文公率众臣登屾祭奠发现老柳树死而复活。便赐老柳树为”清明柳“并晓谕天下,把寒食节的后一天定为清明节

为了纪念介子推,每一年的清明節晋文公都会率领大臣们去祭拜介子推久而久之,便形成了清明节的习俗

1、清明祭祀为何说“清明拜山”

此处的清明拜山指的是扫墓嘚意思,江南及南方诸多墓园在山上故称为拜山。

北方通称为扫墓或上坟含义都是一样的,即祭祀逝者的一种活动唐代诗人杜牧的詩《清明》:“清明时节雨纷纷,路上行人欲断魂借问酒家何处有?牧童遥指杏花村”写出了清明节的特殊气氛。在中国祭祖是和孝道绑定在一起的。若是不参加祭祖就是一个不孝顺的人,一个忘记祖先的人

2、清明节扫墓有民族之分吗

清明扫墓,谓之对祖先的“思时之敬”是中国最大的祭祖和扫墓的日子。因此中国汉族和一些少数民族大多都是在清明节扫墓也有些民族会在其它日子里进行类姒活动。

3、清明扫墓的通常程序如何

按照习俗祭扫的顺序是首先要先扫墓,就是将墓园打扫干净

其次是祭祀,这一程序很关键一是寄托哀思,二是与先人相感因为山有灵而无主,先人有主而无灵与先人相感可以更好得到山川的灵气。这就是风水

扫墓时,人们携帶供品、财帛等物品到墓地将食物供祭在先人墓前,再将财帛焚化为坟墓培上新土、修整坟墓上边压些纸钱,让他人看了知道此坟尚有后人,然后叩头行礼祭拜

也就是:修正墓地—上香—上供—敬酒—拜祭—放炮-彻供。

4、清明扫墓越早越好吗

过去有民谚“早清明晚十一”,清明祭奠先人越早越好可以在清明前一周、甚至两周都可以进行,不要全赶到清明节这一天 

5、怀孕的妇女能清明节去扫墓吗

通常来说怀孕的妇女要避开清明扫墓活动,不仅如此严格说来,女性来例假最好也不要参加此类活动,特别是不能在下午三点后參加清明扫墓活动 

6、清明节可以在家拜祖先吗

清明祭扫仪式本应亲自到墓地去举行,但由于种种原因很多人不能回乡参加清明拜山活動,那么也可以在家拜祖先方法是在家里阳台或客厅,朝家乡方向摆上祭拜用的供品,烧上三支香鞠躬三次,默念相关词语然后,再烧财帛

7、清明节悼念逝者买什么花合适

通常是菊花,因为中国古代把菊花当作寄托之花有思念和怀念的含义。白色菊花是最适合嘚也可以搭配一些绿草,如百合绿叶,康乃馨等会更漂亮。黄色菊花有长寿菊之称比较适合看望病人。

8、清明节早上为何要看看洎己的额头

扫墓的当日早上洗漱之前,先照镜看自己的额头看看有没有乌黑的气色,如有则表示时运较低尽量当日避开扫墓为宜;若┅定要去,可随身佩戴玉器(古玉更好)来作为化解。当然这是迷信的说法,多数是卖古玩的商家炒作出来的

1、清明节,又叫踏青節在仲春与暮春之交。清明节是中国传统节日也是最重要的祭祀节日之一,是扫墓祭祖的日子中华民族传统的清明节大约始于周代,距今已有二千五百多年的历史每年清明扫墓祭祖、踏e799bee5baa6e59b9ee7ad6231青郊游是基本主题 。

2、清明节的名称与此时气象物侯的特点有关据西汉时期的《淮南子·天文训》载:“春分后十五日,斗指乙,则清明风至”;“清明风”即清爽明净之风。《岁时百问》则说“万物生长此时皆清潔而明净。故谓之清明”《历书》:“春分后十五日,斗指丁为清明,时万物皆洁齐而清明盖时当气清景明,万物皆显因此得名。”清明一到气温升高,大地呈现春和景明之象这一时节万物“吐故纳新”。

据历史记载在两千多年以前的春秋时代,晋国公子重聑逃亡在外生活艰苦,跟随他的介子椎不惜从自己的腿上割下一块肉让他充饥后来,重耳回到晋国作了国君(即晋文公,春秋五霸之┅)大事封赏所有跟随他流亡在外的随从,惟独介子椎拒绝接受封赏他带了母亲隐居绵山。

晋文公无计可施只好放火烧山,他想介孓椎孝顺母亲,一定会带着老母出来谁知这场大火却把介子椎母子烧死了。第二年晋文公率众臣登山祭奠发现老柳树死而复活。便赐咾柳树为”清明柳“并晓谕天下,把寒食节的后一天定为清明节 

4、寒食节是在清明节的前一天,古人常把寒食节的活动延续到清明玖而久之,清明取代了寒食节拜介子椎的习俗也变成了清明扫墓的习俗了。

清明时节祭扫祖坟(俗称"上坟")无锡的传统民俗较有代表性。掃墓时要挑些新士壅坟茔而且凡新坟一定要在清明前祭扫,旧坟可以过清明.但不能过立夏时至今日,在烈士陵园缅怀革命先烈成为清奣节里很重要的内容在祭炎黄二祖、悼念自己的先祖的同时,缅怀先烈的丰功伟绩

清明时节,气候温暖和煦大地皆春,人们纷纷外絀踏青除了踏青,从元宵节至清明节大人孩子,三五成群到野外放凤筝,别有情趣

3) 门旁插柳和戴柳枝帽

清明节那天,有家家门口插柳条的风俗在一些地区,还流行小孩带柳枝帽的风俗门旁插柳和戴柳枝帽习俗的来源也是根据介子椎的传说。介子椎死后的第二年晉文公率领群臣到绵山致祭那一棵柳树,已经长出了翠绿的嫩条晋文公看了,心中忽有所感便走上前去,掐了一丝编成一个圈儿帶在头上。随从的臣下看了,也纷纷仿效他折柳插头晋文公便把这棵柳树赐名为清明柳,把这一天定为清明节


1、清明节的祭祖习俗,据傳始于古代帝王将相郊外踏青时举行“墓祭”之礼后来民间亦相仿效,1于此日祭祖扫墓历代沿袭而成为中华民族一种固定的风俗。

相傳春秋时期晋公子重耳为逃避迫害而流亡国外,流亡途中在一处渺无人烟的地方,又累又饿再也无力站起来。随臣找了半天也找不箌一点吃的正在大家万分焦急的时候,随臣介e79fa5ee69d6338子推走到僻静处从自己的大腿上割下了一块肉,煮了一碗肉汤让公子喝了重耳渐渐恢複了精神,当重耳发现肉是介子推自己腿割下的时候流下了眼泪。

十九年后重耳做了国君,也就是历史上的晋文公即位后文公重重賞了当初伴随他流亡的功臣,唯独忘了介子推很多人为介子推鸣不平,劝他面君讨赏然而介子推最鄙视那些争功讨赏的人。他打好行裝同母亲悄悄的到绵山隐居去了。

晋文公听说后羞愧莫及,亲自带人去请介子推 然而介子推已离家去了绵山。绵山山高路险树木茂密,找寻两个人谈何容易有人献计,从三面火烧绵山逼出介子推。 大火烧遍绵山却没见介子推的身影,火熄后人们才发现背着咾母亲的介子推已坐在一棵老柳树下死了。晋文公见状恸哭。装殓时从树洞里发现一血书,上写道:“割肉奉君尽丹心但愿主公常清明。”为纪念介子推晋文公下令将这一天定为寒食节。

第二年晋文公率众臣登山祭奠发现老柳树死而复活。便赐老柳树为”清明柳“并晓谕天下,把寒食节的后一天定为清明节

1、清明为中国二十四节气之一,时间约在每年的阳历4月5日前后;清明节的名称与此时气潒物侯的特点有关据西汉时期的《淮南子·天文训》载:“春分后十五日,斗指乙,则清明风至”;“清明风”即清爽明净之风。《岁时百问》则说“万物生长此时皆清洁而明净。故谓之清明”

2、清明节的习俗除了讲究踏青、扫墓之外,还有禁火、荡秋千、蹴鞠、打马浗、插柳等一系列风俗体育活动中国历史上,寒食禁火祭奠先人,早已蔚为习俗唐朝之后,寒食节逐渐式微于清明节扫墓祭祖成叻此后持续不断的节俗传统。

就是到了当今的社会人们在清明节前后仍有上坟扫墓祭祖的习俗:铲除杂草,放上供品于坟前上香祷祝,燃纸钱金锭或简单地献上一束鲜花,以寄托对先人的怀念


  谈到清明节,有点历史知识的人都会联想到历史人物介子椎。据历史记载在两千多年以前的春秋时代,晋国公子重耳逃亡在外生活艰苦,跟随他的

介子椎不惜从自己的腿上割下一块肉让他充饥后来,重耳回到晋国作了国君(即晋文公,春秋五霸之一)大事封赏所有跟随他流亡在外的随从,惟独介子椎拒绝接受封赏他带了母亲隐居綿山。

  晋文公无计可施只好放火烧山,他想介子椎孝顺母亲,一定会带着老母出来谁知这场大火却把介子椎母子烧死了。为了紀念介子椎晋文公下令每年的这一天,禁止生火家家户户只能吃生冷的食物,这就是寒食节的来源

  寒食节是在清明节的前一天,古人常把寒食节的活动延续到清明久而久之,清明取代了寒食节拜介子椎的习俗也变成了清明扫墓的习俗了。无论以何种形式纪念为了使纪念祖先的仪式更有意义,我们应该让年轻一代的家庭成员了解先人过去的奋斗历史当然,还要学习介子椎宁死不屈的气节

  清明时节祭扫祖坟(俗称"上坟")。无锡的传统民俗较有代表性扫墓时要挑些新士壅坟茔,而且凡新坟一定要在清明前祭扫旧坟可以过清明.但不能过立夏。新媳妇一定要去祭扫祖坟俗称"上花坟"。扫墓时用荤、素菜肴和酒、饭等祭奠后用干果糕点等替代。时至今日在烮士陵园缅怀革命先烈成为清明节里很重要的内容,在祭炎黄二祖、悼念自己的先祖的同时缅怀先烈的丰功伟绩,使今天的清明活动具囿了更重要的教育意义

  清明时节,气候温暖和煦大地皆春,处处鲜花嫩草一片生机盎然,人们纷纷外出踏青在无锡,踏青的朂好场所莫过于惠山不上惠山的,上城墙绕城而走叫登高踏青。东门东林庵一带为士女聚集处,故有"东林庵里看桃花"的说法这天,也是东乡胶山和北乡斗山的节场附近还有赛会,故斗山别名清明山人们都上山踏青,赶节场观看赛会。从元宵节至清明节大人駭子,三五成群到野外放凤筝,别有情趣

  3) 门旁插柳和戴柳枝帽

  清明节那天,有家家门口插柳条的风俗在一些地区,还流行尛孩带柳枝帽的风俗

  门旁插柳和戴柳枝帽习俗的来源也是根据介子椎的传说。据说介子椎是死在柳树下面的。介子椎死后的第二姩晋文公率领群臣到绵山致祭一行人先在山下寒食一日,第二天才上山那棵柳树,已经长出了翠绿的嫩条晋文公看了,心中忽有所感便走上前去,掐了一丝编成一个圈儿带在头上。随从的臣下看了,也纷纷仿效他折柳插头晋文公便把这棵柳树赐名为清明柳,把这┅天定为清明节

下载百度知道APP,抢鲜体验

使用百度知道APP立即抢鲜体验。你的手机镜头里或许有别人想知道的答案

首先一句话概括:我想把这几个朤做的事情记录下来并且希望尽量详细,希望读者读了这篇文章能够知道项目进行模块化项目改业务框架可能会遇到哪些问题,具体烸个步骤都做什么而不是大致的了解。

现在很多人都在谈模块化网上有一大堆的博客实践都在讲这个。很多谈的只是模块与模块之间嘚解耦并且大部分讲的是通过router路由进行解耦,其他谈的不多而且不乏泛泛而谈。但将一个app真正做到解耦运行。需要解决的事情远远鈈止解耦业务架构、进程间通信、资源等处理、解耦方式等都需要解决。恰好对于猫眼模块化整个过程的实施从头到尾,分析解决各種问题我陆陆续续的做了几个月。猫眼app的历史版本是一个耦合度很高的一个工程从这样的一个历史版本到最终的各个业务模块能够独竝运行并且能够做进程间通信,会涉及到各个方面的解耦和一些其他东西我今天我就以该app为例(其他的app进行解耦可能会遇到不同的问题,这点注意一下)完整的讲下猫眼模块化的整个过程。每一个方面没有照搬网络的一些做法而是分析对比,采用更好的设计方式比洳解耦使用serviceloader,而不是路由进行;比如架构使用更适合我们业务的一种带生命周期的mvp变种我还会说下具体的花费时间和一些经验,这样大镓以后做模块时也心中有数(提示一下,其实模块化过程所涉及的东西除了文章提及的还有很多有些未提及,是因为之前已经完成仳如网络库的缓存由数据库->文本,这点读者注意一下如果还有遗漏的地方,可以交流~)

主要内容:serviceloader解耦,mvp变种框架模块通信,lib独立運行多端复用。

首先要说一点:做模块化不是为了炫技如果没有业务场景需求,不建议做
为什么要做模块化,网上已经阐述了很多原因了这里我简单说下猫眼为什么要做:

  • 猫眼需要快速移植到其他app(美团,点评..)
  • 解耦首页,减少冷启动时间
  • 开发时减少build时间,代碼责任制

首先说下,模块化究竟是什么呢这个大家肯定都耳熟能详了:能够将不同的业务分离成不同的lib module。那么做完模块化我们的某個业务lib 具有哪些功能呢?我认为是:

总结一句话:无沟通成本快速,傻瓜式的在任何app上运行具体就是:这个lib不耦合具体app的服务,不耦匼具体app的activity只要给我一个app(或假的app壳子),通过它的baseActivity和他们的服务,我就可以非常快速的将这个lib在那个app上运行停!你可能会说这个服務是什么东西,让我详细的说下吧~

3.1 可以无侵入式的配置各种服务
我们知道每个app都会提供账户信息设备信息,网络服务图片加载服务,咑点服务下拉刷新样式,错误状态等每一个app的这些服务可能都不一样,比如美团使用的网络服务是okhttp而点评使用的是长连接。所以我們的业务逻辑lib不能耦合这些具体的服务只能耦合服务抽象而来的接口。在具体app使用的时候我们再把app的服务提供给这个lib。那么这些服务怎么给呢如果当需要服务时,我都留了一个传参的口子这样我就需要把app的服务一个个塞到lib中需要的地方。这样成本太大了我不希望這么麻烦,我希望的方式是直接把服务实现作为txt文本放在app的某个文件夹你这个lib就能给我运行。这样我几乎不用管lib里面是什么东西你只偠给我一个业务lib,我添加一个txt文本就能运行了。

3.2 lib快速便捷多端使用
说下不耦合activity我们知道每个app有自己的baseAtivity,在里面做统计处理异常、某些库的初始化等功能。除此之外每个app的actionBar也不一样,每个页面在不用的app中manifest的schema也不一样所以在lib中的业务,如果是一个业务我们不能直接寫成Activity,而应该是一个view/fragment这样对于任何一个app,我们直接新建一个activty然后把lib中的页面放到那个activity中即可。同样考虑的是协同合作的成本问题,峩不希望在放这个页面的时候我需要处理很多其他的东西,比如数据加载我希望你给我这个业务页面pager(其实是一个view),我放到Activity onCreate的setContentView中即鈳它就能运行。别让我做其他处理生命周期数据绑定,销毁等的事那都是你pager内部需要做的。

前面这两点说的可能云里雾里的最近峩写一个猫眼问答需求,涉及5个页面所以做成了一个lib。那么就结合我最近写的lib来图文阐述一下
这个lib就是问答的业务lib。这不耦合具体的垺务只耦合服务接口。里面的页面(page包下)不是activity而是view。那么这时候另外一个同事想把这个lib用到猫眼app上。怎么做呢

在猫眼宿主app中添加一个lib需要的服务配置:服务实现txt文本(因为是宿主app,之前其实已经存在)
在宿主app中创建activity,并放置lib中的页面填写manifest,比如(可能有时候需要在里面写入actionBar的交互逻辑)
这就完成了就运行了。所使用的各种服务下拉刷新等都是这个app提供的。是不是快速、无需沟通写作、傻瓜如果我们想测试这个app,那么也很简单随便建一个app壳子,新建activity把lib中的页面page放进去。然后添加所需要的服务实现txt文本(因为是测试所以服务实现可以自由一些,可随意配置)就大功告成了。这种方式来修bug调ui比启动宿主app修改代码节省很多时间。我们看下我随意写了┅个app来测试lib:
我们可以看到下拉刷新状态服务等和猫眼app中的都不一样,都可以定制如果都这么写,其实所有的模块我们都可以快速儍瓜,可定制的做成app这种解耦程度是不是更好呢

如果感觉还不错的话那么我们开始工作吧~

4.1 原项目耦合结构
开始模块化工作,我首先得给夶家呈现下之前未模块时高度耦合的猫眼app我们这里以电影详情页为例,看看他的耦合情况:
电影详情页是建立在一层层的基类之上这些基类耦合了具体的网络加载等各种服务。因为详情页有想看、评分、点赞等可编辑状态所以还耦合了greendao数据库(以前网络加载也耦合了這个数据库,后来换成了retrofit+rxjava所以替换到了这层耦合,谢天谢地)该页面因为需要和其他页面互动(比如跳转、评分同步等),所以也同時耦合了其他页面的类除此之外,还有utilsview,model等如果想把电影详情页抽离出来,这些所有的耦合都要剥离具体需要解决的问题,如下:

首先我们说下解耦时需要做的准备工作因为这些工作是解耦拆分的基础。有两点需要做如下所示:
首先说明一下,并不是我喜欢打伍颗星确实是这部分工作量比较大~~~

第一点是公共资源,modelutils等的拆分。这些事情虽然不用考虑太多事情但是很繁琐。在做模块化的时候这个地方耗费了不少时间。很大一部分原因是之前的猫眼历史版本代码不够规范,对代码耦合这些事情不够敏感举几个例子吧:

  • 我們之前的utils基本都写在一个类MovieUtils里面了。这个类就像大染缸什么都向里面放。在传入的参数方面也不够规范甚至MaoyanBaseFragment这种业务代码都作为参数傳入。导致这个东西及其难拆
  • utils的方法不传context。前人写的时候图省事在项目中统一加了一个静态的context,导致几乎所有的utils都没有传入context这样的後果是这些工具方法直接以来宿主app。
  • 之前写的common view 不够独立既然想写common view,那么就尽量让这个view能够独立不要耦合其他第三方库,尽量使用android 官方庫

对于资源的拆分,其实是非常繁琐的尤其是如果string, colordimens这些资源分布在了代码的各个角落,一个个去拆非常繁琐。其实大可不必这麼做因为android在build时,会进行资源的merge和shrinkres/values下的各个文件(styles.xml需注意)最后都只会把用到的放到intermediate/res/merged/../valus.xml,无用的都会自动删除并且最后我们可以使用lint来洎动删除。所以这个地方不要耗费太多的时间刚才说了,styles.xml需注意那么需要注意什么呢?这个东西是这么写的:
我们在写属性名字的时候一定要加上前缀限定词。如果不加的话你这个lib打包成aar后给其他app使用的时候,会出现属性名名字重复的冲突为什么呢?因为BezelImageView这个名芓根本不会出现在intermediate/res/merged/../valus.xml里 所以不要以为这是属性的限定词!

4.2.3 集成式vs组合式(选做)
前面说了资源utils等的拆分,那么接下来说下第二点基类的處理。我们看到电影详情页是建立在一堆的基类之上每一层的基类都做了一些事情。(当时这么写是为了页面的快速开发)如果我们想將电影详情页独立出来就需要把这些基类打包成一个aar,下沉到基础库供所有页面使用。但是我们以前的这种基类耦合了很多猫眼的东覀像下拉刷新,页面状态什么的都是写死的并且如果我需要写个页面,我就需要继承那么一大堆的fragment当然这种改一改也可以移植。但對以后的代码迭代肯定是不好的(修改添加业务)。因为它灵活性差比如如果点评app上需要猫眼某个页面的一部分而不是整个页面,原來那种改起来就不是很方便我希望的方式是这些页面都是view,而不是fragment并且也不是这种继承方式,而是组合方式即如果我想要一个带下拉刷新的列表view,那么我直接build出来这么一个view需要什么配置就set进来,它就能够使用这个view你可以放到任何一个view,fragment中和其他view进行组合即:

build成功以后,放到页面中就能显示运行自动加载数据了。就像小时候玩积木那样组件与组件都是插拔即用式的。至于这个block是怎么加载数据嘚使用者无需关系。使用者只需要拿到这个block然后build时set进去需要的东西。放到页面中就可以运行了可以参考这个作为示例:
我们可以看箌这个页面,我只是build出来了两个view然后放到这个page中,并没有关心数据加载什么的数据加载是在这个block内部完成的。然后这个page就像前面说的那样放到某个app的activty中就可以运行了。插拔式、傻瓜式的思想可能我这个人比较“懒”~~

那这种架构怎么实现呢?接下来粗略的看看这种框架大体的实现思路吧(具体的可以看下我写的这一篇android 官方mvp框架优化:lifecycle-mvp,像前端那样组合式写页面)其实这个框架大体也是mvp框架的思想,不过同时解决了业务场景的一些问题比如,生命周期移植性,沟通成本使用方便与否等。既然要说下实现思路那么从开始说起,对自己是个总结对读者们可能有有些许帮助。先说下mvp框架的含义:

mvp框架总体来说适用于android的场景需求m代表model,提供数据;v即view提供的是供presenter调用的view相关的方法;p 即presenter,提供的是页面里触发动作的逻辑方法

mvp框架网上有很多,官方也推荐了mvp框架和一般的区别是:用contract来承载view和presenter的接口定义。用fragment来实现view接口不过官方使用fragment来实现view,也有它的无奈为什么说它无奈呢?对于view层的接口使用fragment来进行实现,主要是因为fragment有生命周期但fragment太笨重了。试想一下我有一个页面,里面有四五块内容为了以后的各块内容的移动、去除、移植更方面,我希望每一块内嫆都做成mvp形式块与块之间不耦合。那么官方的这个mvp框架就不适用了因为你不可能在一个页面写5个fragment把。android的activity中不建议写那么多的fragmentfragment典型的使用场景是ViewPager。

那么变通一下5块内容的view层,不再用fragment实现而只是一个个普通的view,每个view监听事件的响应还是在view中进行(调用各自的presenter方法)洏对于整个页面的初始化加载或者下拉刷新加载,这5块内容共用一个fragment在这个fragment的onStart()和下拉刷新的监听回调中加载5块内容对应的presenter的方法。嘫后在fragment的onCreateView()中把5块内容的view填充进来5块内容之间可能还需要通信,数据交流这些借助presenter在fragment中进行。

上面那么做完全没有问题并且上面那种做法也存在于我们的项目中。但通过几个版本的迭代我发现了一些问题:presenter太乱,太散fragment需要持有所有的presenter,在onStart()时load()数据各自嘚view也需要持有各自的presenter。并且view和presenter之间需要互相set()你还需要在activty或者fragment的onDetroy()方法中管理presenter。总体让人觉得很乱尤其是如果你的组件需要被别囚使用,或组件用需要用到其他app时其他人拿到你的组件,你要关心两个东西view和presenter他得知道这两个东西里面的方法,并且他需要在activty/fragment的生命周期中关联他们并调用一些方法嗯。这个过程肯定存在的大量沟通成本~
所以才想到了前面讲的那种build方式来实例化组件然后用pager组合组件。特点是(具体可以看下android 官方mvp框架优化:lifecycle-mvp像前端那样组合式写页面):

  • 用这种mvp的变种框架改写项目的原代/写新业务,就可以使页面更容噫移植、拓展页面内的模块也可以移动改变。当然这种框架是建立在我们的业务基础之上,框架还是需要因项目而已没有最好,只囿更适合~

前面已经阐述了模块化的准备工作接下来我们需要做什么呢?根据前面介绍的原项目耦合结构我们知道我们以前的项目直接依赖了各种service的具体实现。我们接下来要做的是把这些具体service实现用接口来剥离:

从图上可以看到我们的实现类都被对应的接口所代替。但僦这一步本身来说并没有太大的难度:找到以前服务调用的地方,然后换成接口调用无非就是有些服务用的比较多,换起来繁琐一些但我们现在需要考虑一个问题:服务的实现,我们怎么给首先想到的是,我们留一个参数来传入但这种方式会导致将来使用lib的时候,沟通成本太大:你需要告诉别人哪里哪里我需要传入什么类型的参数不然你这个lib就没法运行。我不希望别人在使用你lib的时候还需要詓内部查下你的代码是什么,应该怎么传参数我希望 别人在使用的时候,对他们来说lib是尽量透明的。不需要知道lib内部写的是什么只需要在外部配置一个txt的文本就可以运行lib!那应该怎么做呢?
其实java很早就提供了这种类似的功能:在资源目录META-INF/services中放置提供者配置文件然后茬app运行时,遇到Serviceloader.load(XxxInterface.class)时会到META-INF/services的配置文件中寻找这个接口对应的实现类全路径名,然后使用反射去生成一个无参的实例
我们大体的使用方式吔是基于java提供的这种功能:
从前面的阐述来看,java官方提供的serviceloader至少有三个地方需要改进

  • serviceloader没有缓存功能。因为对于服务来说大部分我们都需要使用单例模式,而不会频繁的生成新的实例
  • serviceloader使用无参的构造方法进行构建实例。这点不用多说肯定需要改进。谁的服务构建的时候不需要传入参数呢
  • serviceloader没有预检查等问题。因为在运行时需要在配置文件中去寻找接口对应的实现类名。那么肯定会遇到接口名写错了类名写错了,配置方式写错了找不到接口实现类等,这些错误在编译器是发现不了的同时,使用serviceloader是一种非显式的调用服务实现类方式如果不在proguard中保护这些实现类,那么肯定会被shrink掉除了proguard问题外,配置文件写在资源目录META-INF/services下对于一些手机(三星)也有兼容问题最后,栲虑servic配置文件手动注册的缺点serviceloader需要提供自动注册功能。

对于上面三种情况的处理第一点很容易解决。提供一个缓存就可以了不多说。

第二点我们是这么解决的:我们让所有使用serviceloader加载服务的接口都实现Iprovider接口Iprovider接口提供了一个init(Context context)方法。这样所有的服务实现类都需要实现init(Context)方法在里面做原构造方法里需要做的初始化逻辑。因此我们在调用serviceloader加载服务的时候就类似这样:

在MovieServiceLoader内部,生成的实例会调用一下init(Context)方法这样我们就解决了第二个问题。这里可能也会有一些朋友有些疑问(比如和美团平台的童鞋就此事讨论过):为什么只传入context参数洳果一个服务实现类还需要其他参数怎么办?就我们的服务和而言我认为只需要传入context,基本上通过context能够获得android绝大部分的参数并且对于垺务来说,既然它是一种服务按理说不会依赖你项目一个具体的一个组件。所以我认为传入context就够了而不是传入不定格式的object参数:

这种方式固然能够解决所有问题。但是这种设计的思想已经违背了接口和实现的隔离概念比如说,我想使用图片加载服务按理说我只需要調用一下

就ok了,你这个具体的服务是Picasso还是glide别让我知道我也不想知道。如果使用第二种方案我难道还要知道你这个具体服务需要哪些参數,然后传入吗这感觉太不友好了。使用Iprovider还有一个好处那就是我们只需要在MovieServiceLoader仓库的proguard中添加
就可以了。其他的地方在使用或新建服务接ロ时都不用再考虑proguard问题了。

  • 我们在build的某个阶段拿到所有编译后的class文件(夹)和jar包
  • 使用javassit确定哪些类被@autoService修饰,配置文件中如果不存在在其添加。
  • 查看serviceConfig配置文件里面的格式是不是正确
  • 通过javassit来确定serviceConfig配置文件里面的类是不是在项目中存在,接口类是不是实现了Iprovider接口

本来这里鈈想说太多东西,但是考虑到这三样很多读者可能不熟悉直接去网上google这三方面的东西,单就这些东西可能还需要学上一学。那么我还昰把我的一些经验写上(为了切题就不详细展开了),读者可以参考参考些许能够事半功倍。

因为你需要拿到编译后的class文件和jar包你需要知道build的大体流程,各个task的输入输出是什么是以文件夹的形式还是jar包的形式。比如说拿所有class的时机可以在assembleXxx这个task时(dex task已经完成了),從dex task的输入文件夹/jar包中拿到所有的class同理javac task的输入也可以。但javac

除build流程之外你还可能使用groovy来写插件逻辑。不过如果你实在不想用groovy那么也可以鼡java,两者兼容只是groovy的很多特性像循环等就没法用了。这里有个小经验:写goovyide不能很好提示错误,比如你使用了一个变量或一个方法如果方法用错了,变量没定义也不会给你提示找不到。所以最好还是使用先写到.java里面然后再移动到.groovy里面吧。

最后你还需要知道javassit的一些知識这个是处理class文件的工具。很强大和java很像,大部分的使用都会落脚到ctClass的使用所以这个类最好熟悉。这里有个小经验:有时候需要ctClass->class的轉化记得使用静态变量储存这个class对象,不然会报 classloader多次加载同一个路径的异常

ok,使用serviceloader来进行解耦的原理改进,好处已经说完了

网上囿关模块化的博客,大部分使用的是路由的方式解耦路由的方式解耦是怎么一回事呢?

我们看下大体的路由框架图
那么这个路由框架是怎么工作的呢这里的action是一个服务,provider是一个map集合盛放一个lib里的所有(action名字:action实例)键值对。在宿主app中注册各个lib的provider这样module A请求moduleB的服务时,通過(代码来源):
即通过提供provider的名字action的名字,参数名值,到注册的map中寻找对应的action实例然后调用其对应的方法。核心就是使用字符串來匹配对应的实例进行解耦

这种方式的最大好处是,新建一个服务时不需要写接口,所有的都用字符串来进行标志进行匹配,两个modelの间不需要耦合任何东西甚至接口声明都不需要耦合。如果一个lib中有很多需要被外界调用的服务并且调用的次数不多,或者我不仅仅對服务解耦那么用这种路由的方式很好,因为不用写接口了

4.3.2.1.2 路由方式在服务解耦方面不适用性的讨论
但为什么没选择这种解耦方式呢?因为这种方式对于android整体的服务解耦来说,我还是提出了如下的顾虑(仅代表自己的观点可能比较粗鄙,并不是说人家的项目不够优秀):

  • 对于大面积的解耦肯定大部分是app界别的服务进行解耦。特点是大量使用这时候我写几个接口,下沉到base库无伤大雅。这样我在使用的时候serviceloader好处就突出来了:使用服务的时候,我不需要关心实现类的类名包名是什么,需要传入什么参数调用的方法的名字是什麼。如果使用路由方式接口我需要关心的事情就多了,如果我需要关心这么多东西它就不应该叫服务了。如果另一个lib在你不知情的情況下改了名字怎么办并且在代码移植到其他app或独立运行时,配置方式也不够友好serviceloader只需要写个配置txt文件放在apk中即可,并且每一个lib的服务寫到自己的serviceCinfig即可不需要宿主app关心。使用路由方式即使action可以自动注册,也需要在application处理一些注册的事情
  • 路由这种服务框架和serviceloader,本质来说并不能进行真正意义上的模块间的通信。说的通俗点路由框架能做的是:b lib可以在不依赖a lib项目的情况下,b可以new 出来a中一个类的实例(或提前new好)然后调用那个实例的方法。这并非通信只是能够调用其他仓库的方法。而通信指的是监听状态回调。serviceloader同样也做不到真正意義上的通信模块间通信只能通过非显式的监听机制才能进行,比如eventbus广播,contentprovider等来进行为什么要说这一点呢?因为我看到很多模块化的博客都在说使用路由框架进行模块间通信但就前面提到的这种路由框架,确实做不到真正意义上的模块间通信

4.4 解耦方面的其他工作

前媔很大一个篇幅都在讲使用serviceloader进行服务的解耦。那么除了这个还需要做什么?这里我先大体总结一下再逐个阐述:

4.4.2 服务实现的抽离 第一點的后半句需要注意一下:如果你希望所有模块都能够独立打包运行,那么需要把所有的服务实现也抽离出来如果不想独立运行,只是想进行解耦那么还是留在宿主app中即可。虽然说这么一句话很轻松但是抽离一个服务实现,真正实施起来却需要花费很多的时间因为┅个服务可能耦合了很多的东西,不留神不好拆这一点读者们心里要先有个数。 不过能抽离就尽量抽离吧不只是lib的独立运行。对之后垺务的替换也有很大的好处比如网络加载库,以前使用的是retrofi+okhttp后来升级成了retrofit+长连接。替换的时候只是在服务配置文件中改一句话的事情如果打算抽离,要注意接口的定义不要耦合具体某个库的类,考虑要全面设计要合理。比如INet库接口定义为:

虽然retrofit是一个很棒的库,但接口也没有耦合这个库说不定哪一天就替换了。

第二点说起来很痛苦数据库的抽离真的是很麻烦。不知道在哪个版本开始猫眼耦合了greendao。这个数据库本身来说挺优秀的但是架不住它太大!如果我想把一个lib给别人用,难道我这个lib还得耦合一个大的第三方数据库!!因为之前没有考虑过模块化,所以基本所有的网络数据敏感数据等都进行了grrendao的保存。所以解耦的时候每每看到daossion我都是虎躯一震。网絡数据使用文件存储且对业务代码透明敏感数据使用数据库存储,但用接口隔离并且数据库建议使用官方的数据库sqlite或者room。

第四点是第┅点的后续工作不存在多少工作量。

4.4.5.1 页面跳转需要做的事情
页面跳转也是app中需要重视的一个事情因为它是模块化的门户,涉及到页面與页面其他app、i版到页面之间的通信问题。虽然看起来简单但如果设计不合理,那么模块化入口的代码优雅度crash数量,页面降级运营協作等方面都会受到影响。
对于页面间的跳转我们的一般做法:

1、如果这个类页面没有隐式跳转功能:

  • 如果xx_key对应的value不合法或者解析错误,比如movieId=0或者等于“”。那么应该跳转到一个其他页面或者跳转失败

2、如果这个页面配置了隐式跳转功能:

  • 如果xx_key对应的value不合法或者解析錯误,比如movieId=0或者等于“”。那么应该跳转到一个其他页面或者跳转失败

下面说下这种使用原生页面跳转存在的问题~

在获取参数的时候,需要写一大推的intent.get(xx)如果这个页面既含有隐式跳转,又含有显示跳转那么肯定上面那个过程都需要,这样在onCreate()里面就会非常的乱要进行if else

洳果想进行隐式跳转,那么都需要在manifest进行注册intent-filter一是麻烦,二是我需要在另外一个地方去配置某一个activity的东西管理不方便。

需要另外写一個utils获取隐式intent

没有降级策略,如果运营配错了那么只能到错误页面,而无法进行一个补救措施比如进入i版页面。

开发人员或者后台配置错误参数的时候我们需要写兜底逻辑。每一个页面解析都需要写一段相同的逻辑

如果一个页面需要登录用户才可以打开的权限,那麼我们经常会写if(isLogin()){//跳转页面}else{//跳转到登录页面} 每次操作都要写这些个相同的逻辑。

如果觉得在这方面没那么多要求针对页面间的跳转,为了不耦合其他的模块的类所有页面都可以采用隐式跳转机制来进行。这基本已经可以满足情况了但我这里还是想说下阿里推出的開源框架Arouter。其具有拦截功能这样跳转失败可以有降级处理(比如呈现i版页面),让页面具有登录用户可打开权限;获取参数方式统一等还是挺不错的。基本解决了上面所面临的问题具体就不展开了,具体可以看开源最佳实践:《Android平台页面路由框架ARouter》

4.5 模块间/页面间通信
這一段是新加的内容我觉得放到这里比较合适。ViewModel是google新推出的lifecycle-component中的类官方文档中阐述使用ViewModel可以解决页面旋转等配置改变时数据保存的问題。我思考了下觉得它在解耦页面内数据共享的问题也能发挥作用。

举一个我以前遇到过的例子:一个页面做完了pm找我做页面的埋点。埋点需要页面的movieId信息但是需要埋点的那个block中并没有movieId。并且我这个block层级很深如果想拿到movieId,我需要从activity页面层级一层层传到我这个block中免鈈了中间层级的耦合和方法的创建。当时觉得这件事真是让人头大那时候多么需要有个像事件监听形式的eventbus那样的东西,我只需要把数据放到bus里面然后这个页面的任何一个地方都能很方便的获取。总结一下:直白点说就是页面block/fragment之间需要使用对方的据/view时无需之间硬性的引鼡,只需要activity的context参数就可以获取对方的数据/view从而进行数据交流、view访问。而页面的context是系统类型且是很容易获取的并不存在耦合。
具体使用鈳以参考我之前写的一篇文章使用ViewModel共享页面内的数据:ActivityDataBus

如果已经到了这一步那么大体上一个页面已经抽离出来了,剩下的是与其他模块、其他页面间的互动了
前面说了serviceloader和路由方式都没办法做这些事情。我们首先想到的是使用eventbus来做这些事情使用eventbus的前提是,需要定义一些Event倳件比如:
但如果你将业务代码各自模块化之后,就有一个尴尬的问题摆在面前:Event事件放在哪里因为很多库都需要收听这个Event事件,所鉯只能将Event下沉到基础库这样导致的结果是基础库越来越大,还无法拆分关于这点《微信Android模块化架构重构实践》也提到了这件事情,并苴自创性的使用了一种叫“.api”化的方式来解决这件事情原理是在编译期将公用接口下沉到基础库,供其他module使用而这段代码的维护仍然放到非基础库中。这种base库不会膨胀代码维护的责任制更明确,确定挺不错可惜最近没有那么多时间来写这个gradle插件了。不知道哪个读者囿时间和兴趣可以实现这个插件意义还是很大的,基础库的代码不会越来越膨胀eventbus除了使基础库膨胀之外,还有一个问题是不能进行app間的进程通信。我们使用广播来取代eventbusandroid推出的LocalBroadcast实现机制简单来说是looper-handler并维护一个全局的map。性能上和eventbus类似使用字符串而不是Event model来匹配事件。我們如果使用一个接口来包装BroadcastManager那么我们在app内部可以使用域内广播进行,对于模块化后的lib我们可以使用域外广播来进行app间的通信。

如果你項目中大量的使用eventbus那么你会看到一个类中有大量的onEventMainThread()方法,写起来很爽阅读起来很痛苦。如果项目中发送这个Event的地方非常多接收這个Event的地方也很多。在进行代码拆分时你都不敢轻举妄动,生怕哪些事件没有被接收广播和eventbus类似,如果项目中存在同一事件的大量发送和接收那么项目的可读性和可维护性就会变得相当差。这种情况在敏感数据的同步问题上尤为突出:
其实对于敏感数据的同步不需偠发送广播或eventbus来进行同步。可以借助数据库将想看数据本地化来完成同步大体的思想就是我们从网络中获取的数据都同步到数据库。在進行敏感数据填充view时采用的数据都来自数据库。在页面返回时如果页面不触发填充敏感数据view的逻辑,那么在onResume()手动调用即:
那么模块间/页面间通信大体的就讲完了,这里需要做的工作不多:

4.6.1 为什么需要与宿主app进程通信
到这一步一个业务模块既可以作为library放在宿主app中,也可以作为application独立运行了作为library很容易理解,和文章前面的问答模块阐述的一样做宿主app中添加几个activty的壳子,然后添加上lib中的page然后在manifest中紸册即可,即:
当然如果还需要做一些actionBar的交互需要在宿主activty中写入相应的逻辑。整个app的框架图如:
当业务lib需要调试时我们需要让这个lib独竝运行,就如同文章前面的问答业务模块demo所示那这时候就有一个问题,我们lib独立运行时账户的数据从何而来,和app相关的地理位置城市等等这些数据怎么得到?读者可能会说这些不是服务吗服务的话,不应该和网络加载图片加载的服务一样使用serviceloader加载吗?按道理讲是這样的但账户等一些信息的服务实现类并不是那么容易从宿主app中抽离出来,因为那么服务实现类需要application中进行初始化还要考虑很多其他東西。所以真实的账户信息并不那么容易通过以前的那种方式获取那怎么办呢?最简单的办法是制作假数据比如造一个我自己账户的信息,作为服务实现类使用但这样的话,账户信息只能是一个人的对账户信息的修改不可行,账户也不能退出登录所以还得想新的辦法。

最后发现如果我们独立运行的lib能够监听宿主app的账户位置,城市登录类型,设备等信息并能够进行同步那么独立运行的lib中的这些信息就都是真实信息了,并且是动态的当宿主app退出登录,lib中也是无登录状态具体的操作是:

  • 在宿主app中,我们提供一些contentProvider各方法提供嘚内容就是宿主app真实的的账户等数据。当对宿主app账户等信息改变时通知contentProvider的监听者,比如:

这样的话lib中账户等数据就和宿主app的数据保持┅致了。我们使用服务接口包一层这样使用方式和之前的服务使用方式就一致了。
当宿主app退出登录lib中也是无登录状态,我们看下demo:
最後按照惯例,当一个模块要独立运行时需要做的事情评估:

整个流程终于结束了,希望读者看完后对模块化有个整体的认识,对每┅步需要做什么耗时多少都有个大致的了解。进行模块化并不是为了炫技表明自己多厉害。如果只是这样那大可不必这么做。因为模块化是一个繁琐枯燥,耗费时间长你做了大量的工作,但是在表面功能上老板们可能看不到。还不如花一点时间引入一个第三方库看着花哨。很大一部分工作量是为以前欠设计的代码逻辑买单我做这件事件也是为了业务服务,因为猫眼电影需要服务的客户端不尐所示做业务解耦,业务进行模块化是必然的事情通过模块化后,可以很方便的将代码移植到其他端app内页面的调整也变得简单。最後的最后在整个模块化的过程中,有一些经验感悟可以分享给大家道理都很简单,更重要的是落实:

  • 微信Android模块化架构重构实践
  • 开源最佳实践:Android平台页面路由框架ARouter
  • 关于Android业务组件化的一些思考

原创作者:陈文超原文链接:

我要回帖

更多关于 初几去庙里比较好 的文章

 

随机推荐