Android面试题架构篇,如果喜欢请持续关注和推荐。
如何实现一个网络框架(参考Volley)
1.缓存队列,以url为key缓存内容可以参考Bitmap的处理方式,这里单独开启一个线程。
2.网络请求队列,使用线程池进行请求。
3.提供各种不同类型的返回值的解析如String,Json,图片等等。
mvc、mvp、mvvm:
1.mvc:数据、View、Activity,View将操作反馈给Activity,Activitiy去获取数据,数据通过观察者模式刷新给View。循环依赖
1.Activity重,很难单元测试
2.View和Model耦合严重
2.mvp:数据、View、Presenter,View将操作给Presenter,Presenter去获取数据,数据获取好了返回给Presenter,Presenter去刷新View。PV,PM双向依赖
1.接口爆炸
2.Presenter很重
3.mvvm:数据、View、ViewModel,View将操作给ViewModel,ViewModel去获取数据,数据和界面绑定了,数据更新界面更新。
1.viewModel的业务逻辑可以单独拿来测试
2.一个view 对应一个 viewModel 业务逻辑可以分离,不会出现全能类
3.数据和界面绑定了,不用写垃圾代码,但是复用起来不舒服
ClassLoader的基础知识
1.双亲委托:一个ClassLoader类负责加载这个类所涉及的所有类,在加载的时候会判断该类是否已经被加载过,然后会递归去他父ClassLoader中找。
2.可以动态加载Jar通过URLClassLoader
3.ClassLoader 隔离问题 JVM识别一个类是由:ClassLoader id+PackageName+ClassName。
4.加载不同Jar包中的公共类:
1.让父ClassLoader加载公共的Jar,子ClassLoader加载包含公共Jar的Jar,此时子ClassLoader在加载公共Jar的时候会先去父ClassLoader中找。(只适用Java)
2.重写加载包含公共Jar的Jar的ClassLoader,在loadClass中找到已经加载过公共Jar的ClassLoader,也就是把父ClassLoader替换掉。(只适用Java)
3.在生成包含公共Jar的Jar时候把公共Jar去掉。
插件化框架描述:dynamicLoadApk为例子
1.可以通过DexClassLoader来对apk中的dex包进行加载访问
2.如何加载资源是个很大的问题,因为宿主程序中并没有apk中的资源,所以调用R资源会报错,所以这里使用了Activity中的实现ContextImpl的getAssets()和getResources()再加上反射来实现。
3.由于系统启动Activity有很多初始化动作要做,而我们手动反射很难完成,所以可以采用接口机制,将Activity的大部分生命周期提取成接口,然后通过代理Activity去调用插件Activity的生命周期。同时如果像增加一个新生命周期方法的时候,只需要在接口中和代理中声明一下就行。
4.缺点:
1.慎用this,因为在apk中使用this并不代表宿主中的activity,当然如果this只是表示自己的接口还是可以的。除此之外可以使用that代替this。
2.不支持Service和静态注册的Broadcast
3.不支持LaunchMode和Apk中Activity的隐式调用。
热修复:Andfix为例子
1.大致原理:apkpatch将两个apk做一次对比,然后找出不同的部分。可以看到生成的apatch了文件,后缀改成zip再解压开,里面有一个dex文件。通过jadx查看一下源码,里面就是被修复的代码所在的类文件,这些更改过的类都加上了一个_CF的后缀,并且变动的方法都被加上了一个叫@MethodReplace的annotation,通过clazz和method指定了需要替换的方法。然后客户端sdk得到补丁文件后就会根据annotation来寻找需要替换的方法。最后由JNI层完成方法的替换。
2.无法添加新类和新的字段、补丁文件很容易被反编译、加固平台可能会使热补丁功能失效
oKhttp原理:
1.同步和异步:
1.异步使用了Dispatcher来将存储在 Deque 中的请求分派给线程池中各个线程执行。
2.当任务执行完成后,无论是否有异常,finally代码段总会被执行,也就是会调用Dispatcher的finished函数,它将正在运行的任务Call从队列runningAsyncCalls中移除后,主动的把缓存队列向前走了一步。
2.连接池:
1.一个Connection封装了一个socket,ConnectionPool中储存s着所有的Connection,StreamAllocation是引用计数的一个单位
2.当一个请求获取一个Connection的时候要传入一个StreamAllocation,Connection中存着一个弱引用的StreamAllocation列表,每当上层应用引用一次Connection,StreamAllocation就会加一个。反之如果上层应用不使用了,就会删除一个。
3.ConnectionPool中会有一个后台任务定时清理StreamAllocation列表为空的Connection。5分钟时间,维持5个socket
3.选择路线与建立连接
1.连接池中已经存在连接,就从中取出(get)RealConnection,如果没有命中就进入下一步
2.根据选择的路线(Route),调用Platform.get().connectSocket选择当前平台Runtime下最好的socket库进行握手
3.将建立成功的RealConnection放入(put)连接池缓存
4.如果存在TLS,就根据SSL版本与证书进行安全握手
5.构造HttpStream并维护刚刚的socket连接,管道建立完成
1.无代理,那么在本地使用DNS查找到ip,注意结果是数组,即一个域名有多个IP,这就是自动重连的来源
2.有代理HTTP:设置socket的ip为代理地址的ip,设置socket的端口为代理地址的端口
3.代理好处:HTTP代理会帮你在远程服务器进行DNS查询,可以减少DNS劫持。
1.选择路线有两种方式:
2.建立连接
4.职责链模式:缓存、重试、建立连接等功能存在于拦截器中网络请求相关,主要是网络请求优化。网络请求的时候遇到的问题
retrofit的了解
1.动态代理创建一个接口的代理类
2.通过反射解析每个接口的注解、入参构造http请求
3.获取到返回的http请求,使用Adapter解析成需要的返回值。
Xutils, OKhttp, Volley, Retrofit对比
Xutils:
这个框架非常全面,可以进行网络请求,可以进行图片加载处理,可以数据储存,还可以对view进行注解,使用这个框架非常方便,但是缺点也是非常明显的,使用这个项目,会导致项目对这个框架依赖非常的严重,一旦这个框架出现问题,那么对项目来说影响非常大的。
OKhttp:
Android开发中是可以直接使用现成的api进行网络请求的。就是使用HttpClient,HttpUrlConnection进行操作。okhttp针对Java和Android程序,封装的一个高性能的http请求库,支持同步,异步,而且okhttp又封装了线程池,封装了数据转换,封装了参数的使用,错误处理等。API使用起来更加的方便。但是我们在项目中使用的时候仍然需要自己在做一层封装,这样才能使用的更加的顺手。
Volley:
Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。
Retrofit:
Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。
Volley VS OkHttp
Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。
OkHttp VS Retrofit
毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。 Volley VS Retrofit 这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些,要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。
Universal-ImageLoader,Picasso,Fresco,Glide对比
Fresco
是Facebook推出的开源图片缓存工具,主要特点包括:两个内存缓存加上 Native 缓存构成了三级缓存
优点:
图片存储在安卓系统的匿名共享内存, 而不是虚拟机的堆内存中, 图片的中间缓冲数据也存放在本地堆内存, 所以, 应用程序有更多的内存使用, 不会因为图片加载而导致oom, 同时也减少垃圾回收器频繁调用回收 Bitmap 导致的界面卡顿, 性能更高。
渐进式加载 JPEG 图片, 支持图片从模糊到清晰加载。
图片可以以任意的中心点显示在 ImageView, 而不仅仅是图片的中心。
JPEG 图片改变大小也是在 native 进行的, 不是在虚拟机的堆内存, 同样减少 OOM。
很好的支持 GIF 图片的显示。
缺点:
框架较大, 影响 Apk 体积
使用较繁琐
Universal-ImageLoader:
(估计由于HttpClient被Google放弃,作者就放弃维护这个框架)
优点:
支持下载进度监听
可以在 View 滚动中暂停图片加载,通过 PauseOnScrollListener 接口可以在 View 滚动中暂停图片加载。
默认实现多种内存缓存算法 这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。
支持本地缓存文件名规则定义
Picasso
优点
自带统计监控功能。支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
支持优先级处理。每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。
支持延迟到图片尺寸计算完成加载
支持飞行模式、并发线程数根据网络类型而变。 手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4,4g 为 3,3g 为 2。 这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。
“无”本地缓存。无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。
Glide
优点
不仅仅可以进行图片缓存还可以缓存媒体文件。Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。
支持优先级处理。
与 Activity/Fragment 生命周期一致,支持 trimMemory。Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。
支持 okhttp、Volley。Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。
内存友好。Glide 的内存缓存有个 active 的设计,从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。内存缓存更小图片,Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小与 Activity/Fragment 生命周期一致,支持 trimMemory。图片默认使用默认 RGB_565 而不是 ARGB_888,虽然清晰度差些,但图片更小,也可配置到 ARGB_888。
Glide 可以通过 signature 或不使用本地缓存支持 url 过期