您的位置:首页 > 运维架构 > 网站架构

Android应用架构的一些思考-框架模块化

2017-02-04 10:56 651 查看
系列文章导航:

1. Android应用架构的一些思考-从零开始

2.Android应用架构的一些思考-基础版架构的整体搭建

3.Android应用架构的一些思考-框架模块化

在Android开发的路上,会经常经历到如下的问题:每建一个新项目,都要编写基类,从老项目中把工具类复制过来,编写空页面最后把整个工程搭建起来;然后建立一个任务美其名曰:框架搭建:) 这样折腾一下基本一天两天时间就没有了,而且有的时候有些以前写过的比较少用的代码,如果新项目中用到,还要打开老项目找,适配到新项目上,贼浪费时间。在初学Android做各种Demo的时候,我就在想,有没有办法做一个自己的代码库,想用什么代码就快速拿来用,一开始考虑用工具类,但是很明显一大堆乱七八糟的工具类使用起来并不是很方便,也不适用于封装一些需要系统环境的功能。

在有了分层框架这个思路之后,我就开始写Demo,在写Demo的过程中有一天突然就像被雷劈了一样脑子里突然有个灯泡通了电:既然编写的基类和工具类都已经是跟具体业务无关了,那么为什么不将其抽出来做成一个Module,变成项目无关呢?

很幸运Android Studio的工程管理模式为我这个思路提供了很大便利,将框架变成框架Module作为一个Library Module导入工程变成了一件比较简单的事情,Library的Manifest也可以和主工程的Manifest合并。于是我立即开始构思如何搭建我自己的这个“代码库框架”。

代码如需参考,请看我的github:QuickDevFramework

0. 整体结构划分

整个框架模块的核心思路就是把一个项目分成至少两部分:app工程模块(app)和框架模块(framework),app module主要是一个app的业务实现,比如UI,接口实现,第三方SDK集成相关组件,项目的自定义View和自定义组件等,而框架则要尽可能的做到项目无关,提供app模块所需通用工具类,部分封装的功能则通过基类供app工程的业务组件类继承来实现。最终期望实现的效果是新建一个项目时,导入框架模块,继承框架基类,即享受框架提供的全部功能,大幅减少框架搭建的工作量。

整个模块的效果图如下:



基于框架模块的工程结构图

这样规模的框架必然会涉及到第三方库的选择,比较常见对框架影响比较高的有,网络调用库,比如Retrofit、volley还是其他;异步处理库如RxJava;组件间通信如EventBus。这些库的使用必然是框架层面的,会直接影响到框架模块对于不同功能的封装,而且根据使用的架构模式(MVC/MVP/MVVM),框架的结构也会有所不同,因此我自己准备将框架封装为若干版本以供使用,目前仅有基于Retrofit的基础版本,后续的会慢慢跟上。

1. 功能封装

提前说明一下:因为代码量比较大而且都是简单代码的封装,在这里部分功能的叙述上为了保证阅读连贯我就不贴代码了,有需要看代码的同学可以在我github项目上查看。github项目:QuickDevFramework

我个人设计的框架模块整体包结构如下:



activity, adapter, dialog, fragment, manager都用来存放相应组件的基类和框架会用到的实现,net是网络模块的封装,support包存放了一些常用API的封装,而util包则只提供简易的工具类,widget存放项目无关的自定义控件。
项目虽然是一个框架,但也是自己的代码库,处于长期更新的状态,目前已经封装了一部分常用的功能。后续会支持更多,对于一个框架模块应该封装哪些功能,我挑几个重要的来说一下:

