GithubApp的软件系统设计方案
项目背景介绍
-
本目的目标是实现一个手机端的github应用,基于的开发语言是Android和Flutter,之所以选择用Flutter是考虑到其作为一种新的技术,抹平了Android和Ios之间差异性,是一项火热的新技术。
-
基于此本项目实现了Github的各种功能。主要包括,Login、Star、Fork、Comments、Follow、Clone、Git Branch、添加书签、查看文件树、阅读Markdown文档、切换主题等功能。所需要实现的页面包括Star、Trendings、Notification、News、Repos、Bookmarks、FeaturedTopics以及个人主页和项目主页等。
-
在网络通信方面,本项目也是采用了最新的GraphQl和最火热的Restful向Github请求数据,以实现用户各类信息的显示和操作。
-
为了提高项目的效率和减少内存泄露和系统卡顿,我们集成了Okhttp、Glide、ButterKnife、ARouter、Toasty、Flutter Boot和Flutter Boost等开源框架。
-
在实现了基本的功能之后,本项目又对github的app进行了全方位的优化,包括
- 调整代码架构,模块拆分为MVP模式,分层架构,降低模块耦合,提高代码复用。
- 集成Leakcanary,优化内存,防止OOM。
- 集成Blockcanary,优化多线程带来的卡顿。
- 布局、渲染优化--调试GPU使界面70%为蓝色,防止过度绘制。
- 优化网络速度,通过连接复用、Gzip压缩、断点续传、请求合并降低网络延时。
系统架构
MVP对逻辑进行了严格的提取,Presenter用于连接Model和View,负责所有逻辑处理(不仅限于表现逻辑,而是具体的业务逻辑)。
在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。Presenter就像View和Model的发言人(契合Presenter的字面义),它们相互之间是不可见的,只能通过Presenter来“传话”。View向Presenter请求数据,Presenter把数据送到Model,或者Presenter监听Model的数据变化,接收View层的动作,通知View层的视图发生变化。
由此看出,MVP代码解耦性应该是最好的、而且代码结构清晰、可复用性高、扩展性高,方便单元测试。

