九、架构设计
9.1 MVC模式
MVC模式介绍:
MVC是Model-View-Controller的简称
Model:模型层,负责处理数据的加载或者存储
View:视图层,负责界面数据的展示,与用户进行交互
Controller:控制器层,负责逻辑业务的处理
MVC模式的特点:
1.耦合性低;
2.可扩展性好;
3.模块职责划分明确
MVC模式的不足:
XML文件作为视图层,所做的事情比较有限,所以Activity作为Controller同时也承担了一部分的View视图显示工作,导致Activity代码庞大,维护困难
9.2 MVP模式
mvp模式简化的Activity的代码逻辑,将复杂的逻辑代码提取到了Presenter中进行处理。对应的耦合度降低。
一张图总结Mvc和Mvp的区别
其实最明显的区别就是,MVC中中Model和View直接交互的,而MVP中很明显,Model与View之间的交互由Presenter完成,Presenter与View之间的交互是通过接口的。
9.3 MVVM模式
MVVM模式不是四层,任然是3层,分别是Model、View、ViewModel
Model :负责数据实现和逻辑处理,类似MVP。
View : 对应于Activity和XML,负责View的绘制以及与用户交互,类似MVP。
ViewModel : 创建关联,将model和view绑定起来,如此之后,我们model的更改,通过viewmodel反馈给view,从而自动刷新界面。
通常情况下,数据的流向是单方面的,只能从代码流向UI,也就是单向绑定;而双向绑定的数据流向是双向的,当业务代码中的数据改变时,UI上的数据能够得到刷新;当用户通过UI交互编辑了数据时,数据的变化也能自动的更新到业务代码中的数据上。而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个关键的工具,它是支持双向绑定的。
下面就一起来看看他们的职责和协同工作的原理。
Model层就是职责数据的存储、读取网络数据、操作数据库数据以及I/O,一般会有一个ViewModel对象来调用获取这一部分的数据。
我感觉这里的View才是真正的View,为什么这么说?View层做的仅仅和UI相关的工作,我们只在XML、Activity、Fragment写View层的代码,View层不做和业务相关的事,也就是我们的Activity 不写和业务逻辑相关代码,一般Activity不写更新UI的代码,如果非得要写,那更新的UI必须和业务逻辑和数据是没有关系的,只是单纯UI逻辑来更新UI,比如:滑动时头部颜色渐变、editttext根据输入内容显示隐藏等,简单的说:View层不做任何业务逻辑、不涉及操作数据、不处理数据、UI和数据严格的分开。
ViewModel 只做和业务逻辑和业务数据相关的事,不做任何和UI、控件相关的事,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,操作的也都是对数据进行操作,这些个数据源绑定在相应的控件上会自动去更改UI,开发者不需要关心更新UI的事情。
总结一下:View层的Activity通过DataBinding生成Binding实例,把这个实例传递给ViewModel,ViewModel层通过把自身与Binding实例绑定,从而实现View中layout与ViewModel的双向绑定。mvvm的缺点数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
9.4 谈谈你对Android设计模式的理解
设计模式的六大原则:
1.单一职责原则:对于一个类而言,应该仅有一个引起它变化的原因。通俗地理解是不要在Activity中写Bean文件、网络请求处理和Adapter等。
2.开放封闭原则:类、模块、函数等应该是可以拓展,但是不可以修改。在开发中,需求是变化的,如果有新的需求,我们就要重新把类改一遍显然是很爆炸的,所有进来通过扩展的方式实现
3.迪米特原则:一个软件应当尽可能少地和其他实体发生相互作用。
4.接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上,接口的负责化要适度
5.依赖倒置原则:高层模块不应该依赖底层模块,两者都应该依赖于抽象。抽象不应该依赖细节,细节应该依赖与抽象。读起来很拗口,在Java中,抽象是指接口和抽象类;细节是实现类;高层模块就是调用者,底层模块就是具体的实现类,我的理解是类跟类之间的依赖通过抽象产生的。
6.里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象。
要是如何使用wait notify/notifyAll
//仓库代码 主要处理同步问题 public class Storage { private final int MAX_SIZE = 100;//仓库最大容量 private List list = new LinkedList();//产品存储在这里 public void produce(int num) {//生产num个产品 synchronized (list) { //一定是while,因为wait被唤醒后需要判断是不是满足生产条件 while(list.size()+num > MAX_SIZE) { System.out.println("暂时不能执行生产任务"); try{ list.wait(); } catch ( InterruptedException e) { e.printStackTrace(); } } //满足生产条件开始生产 for(int i = 0; i < num; i++) { list.add(new Object()); } System.out.println("已生产产品数"+num+" 仓库容量"+list.size()); list.notifyAll(); } } public void consume(int num) {//消费num个产品 synchronized (list) { while(list.size() < num) { System.out.println("暂时不能执行消费任务"); try{ list.wait(); } catch ( InterruptedException e) { e.printStackTrace(); } } //满足消费条件开始消费 for(int i = 0; i < num; i++) { list.remove(); } System.out.println("已消费产品数"+num+" 仓库容量"+list.size()); list.notifyAll(); } } }
之后定义生产者消费者线程
//生产者 public class Producer extends Thread { private int num;//生产的数量 public Storage storage;//仓库 public Producer(Storage storage) { this.storage = storage; } public void setNum(int num) { this.num = num; } public void run() { storage. produce(num); } } //消费者 public class Consumer extends Thread { private int num;//消费的数量 public Storage storage;//仓库 public Consumer(Storage storage) { this.storage = storage; } public void setNum(int num) { this.num = num; } public void run() { storage. consume(num); } }
主程序 用来开启多个生产者消费者线程执行操作
public class Test{ public static void main(String[] args) { Storage storage = new Storage(); Producer p1 = new Producer(storage); Producer p2 = new Producer(storage); Producer p3 = new Producer(storage); Producer p4 = new Producer(storage); Producer p5 = new Producer(storage); Consumer c1 = new Consumer(storage); Consumer c2 = new Consumer(storage); Consumer c3 = new Consumer(storage); p1.setNum(10); p2.setNum(20); p3.setNum(10); p4.setNum(80); p5.setNum(10); c1.setNum(50); c2.setNum(20); c3.setNum(20); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); } }
核心的就是每个线程拿到锁之后检测是不是满足条件,不满足则wait释放锁及CPU资源,等待被唤醒之后判断条件满足的话执行生产/消费操作,然后唤醒别的等待的线程,此线程结束
装饰者与适配者模式的区别
适配器模式将一个类的接口,转化成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。
装饰者模式:动态的将责任附加到对象上(因为利用组合而不是继承来实现,而组合是可以在运行时进行随机组合的)。若要扩展功能,装饰者提供了比继承更富有弹性的替代方案(同样地,通过组合可以很好的避免类暴涨,也规避了继承中的子类必须无条件继承父类所有属性的弊端)。
关于适配器模式和装饰者模式:个人理解为,适配器是横向的转换接口,而装饰是纵向的包装接口。
适配器模式将一个对象包装起来以改变其接口,装饰者模式将一个对象包装起来以增加新的行为和责任,外观模式将一群对象“包装”起来以简化其接口。
理解RxJava:https://www.cnblogs.com/JohnTsai/p/5695560.html
9.5 说说EventBus作用,实现方式,代替EventBus的方式
EventBus是什么:
替代:RxJava实现RxBus
RXBus(RxBus的核心功能是基于Rxjava的)
RxJava写一个RxBus,来实现EventBus的发布 / 订阅的事件总线功能。
或者按照evenbus原理来写:储存反射
9.6 从0设计一款App整体架构,如何去做?
想要设计App的整体框架,首先要清楚我们做的是什么
一般我们与网络交互数据的方式有两种:主动请求(http),长连接推送
结合网络交互数据的方式来说一下我们开发的App的类型和特点:
所以这类App的网络调用相当频繁,而且需要考虑到网络差,没网络等情况下,App的运行,成熟的商业应用的网络调用一般是如下流程:
UI发起请求 - 检查缓存 - 调用网络模块 - 解析返回JSON / 统一处理异常 - JSON对象映射为Java对象 - 缓存 - UI获取数据并展示
这之中可以看到很明显职责划分,即:数据获取;数据管理;数据展示
9.7 谈谈对java状态机理解
一、有限状态机(FSM)
有限状态机(Finite State Machine)是表示有限个状态(State)以及在这些状态(State)之间的转移(Transition)和动作(Action)等行为的数据模型。
总的来说,有限状态机系统,是指在不同阶段呈现出不同的运行状态的系统,这些状态是有限的、不重叠的。
这样的系统在某一时刻一定会处于其所有状态中的一个状态,此时它接收一部分允许的输入,产生一部分可能的响应,并迁移到一部分可能的状态。
有限状态机的要素:
(1)State(状态)
状态(State),就是一个系统在其生命周期中某一个时刻的运行情况,此时,系统会执行一些操作,或者等待一些外部输入。并且,在当前形态下,可能会有不同的行为和属性。
(2)Guard(条件)
状态机对外部消息进行响应时,除了需要判断当前的状态,还需要判断跟这个状态相关的一些条件是否成立。这种判断称为 Guard(条件)。Guard 通过允许或者禁止某些操作来影响状态机的行为。
(3)Event(事件)
事件(Event),就是在一定的时间和空间上发生的对系统有意义的事情,事件通常会引起状态的变迁,促使状态机从一种状态切换到另一种状态。
(4)Action(动作)
当一个事件(Event)被状态机系统分发的时候,状态机用 动作(Action)来进行响应,比如修改一下变量的值、进行输入输出、产生另外一个 Event 或者迁移到另外一个状态等
(5)Transition(迁移)
从一个状态切换到另一个状态被称为 Transition(迁移)。引起状态迁移的事件被称为触发事件(triggering event),或者被简称为触发(trigger)。
二、分层状态机(HFSM)
在有限状态机中,虽说状态是有限的,但是当状态太多的时候却不是那么好维护的,这个时候就需要将一些具有公共属性的状态分类,抽离出来,将同类型的状态作为一个状态机,然后再做一个大的状态机,来维护这些子状态。
例如,在有限状态机中,我们的状态图是这样的:
但在分层状态机中,我们的状态是这样的:
这样一来,我们就不需要考量所有的状态之间的关系,定义所有状态之间的跳转链接。
只需要使用层次化的状态机,将所有的行为分类,把几个小的状态归并到一个大的状态里面,然后再定义高层状态和高层状态中内部小状态的跳转链接。
分层状态机从某种程度上就是限制了状态机的跳转,而且高层状态内部的状态是不需要关心外部状态的跳转的,这也做到了无关状态间的隔离,在每个状态内部只需要关心自己的小状态的跳转就可以了,这样就大大的降低了状态机的复杂度。
9.8 Fragment如果在Adapter中使用应该如何解耦?
startActivityForResult是Activity类里的方法,在原Activity里通过Intent跳转到其他类再跳回到原Activity里时候,回传数据所用在Adapter以及其他非Activity类使用的时候,可以将由原Activity类传入的Context强转为Activity类,再在原Activity里重写onActivityResult方法接受到返回值。
9.9 Binder机制及底层实现
Binder在C/S中的流程如下:
Binder通信机制流程(整体框架)
上图即是Binder的通信模型。我们可以发现:
为了方便理解,我们可以把SM理解成DNS服务器; 那么Binder Driver 就相当于路由的功能。
步骤一:Server向SM注册服务
注:引用和实体。这里,对于一个用于通信的实体,可以有多个该实体的引用。如果一个进程持有某个实体,其他进程也想操作该实体,最高效的做法是去获得该实体的引用,再去操作这个引用。
有些资料把实体称为本地对象,引用成为远程对象。可以这么理解:引用是从本地进程发送给其他进程来操作实体之用,所以有本地和远程对象之名。
步骤二:Client从SM获得Service的远程接口
Server向SM注册了Binder实体及其名字后,Client就可以通过Service的名字在SM的查找表中获得该Binder的引用了 (BpBinder)。
之 后,Client就可以利用XXXService的引用使用XXXService的服务了。如果有更多的Client请求该Service,系统中就会有更多的Client获得这个引用。
建立C/S通路后
首先要理清一个概念:client拥有自己Binder的实体,以及Server的Binder的引用;Server拥有自己Binder的实体,以及Client的Binder的引用。我们也可以从接收方和发送方的方式来理解:
也就是说,我们在建立了C/S通路后,无需考虑谁是Client谁是Server,只要理清谁是发送方谁是接收方,就能知道Binder的实体和引用在哪边。
9.10 对于应用更新这块是如何做的?(解答:灰度,强制更新,分区域更新)?
灰度:(能够平滑过渡的一种发布方式)
(1)找单一渠道投放特别版本。
(2)做升级平台的改造,允许针对部分用户推送升级通知甚至版本强制升级。
(3)开放单独的下载入口。
还有,灰度版最好有收回的能力,一般就是强制升级下一个正式版
增量更新:bsdiff:二进制差分工具bspatch是相应的补丁合成工具,根据两个不同版本的二进制文件,生成补丁文件.patch文件。通过bspatch使旧的apk文件与不定文件合成新的apk。 不足:要区分版本,内置及版本相同破解版apk无法增量更新,最好进行sha1sum校验,保证基础包的一致性。