动态权限管理
在Android 6.0添加了动态权限之后,对于动态权限管理的适配成了App必须要做的事情,总不能万年卡着API 23以下吧,默认的权限管理由ActivityCompat类的一系列Permission相关方法去处理,需要申请权限,然后在Activity/Fragment中的OnRequestPermissionResult中回调处理。如果不作任何封装去搞动态权限适配,那真是要把代码写的乱七八糟……
目前我在框架中对权限管理做的封装是由PermissionWrapper类来处理的,将权限检查、申请、回调、用户完全禁止时的操作封装为一个流式调用,需要在BaseActivity基类中的OnRequestPermissionResult中调用PermissionWrapper的handleCallback()去处理权限申请的回调,这样在继承了基类的Activity下做需要申请权限的操作时,只需要这样做即可:
PermissionWrapper.performWithPermission(Manifest.permission.CAMERA)
.showRationaleBeforeRequest("请授予相机权限")
.doOnProhibited(new PermissionProhibitedListener() {
@Override
public void onProhibited(String permission) {
PermissionWrapper.showPermissionProhibitedDialog(getActivity(), permission);
}
})
.perform(getActivity(),new RequestPermissionCallback() {
@Override
public void onGranted() {
AlertDialogWrapper.showAlertDialog("权限已授予");
}

@Override
public void onDenied() {
AlertDialogWrapper.showAlertDialog("未授予权限");
}
});

Dialog封装
Dialog也是经常要使用的功能,Google推荐将Dialog用DialogFragment封装。用于给用户强提示的AlertDialog,经常需要从接口回调或者其他线程中弹出,做统一的封装十分有必要,我在框架中利用EventBus做了两种AlertDialog的封装,一种是应用内的AlertDialog,功能比较多,可以设置确认和取消的回调,另一种是基于Activity封装的Dialog,可以从Service唤起,只能向用户发送消息。
update: 
在近期的测试中,发现使用广播构建的Dialog并不是很稳定,在诸如Activity切换等时机弹出Dialog容易造成Dialog无法正常显示和应用崩溃等问题,因此将框架中的Dialog模块改由透明Activity管理,回调方法等闭包数据通过静态类传输。不过使用这种方法需要十分注意内存泄露的问题。

另一方面,很多App都有使用从底部弹出Dialog这种Dialog样式的需求,有了框架模块便可以很好的封装这样的功能。我在这里采用了基类的形式,让DialogFragment继承BaseBottomDialogFragment就可以使用底部弹出的功能。
除此之外,还有很多特定功能的Dialog,如日期选择,地址选择其实也可以封装使用,我自己的框架中会陆续接入。

网络模块封装
这个我认为是框架封装主要的一部分,因为虽然很多网络异步调用库已经实现了核心功能,但是要使其变得易用且合适自己,必须要做相应的封装。比如Retrofit,Retrofit是一个高度封装的网络框架了。但也有很多常用功能的处理是比较麻烦的。比如之前提到的根据接口选择性的在请求Header中附带Cookie,或者是配置自定义的Headers,网络请求耗时测量等。我在网络模块中对Retrofit的封装主要实现了以下几点,供大家参考:

Retrofit构建类RetrofitApiBuilder,在原有Retrofit构建方式的基础上封装了更为简便的Builder类,将添加自定义Headers等细节交由RetrofitApiBuilder处理,同时设置了一些Retrofit构建所必须参数的默认实现,如ConvertFactory,大幅简化Retrofit Api的构建过程。
ClientApi clientApi = FrameworkRetrofitManager.createRetrofitBuilder("http://www.weather.com.cn/")
.build(ClientApi.class); //这样就完成了RestApi的构建

提供了针对接口的Cookie管理模式,可以通过注解决定接口是否需要在请求时向Header中加入缓存Cookie
ClientApi clientApi = FrameworkRetrofitManager.createRetrofitBuilder("http://www.weather.com.cn/")
.setCookieMode(CookieMode.ADD_BY_ANNOTATION)
.build(ClientApi.class);