MVVM和MVP比较
MVVM的双向绑定技术和数据状态管理技术是它的技术亮点,但这也会带来一定的问题,即View和ViewModel之间界限不清,对初学者而言写出结构清晰的代码较难;绑定带来的就是View不够灵活,复用性不高;而且绑定使得bug不易调试。我们项目对View和Model数据更新的需求不高,而且希望能写出结构规范、清晰的代码,所以我们不选择MVVM。
MVC和MVP比较
Android开发天然的就是MVC结构,Activity对应了MVC中的V和C,随着项目的深入,Activity处理的东西越来越多,代码也越来越臃肿,而且在修改需求时,因为V和C的耦合性,即使是一个小小的View的改变,也要在Activity臃肿的代码中找上半天,这对于程序员而言是件非常痛苦的事。
为了解决这种不必要的痛苦,提高开发效率,MVP把逻辑层抽出来成P层,Activity只充当V的角色,业务逻辑控制交给了Presenter。要是遇到需求逻辑上的更改就可以只需要修改P层了或者遇到逻辑上的大改我们可以直接重写一个P也可以,所以MVP模式对于APP来对控制逻辑和UI的解耦来说是一个不错的选择,所以我们最终选择的是MVP架构模式。
接口设计
本项目采用GraphQl的API接口设计,原因是,相比于Restful,GraphQL 为API 中的数据提供了一套易于理解的完整描述,使得客户端能够根据自己的需求,准确地获得需要的数据,而且没有任何冗余。其基于类型和字段的方式进行组织,有着添加字段和类型而无需影响现有查询的特点。因此,本项目通过Github API V4设计了如下几个主要的接口。
[官方地址](https://docs.github.com/en/free-pro-team@latest/graphql/overview/explorer)
(1) 接口: getUserStarredRepoList
接口说明:用于获取用户star过的版本库
接口参数:用户的唯一身份标识、排序方式(时间升序、时间降序)
返回结果:star的版本库名称、拥有者的头像、简介、语言(可能为空)
(2) 接口:getLoginInfo
接口说明:用于获取用户的基本信息
接口参数:用户的唯一身份标识
返回结果:用户的头像、简介、邮件地址
(3) 接口:getNotification
接口说明:用于获取与用户相关的所有Issues
接口参数:用户的唯一身份标识、排序方式(时间升序、时间降序、是否closed)
返回结果:Issues的相关信息(Issues的id,评论的数目、issue的名称、评论数目)
设计模式
网络层的责任链模式
思是用来处理相关事务责任的一条执行链,链上拥有若干节点,,如果某个节点处理完了就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。举一个百科上的例子:
假天数如果是半天到1天,可能直接项目经理批准即可;
如果是1到3天的假期,需要项目主管批准;
如果是3天到30天,则需要部门经理审批;
其中本项目使用的网络层责任链模式如下:
- 重试拦截器 (RetryAndFollowUpInterceptor)
- 基础的拦截器(BridgeInterceptor)
- 缓存拦截器 (CacheInterceptor)
- 连接的拦截器(ConnectInterceptor)
- CallServerInterceptor

基于上述的责任链模式,本项目的网络请求的历程图大抵可以总结为下:

此外,在okhttp中,还是用了以下的设计模式:
- 建造者模式:
将对象的创建与表示相分离,使同样的构建过程产生不一样的表示,我们可以将这样的设计模式我们称之为建造者模式,也称构建者模式。一般用于比较复杂的构建对象。 - 工厂模式:
OkHttp中的工厂模式的使用有CacheStrategy.Factory,这是一个简单工厂模式,主要也是用于生成一个CacheStrategy对象。然而工厂模式和建造者模式的区别为建造者模式和工厂模式都是用于生成一个对象,都属于创建型的设计模式,但是他们的侧重点是有所不同的。可以看到工厂模式的生成对象的过程更复杂,侧重于对象的生成过程,而建造者模式更侧重于各个参数配置的组合,生成过程反而不那么复杂。 - 门面模式(外观模式):
提供一个统一的接口去访问多个子系统的多个不同的接口,它为子系统中的一组接口提供一个统一的高层接口。也称外观模式。
简单说就是提供一个类提供一些接口供客户端使用,隐藏内部调用的复杂性。OkHttp中提供了一个类OkHttpClient来供用户使用,其内部关联了大量的对象,以及处理细节,但是用户只需要根据OkHttpClient提供的接口使用即可。从这方面看确实大部分的开源框架都在使用门面模式。
View和GroupView 组合模式
-
本项目在事件分发机制中调用了组合模式,具体来说是当一个应用启动的时候,会启动一个主Activity,Android系统会根据Activity的布局来对它进行绘制(而ViewGroup还需要负责通知自己的子View进行绘制操作)。
在本项目中requestLayout、bringChildToFront等较为常用,ViewGroup除了实现的这俩个接口与View不同以外,还有重要的一点是ViewGroup是抽象类,其将View中的onLayout方法重置为抽象方法,也就是容器子类必须实现该方法来实现布局定位,我们知道View中的该方法是一个空实现,因为对于一个普通的View来说该方法没有实现价值。但是ViewGroup不一样,要必须实现。除此以外,在View中比较重要的两个测绘流程的方法onMeasure和onDraw在ViewGroup中都没有被重写,相对于onMeasure方法,在ViewGroup中增加了一些计算子View的方法,如measureChildren等;而对于onDraw方法,ViewGroup定义了一个dispatchDraw方法来调用其每一个子View的onDraw方法,由此可见,ViewGroup真的就像一个容器一样,其职责只是负责对子元素的操作而非具体的个体行为。
Context的装饰者模式
Context在本项目中是一个访问application环境全局信息的接口,通过它可以访问application的资源和相关的类,其关系如下图所示:

具体来说:在本项目中Activity是一个装饰者,用来装饰Context的实现类ContextImpl。当我们创建完Activity时,此时Activity中的mBase指向的就是我们通过createAppContext,其赋值的地方就是app.attach(context)。同时ContextImpl也通过设置setOuterContext与Activity关联。同样的我们创建Application和Service的时候也会创建对应的ContextImpl并且设置与ContextImpl关联。,当我们在Activity中或者Application或者Service中或者调用某个Context的getBaseContext方法的时候,获取的便是这个ContextImp,以此实现了装饰者模式的委托。其具体的交互流程如下所示:

数据模型设计
repository信息
| 字段 | 类型 | 描述 |
|---|---|---|
| title | String | 项目名称 |
| createdAt | String | 创建的时间 |
| number | int | 有关项目的成员个数 |
| description | String | 有关项目的描述 |
| language | String | 项目所使用的的语言 |
| avatarUrl | int | 项目拥有者 |
| description | String | 有关项目的描述 |
| language | String | 项目所使用的的语言 |
用户信息
| 字段 | 类型 | 描述 |
|---|---|---|
| String | 用户的邮件地址 | |
| name | String | 用户的姓名 |
| avatarUrl | String | 用户的Url头像 |
| login | String | 用户的id,唯一标识 |
| bio | String | 用户的个人简介 |
项目源代码文件结构
-
网络操作层
-
flutter与原生模块桥接层
-
原生部分

-
flutter部分

-
Flutter页面实现部分

-
原生页面实现层

- 布局xml文件

网络层的技术选型
选择okhttp的理由
市面中比较成熟的网络框架主要有 Volley、Retrofit和Okhttp,选择Okhttp的原因如下:
- volley是由Google公司推出的小巧轻便的异步请求库,优点简单和速度快。然而不选择它是因为以下两点
• 其不支持同步加载,而项目中的部分信息(如主页 中用户的login和头像)需要用到同步加载。
其只是用同步主要基于两方面的考虑:
ⅰ. 用户的个人信息在很多其他页面都会用到,并且只是请求的login和头像,数据量不大,通过阻塞获得该用户的基本信息,可以在其他很多页面直接复用,
ⅱ. 通过阻塞该小数据量的信息,可以第一时间判断用户与github服务器的网络连接是否正常,避免心急的用户来回切换到其他页面造成资源的浪费)
• 只支持http请求,不支持https。而项目中用到的请求就是基于https的。 - Okhttp是Square公司设计的目前使用人数最多的网络层框架,支持https、同步和异步请求,满足了项目的基本需求GZIP压缩下载(还没深入了解,不说的好)。 同时其还具有如下几个方面的优点
• 内置连接池,支持接的复用。
• 使用了线程池
• 基于责任拦截模式,避免了不必要的请求,以及创建了很好的缓存机制。 - Retrofit也是Square公司的杰作,其默认是在Okhttp之上进行的封装,使得用户编写网络请求的时候更加简单,并且运用了很多设计模式(装饰者、适配器、(动态代理、外观模式)),实现了超级解耦。但遗憾的是,其是基于RestfulApi的,然而本项目用的是GraphQl因此不予考虑。
系统概念原型的核心工作机制
Flutter与原生的通信,在宏观上来讲如下所示:

