如何理解Android架构

发布时间:2021-10-14 15:42:09 作者:iii
来源:亿速云 阅读:122

本篇内容介绍了“如何理解Android架构”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1. 模块化的意义何在?

1.1 基本概念以及底层思想

所有的模块化都是为了满足单一设计原则 (字面意思理解即可),一个函数或者一个类再或者一个模块,职责越单一复用性就越强,同时能够间接降低耦合性

在软件工程的背景下,改动就会有出错的可能,不要说"我注意一点就不会出错"这种话,因为人不是机器。我们能做的就是尽可能让模块更加单一,职责越单一影响到外层模块的可能性就越小,这样出错的概率也就越低。

所以模块化核心思想即:单一设计原则

1.2 我们要基于哪些特性去做模块化划分?

做模块化处理的时候尽量基于两种特性进行功能特性、业务特性

功能特性

业务特性

为什么说业务特性优先级要高于功能特性?

举个例子如下图:

如何理解Android架构

相信很多人见过或者正在使用这种分包方式,在业务层把所有的Adapter、Presenter、Activity等等都放在对应的包中,这种方式合理吗?先说答案不合理,首先这已经是在业务层,我们做的所有事情其实都在为业务层服务,所以业务的优先级应该是最高的,我们应当优先根据业务特性将对应的类放入到同一个包中。

功能模块核心是功能,应当以功能进行模块划分。业务模块核心是业务,应当优先以业务进行模块划分,其次再以功能进行模块划分。

1.3 Android如何做分层处理?

前端开发其实就是做数据搬运,再展示到视图中。数据与视图是两个不同的概念,为了提高复用性以及可维护性,我们应当根据单一设计原则我们应当将二者进行分层处理,所以无论是MVC、MVP还是MVVM最核心的点都是将数据与视图进行分层。

绊脚石:

//原始逻辑 数据层 Model{     title } UI层 View{     textView = model.title }  //后端调整后 数据层 Model{     title     prefix } UI层 View{     textView = model.prefix + model.title }

起初我们的textView显示的是model中的title,但后端调整后我们需要在model中加一个prefix字段,同时textView显示内容也要做一次字符串拼接。视图层因为数据层的改动而被动做了修改。既然做了分层我们想要的肯定是视图、数据互不干扰,如何解决?往下看...

1.4 Data Mapper或许是解药

Data Mapper是后端常用的一个概念,一般情况下他们是不会直接使用数据库里面的字段,而是加一个Data  Mapper(数据映射)将数据库表转按需换成Java Bean,这样做的好处也很明显,表结构甭管怎么折腾都不会影响到业务层代码。

对于前端我觉得可以适当引入Data Mapper,将后端数据转换成本地模型,本地模型只与设计图对应,将后端业务与视图完全隔离。这也就解决了 1.3  面临的问题,具体方式如下:

