1.《易捷通 易捷通自助收款机》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《易捷通 易捷通自助收款机》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/tiyu/738489.html
1.《易捷通 易捷通自助收款机》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《易捷通 易捷通自助收款机》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/tiyu/738489.html
到今天上午10点半,今年高考正式结束!这也意味着浙江省历时8年的“自选模块”科目已经退出高考阶段。昨天下午,嘉兴部分老师在微信朋友圈告别“老高考”。到今天上午10点半,今年高考正式结...
1.核心资源(我是谁,我有什么); 2.关键业务(做什么); 3.客户群(我能帮谁); 4.价值服务(如何帮助别人); 5.渠道准入(如何提升自己); 6.客户关系(如何相互处理); 7.重...
授时是卫星导航系统的重要功能,授时精度、定位精度和测速精度被称为导航定位系统的三大指标。GNSS授时模块专业制造商SKYLAB简单介绍了GPS/北斗授时模块是如何实现授时功能的。 如果我们想...
这个库是问答的业务库。这不耦合特定的服务,只耦合服务接口。里面的页面(页面包下)不是活动,而是视图。然后这个时候,另一个同事想在猫眼app上用这个lib。怎么做?
在猫眼应用的build.gradle下添加这个lib依赖项:
编译' com . Mao Yan . Android . business:movie:1 . 0 . 2 . 3 '
在猫眼主机app中添加一个lib需要的服务配置:服务实现txt文本(因为是主机app,所以之前实际存在)。
Txt里面是:
在宿主应用程序中创建活动,将页面放在lib中,并填写清单,例如(有时可能需要在其中编写actionBar的交互逻辑)
就这样,它开始跑了。使用的各种服务,比如下拉刷新,都是这个app提供的。是不是很快,没有交流,很蠢?如果要测试这个app,也很简单。随意创建一个app shell,创建一个新的活动,把页面放入lib。然后添加服务实现所需的txt文本(因为是测试,所以服务实现可以自由配置),就大功告成了。这种修复bug和调整ui的方法比启动宿主应用程序修改代码节省了大量时间。让我们看看我随机写了一个测试lib的应用:
电影详细信息页面基于基类的层,这些层与各种服务(如特定的网络加载)相耦合。因为详细页面有想看、评分、赞等可编辑状态。,也是和greendao数据库耦合的(这个数据库之前也是和网络加载耦合的,但是被改装+rxjava代替了,所以这个耦合被代替了,感谢上帝)。这个页面需要和其他页面交互(比如跳转、评分同步等。),所以也与其他页面的类相耦合。另外还有utils,view,model等等。如果你想拉出电影细节页面,所有这些耦合都应该被剥离。需要解决的具体问题如下:
4.2准备工作-4.2.1工作量评估
首先说一下脱钩的准备工作。因为这些任务是解耦和拆分的基础。有两件事要做,如下所示:
首先,我不喜欢玩五星。确实这部分工作量比较大~ ~ ~
-4.2.2公共资源、模型、设施等的分割。-4.2.2.1耦合示例
第一点是公共资源、模式、公用事业等的分割。这些东西虽然不需要考虑太多,但是很复杂。这个地方模块化的时候花了很多时间。很大一部分原因是之前的猫眼版本历史代码不规范,对代码耦合不够敏感。举几个例子:
我们之前的utils基本都写在一个类MovieUtils里面了。这个类就像大染缸。什么都向里面放。在传入的参数方面也不够规范,甚至MaoyanBaseFragment这种业务代码都作为参数传入。导致这个东西及其难拆。utils的方法不传context。前人写的时候图省事,在项目中统一加了一个静态的context,导致几乎所有的utils都没有传入context,这样的后果是这些工具方法直接以来宿主app。之前写的common view 不够独立。既然想写common view,那么就尽量让这个view能够独立,不要耦合其他第三方库,尽量使用android 官方库。- - 4.2.2.2 资源拆分经验资源的拆分其实很繁琐。尤其是如果字符串、颜色、dimens等资源分布在代码的每个角落,一个一个的拆解,非常麻烦。其实不用这么做。因为安卓在构建的时候会合并收缩资源。最后,res/values下的所有文件(应该注意styles.xml)将只放入intermediate/RES/merged/../valos.xml,无用的文件将被自动删除。最后我们可以用lint自动删除。所以不要在这个地方待太久。就像我刚才说的,styles.xml要注意。那么需要注意什么呢?这个东西是这样写的:
当我们写属性名时,我们必须添加前缀限定符。如果不添加,当你的lib打包成aar被其他应用使用时,会出现属性名重复的冲突。为什么?因为名称BezelImageView没有出现在intermediate/res/merged/../value.xml根本不要以为是属性的限定符!
-4.2.3集成型与组合型(可选)
我们讨论了资源的分割,比如utils,然后我们讨论了第二点,基类的处理。我们看到电影细节页面是建立在一堆基类之上的。每一层的基类都做了一些事情。(当时是为了页面的快速发展而写的。)如果我们想要分离电影细节页面,我们需要将这些基类打包到一个aar中,并将它们放入基本库中,供所有页面使用。但是我们之前的基类是耦合了很多猫眼的东西,比如下拉刷新,页面状态等等,如果我需要写一个页面,我需要继承很多片段。当然这种改变也是可以移植的。但是,对于未来的代码迭代(修改和添加服务)来说,肯定不是好事。因为灵活性差。比如评论app需要某个页面的一部分,而不是整个页面,那么改变原来的就不太方便了。我希望这些页面都是视图,而不是片段。而且不是这种传承方式,而是一种结合方式。也就是说,如果我想要一个带有下拉刷新的列表视图,那么我可以直接构建这样一个视图,如果我需要任何配置,可以设置它,并且可以使用它。您可以将这个视图放入任何视图中,将其分割并与其他视图组合。即:
moviercpagefulltorefreshstatusblock是一个视图,可用于组合任何页面上的视图。
-4.2.3.1组件的插件和插件设计
其实我的做法更大胆或者说“懒”。希望在我的MovieR page purll store refresh status block成功构建后,可以在页面上运行显示,自动加载数据。就像小时候玩积木一样,组件和组件都是即插即用的。至于这个块如何加载数据,对用户来说并不重要。用户只需要得到这个块,然后在构建的时候设置自己需要什么。放到页面上就可以运行了。你可以以此为例:
我们可以看到这一页。我只是建立了两个视图,并把它们放在这个页面上。我不在乎数据加载。数据加载在这个块内完成。然后,如前所述,这个页面可以被应用程序激活并运行。外挂和傻瓜式思维,可能是我“懒”了吧~ ~
这个架构怎么实现?接下来,我们粗略的看一下这个框架的总体实现思路(具体来说,我们看一下我写的这篇关于android官方mvp框架优化的文章:lifecycle-mvp,像前端一样组合写页面)。其实这个框架也是mvp框架的思路,但是也解决了业务场景的一些问题,比如生命周期、可移植性、通信成本、易用性等。既然要讲实现思路,那就从头给自己总结一下,可能对读者有帮助。先说一下mvp框架的含义:
-4.2.3.2 MVP框架含义
一般来说,mvp框架适合安卓场景。m代表模型,提供数据;v是视图,提供视图相关的方法供演示者调用;p是presenter,它提供了一种逻辑方法来触发页面中的动作。
-4 . 2 . 3 . 3的官方mvp框架的缺点
网上有很多mvp框架,官方也推荐mvp框架。一般的区别是,合同用于承载视图和演示者的接口定义。用片段实现视图接口。然而官方用片段实现观有其无奈之处。为什么无奈?对于视图层的接口,用fragment来实现,主要是因为fragment有生命周期。但是碎片太蠢太重。想象一下,我有一个页面,里面有四五条内容。为了以后移动、移除、移植每一段内容,希望每一段内容都做成mvp形式,块与块之间不耦合。那么官方的mvp框架就不适用了。因为你不能在一页上写五个片段。不建议在android活动中写那么多片段,片段的典型使用场景是ViewPager。
-4.2.3.4通常是灵活的
为了灵活起见,五段内容的视图层不再用片段实现,而只是普通视图,每个视图监听事件的响应仍然在view中进行(调用自己的presenter方法)。对于整个页面的初始化加载或者下拉刷新加载,这五条内容共享一个片段,这五条内容对应的presenter方法加载在这个片段的onStart()和下拉刷新的监听回调中。然后在片段的onCreateView()中填充5段内容的视图。这五项内容之间可能需要通信和数据交换,这是通过演示者分段进行的。
-4.2.3.5有生命周期的MVP:生命周期-MVP
这样做绝对没有问题,以上做法在我们的项目中也是存在的。但是通过几个版本的迭代,我发现了一些问题:主持人太乱,分散。片段需要容纳所有演示者,并在onStart()时加载()数据。每个视图还需要有自己的演示者。并且视图和演示者需要相互设置()。您还需要在activty或fragment的onDetroy()方法中管理演示者。总的来说,人们感到困惑。尤其是你的组件需要别人使用,或者组件的使用需要其他应用,别人会得到你的组件。你要关心两件事,即观点和主持人。他必须知道这两件事情中的方法,他需要把它们关联起来,在activity/fragment的生命周期中调用一些方法。嗯。这个过程肯定有很大的沟通成本~
这就是为什么我想到前面提到的构建方法来实例化组件,然后使用寻呼机来组合组件。特点是(具体来说,你可以看看安卓官方的mvp框架优化:生命周期-mvp,像前端一样组合写页面):
使用lifecycle-component这个组件提供生命周期。presenter被view层内部持有,不向外暴露。build创建view实例时,提供TypeFactory,用于业务的扩展。业务代码分层。用这种mvp的变种框架改写项目的原代/写新业务,就可以使页面更容易移植、拓展,页面内的模块也可以移动改变。当然,这种框架是建立在我们的业务基础之上,框架还是需要因项目而已,没有最好,只有更适合~4.3 接口的抽离模块化的准备工作前面已经介绍过了,接下来我们需要做什么?根据前面介绍的原项目的耦合结构,我们知道我们之前的项目直接依赖于各种服务的具体实现。我们接下来要做的是用接口剥离这些具体的服务实现:
-4.3.1使用servieloader解耦-对服务实现类的非显式调用
-4.3.1.1官方服务加载程序
从图中可以看出,我们的实现类被相应的接口所取代。但就这一步本身而言,并不太难:找到之前调用服务的地方,然后用接口调用。无非是一些服务用的比较多,换起来比较麻烦。但现在我们需要考虑一个问题:如何才能提供服务?首先想到的是我们留下一个参数传入。但是这种方式会导致以后使用lib的时候通信开销太大:你需要告诉别人我需要传入的参数在哪里,传入什么类型的参数。否则,您的lib将不起作用。我不希望别人在使用你的lib时检查你的代码是什么,如何传递参数。我希望lib在别人使用的时候尽可能的透明。你不需要知道lib里面写了什么,你只需要在外面配置一个txt文本就可以运行lib了!那么我们该怎么办呢?
其实java很早就提供了这个类似的功能:把提供者配置文件放在资源目录META-INF/services中,当app运行时,遇到service loader . load(xxxinterface . class)时,会在META-INF/services的配置文件中查找这个接口对应的实现类的完整路径名,然后使用反射生成一个不带参数的实例。
我们的一般用法也是基于java提供的这个函数:
-4.3.1.2官方服务加载器的改造
-4.3.1.2.1官方服务装载机的缺陷
从前面的解释来看,java提供的官方serviceloader至少有三个方面需要改进。
serviceloader没有缓存功能。因为对于服务来说,大部分我们都需要使用单例模式,而不会频繁的生成新的实例。serviceloader使用无参的构造方法进行构建实例。这点不用多说,肯定需要改进。谁的服务构建的时候不需要传入参数呢?serviceloader没有预检查等问题。因为在运行时,需要在配置文件中去寻找接口对应的实现类名。那么肯定会遇到接口名写错了,类名写错了,配置方式写错了,找不到接口实现类等,这些错误在编译器是发现不了的。同时,使用serviceloader是一种非显式的调用服务实现类方式,如果不在proguard中保护这些实现类,那么肯定会被shrink掉。除了proguard问题外,配置文件写在资源目录META-INF/services下对于一些手机(三星)也有兼容问题。最后,考虑servic配置文件手动注册的缺点,serviceloader需要提供自动注册功能。第一点在处理以上三种情况时很容易解决。只提供一个缓存,我就不多说了。
- 4.3.1.2.2 serviceloader构造示例
其次,我们解决了这个问题:我们让所有使用serviceloader加载服务的接口都实现了Iprovider接口。Iprovider接口提供了一个init(上下文上下文)方法。这样,所有的服务实现类都需要实现init(Context)方法,并在原构造方法中做初始化逻辑。因此,当我们调用serviceloader来加载服务时,它类似于这样:
imageloaderiimageloader = movieserviceloader . getservice(context,imageloader . class);
在MovieServiceLoader中,生成的实例将调用init(上下文)方法一次。就这样,我们解决了第二个问题。这里可能有朋友有一些疑问(比如美团平台童鞋讨论过这件事):为什么只传上下文参数。服务实现类需要其他参数怎么办?就我们的服务和而言,我认为我们只需要在上下文中传递,基本上我们可以通过上下文获得安卓的大部分参数。而对于服务,既然是服务,那么说它不会依赖于你的项目的某个具体的组成部分也是合理的。所以我觉得传入上下文就够了,不要传入格式不定的对象参数:
MovieServiceLoader.getService(对象...params,imageloader . class);
这样当然可以解决所有问题。然而,这种设计思想违背了接口和实现之间的隔离概念。例如,我想使用图像加载服务,所以我只需要调用它
imageLoader = movieserviceloader . getservice(context,imageLoader . class);
没事的。别让我知道你的具体服务是毕加索还是格莱德。我也不想知道。如果用第二种方案,我需要知道你具体的服务需要什么参数,然后传进去吗?感觉好不友好。使用Iprovider的另一个好处是,我们只需要在MovieServiceLoader仓库的proguard中添加它
够了。在其他地方,当使用或创建新的服务接口时,不需要考虑proguard。
- 4.3.1.2.3服务加载器预处理-渐变插件
第三个要解决的问题是serviceloader的预检验。解决方法是编写一个gradle插件。插件的一般流程是:
我们在build的某个阶段拿到所有编译后的class文件(夹)和jar包。使用javassit确定哪些类被@autoService修饰,配置文件中如果不存在,在其添加。查看serviceConfig配置文件里面的格式是不是正确。通过javassit来确定serviceConfig配置文件里面的类是不是在项目中存在,接口类是不是实现了Iprovider接口。- - - 4.3.1.2.4 需要用到的知识:build流程,javassit,groovy这里不想说太多,但是考虑到这三点,很多读者可能并不熟悉。直接去网上谷歌,光是这些东西,你可能需要学点东西。然后我写下我的一些经历(为了针对性,我就不详细展开了)。读者可以参考参考,有些可以事半功倍。
因为需要得到编译后的类文件和jar包,所以需要知道build的一般流程,每个任务的输入输出是什么,是以文件夹的形式还是jar包的形式。比如你获取所有类的时候,可以在组装XXX的时候从dex任务的输入文件夹/jar包中获取所有类(dex任务任务已经完成)。同理,也可以使用javac task的输入。但是不允许javac任务输出,因为javac任务输出的中间/类文件夹只包含项目中的类文件,不包含aar对应的中间/分解-aar文件中的类文件。转换也是一种实现。给出了转换的输入输出文件路径,输入类都是类。
除了构建过程,您还可以使用groovy来编写插件逻辑。然而,如果你真的不想使用groovy,你也可以使用java,Java是相互兼容的,但是groovy的许多特性,比如循环,是不能使用的。这里有一点经验:写goovy,ide不能很好的提示错误,比如你用了一个变量或者一个方法,如果方法错了,变量就是未定义的。不会给你找不到的暗示。所以,还是写信给比较好。java,然后转到。太棒了。
最后,你需要了解一些javassit的知识,这是一个处理类文件的工具。它非常强大,类似于java,它的大部分使用将基于ctClass。所以这个类应该比较熟悉。这里有一点经验:有时候需要CTClass>:对于类转换,记得用静态变量来存储这个类对象,否则会报告classloader多次加载同一个路径的异常。
好了,用serviceloader解耦的原理、改进和好处都说完了。
-4.3.2服务加载器解耦与路由模式解耦
网上大部分关于模块化的博客都是使用路由解耦的。什么是路由模式的解耦?
-4.3.2.1路由模式的解耦描述
让我们看看一般的路由框架
那么这个路由框架是如何工作的呢?这里的操作是一个服务,而提供者是一个映射集,它保存了lib中的所有键值对(操作名:操作实例)。在宿主应用程序中注册每个库的提供者。这样,当模块A请求moduleB的服务时,它传递(代码来源):
即通过提供提供者名称、动作名称、参数名称和值,在注册的映射中找到对应的动作实例,然后调用其对应的方法。核心是使用字符串匹配对应的实例进行解耦。
-4.3.2.1.1去耦路由模式的优势
这种方法最大的优点是,在创建新的服务时,不需要编写接口,所有的接口都用字符串标记进行匹配,两个模型之间不需要耦合任何东西,甚至接口声明也不需要耦合。如果一个lib中有很多服务需要外界调用,调用的次数不多,或者如果我只对服务进行解耦,那么这种路由方式非常好,因为不需要写接口。
-4.3.2.1.2讨论路由在服务解耦中的不适用性
但是为什么不选择这种脱钩方式呢?因为这样,对于安卓的整体服务脱钩,我还是提出了以下关注点(仅代表我自己的观点,可能比较粗糙,不是说别人的项目不够优秀):
对于大面积的解耦,肯定大部分是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等来进行。为什么要说这一点呢?因为我看到很多模块化的博客都在说使用路由框架进行模块间通信。但就前面提到的这种路由框架,确实做不到真正意义上的模块间通信。好了,serviceloader解耦vs route解耦在这里。
4.4关于解耦的其他工作-4.4.1工作评估
前面的大部分工作是关于通过使用serviceloader来分离服务。那么除此之外还需要做什么呢?这里我就概括一下,然后逐一说明:
-4.4.2服务实施的撤销
注意第一点的后半部分:如果想让所有模块独立打包运行,需要把所有服务实现都拉出来。如果不想独立运行,只想解耦,那么可以留在宿主app。虽然说这样的话很容易。但是没有一个服务实现要花很多时间去实现,因为一个服务可能会耦合很多东西,不小心很难拆解。读者应该记住一个数字。但是如果你能把它拉走,试着把它拉走,而不仅仅是lib的独立运作。对于之后的服务更换也是大有裨益的。比如网络加载库,之前用的是retrofi+okhttp,后来升级为改装+长连接。替换只是更改服务配置文件中的一句话。如果打算抽离,要注意接口的定义,不要耦合某个特定库的类,综合考虑,合理设计。例如INet库,接口定义为:
publiciinterceinetserviceextendsiprovider {
& ltT>。t create(FinalClass & lt;T>。服务,字符串获取数据策略,字符串缓存时间);
}
虽然改装是一个很棒的库,但是接口没有耦合这个库。也许有一天会被取代。
-4.4.3撤销数据库
第二点说起来很痛苦。拉走数据库真的很麻烦。不知道是哪个版本开始的。猫眼再加上绿岛。这个数据库本身很优秀,但是太大了装不下!如果我想给别人一个lib,我必须把这个lib和一个大型的第三方数据库连接起来吗?!!因为之前没有考虑模块化,基本上所有的网络数据和敏感数据都被grrendao保存了。所以每次在解耦的时候看到刀会话,都觉得自己像只老虎。网络数据存储在文件中,对业务代码透明。敏感数据存储在数据库中,但通过接口隔离,建议数据库使用官方数据库sqlite或room。
-4.4.4告别黄油刀
第三点意味着,如果你想独立地模块化业务代码,你必须像butterknife框架一样告别视图注入功能。安卓ADT14启动后,库的R资源不再是最终的,所以不能在库中使用R.id.xx,需要改用findViewById();也不能用switch(R.id.xx),需要用if...否则。
第四点是第一点的后续工作。工作不多。
-4.4.5页面跳转-4.4.5.1页面跳转需要做什么
页面跳转在app中也是很重要的一件事,因为它是一个模块化的门户,涉及到页面和其他应用的沟通,我的版本和页面。虽然看似简单,但如果设计不合理,模块化门户的代码优雅性、崩溃量、页面退化、操作协作等方面都会受到影响。
对于页面之间的跳转,我们的一般做法是:
如果这个类页面没有隐式跳转功能: 那么直接在其他页面首先 获取intent(getContext(),TargetActivity.class(,然后intent添加参数。 最后starActivity(getContext(),intent)。 在目标activty的onCreate()里面getIntent().getString(xx_key,defaultValue)等获取参数; 如果xx_key对应的value不合法或者解析错误,比如movieId=0,或者等于“”。那么应该跳转到一个其他页面或者跳转失败。如果这个页面配置了隐式跳转功能: 那么在其他页面你首先得创建一个createXxxActivityIntent()的utils方法,在里面传入落地页的path,参数key,参数value。 在manifest中声明。 在目标activty的onCreate()里面getIntent().getData().parseBoolean(xx_key,defaultValue)…等获取参数 如果xx_key对应的value不合法或者解析错误,比如movieId=0,或者等于“”。那么应该跳转到一个其他页面或者跳转失败。- - 4.4.5.2 android原生页面跳转存在的问题来说说这个原生页面跳转的问题吧~
在获取参数的时候,需要写一大推的intent.get(xx),如果这个页面既含有隐式跳转,又含有显示跳转,那么肯定上面那个过程都需要,这样在onCreate()里面就会非常的乱。要进行if else如果想进行隐式跳转,那么都需要在manifest进行注册intent-filter。一是麻烦,二是我需要在另外一个地方去配置某一个activity的东西,管理不方便。需要另外写一个utils获取隐式intent。没有降级策略,如果运营配错了,那么只能到错误页面,而无法进行一个补救措施,比如进入i版页面。开发人员或者后台配置错误参数的时候,我们需要写兜底逻辑。每一个页面解析都需要写一段相同的逻辑。如果一个页面需要登录用户才可以打开的权限,那么我们经常会写if(isLogin()){//跳转页面}else{//跳转到登录页面} ,每次操作都要写这些个相同的逻辑。如果你觉得这方面没有那么多要求,对于页面之间的跳转,为了不耦合其他模块的类,所有页面都可以采用隐式跳转机制。这已经基本满足情况了。但我还是想说一下阿里推出的开源框架Arouter。它具有拦截功能,使跳转失败可以降级(如呈现I版页面),使页面具有登录用户可以打开的权限;获取参数的统一方式等。挺好的。基本解决了以上问题。具体就不展开了。具体来说,你可以看到开源的最佳实践:“Android平台页面路由框架ARouter”
4.5模块/页面之间的通信-4.5.0使用视图模型在页面之间共享数据
这一段是新增加的。我觉得这里比较合适。ViewModel是谷歌引入的生命周期组件中的一个新类。官方文件声明使用ViewModel可以解决页面旋转等配置变化时的数据保存问题。想了想,觉得也能起到解耦页面内数据共享的问题。
举个我之前遇到的例子:pm让我做一个页面完成后的嵌入点。嵌入点需要页面的movieId信息,但是需要嵌入点的块中没有movieId。而且我的街区级别很深。如果我想得到movieId,我需要把它从活动页面级转移到我的块,这就不可避免地导致了中间层的耦合和方法的创建。当时觉得真的是大事。当时,需要像eventbus这样的事件监控形式。我只需要把数据放在总线上,然后就可以很容易地在这个页面的任何地方获得它。总结一下:说白了,当需要在块/片段页面之间使用对方的数据/视图时,不需要在它们之间进行硬性引用,只需要活动的上下文参数就可以获取对方的数据/视图,从而交换数据,访问视图。但是页面的上下文是系统类型,容易获取,没有耦合。
具体使用请参考我之前写的一篇文章,用ViewModel分享页面中的数据:ActivityDataBus
-4.5.1为什么要去掉eventbus,使用广播
如果到了这个阶段,一个页面已经被拉出来,剩下的就是与其他模块和其他页面的交互。
如前所述,serviceloader和routing方法都不能做这些事情。首先想到的是使用eventbus来做这些事情。使用Eventbus的前提是需要定义一些事件事件。例如:
但是如果把业务代码模块化,就有一个尴尬的问题:Event在哪里?因为很多库需要监听这个Event,所以只能将Event沉入基本库。结果基本库越来越大,分不开了。在这一点上,《微信安卓模块化架构重构实践》也提到了这件事,并且创造性地使用了一种叫做的方式。api”来解决这个问题。原理是将公共接口沉入基础库,供其他模块在编译时使用,而这段代码的维护仍在非基础库中。这个基础库就不扩展了,代码维护的责任制更清晰了,挺好的。可惜最近没多少时间写这个gradle插件了。不知道哪位读者有时间和兴趣来实现这个插件。意义还是很大的,基础库的代码不会越来越膨胀。Eventbus不仅扩展了基础库,还存在一个无法在应用之间进行通信的问题。我们用广播代替eventbus。android引入的LocalBroadcast实现机制只是一个循环处理器,并维护一个全局映射。性能类似于事件总线,使用字符串而不是事件模型来匹配事件。如果我们使用一个接口来包装BroadcastManager,那么我们可以在应用程序内部使用域内广播,对于模块化的lib,我们可以使用域外广播来在应用程序之间进行通信。
-4.5.2不要乱播
如果在项目中大量使用eventbus,会看到一个类中有很多onEventMainThread()方法,写起来很爽,读起来很痛苦。如果项目中有许多地方可以发送此事件,则有许多地方可以接收此事件。当你拆分代码的时候,你不敢轻举妄动,怕哪些事件没有收到。广播类似于eventbus。如果一个项目中有很多相同事件的发送和接收,项目的可读性和可维护性就会变得相当差。这种情况在敏感数据的同步中尤为突出:
实际上,对于敏感数据的同步,不需要发送广播或eventbus进行同步。同步可以通过在数据库的帮助下本地化所需的数据来实现。总的想法是,我们从网络上获得的所有数据都与数据库同步。当用敏感数据填充视图时,所有使用的数据都来自数据库。当页面返回时,如果页面没有触发填充敏感数据视图的逻辑,则在onResume()手动调用,即:
那么模块/页面之间的通信一般就完成了,这里没有太多的工作要做:
4.6 lib独立运行
-4.6.1为什么需要与主机app流程沟通
此时,业务模块可以作为库放在宿主应用程序中,也可以作为应用程序独立运行。作为图书馆,很好理解。就像文章前面的问答模块一样,给宿主app添加几个activty的shells,然后在lib中添加页面,再在manifest中注册,也就是:
当然,如果需要做一些actionBar交互,需要在主机activty中编写相应的逻辑。整个app的框架图如下:
当需要调试业务lib时,我们需要让lib独立运行,如前一篇文章中的问答业务模块演示所示。这时候,就有问题了。我们的lib独立运行时,账号数据从哪里来,如何获取与app的地理位置和城市相关的数据?读者可能会说这些不是服务?服务,不应该像网络加载和图片加载一样用serviceloader加载吗?原则上是这样的,但是账户等一些信息的服务实现类并不是那么容易从主机app中拉出的,因为服务实现类需要在应用中进行初始化,还有很多其他的事情需要考虑。所以真实的账户信息不是那么容易通过之前的方式获取的,那怎么办?最简单的方法就是创建假数据,比如创建自己账号的信息,作为服务实现类使用。但这种情况下,账号信息只能属于一个人,修改账号信息不可行,账号无法注销。所以要想新办法。
-4.6.2与主机应用程序流程的沟通流程
最后发现,如果我们自主运行的lib能够监控主机app的账号、位置、城市、登录类型、设备等信息并进行同步,那么自主运行的lib中的所有信息都是真实动态的。主机app注销时,lib中没有登录状态。具体操作是:
在宿主app中,我们提供一些contentProvider,各方法提供的内容就是宿主app真实的的账户等数据。当对宿主app账户等信息改变时,通知contentProvider的监听者,比如:publicationeventmainthread(LoginEvent LoginEvent){
getContext()。getContentResolver()。notifyChange(Uri . parse(" content://com . Mao Yan . Android . RuneNV/login session "),null);
}
在独立app中,其扮演contentProvider的监听者:mcontentresolver . registercontentobserver(Uri . parse(" content://com . Mao Yan . Android . runen v/device session "),false,newContentObserver(null) {
@覆盖
publicationchange(BooleanSelfChange){
super . OnChange(SelfChange);
reloadeenvironment();
}
});
这样一来,lib中的account等数据与宿主app的数据是一致的。我们使用服务接口包层,使使用模式与之前的服务使用模式一致。
总体示意图如下:
主机app注销时,lib中没有登录状态。让我们看一下演示: