如果你要设计App的整体框架,你首先要明白我们在做什么。
一般来说,我们与网络交互的方式有两种:主动请求(http)和长连接推送。
我们根据网络交互数据的方式来谈谈我们开发的App的类型和特点:
一般我们做的app都是类型1。简单来说,这类app的主要工作是
从服务器拉取数据并将其显示给用户。 将用户在客户端修改的数据上传到服务器进行处理。
因此,该类App的网络调用相当频繁,需要考虑该App在网络较差或无网络等情况下的运行情况。 成熟商业应用的网络调用一般遵循以下流程:
UI发起请求 - 检查缓存 - 调用网络模块 - 解析返回JSON/统一处理异常 - JSON对象映射为Java对象 - 缓存 - UI获取数据并显示
我们可以看到明确的职责分工,即:数据采集; 数据管理; 数据显示
一旦确定了自己的职责,就可以开始做正事了。
1. 传统Android App架构
Android最原生、最基础的架构可以理解为MVC。 控制器是Activity和Fragment。 然而,这两者掌握了Android系统中的绝大多数资源,并且直接控制内部的View。 因此,传统的Android App一般都是以Activity和Fragment为核心,将网络模块、数据库管理模块、文件管理模块、常用工具类等分离成几个工具包供Activity和Fragment使用。
这是一个比较基础的Android项目架构。 市场上的大多数应用程序都是这种形状。
优点:开发简单,面向页面; 建设水平好的话,项目基本已经模块化了。 基于Activity和Fragment这两个神一般的存在,很多事情都可以直接完成,不需要绕道。
缺点:由于是面向页面的,维护比较困难。 有些需要共享的业务逻辑会很烦人。 不要重复你自己。 您想重复吗? 如果不想重复,就得写模块。 随着时间的推移,项目最终会变成一堆杂乱的小模块。 另一方面,测试也很困难,因为所有的数据处理都在Activity和Fragment中。 如果想先用假数据来显示,就必须直接改变Activity和Fragment的数据控制逻辑。
还有一个最烦人的问题,就是随着业务的复杂化,Activity和Fragment的代码量急剧增加。 例如,如果你只管理一个电商应用购物车中的商品,无非就是查看、删除、修改。 调用,列表管理,300多行代码都要搞定。 如果现在添加优惠券提醒怎么办? 光有优惠券还不够,还有满减,还有订单组合,还得算运费。 还需要能够领取优惠券……哦,我忘了,一般都会有商品推荐的。 好吧,现在有两个列表需要管理。 你认为2000行代码就能停止CartActivity吗?
从上面缺点的描述中我们可以看出一个很大的痛点是:Activity和Fragment不应该照顾那么多的数据处理逻辑。
2. 分层架构
如果你仔细观察你的项目,你可以发现大多数数据处理代码不需要使用Activity和Fragment持有的资源(比如Context),很多时候我们需要多个页面共享一组数据和请求逻辑,这是非常困难的。 经典的例子就是应用程序中的User对象,它一般是全局单例。
写了这么多全局数据源,很容易想到将数据处理抽取到一个统一的层,向上层提供数据接口。 上层不关心数据的来源(内存、缓存、网络),因为不需要从Activity中获取资源,而是从Fragment中获取资源,其主要工作是数据处理,所以这一层没什么可关心的与UI一起做,这大大提高了可重用性。 我将这一层称为DataManager 层。
这是我的一个项目的包结构
Activity和Fragment剥离数据处理职责后,它们持有DataManager的引用,负责获取和显示数据,将数据传递给DataManager,并且从不进行网络请求或缓存读写。
举个例子,页面加载
一般来说,分页加载接口返回的数据是这样的
{
"code":0,
"message":"success",
"data":{
"page":1,
"totalPage":10,
"pageSize":20,
"total":200,
"list":[......]
}
}
传统的写法中,一般会在Activity/Fragment中缓存page、totalPage、pageSize来进行分页请求,根据请求结果刷新数据并判断是否还有; 每个分页接口必须写一次。 如果这个逻辑是放在DataManager中会发生什么?这是我写的
//定义回调接口
public interface ActionCallback<T> {
void onSuccess(T data);
void onFailure(String message, Throwable e);
}
分页加载DataManager实现
public class PageLoadDataManager extends BaseDataManager {
private static final int PAGE_COUNT = 20;
private List<Data> mDataList = new ArrayList<>();
private int currentPage = 0;
private int totalPage = 0;
public PageLoadDataManager() {
// init something......
}
public void loadData(final boolean refresh, ActionListener<Boolean> listener) {
if (refresh) {
currentPage = 0;
}
currentPage++;
RequestParams params = new RequestParams();
params.put("page", currentPage);
Request request = new Request(url, params);
request.request(new RequestCallback(){
@Override
public void onSuccess(JSONObject data) {
if (refresh) {
mDataList.clear();
}
totalPage = response.optInt("total_page");
// 返回数据添加到 mDataList ......
if (listener != null) {
boolean hasMore = currentPage <= totalPage
listener.onSuccess(hasMore);
}
}
@Override
public void onFailure(String message, Throwable e) {
if (listener != null) {
listener.onFailure(message, e);
}
}
});
}
public List<Data> getDataList() {
return mDataList;
}
}
Activity/Fragment初始化DataManager后,只需要将数据源绑定到Adapter即可。 loadData设置的回调告诉上层是否还有数据。 UI层调用adapter.notifyDataSetChanged(); 至于数据从哪里来,分页逻辑根本不重要。 需要UI层管理。 UI层只需要通过loadData(refresh)告诉DataManager是否需要重新加载分页,完美契合下拉刷新的逻辑。
当然在此基础上实现数据库缓存读写是没有压力的。 DataManager还可以轻松实现对某个数据的多个接口的统一管理,通过单例模式或者其他管理方式将数据分布到多个页面。
优点:大大减轻Activity/Fragment的压力,实现数据的统一管理,DataManager层成为独立于UI的AppSDK层
缺点:需要添加嵌套回调。 这个问题在引入RxJava之后就完美解决了。
事实上,至此,几万行代码已经能够满足大部分中小型App的框架需求了。 而且,分层架构具有数据统一处理、代码复用性高的特点,使其成为项目中按照框架思想实现业务最快、最可靠的方式。 开发方法。
我认为优秀的框架一个很重要的特点就是能够方便业务开发,而不是给开发带来麻烦。 例如,分层设计后,无论开发时间多么紧张,依靠分层框架仍然是最快、最安全的开发方式。 如果一个接口直接写在UI中的话,就意味着数据管理层提供的一切便利都不能直接使用游戏开发 数据库,而其他UI如果使用这个接口,就得重新复制粘贴并更改。 相反,依靠框架,网络调用只实现一次游戏开发 数据库,上层可以复用这个业务接口(典型:关注、收藏等)
即便如此,随着项目规模进一步增大,DataManager和Activity/Fragment的压力依然会增大,更高的测试要求需要Activity/Fragment代码进一步分离。这时候可以看看MVP和MVVM
3.MVP架构
MVC的C既保存了具体的Model,又保存了具体的View,所以C非常臃肿。 即使从层次架构中去掉DataManager,本质上仍然是MVC架构,而MVP和MVVM都是C持有特定的View。 我写过一些文章,其中MVP就是把大量的View Model交互分离出来,交给Presenter,Presenter持有抽象的View。
去年写这个答案的时候,我是这么写的:
看起来很不错,但是在尝试应用的时候发现网上很多博客使用的Demo写法并不实用。 就是抽象出很多View接口,然后创建一个Presenter类作为Presenter。 这样我就可以写一些简单的列表获取和登录了。类看起来很漂亮,似乎实现了代码分离,但是当业务场景变得复杂时就变得有点痛苦了。
那时我只是在尝试。 这是一个很感性的理解,觉得不切实际,我也没有多说什么。 当时我正在做一个购物中心应用,在使用MVP编写购物车等复杂场景时遇到了很大的困难。 这让我怀疑我使用 MVP 是否给自己带来了麻烦。 本来写登录信息还可以,但是当写进购物车的时候,我开始怀疑人生了。
一个ICartView需要写多少个接口? 购物车查看、删除和修改、优惠券充值和折扣查看、订单领取、价格计算、运费……二十个接口缺一不可吧? 那么这个抽象View除了供CartActivity使用之外,还有其他用途吗?
如果我写ICartView、IBonusView、IXXXView...但是有些接口不需要删除购物车列表,是否需要进一步细分? 然后让Activity实现一堆接口? 既然如此,万一有一天需求发生变化怎么办……
Presenter听起来很酷,它是leader,但是它没有Activity和Fragment的资源。 怎样才能让它占主导地位呢? 当我需要获取系统的一些信息(需要Context)时该怎么办? 不持有Context可以再次打开界面吗?
写那么多接口、接口实现、Presenter、几百行代码和n个类,只是为了把1到200行代码移出Activity?
放弃吧...
后来Google出了TODO-MVP,但是发现它和上面的demo写法一样麻烦,我也没有实际使用。 后来反编译了一个大型App,发现正好是MVP架构,于是仔细看了下代码。 就像我最初的想法一样,一个IXXXView应该有多少功能就写多少接口。 看了Presenter的实现,突然明白为什么感觉不实用了:
任何尝试构建其他东西来代替 Activity/Fragment 都是自找麻烦。
MVP就是一个典型的例子
既然MVP将Activity/Fragment抽象成了View氛围,那么就意味着当它作为抽象View使用时,生命周期、Context等极其重要的资源Presenter看不到,但不使用这些东西也是不可能的。 。 为了让Presenter能够使用这些,Presenter必须持有Context并绑定Activity和Fragment的生命周期。 即便如此,在某些需要确定Activity和Fragment的使用的情况下,仍然需要使用强制转换。
正是因为Presenter的“一统”,Presenter和Activity/Fragment绑定度高游戏开发素材,Presenter和IXXXView的复用性很少。
这是我对当前 Android MVP 的看法。 如果谁有更好的实践经验,可以在评论中告诉我。
4.MVVM架构
在我学习MVP的时候,MVVM也是一个非常流行的概念,并且有很多基于数据绑定框架的demo。 不过,我看完这个方案后,立即拒绝了。 大多数应用程序从接口获取数据后都会进行数据转换。 ,即使获取图片URL,也会在Java层添加后缀来获取缩略图。 有些要根据数据源来控制View的大小、显示和隐藏。 XML 能做的太少了。 如果Model与XML绑定,大规模应用我们会面临多少陷阱...
与MVP相比,MVVM最重要的概念是“数据绑定”。 Presenter还持有抽象View。 ViewModel 甚至不需要这个。 View通过ViewModel订阅自己需要的数据源,ViewModel向View提供变化的数据。 界面。 当View的操作导致数据改变或者数据源改变时,ViewModel通过订阅的方式通知View,View更新视图。
这就是 MVVM 吸引人的地方。 ViewModel只提供数据订阅和数据接口,与UI分离。 ViewModel比Presenter小,复用性比Presenter好很多,并且可以基于分层架构稍加修改。 可以实现。 唯一的痛点是:如何实现数据绑定?
之前提到的数据绑定不太令人满意,但 Google I/O 2017 上发布的 android-architecture-components 很好地解决了这个问题。
ViewModel组件规范了ViewModel的位置、生命周期、生成方法,以及一个Activity下多个Fragment之间共享ViewModel数据的问题。
LiveData组件在Java层面提供了View订阅ViewModel数据源的实现方案,非常轻量级。
ViewModel的引入可以很好的处理Activity销毁重建时大规模数据恢复的问题,以及多个接口依赖一个接口返回数据的场景。 在这两个组件的规范下实现MVVM架构将是非常容易和有意义的。
由于我在项目中大规模使用了RxJava,因此我使用RxJava解决方案实现了数据绑定。
有关使用 android-architecture-components 组件实现 MVVM 的信息,请参考
googlesamples/android-架构-组件
关于新的MVVM结构的思想,我推荐这三篇文章。
Android 官方架构组件指南
Android官方对ViewModel的架构组件介绍
Android官方架构组件介绍-LiveData
5.组件化和插件化
这两个概念这两年非常流行,但是需要注意的是,这两个概念和上面的东西不是一个级别的。 组件化和插件化是比上面提到的混乱更高层次的东西。 是针对整个大项目下的几个小模块,而这些小模块如何构建还是要看上面的内容:)
6. 总结
一般来说,我们做app的时候,比如小型外包,其实不会使用MVP、MVVM这样的架构。 分层架构足以让我们快速高效地开发应用程序。 选择哪种框架不仅取决于您的应用程序的类型。 ,这还取决于您的应用程序的规模。 基于分层架构,只要接口实现得足够好,代码足够规范,切换到MVVM这样的架构并不困难。
如果你有现成的、成熟的框架,那是不言而喻的,但如果你的应用只有几千行代码,为了追求MVVM,你写了十几个类,踩了很多坑,只是把一个Activity里几十行代码画成ViewModel的话,那不是完全不一样了吗?
最后分享一下我自己的代码库和基础框架项目。 是否有集成RxJava的基础分支,集成RxJava的分层框架分支,以及使用android-arch-components的mvvm-rx分支。 目前,网络通话仍处于开放状态。 这不是很完美。 后面我会逐步完善演示示例。 希望对大家有所帮助。
ShonLin/QuickDevFramework
- - - - - - - 结尾 - - - - - - -
参考
【翻译】Android应用架构-上网的日子
Android App整体架构设计的思考(一)
如何构建一个高质量的Android项目框架并详细描述框架的结构? ——马天宇的回答
Android MVP详解(第1部分)
Android 官方架构组件指南
Android官方对ViewModel的架构组件介绍
Android官方架构组件介绍-LiveData