数据层 Model{     title     prefix } 本地模型(与设计图一一对应) LocalModel{     //将后端模型转换为本地模型     title = model.prefix + model.title } UI层 View{     textView = localModel.title }

LocalModel相当于一个中间层,通过适配器模式将数据层与视图层做隔离。

前端引入Data  Mapper后可以脱离后端进行开发,只要需求明确就可以做视图层的开发,完全不需要担心后端返回什么结构、字段。并且这种做法是一劳永逸的,比如后端需要对某些字段做调整,我们可以不暇思索直奔数据层,涉及到的调整100%不会影响到视图层

注意点:

1.5 无处安放的业务逻辑

关于业务逻辑其实是一个很笼统的概念,甚至可以将任意一行代码称之为业务逻辑,如此宽泛的概念我们该如何去理解?我先大致将它分为两个方面:

前面我们说到,Android开发应该具备数据层跟视图层,那业务逻辑放在哪一层比较合适呢?比如MVVM模式下大家都说将业务逻辑放到ViewModel处理,这么说也没有太大的问题,但如果一个界面足够复杂那对应的ViewModel代码可能会有成百上千行,看起来会很臃肿可读性也非常差。最重要的一点这些业务很难编写单元测试用例。

关于业务逻辑我建议单独写一个use case处理。

use case通常放在ViewModel/Presenter与数据层之间,业务逻辑以及Data Mapper都应该放在use  case中,每一个行为对应一个use case。这样就解决了ViewModel/Presenter臃肿的问题,同时更方便编写测试用例。

注意点:

2. 合理分层是给 数据驱动UI 做铺垫

先说结论:数据驱动UI的本质是控制反转

2.1 什么是 控制反转?

控制即对程序流程的控制,一般由我们开发者承担,此过程为控制。但开发者是人所以不可避免出现错误,此时可以将角色做一个反转由成熟的框架负责整个流程,程序员只需要在框架预留的扩展点上,添加跟自己的业务代码,就可以利用框架来驱动整个程序流程的执行,此过程为反转。

控制反转概念和设计原则中的依赖倒置很相似,只是少了一个依赖抽象。

打个比方:

2.2 什么是数据驱动UI?

通俗一点说就是当数据改变时对应的UI也要跟着变,反过来说当需要改变UI只需要改变对应的数据即可。现在比较流行的UI框架如Flutter、Compose、Vue其本质都是基于函数式编程实现数据驱动UI,它们共同的目的都是为了解决数据,UI一致性问题。

在当前的Android中可以使用DataBinding实现同样的效果,以Jetpack  MVVM为例:ViewModel从Repository拿到数据暂存到ViewModel对应的ObservableFiled即可实现数据驱动UI,但前提是从Repository拿到的数据可以直接用,如果在Activity或者Adapter做数据二次处理再notify  UI,已经违背数据驱动UI核心思想。所以想实现数据驱动UI必须要有合理的分层(UI层拿到的数据无需处理,可以直接用),Data  Mapper恰好解决这一问题,同时也可规避大量编写BindAdapter的现状。

DataBinding并非函数式编程,它只是通过AbstractProcessor生成中间代码,将数据映射到XML中

2.3 为什么说数据驱动UI底层思想是控制反转?

当前Android生态能实现数据绑定UI的框架只有两个:DataBinding、Compose(暂不讨论)

在引入DataBinding之前渲染一条数据通常需要两步,如下:

var title = "iOS" fun setTitle(){      //第一步更改数据源      title = "Android"      //第二个更改UI      textView = title }

共需要两步更改数据源、更改UI,数据源跟UI有一个忘记修改便会出现BUG,千万不要说:“两个我都不会忘记修改”,当面临复杂的逻辑以及十几个甚至几十个的数据源很难保证不出错。这种问题可以通过DataBinding解决,只需更改对应的ObservableFiledUI便会同步修改,控制UI状态也从个人反转到的DataBinding,个人疏忽的事情DataBinding可不会。

所以说数据驱动UI底层思想是控制反转

2.4 为什么引入Diff?

引入diff之前:

引入diff之后:

3 为什么我建议使用 函数式编程

3.1 什么是 函数式编程?

说的通俗点就是给定一个初始值,经过函数链的运行会得到一个目标值,运算的过程中外部没有插手的权限,同时不做与本身无关的操作,从根本上解决了不可预期错误的产生。

举个例子:

//Kotlin代码  listOf(10, 20).map {    it + 1 }.forEach {    Log.i("list", "$it") }

上面这种链式编程就是标准的函数式编程,输入到输出之间开发者根本没有插手的机会(即Log.i(..)之前开发者没有权限处理list),所以整个流程是100%安全的,RxJava、Flow、链式高阶函数都是标准的函数式编程,它们从规范层面解决数据安全问题。所以我建议在Kotlin中  碰到数据处理尽量使用链式高阶函数(RxJava、Kotlin Flow亦然)。

3.2 Android视图开发可以借鉴函数式编程思想

Android视图开发大都遵循如下流程:请求-->处理数据-->渲染UI,这一流程可以借鉴函数式编程,将请求作为入口,渲染做为出口,在这个流程中尽量不做与当前行为无关的事(这也要求ViewModel,Repository中的函数要符合单一原则)。这样说有点笼统,下面举个反例:

View{        //刷新        fun refresh(){            ViewModel.load(true)        }        //加载更多        fun loadMore(){            ViewModel.load(false)        }    }     ViewModel{        //加载数据        load(isRefresh){            if (isRefresh){                //刷新            }else{                //加载更多            }        }    }

View层有刷新、加载更多两种行为,load(isRefresh)一个入口,两个出口。面临的问题很明显,修改刷新或加载更多都会对对方产生影响,违反开闭原则中的闭(对修改关闭:行为没变不准修改源代码),导致存在不可预期的问题产生。可以借鉴函数式编程思想对其进行改进,将ViewModel的load函数拆分成refresh和loadMore,这样刷新和加载更多两种行为、两个入口、两个出口互不干涉,通过函数的衔接形成两条独立的业务链条。

函数式编程可以约束我们写出规范的代码,面对不能使用函数式编程的场景,我们可以尝试自我约束往函数式编程方向靠拢,大致也能实现相同的效果。

“如何理解Android架构”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

推荐阅读:
  1. 8月顺利拿到OPPO公司Android架构师offer,一面+部长面
  2. 目前全球最主流的四种Android架构简介与理解

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

android

上一篇:Android中Dialog该如何去除白色边框代码

下一篇:vbs如何实现网络PING包查询

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》