各个部分的作用依次是
Native层
- Container:Native容器,对应代码BoostFlutterActivity
- Container Manager:容器的管理者,对应代码FlutterViewContainerManager
- Messaging:基于Channel的消息通信,对应代码FlutterBoostPlugin
Dart层:dart代码不在项目之中,具体存储在flutter的sdk缓存里,double shift可以找到
- Container:Flutter用来容纳Widget的容器,对应代码BoostContainer
- Container Manager:Flutter容器的管理,提供show,remove等Api,对应代码BoostContainerManager
- Coordinator: 协调器,接受Messaging消息,负责调用Container Manager的状态管理。对应代码ContainerCoordinator
- Messaging:基于Channel的消息通信,对应代码BoostChannel
本项目最大的难点便是将flutter与Android的原生进行整合,其二者之间的桥接方式复杂且耗时,具体实现如下图所示:

此外本项目的启动涉及了ClassLoader中的类加载机制,其具体集成关系如下所示:

总结
通过此次对工程实践项目的在此总结,对软件开发过程中的软件系统设计方案又有了更新和深刻的认识,主要做了以下的工作。
- 通过总结项目的设计方案和API解口发现了项目的过拟合现象,并修改了接口使得项目更加符合软件体系的要求规范。
- 再次练习了UML图和系统概念原型图,对其有了更深刻的认识和理解。
- 由于本项目的数据均是通过网络请求获取的github官方数据,因此不涉及数据库部分的结构部分。
- 整合源代码目录的文件结构,将其按功能类型进行分类。
- 记录了在网络架构模块的技术选型。
- 整理总结了Flutter和原生的核心工作机制,并通过代码流程图对代码运行的逻辑进行了更深一步的梳理。

浙公网安备 33010602011771号