https适配, 对于需要客户端证书的https配置只需传入Https必要的参数即可实现对https声明接口的支持
提供了Http请求信息抓取Interceptor类,可以抓取到详细的http请求信息,包含请求耗时等参数
HttpInfoCatchInterceptor infoCatchInterceptor = new HttpInfoCatchInterceptor();
infoCatchInterceptor.setCatchEnabled(true);
infoCatchInterceptor.setHttpInfoCatchListener(new HttpInfoCatchListener() {
@Override
public void onInfoCaught(HttpInfoEntity entity) {
//do something......
}
});
ClientApi clientApi = FrameworkRetrofitManager.createRetrofitBuilder("http://www.weather.com.cn/")
.addCustomInterceptor(infoCatchInterceptor)
.build(ClientApi.class);


Notification封装
Android Notification也是比较重要的一个部分,不过Notification的构建是比较繁琐的,比如icon,contentTitle,contentText为构建一个Notification的必须参数,如果这三个在构建时不配置,代码不会报错,但Notification也不会显示,这样的问题如果以前没处理过就会比较难排查。
另一方面在大部分商业应用中,可以看到对于通知的处理一般是在应用处于启动状态时,点击通知直接打开目标页面;如果应用处于未启动状态,点击通知后先启动应用,再打开目标页面,这样的功能也是必须要自己实现的。

Log封装
Log在框架中也是比较重要的一部分,因为这一部分是跨框架和app的,框架中会使用Log,app中也会使用,而且有很多强大的第三方log库,因此有两种方案,要么使用一种第三方log库比如Logger作为Log管理,要么自己封装,因为要考虑到框架的通用性和可扩展性,我选择在框架中尽量少的使用第三方库,目前除了网络用Retrofit外只使用了EventBus,对于Log的封装我选择了提供如下功能:

格式化输出Log,避免因为输出内容过长导致单条Log无法完全输出内容
构建多行Log输出,避免因为格式化后需要log调试时多个地方集中输出Log时信息过于散乱的问题出现,采用Builder的形式输出Log,在代码片段执行完之后统一输出。
Log输出限制,比如在正式版本中关闭Log,设置Log的输出等级
Log输出的具体实现通过LogInterface来管理,这样就可以根据需求切换不同的Log实现,不会影响到框架内的Log

除了以上内容之外,我认为还有很多其他值得封装的部分,如文件管理,上传下载,应用更新等,有些内容可以在工程搭建完成后进行针对性扩展。

2. 关于框架侵入性

所谓侵入性,即使用该框架时,需要让自己的业务类继承框架基类或者实现框架的接口,也就是程序对框架本身有依赖,剔除框架后程序就无法运行必须进行大规模修改,而非侵入式框架则只需修改对应的内容。
作为一个应用的框架模块,框架模块肯定是又侵入性的,以此来提供一些需要继承才能提供的便利功能,否则这个模块就只是一个工具类库,提供的功能也比较有限,因此我个人认为最好的办法是将框架模块直接作为Module导入以保证针对性开发的便利。
不过作为一个代码库,应该保证除框架主干以外的内容尽量减少对框架的依赖。以便在其他已经成型的项目中需要使用相关功能时能够快速集成。

3. 关于组件化的一些构想

当项目规模扩大的时候,组件化或者插件化成为了目前比较流行的趋势,组件化架构为项目编译速度和并行开发提供了比较可靠的保障。组件化架构,最主要的就是路由中间件的实现,个人理解的组件化架构App的结构如图所示:



在这样的架构中,原来的框架模块成为了处于最底层的为所有业务模块提供支持的Library,因此路由组件的实现其实也可以放在框架模块中作为整个工程的支持,因此在组件化上面,无论使用第三方框架还是自己实现路由中间件,对于已经使用框架模块化的项目影响并不是很大。应该说还是有比较高的灵活性吧。

4. 一点感想

最初做这一个框架项目的目的就是为了减少使代码清晰,减少开发时的冗余工作,结果几个模块封装下来,发现自己的水平还真的是很欠=。= 很多模块封装用了不少时间。
用的开发框架作为自己的代码库我个人觉得是一个很不错的玩法,但这是一项庞大的工程,是在开发过程中不断积累代码,不断提炼,才能保证封装模块的实用性,不可一蹴而就为了封装而封装,在今后的开发过程中还是要继续努力完善。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息