【建议收藏】2020年中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂(Android高级篇下)

前言

成为一名优秀的Android开发,须要一份完备的知识体系,在这里,让咱们一块儿成长为本身所想的那样~。

🔥 A awesome android expert interview questions and answers(continuous updating ...)javascript

从几十份顶级面试仓库和300多篇高质量面经中总结出一份全面成体系化的Android高级面试题集。html

欢迎来到2020年中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂的Android高级篇下。前端

3、Android优秀三方库源码

一、你项目中用到哪些开源库?说说其实现原理?

1、网络底层框架:OkHttp实现原理

这个库是作什么用的?

网络底层库,它是基于http协议封装的一套请求客户端,虽然它也能够开线程,但根本上它更偏向真正的请求,跟HttpClient, HttpUrlConnection的职责是同样的。其中封装了网络请求get、post等底层操做的实现。java

为何要在项目中使用这个库?
  • OkHttp 提供了对最新的 HTTP 协议版本 HTTP/2 和 SPDY 的支持,这使得对同一个主机发出的全部请求均可以共享相同的套接字链接。
  • 若是 HTTP/2 和 SPDY 不可用,OkHttp 会使用链接池来复用链接以提升效率。
  • OkHttp 提供了对 GZIP 的默认支持来下降传输内容的大小。
  • OkHttp 也提供了对 HTTP 响应的缓存机制,能够避免没必要要的网络请求。
  • 当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址。
这个库都有哪些用法?对应什么样的使用场景?

get、post请求、上传文件、上传表单等等。react

这个库的优缺点是什么,跟同类型库的比较?
  • 优势:在上面
  • 缺点:使用的时候仍然须要本身再作一层封装。
这个库的核心实现原理是什么?若是让你实现这个库的某些核心功能,你会考虑怎么去实现?

OkHttp内部的请求流程:使用OkHttp会在请求的时候初始化一个Call的实例,而后执行它的execute()方法或enqueue()方法,内部最后都会执行到getResponseWithInterceptorChain()方法,这个方法里面经过拦截器组成的责任链,依次通过用户自定义普通拦截器、重试拦截器、桥接拦截器、缓存拦截器、链接拦截器和用户自定义网络拦截器以及访问服务器拦截器等拦截处理过程,来获取到一个响应并交给用户。其中,除了OKHttp的内部请求流程这点以外,缓存和链接这两部份内容也是两个很重要的点,掌握了这3点就说明你理解了OkHttp。android

各个拦截器的做用:
  • interceptors:用户自定义拦截器
  • retryAndFollowUpInterceptor:负责失败重试以及重定向
  • BridgeInterceptor:请求时,对必要的Header进行一些添加,接收响应时,移除必要的Header
  • CacheInterceptor:负责读取缓存直接返回(根据请求的信息和缓存的响应的信息来判断是否存在缓存可用)、更新缓存
  • ConnectInterceptor:负责和服务器创建链接

ConnectionPool:git

一、判断链接是否可用,不可用则从ConnectionPool获取链接,ConnectionPool无链接,建立新链接,握手,放入ConnectionPool。github

二、它是一个Deque,add添加Connection,使用线程池负责定时清理缓存。web

三、使用链接复用省去了进行 TCP 和 TLS 握手的一个过程。面试

  • networkInterceptors:用户定义网络拦截器
  • CallServerInterceptor:负责向服务器发送请求数据、从服务器读取响应数据
你从这个库中学到什么有价值的或者说可借鉴的设计思想?

使用责任链模式实现拦截器的分层设计,每个拦截器对应一个功能,充分实现了功能解耦,易维护。

手写拦截器?
OKhttp针对网络层有哪些优化?
网络请求缓存处理,okhttp如何处理网络缓存的?
HttpUrlConnection 和 okhttp关系?
Volley与OkHttp的对比:

Volley:支持HTTPS。缓存、异步请求,不支持同步请求。协议类型是Http/1.0, Http/1.1,网络传输使用的是 HttpUrlConnection/HttpClient,数据读写使用的IO。 OkHttp:支持HTTPS。缓存、异步请求、同步请求。协议类型是Http/1.0, Http/1.1, SPDY, Http/2.0, WebSocket,网络传输使用的是封装的Socket,数据读写使用的NIO(Okio)。 SPDY协议相似于HTTP,但旨在缩短网页的加载时间和提升安全性。SPDY协议经过压缩、多路复用和优先级来缩短加载时间。

Okhttp的子系统层级结构图以下所示:

image

网络配置层:利用Builder模式配置各类参数,例如:超时时间、拦截器等,这些参数都会由Okhttp分发给各个须要的子系统。 重定向层:负责重定向。 Header拼接层:负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。 HTTP缓存层:负责读取缓存以及更新缓存。 链接层:链接层是一个比较复杂的层级,它实现了网络协议、内部的拦截器、安全性认证,链接与链接池等功能,但这一层尚未发起真正的链接,它只是作了链接器一些参数的处理。 数据响应层:负责从服务器读取响应的数据。 在整个Okhttp的系统中,咱们还要理解如下几个关键角色:

OkHttpClient:通讯的客户端,用来统一管理发起请求与解析响应。 Call:Call是一个接口,它是HTTP请求的抽象描述,具体实现类是RealCall,它由CallFactory建立。 Request:请求,封装请求的具体信息,例如:url、header等。 RequestBody:请求体,用来提交流、表单等请求信息。 Response:HTTP请求的响应,获取响应信息,例如:响应header等。 ResponseBody:HTTP请求的响应体,被读取一次之后就会关闭,因此咱们重复调用responseBody.string()获取请求结果是会报错的。 Interceptor:Interceptor是请求拦截器,负责拦截并处理请求,它将网络请求、缓存、透明压缩等功能都统一块儿来,每一个功能都是一个Interceptor,全部的Interceptor最 终链接成一个Interceptor.Chain。典型的责任链模式实现。 StreamAllocation:用来控制Connections与Streas的资源分配与释放。 RouteSelector:选择路线与自动重连。 RouteDatabase:记录链接失败的Route黑名单。

本身去设计网络请求框架,怎么作?
从网络加载一个10M的图片,说下注意事项?
http怎么知道文件过大是否传输完毕的响应?
谈谈你对WebSocket的理解?
WebSocket与socket的区别?

2、网络封装框架:Retrofit实现原理

这个库是作什么用的?

Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。Retrofit 2.0 开始内置 OkHttp,前者专一于接口的封装,后者专一于网络请求的高效。

为何要在项目中使用这个库?

一、功能强大:

  • 支持同步、异步
  • 支持多种数据的解析 & 序列化格式
  • 支持RxJava

二、简洁易用:

  • 经过注解配置网络请求参数
  • 采用大量设计模式简化使用

三、可扩展性好:

  • 功能模块高度封装
  • 解耦完全,如自定义Converters
这个库都有哪些用法?对应什么样的使用场景?

任何网络场景都应该优先选择,特别是后台API遵循Restful API设计风格 & 项目中使用到RxJava。

这个库的优缺点是什么,跟同类型库的比较?
  • 优势:在上面
  • 缺点:扩展性差,高度封装所带来的必而后果,若是服务器不能给出统一的API形式,会很难处理。
这个库的核心实现原理是什么?若是让你实现这个库的某些核心功能,你会考虑怎么去实现?

Retrofit主要是在create方法中采用动态代理模式(经过访问代理对象的方式来间接访问目标对象)实现接口方法,这个过程构建了一个ServiceMethod对象,根据方法注解获取请求方式,参数类型和参数注解拼接请求的连接,当一切都准备好以后会把数据添加到Retrofit的RequestBuilder中。而后当咱们主动发起网络请求的时候会调用okhttp发起网络请求,okhttp的配置包括请求方式,URL等在Retrofit的RequestBuilder的build()方法中实现,并发起真正的网络请求。

你从这个库中学到什么有价值的或者说可借鉴的设计思想?

内部使用了优秀的架构设计和大量的设计模式,在我分析过Retrofit最新版的源码和大量优秀的Retrofit源码分析文章后,我发现,要想真正理解Retrofit内部的核心源码流程和设计思想,首先,须要对它使用到的九大设计模式有必定的了解,下面我简单说一说:

一、建立Retrofit实例:

  • 使用建造者模式经过内部Builder类创建了一个Retroift实例。
  • 网络请求工厂使用了工厂方法模式。

二、建立网络请求接口的实例:

  • 首先,使用外观模式统一调用建立网络请求接口实例和网络请求参数配置的方法。
  • 而后,使用动态代理动态地去建立网络请求接口实例。
  • 接着,使用了建造者模式 & 单例模式建立了serviceMethod对象。
  • 再者,使用了策略模式对serviceMethod对象进行网络请求参数配置,即经过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络的url地址、网络请求执行器、网络请求适配器和数据转换器。
  • 最后,使用了装饰者模式ExecuteCallBack为serviceMethod对象加入线程切换的操做,便于接受数据后经过Handler从子线程切换到主线程从而对返回数据结果进行处理。

三、发送网络请求:

  • 在异步请求时,经过静态delegate代理对网络请求接口的方法中的每一个参数使用对应的ParameterHanlder进行解析。

四、解析数据

五、切换线程:

  • 使用了适配器模式经过检测不一样的Platform使用不一样的回调执行器,而后使用回调执行器切换线程,这里一样是使用了装饰模式。

六、处理结果

Android:主流网络请求开源库的对比(Android-Async-Http、Volley、OkHttp、Retrofit)

www.jianshu.com/p/050c6db5a…

3、响应式编程框架:RxJava实现原理

RxJava 变换操做符 map flatMap concatMap buffer?
  • map:【数据类型转换】将被观察者发送的事件转换为另外一种类型的事件。
  • flatMap:【化解循环嵌套和接口嵌套】将被观察者发送的事件序列进行拆分 & 转换 后合并成一个新的事件序列,最后再进行发送。
  • concatMap:【有序】与 flatMap 的 区别在于,拆分 & 从新合并生成的事件序列 的顺序与被观察者旧序列生产的顺序一致。
  • buffer:按期从被观察者发送的事件中获取必定数量的事件并放到缓存区中,而后把这些数据集合打包发射。
RxJava中map和flatmap操做符的区别及底层实现
手写rxjava遍历数组。
你认为Rxjava的线程池与大家本身实现任务管理框架有什么区别?

4、图片加载框架:Glide实现原理

这个库是作什么用的?

Glide是Android中的一个图片加载库,用于实现图片加载。

为何要在项目中使用这个库?

一、多样化媒体加载:不只能够进行图片缓存,还支持Gif、WebP、缩略图,甚至是Video。

二、经过设置绑定生命周期:能够使加载图片的生命周期动态管理起来。

三、高效的缓存策略:支持内存、Disk缓存,而且Picasso只会缓存原始尺寸的图片,内Glide缓存的是多种规格,也就是Glide会根据你ImageView的大小来缓存相应大小的图片尺寸。

四、内存开销小:默认的Bitmap格式是RGB_565格式,而Picasso默认的是ARGB_8888格式,内存开销小一半。

这个库都有哪些用法?对应什么样的使用场景?

一、图片加载:Glide.with(this).load(imageUrl).override(800, 800).placeholder().error().animate().into()。

二、多样式媒体加载:asBitamp、asGif。

三、生命周期集成。

四、能够配置磁盘缓存策略ALL、NONE、SOURCE、RESULT。

这个库的优缺点是什么,跟同类型库的比较?

库比较大,源码实现复杂。

这个库的核心实现原理是什么?若是让你实现这个库的某些核心功能,你会考虑怎么去实现?
  • Glide&with:

一、初始化各式各样的配置信息(包括缓存,请求线程池,大小,图片格式等等)以及glide对象。

二、将glide请求和application/SupportFragment/Fragment的生命周期绑定在一块。

  • Glide&load:

设置请求url,并记录url已设置的状态。

三、Glide&into:

一、首先根据转码类transcodeClass类型返回不一样的ImageViewTarget:BitmapImageViewTarget、DrawableImageViewTarget。

二、递归创建缩略图请求,没有缩略图请求,则直接进行正常请求。

三、若是没指定宽高,会根据ImageView的宽高计算出图片宽高,最终执行到onSizeReay()方法中的engine.load()方法。

四、engine是一个负责加载和管理缓存资源的类

  • 常规三级缓存的流程:强引用->软引用->硬盘缓存

当咱们的APP中想要加载某张图片时,先去LruCache中寻找图片,若是LruCache中有,则直接取出来使用,若是LruCache中没有,则去SoftReference中寻找(软引用适合当cache,当内存吃紧的时候才会被回收。而weakReference在每次system.gc()就会被回收)(当LruCache存储紧张时,会把最近最少使用的数据放到SoftReference中),若是SoftReference中有,则从SoftReference中取出图片使用,同时将图片从新放回到LruCache中,若是SoftReference中也没有图片,则去硬盘缓存中中寻找,若是有则取出来使用,同时将图片添加到LruCache中,若是没有,则链接网络从网上下载图片。图片下载完成后,将图片保存到硬盘缓存中,而后放到LruCache中。

  • Glide的三层缓存机制:

Glide缓存机制大体分为三层:内存缓存、弱引用缓存、磁盘缓存。

取的顺序是:内存、弱引用、磁盘。

存的顺序是:弱引用、内存、磁盘。

三层存储的机制在Engine中实现的。先说下Engine是什么?Engine这一层负责加载时作管理内存缓存的逻辑。持有MemoryCache、Map<Key, WeakReference<EngineResource<?>>>。经过load()来加载图片,加载先后会作内存存储的逻辑。若是内存缓存中没有,那么才会使用EngineJob这一层来进行异步获取硬盘资源或网络资源。EngineJob相似一个异步线程或observable。Engine是一个全局惟一的,经过Glide.getEngine()来获取。

须要一个图片资源,若是Lrucache中有相应的资源图片,那么就返回,同时从Lrucache中清除,放到activeResources中。activeResources map是盛放正在使用的资源,以弱引用的形式存在。同时资源内部有被引用的记录。若是资源没有引用记录了,那么再放回Lrucache中,同时从activeResources中清除。若是Lrucache中没有,就从activeResources中找,找到后相应资源引用加1。若是Lrucache和activeResources中没有,那么进行资源异步请求(网络/diskLrucache),请求成功后,资源放到diskLrucache和activeResources中。

Glide源码机制的核心思想:

使用一个弱引用map activeResources来盛放项目中正在使用的资源。Lrucache中不含有正在使用的资源。资源内部有个计数器来显示本身是否是还有被引用的状况,把正在使用的资源和没有被使用的资源分开有什么好处呢??由于当Lrucache须要移除一个缓存时,会调用resource.recycle()方法。注意到该方法上面注释写着只有没有任何consumer引用该资源的时候才能够调用这个方法。那么为何调用resource.recycle()方法须要保证该资源没有任何consumer引用呢?glide中resource定义的recycle()要作的事情是把这个不用的资源(假设是bitmap或drawable)放到bitmapPool中。bitmapPool是一个bitmap回收再利用的库,在作transform的时候会从这个bitmapPool中拿一个bitmap进行再利用。这样就避免了从新建立bitmap,减小了内存的开支。而既然bitmapPool中的bitmap会被重复利用,那么确定要保证回收该资源的时候(即调用资源的recycle()时),要保证该资源真的没有外界引用了。这也是为何glide花费那么多逻辑来保证Lrucache中的资源没有外界引用的缘由。

你从这个库中学到什么有价值的或者说可借鉴的设计思想?

Glide的高效的三层缓存机制,如上。

Glide如何肯定图片加载完毕?
Glide使用什么缓存?
Glide内存缓存如何控制大小?
计算一张图片的大小

图片占用内存的计算公式:图片高度 * 图片宽度 * 一个像素占用的内存大小。因此,计算图片占用内存大小的时候,要考虑图片所在的目录跟设备密度,这两个因素其实影响的是图片的宽高,android会对图片进行拉升跟压缩。

加载bitmap过程(怎样保证不产生内存溢出)

因为Android对图片使用内存有限制,如果加载几兆的大图片便内存溢出。Bitmap会将图片的全部像素(即长x宽)加载到内存中,若是图片分辨率过大,会直接致使内存OOM,只有在BitmapFactory加载图片时使用BitmapFactory.Options对相关参数进行配置来减小加载的像素。

BitmapFactory.Options相关参数详解:

(1).Options.inPreferredConfig值来下降内存消耗。

好比:默认值ARGB_8888改成RGB_565,节约一半内存。

(2).设置Options.inSampleSize 缩放比例,对大图片进行压缩 。

(3).设置Options.inPurgeable和inInputShareable:让系统能及时回收内存。

A:inPurgeable:设置为True时,表示系统内存不足时能够被回收,设置为False时,表示不能被回收。

B:inInputShareable:设置是否深拷贝,与inPurgeable结合使用,inPurgeable为false时,该参数无心义。
复制代码

(4).使用decodeStream代替decodeResource等其余方法。

Android中软引用与弱引用的应用场景。

Java 引用类型分类:

image

在 Android 应用的开发中,为了防止内存溢出,在处理一些占用内存大并且生命周期较长的对象时候,能够尽可能应用软引用和弱引用技术。

  • 一、软/弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列能够得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软 / 弱引用。
  • 二、若是只是想避免 OOM 异常的发生,则能够使用软引用。若是对于应用的性能更在乎,想尽快回收一些占用内存比较大的对象,则能够使用弱引用。
  • 三、能够根据对象是否常用来判断选择软引用仍是弱引用。若是该对象可能会常用的,就尽可能用软引用。若是该对象不被使用的可能性更大些,就能够用弱引用。
Android里的内存缓存和磁盘缓存是怎么实现的。

内存缓存基于LruCache实现,磁盘缓存基于DiskLruCache实现。这两个类都基于Lru算法和LinkedHashMap来实现。

LRU算法能够用一句话来描述,以下所示:

LRU是Least Recently Used的缩写,最近最少使用算法,从它的名字就能够看出,它的核心原则是若是一个数据在最近一段时间没有使用到,那么它在未来被访问到的可能性也很小,则这类数据项会被优先淘汰掉。

LruCache原理

以前,咱们会使用内存缓存技术实现,也就是软引用或弱引用,在Android 2.3(APILevel 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得再也不可靠。

其实LRU缓存的实现相似于一个特殊的栈,把访问过的元素放置到栈顶(若栈中存在,则更新至栈顶;若栈中不存在则直接入栈),而后若是栈中元素数量超过限定值,则删除栈底元素(即最近最少使用的元素)。

它的内部存在一个 LinkedHashMap 和 maxSize,把最近使用的对象用强引用存储在 LinkedHashMap 中,给出来 put 和 get 方法,每次 put 图片时计算缓存中全部图片的总大小,跟 maxSize 进行比较,大于 maxSize,就将最久添加的图片移除,反之小于 maxSize 就添加进来。

LruCache的原理就是利用LinkedHashMap持有对象的强引用,按照Lru算法进行对象淘汰。具体说来假设咱们从表尾访问数据,在表头删除数据,当访问的数据项在链表中存在时,则将该数据项移动到表尾,不然在表尾新建一个数据项。当链表容量超过必定阈值,则移除表头的数据。

详细来讲就是LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,若是满了就用LinkedHashMap的迭代器删除队头元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法得到对应集合元素,同时会更新该元素到队尾。

LruCache put方法核心逻辑

在添加过缓存对象后,调用trimToSize()方法,来判断缓存是否已满,若是满了就要删除近期最少使用的对象。trimToSize()方法不断地删除LinkedHashMap中队头的元素,即近期最少访问的,直到缓存大小小于最大值(maxSize)。

LruCache get方法核心逻辑

当调用LruCache的get()方法获取集合中的缓存对象时,就表明访问了一次该元素,将会更新队列,保持整个队列是按照访问顺序排序的。

为何会选择LinkedHashMap呢?

这跟LinkedHashMap的特性有关,LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,不然以插入顺序为序排序元素。

LinkedHashMap原理

LinkedHashMap 几乎和 HashMap 同样:从技术上来讲,不一样的是它定义了一个 Entry<K,V> header,这个 header 不是放在 Table 里,它是额外独立出来的。LinkedHashMap 经过继承 hashMap 中的 Entry<K,V>,并添加两个属性 Entry<K,V> before,after,和 header 结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。

DisLruCache原理

DiskLruCache与LruCache原理类似,只是多了一个journal文件来作磁盘文件的管理,以下所示:

libcore.io.DiskLruCache
1
1
1

DIRTY 1517126350519
CLEAN 1517126350519 5325928
REMOVE 1517126350519
复制代码

注:这里的缓存目录是应用的缓存目录/data/data/pckagename/cache,未root的手机能够经过如下命令进入到该目录中或者将该目录总体拷贝出来:

//进入/data/data/pckagename/cache目录
adb shell
run-as com.your.packagename 
cp /data/data/com.your.packagename/

//将/data/data/pckagename目录拷贝出来
adb backup -noapk com.your.packagename
复制代码

咱们来分析下这个文件的内容:

第一行:libcore.io.DiskLruCache,固定字符串。 第二行:1,DiskLruCache源码版本号。 第三行:1,App的版本号,经过open()方法传入进去的。 第四行:1,每一个key对应几个文件,通常为1. 第五行:空行 第六行及后续行:缓存操做记录。 第六行及后续行表示缓存操做记录,关于操做记录,咱们须要了解如下三点:

DIRTY 表示一个entry正在被写入。写入分两种状况,若是成功会紧接着写入一行CLEAN的记录;若是失败,会增长一行REMOVE记录。注意单独只有DIRTY状态的记录是非法的。 当手动调用remove(key)方法的时候也会写入一条REMOVE记录。 READ就是说明有一次读取的记录。 CLEAN的后面还记录了文件的长度,注意可能会一个key对应多个文件,那么就会有多个数字。

Bitmap 压缩策略

加载 Bitmap 的方式:

BitmapFactory 四类方法:

  • decodeFile( 文件系统 )
  • decodeResourece( 资源 )
  • decodeStream( 输入流 )
  • decodeByteArray( 字节数 )

BitmapFactory.options 参数:

  • inSampleSize 采样率,对图片高和宽进行缩放,以最小比进行缩放(通常取值为 2 的指数)。一般是根据图片宽高实际的大小/须要的宽高大小,分别计算出宽和高的缩放比。但应该取其中最小的缩放比,避免缩放图片过小,到达指定控件中不能铺满,须要拉伸从而致使模糊。
  • inJustDecodeBounds 获取图片的宽高信息,交给 inSampleSize 参数选择缩放比。经过 inJustDecodeBounds = true,而后加载图片就能够实现只解析图片的宽高信息,并不会真正的加载图片,因此这个操做是轻量级的。当获取了宽高信息,计算出缩放比后,而后在将 inJustDecodeBounds = false,再从新加载图片,就能够加载缩放后的图片。

高效加载 Bitmap 的流程:

  • 一、将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 true 并加载图片
  • 二、从 BitmapFactory.Options 中取出图片原始的宽高信息,对应于 outWidth 和 outHeight 参数
  • 三、根据采样率规则并结合目标 view 的大小计算出采样率 inSampleSize
  • 四、将 BitmapFactory.Options 的 inJustDecodeBounds 设置为 false 从新加载图片
Bitmap的处理:

当使用ImageView的时候,可能图片的像素大于ImageView,此时就能够经过BitmapFactory.Option来对图片进行压缩,inSampleSize表示缩小2^(inSampleSize-1)倍。

BitMap的缓存:

1.使用LruCache进行内存缓存。

2.使用DiskLruCache进行硬盘缓存。

实现一个ImageLoader的流程

同步异步加载、图片压缩、内存硬盘缓存、网络拉取

  • 1.同步加载只建立一个线程而后按照顺序进行图片加载
  • 2.异步加载使用线程池,让存在的加载任务都处于不一样线程
  • 3.为了避免开启过多的异步任务,只在列表静止的时候开启图片加载

具体为:

  • 一、ImageLoader做为一个单例,提供了加载图片到指定控件的方法:直接从内存缓存中获取对象,若是没有则用一个ThreadPoolExecutor去执行Runnable任务来加载图片。ThreadPoolExecutor的建立须要指定核心线程数CPU数+1,最大线程数CPU数*2+1,线程闲置超市时长10s,这几个关键数据,还能够加入ThreadFactory参数来建立定制化的线程。
  • 二、ImageLoader的具体实现loadBitmap:先从内存缓存LruCache中加载,若是为空再从磁盘缓存中加载,加载成功后记得存入内存缓存,若是为空则从网络中直接下载输出流到磁盘缓存,而后再从磁盘中加载,若是为空而且磁盘缓存没有被建立的话,直接经过BitmapFactory的decodeStream获取网络请求的输入流获取Bitmap对象。
  • 三、v4包的LruCache能够兼容到2.2版本,LruCache采用LinkedHashMap存储缓存对象。建立对象只须要提供缓存容量并重写sizeOf方法:做用是计算缓存对象的大小。有时须要重写entryRemoved方法,用于回收一些资源。
  • 四、DiskLruCache经过open方法建立,设置缓存路径,缓存容量。缓存添加经过Editor对象建立输出流,下载资源到输出流完成后,commit,若是失败则abort撤回。而后刷新磁盘缓存。缓存查找经过Snapshot对象获取输入流,获取FileDescriptor,经过FileDescriptor解析出Bitmap对象。
  • 五、列表中须要加载图片的时候,当列表在滑动中不进行图片加载,当滑动中止后再去加载图片。
Bitmap在decode的时候申请的内存如何复用,释放时机
图片库对比

stackoverflow.com/questions/2…

www.trinea.cn/android/and…

Fresco与Glide的对比:

Glide:相对轻量级,用法简单优雅,支持Gif动态图,适合用在那些对图片依赖不大的App中。 Fresco:采用匿名共享内存来保存图片,也就是Native堆,有效的的避免了OOM,功能强大,可是库体积过大,适合用在对图片依赖比较大的App中。

Fresco的总体架构以下图所示:

image

DraweeView:继承于ImageView,只是简单的读取xml文件的一些属性值和作一些初始化的工做,图层管理交由Hierarchy负责,图层数据获取交由负责。 DraweeHierarchy:由多层Drawable组成,每层Drawable提供某种功能(例如:缩放、圆角)。 DraweeController:控制数据的获取与图片加载,向pipeline发出请求,并接收相应事件,并根据不一样事件控制Hierarchy,从DraweeView接收用户的事件,而后执行取消网络请求、回收资源等操做。 DraweeHolder:统筹管理Hierarchy与DraweeHolder。 ImagePipeline:Fresco的核心模块,用来以各类方式(内存、磁盘、网络等)获取图像。 Producer/Consumer:Producer也有不少种,它用来完成网络数据获取,缓存数据获取、图片解码等多种工做,它产生的结果由Consumer进行消费。 IO/Data:这一层即是数据层了,负责实现内存缓存、磁盘缓存、网络缓存和其余IO相关的功能。 纵观整个Fresco的架构,DraweeView是门面,和用户进行交互,DraweeHierarchy是视图层级,管理图层,DraweeController是控制器,管理数据。它们构成了整个Fresco框架的三驾马车。固然还有咱们 幕后英雄Producer,全部的脏活累活都是它干的,最佳劳模👍

理解了Fresco总体的架构,咱们还有了解在这套矿建里发挥重要做用的几个关键角色,以下所示:

Supplier:提供一种特定类型的对象,Fresco里有不少以Supplier结尾的类都实现了这个接口。 SimpleDraweeView:这个咱们就很熟悉了,它接收一个URL,而后调用Controller去加载图片。该类继承于GenericDraweeView,GenericDraweeView又继承于DraweeView,DraweeView是Fresco的顶层View类。 PipelineDraweeController:负责图片数据的获取与加载,它继承于AbstractDraweeController,由PipelineDraweeControllerBuilder构建而来。AbstractDraweeController实现了DraweeController接口,DraweeController 是Fresco的数据大管家,因此的图片数据的处理都是由它来完成的。 GenericDraweeHierarchy:负责SimpleDraweeView上的图层管理,由多层Drawable组成,每层Drawable提供某种功能(例如:缩放、圆角),该类由GenericDraweeHierarchyBuilder进行构建,该构建器 将placeholderImage、retryImage、failureImage、progressBarImage、background、overlays与pressedStateOverlay等 xml文件或者Java代码里设置的属性信息都传入GenericDraweeHierarchy中,由GenericDraweeHierarchy进行处理。 DraweeHolder:该类是一个Holder类,和SimpleDraweeView关联在一块儿,DraweeView是经过DraweeHolder来统一管理的。而DraweeHolder又是用来统一管理相关的Hierarchy与Controller DataSource:相似于Java里的Futures,表明数据的来源,和Futures不一样,它能够有多个result。 DataSubscriber:接收DataSource返回的结果。 ImagePipeline:用来调取获取图片的接口。 Producer:加载与处理图片,它有多种实现,例如:NetworkFetcherProducer,LocalAssetFetcherProducer,LocalFileFetchProducer。从这些类的名字咱们就能够知道它们是干什么的。 Producer由ProducerFactory这个工厂类构建的,并且全部的Producer都是像Java的IO流那样,能够一层嵌套一层,最终只获得一个结果,这是一个很精巧的设计👍 Consumer:用来接收Producer产生的结果,它与Producer组成了生产者与消费者模式。 注:Fresco源码里的类的名字都比较长,可是都是按照必定的命令规律来的,例如:以Supplier结尾的类都实现了Supplier接口,它能够提供某一个类型的对象(factory, generator, builder, closure等)。 以Builder结尾的固然就是以构造者模式建立对象的类。

Bitmap如何处理大图,如一张30M的大图,如何预防OOM?

blog.csdn.net/guolin_blog…

blog.csdn.net/lmj62356579…

使用BitmapRegionDecoder动态加载图片的显示区域。

Bitmap对象的理解。
对inBitmap的理解。
本身去实现图片库,怎么作?(对扩展开发,对修改封闭,同时又保持独立性,参考Android源码设计模式解析实战的图片加载库案例便可)
写个图片浏览器,说出你的思路?

5、事件总线框架:EventBus实现原理

6、内存泄漏检测框架:LeakCanary实现原理

这个库是作什么用?

内存泄露检测框架。

为何要在项目中使用这个库?
  • 针对Android Activity组件彻底自动化的内存泄漏检查,在最新的版本中,还加入了android.app.fragment的组件自动化的内存泄漏检测。
  • 易用集成,使用成本低。
  • 友好的界面展现和通知。
这个库都有哪些用法?对应什么样的使用场景?

直接从application中拿到全局的 refWatcher 对象,在Fragment或其余组件的销毁回调中使用refWatcher.watch(this)检测是否发生内存泄漏。

这个库的优缺点是什么,跟同类型库的比较?

检测结果并非特别的准确,由于内存的释放和对象的生命周期有关也和GC的调度有关。

这个库的核心实现原理是什么?若是让你实现这个库的某些核心功能,你会考虑怎么去实现?

主要分为以下7个步骤:

  • 一、RefWatcher.watch()建立了一个KeyedWeakReference用于去观察对象。
  • 二、而后,在后台线程中,它会检测引用是否被清除了,而且是否没有触发GC。
  • 三、若是引用仍然没有被清除,那么它将会把堆栈信息保存在文件系统中的.hprof文件里。
  • 四、HeapAnalyzerService被开启在一个独立的进程中,而且HeapAnalyzer使用了HAHA开源库解析了指定时刻的堆栈快照文件heap dump。
  • 五、从heap dump中,HeapAnalyzer根据一个独特的引用key找到了KeyedWeakReference,而且定位了泄露的引用。
  • 六、HeapAnalyzer为了肯定是否有泄露,计算了到GC Roots的最短强引用路径,而后创建了致使泄露的链式引用。
  • 七、这个结果被传回到app进程中的DisplayLeakService,而后一个泄露通知便展示出来了。

简单来讲就是:

在一个Activity执行完onDestroy()以后,将它放入WeakReference中,而后将这个WeakReference类型的Activity对象与ReferenceQueque关联。这时再从ReferenceQueque中查看是否有该对象,若是没有,执行gc,再次查看,仍是没有的话则判断发生内存泄露了。最后用HAHA这个开源库去分析dump以后的heap内存(主要就是建立一个HprofParser解析器去解析出对应的引用内存快照文件snapshot)。

流程图:

image

源码分析中一些核心分析点:

AndroidExcludedRefs:它是一个enum类,它声明了Android SDK和厂商定制的SDK中存在的内存泄露的case,根据AndroidExcludedRefs这个类的类名就可看出这些case都会被Leakcanary的监测过滤掉。

buildAndInstall()(即install方法)这个方法应该仅仅只调用一次。

debuggerControl : 判断是否处于调试模式,调试模式中不会进行内存泄漏检测。为何呢?由于在调试过程当中可能会保留上一个引用从而致使错误信息上报。

watchExecutor : 线程控制器,在 onDestroy() 以后而且主线程空闲时执行内存泄漏检测。

gcTrigger : 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 以后会再检测一次,仍然泄漏的断定为内存泄漏,最后根据heapDump信息生成相应的泄漏引用链。

gcTrigger的runGc()方法:这里并无使用System.gc()方法进行回收,由于system.gc()并不会每次都执行。而是从AOSP中拷贝一段GC回收的代码,从而相比System.gc()更可以保证进行垃圾回收的工做。

Runtime.getRuntime().gc();
复制代码

子线程延时1000ms;

System.runFinalization();

install方法内部最终仍是调用了application的registerActivityLifecycleCallbacks()方法,这样就可以监听activity对应的生命周期事件了。

在RefWatcher#watch()中使用随机的UUID保证了每一个检测对象对应的key 的惟一性。

在KeyedWeakReference内部,使用了key和name标识了一个被检测的WeakReference对象。在其构造方法中将弱引用和引用队列 ReferenceQueue 关联起来,若是弱引用reference持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列referenceQueue中。即 KeyedWeakReference 持有的 Activity 对象若是被GC回收,该对象就会加入到引用队列 referenceQueue 中。

使用Android SDK的API Debug.dumpHprofData() 来生成 hprof 文件。

在HeapAnalyzerService(类型为IntentService的ForegroundService)的runAnalysis()方法中,为了不减慢app进程或占用内存,这里将HeapAnalyzerService设置在了一个独立的进程中。

你从这个库中学到什么有价值的或者说可借鉴的设计思想?
leakCannary中如何判断一个对象是否被回收?如何触发手动gc?c层实现?
BlockCanary原理:

该组件利用了主线程的消息队列处理机制,应用发生卡顿,必定是在dispatchMessage中执行了耗时操做。咱们经过给主线程的Looper设置一个Printer,打点统计dispatchMessage方法执行的时间,若是超出阀值,表示发生卡顿,则dump出各类信息,提供开发者分析性能瓶颈。

7、依赖注入框架:ButterKnife实现原理

ButterKnife对性能的影响很小,由于没有使用使用反射,而是使用的Annotation Processing Tool(APT),注解处理器,javac中用于编译时扫描和解析Java注解的工具。在编译阶段执行的,它的原理就是读入Java源代码,解析注解,而后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器不能改变读入的Java类,好比不能加入或删除Java方法。

AOP IOC 的好处以及在 Android 开发中的应用

8、依赖全局管理框架:Dagger2实现原理

9、数据库框架:GreenDao实现原理

数据库框架对比?
数据库的优化
数据库数据迁移问题
数据库索引的数据结构
平衡二叉树
  • 一、非叶子节点只能容许最多两个子节点存在。
  • 二、每个非叶子节点数据分布规则为左边的子节点小当前节点的值,右边的子节点大于当前节点的值(这里值是基于本身的算法规则而定的,好比hash值)。
  • 三、树的左右两边的层级数相差不会大于1。

使用平衡二叉树能保证数据的左右两边的节点层级相差不会大于1.,经过这样避免树形结构因为删除增长变成线性链表影响查询效率,保证数据平衡的状况下查找数据的速度近于二分法查找。

image

目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree做为索引结构。

B-Tree

B树和平衡二叉树稍有不一样的是B树属于多叉树又名平衡多路查找树(查找路径不仅两个)。

  • 一、排序方式:全部节点关键字是按递增次序排列,并遵循左小右大原则。
  • 二、子节点数:非叶节点的子节点数>1,且<=M ,且M>=2,空树除外(注:M阶表明一个树节点最多有多少个查找路径,M=M路,当M=2则是2叉树,M=3则是3叉)。
  • 三、关键字数:枝节点的关键字数量大于等于ceil(m/2)-1个且小于等于M-1个(注:ceil()是个朝正无穷方向取整的函数 如ceil(1.1)结果为2)。
  • 四、全部叶子节点均在同一层、叶子节点除了包含了关键字和关键字记录的指针外也有指向其子节点的指针只不过其指针地址都为null对应下图最后一层节点的空格子。

image

B树相对于平衡二叉树的不一样是,每一个节点包含的关键字增多了,把树的节点关键字增多后树的层级比原来的二叉树少了,减小数据查找的次数和复杂度。

B+Tree
规则:
  • 一、B+跟B树不一样B+树的非叶子节点不保存关键字记录的指针,只进行数据索引。
  • 二、B+树叶子节点保存了父节点的全部关键字记录的指针,全部数据地址必需要到叶子节点才能获取到。因此每次数据查询的次数都同样。
  • 三、B+树叶子节点的关键字从小到大有序排列,左边结尾数据都会保存右边节点开始数据的指针。
  • 四、非叶子节点的子节点数=关键字数(来源百度百科)(根据各类资料 这里有两种算法的实现方式,另外一种为非叶节点的关键字数=子节点数-1(来源维基百科),虽然他们数据排列结构不同,但其原理仍是同样的Mysql 的B+树是用第一种方式实现)。

image

特色:

一、B+树的层级更少:相较于B树B+每一个非叶子节点存储的关键字数更多,树的层级更少因此查询数据更快。

二、B+树查询速度更稳定:B+全部关键字数据地址都存在叶子节点上,因此每次查找的次数都相同因此查询速度要比B树更稳定。

三、B+树自然具有排序功能:B+树全部的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。

四、B+树全节点遍历更快:B+树遍历整棵树只须要遍历全部的叶子节点便可,而不须要像B树同样须要对每一层进行遍历,这有利于数据库作全表扫描。

B树相对于B+树的优势是,若是常常访问的数据离根节点很近,而B树的非叶子节点自己存有关键字其数据的地址,因此这种数据检索的时候会要比B+树快。

B*Tree

B*树是B+树的变种,相对于B+树他们的不一样之处以下:

  • 一、首先是关键字个数限制问题,B+树初始化的关键字初始化个数是cei(m/2),b树的初始化个数为(cei(2/3m))。

  • 二、B+树节点满时就会分裂,而B*树节点满时会检查兄弟节点是否满(由于每一个节点都有指向兄弟的指针),若是兄弟节点未满则向兄弟节点转移关键字,若是兄弟节点已满,则从当前节点和兄弟节点各拿出1/3的数据建立一个新的节点出来。

在B+树的基础上因其初始化的容量变大,使得节点空间使用率更高,而又存有兄弟节点的指针,能够向兄弟节点转移关键字的特性使得B*树分解次数变得更少。

image

结论:
  • 一、相同思想和策略:从平衡二叉树、B树、B+树、B*树整体来看它们贯彻的思想是相同的,都是采用二分法和数据平衡策略来提高查找数据的速度。
  • 二、不一样的方式的磁盘空间利用:不一样点是他们一个一个在演变的过程当中经过IO从磁盘读取数据的原理进行一步步的演变,每一次演变都是为了让节点的空间更合理的运用起来,从而使树的层级减小达到快速查找数据的目的;

还不理解请查看:平衡二叉树、B树、B+树、B*树 理解其中一种你就都明白了

4、热修复、插件化、模块化、组件化、Gradle、编译插桩技术

一、热修复和插件化

Android中ClassLoader的种类&特色

  • BootClassLoader(Java的BootStrap ClassLoader): 用于加载Android Framework层class文件。
  • PathClassLoader(Java的App ClassLoader): 用于加载已经安装到系统中的apk中的class文件。
  • DexClassLoader(Java的Custom ClassLoader): 用于加载指定目录中的class文件。
  • BaseDexClassLoader: 是PathClassLoader和DexClassLoader的父类。

热修补技术是怎样实现的,和插件化有什么区别?

插件化:动态加载主要解决3个技术问题:

  • 一、使用ClassLoader加载类。
  • 二、资源访问。
  • 三、生命周期管理。

插件化是体如今功能拆分方面的,它将某个功能独立提取出来,独立开发,独立测试,再插入到主应用中。以此来减小主应用的规模。

热修复:

缘由:由于一个dvm中存储方法id用的是short类型,致使dex中方法不能超过65536个。

代码热修复原理:
  • 将编译好的class文件拆分打包成两个dex,绕过dex方法数量的限制以及安装时的检查,在运行时再动态加载第二个dex文件中。
  • 热修复是体如今bug修复方面的,它实现的是不须要从新发版和从新安装,就能够去修复已知的bug。
  • 利用PathClassLoader和DexClassLoader去加载与bug类同名的类,替换掉bug类,进而达到修复bug的目的,原理是在app打包的时候阻止类打上CLASS_ISPREVERIFIED标志,而后在热修复的时候动态改变BaseDexClassLoader对象间接引用的dexElements,替换掉旧的类。

相同点:

都使用ClassLoader来实现加载新的功能类,均可以使用PathClassLoader与DexClassLoader。

不一样点:

热修复由于是为了修复Bug的,因此要将新的类替代同名的Bug类,要抢先加载新的类而不是Bug类,因此多作两件事:在原先的app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志,还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements,这样才能抢先代替Bug类,完成系统不加载旧的Bug类.。 而插件化只是增长新的功能类或者是资源文件,因此不涉及抢先加载新的类这样的使命,就避过了阻止相关类去打上CLASS_ISPREVERIFIED标志和还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements.

因此插件化比热修复简单,热修复是在插件化的基础上在进行替换旧的Bug类。

热修复原理:

资源修复:

不少热修复框架的资源修复参考了Instant Run的资源修复的原理。

传统编译部署流程以下:

Instant Run编译部署流程以下:

  • Hot Swap:修改一个现有方法中的代码时会采用Hot Swap。
  • Warm Swap:修改或删除一个现有的资源文件时会采用Warm Swap。
  • Cold Swap:有不少状况,如添加、删除或修改一个字段和方法、添加一个类等。

Instant Run中的资源热修复流程:

  • 一、建立新的AssetManager,经过反射调用addAssetPath方法加载外部的资源,这样新建立的AssetManager就含有了外部资源。
  • 二、将AssetManager类型的mAssets字段的引用所有替换为新建立的AssetManager。
代码修复:

一、类加载方案:

65536限制:

65536的主要缘由是DVM Bytecode的限制,DVM指令集的方法调用指令invoke-kind索引为16bits,最多能引用65535个方法。

LinearAlloc限制:

  • DVM中的LinearAlloc是一个固定的缓存区,当方法数超过了缓存区的大小时会报错。

Dex分包方案主要作的是在打包时将应用代码分红多个Dex,将应用启动时必须用到的类和这些类的直接引用类放到Dex中,其余代码放到次Dex中。当应用启动时先加载主Dex,等到应用启动后再动态地加载次Dex,从而缓解了主Dex的65536限制和LinearAlloc限制。

加载流程:

  • 根据dex文件的查找流程,咱们将有Bug的类Key.class进行修改,再将Key.class打包成包含dex的补丁包Patch.jar,放在Element数组dexElements的第一个元素,这样会首先找到Patch.dex中的Key.class去替换以前存在Bug的Key.class,排在数组后面的dex文件中存在Bug的Key.class根据ClassLoader的双亲委托模式就不会被加载。

类加载方案须要重启App后让ClassLoader从新加载新的类,为何须要重启呢?

  • 这是由于类是没法被卸载的,要想从新加载新的类就须要重启App,所以采用类加载方案的热修复框架是不能即时生效的。

各个热修复框架的实现细节差别:

  • QQ空间的超级补丁和Nuwa是按照上面说的将补丁包放在Element数组的第一个元素获得优先加载。
  • 微信的Tinker将新旧APK作了diff,获得path.dex,再将patch.dex与手机中APK的classes.dex作合并,生成新的classes.dex,而后在运行时经过反射将classes.dex放在Elements数组的第一个元素。
  • 饿了么的Amigo则是将补丁包中每一个dex对应的Elements取出来,以后组成新的Element数组,在运行时经过反射用新的Elements数组替换掉现有的Elements数组。

二、底层替换方案:

当咱们要反射Key的show方法,会调用Key.class.getDeclaredMethod("show").invoke(Key.class.newInstance());,最终会在native层将传入的javaMethod在ART虚拟机中对应一个ArtMethod指针,ArtMethod结构体中包含了Java方法的全部信息,包括执行入口、访问权限、所属类和代码执行地址等。

替换ArtMethod结构体中的字段或者替换整个ArtMethod结构体,这就是底层替换方案。

AndFix采用的是替换ArtMethod结构体中的字段,这样会有兼容性问题,由于厂商可能会修改ArtMethod结构体,致使方法替换失败。

Sophix采用的是替换整个ArtMethod结构体,这样不会存在兼容问题。

底层替换方案直接替换了方法,能够当即生效不须要重启。采用底层替换方案主要是阿里系为主,包括AndFix、Dexposed、阿里百川、Sophix。

三、Instant Run方案:

什么是ASM?

ASM是一个java字节码操控框架,它可以动态生成类或者加强现有类的功能。ASM能够直接产生class文件,也能够在类被加载到虚拟机以前动态改变类的行为。

Instant Run在第一次构建APK时,使用ASM在每个方法中注入了相似的代码逻辑:当change不为null时,则调用它的accessdispatch方法,参数为具体的方法名和方法参数。当MainActivity的onCreate方法作了修改,就会生成替换类MainActivityoverride,这个类实现了IncrementalChange接口,同时也会生成一个AppPatchesLoaderImpl类,这个类的getPatchedClasses方法会返回被修改的类的列表(里面包含了MainActivity),根据列表会将MainActivity的change设置为MainActivityoverride。最后这个change就不会为null,则会执行MainActivityoverride的accessdispatch方法,最终会执行onCreate方法,从而实现了onCreate方法的修改。

借鉴Instant Run原理的热修复框架有Robust和Aceso。

动态连接库修复:

从新加载so。

加载so主要用到了System类的load和loadLibrary方法,最终都会调用到nativeLoad方法。其会调用JavaVMExt的LoadNativeLibrary函数来加载so。

so修复主要有两个方案:

  • 一、将so补丁插入到NativeLibraryElement数组的前部,让so补丁的路径先被返回和加载。
  • 二、调用System的load方法来接管so的加载入口。

为何选用插件化?

在Android传统开发中,一旦应用的代码被打包成APK并被上传到各个应用市场,咱们就不能修改应用的源码了,只能经过服务器来控制应用中预留的分支代码。可是不少时候咱们没法预知需求和忽然发生的状况,也就不能提早在应用代码中预留分支代码,这时就须要采用动态加载技术,即在程序运行时,动态加载一些程序中本来不存在的可执行文件并运行这些文件里的代码逻辑。其中可执行文件包括动态连接库so和dex相关文件(dex以及包含dex的jar/apk文件)。随着应用开发技术和业务的逐步发展,动态加载技术派生出两个技术:热修复和插件化。其中热修复技术主要用来修复Bug,而插件化技术则主要用于解决应用愈来愈庞大以及功能模块的解耦。详细点说,就是为了解决如下几种状况:

  • 一、业务复杂、模块耦合:随着业务愈来愈复杂,应用程序的工程和功能模块数量会愈来愈多,一个应用可能由几十甚至几百人来协同开发,其中的一个工程可能就由一个小组来进行开发维护,若是功能模块间的耦合度较高,修改一个模块会影响其它功能模块,势必会极大地增长沟通成本。
  • 二、应用间的接入:当一个应用须要接入其它应用时,如淘宝,为了将流量引流到其它的淘宝应用如:飞猪旅游、口碑外卖、聚划算等等应用,如使用常规技术有两个问题:可能要维护多个版本的问题或单个应用体积将会很是庞大的问题。
  • 三、65536限制,内存占用大。

插件化的思想:

安装的应用能够理解为插件,这些插件能够自由地进行插拔。

插件化的定义:

插件通常是指通过处理的APK,so和dex等文件,插件能够被宿主进行加载,有的插件也能够做为APK独立运行。

将一个应用按照插件的方式进行改造的过程就叫做插件化。

插件化的优点:

  • 低耦合
  • 应用间的接入和维护更便捷,每一个应用团队只须要负责本身的那一部分。
  • 应用及主dex的体积也会相应变小,间接地避免了65536限制。
  • 第一次加载到内存的只有淘宝客户端,当使用到其它插件时才会加载相应插件到内存,以减小内存占用。

插件化框架对比:

  • 最先的插件化框架:2012年大众点评的屠毅敏就推出了AndroidDynamicLoader框架。
  • 目前主流的插件化方案有滴滴任玉刚的VirtualApk、360的DroidPlugin、RePlugin、Wequick的Small框架。
  • 若是加载的插件不须要和宿主有任何耦合,也无须和宿主进行通讯,好比加载第三方App,那么推荐使用RePlugin,其余状况推荐使用VirtualApk。因为VirtualApk在加载耦合插件方面是插件化框架的首选,具备广泛的适用性,所以有必要对它的源码进行了解。

插件化原理:

Activity插件化:

主要实现方式有三种:

  • 反射:对性能有影响,主流的插件化框架没有采用此方式。
  • 接口:dynamic-load-apk采用。
  • Hook:主流。

Hook实现方式有两种:Hook IActivityManager和Hook Instrumentation。主要方案就是先用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来经过AMS的校验,接着在合适的时机用插件Activity替换占坑的Activity。

Hook IActivityManager:

一、占坑、经过校验:

在Android 7.0和8.0的源码中IActivityManager借助了Singleton类实现单例,并且该单例是静态的,所以IActivityManager是一个比较好的Hook点。

接着,定义替换IActivityManager的代理类IActivityManagerProxy,因为Hook点IActivityManager是一个接口,建议这里采用动态代理。

  • 拦截startActivity方法,获取参数args中保存的Intent对象,它是本来要启动插件TargetActivity的Intent。
  • 新建一个subIntent用来启动StubActivity,并将前面获得的TargetActivity的Intent保存到subIntent中,便于之后还原TargetActivity。
  • 最后,将subIntent赋值给参数args,这样启动的目标就变为了StubActivity,用来经过AMS的校验。

而后,用代理类IActivityManagerProxy来替换IActivityManager。

  • 当版本大于等于26时,使用反射获取ActivityManager的IActivityManagerSingleton字段,小于时则获取ActivityManagerNative中的gDefault字段。
  • 而后,经过反射获取对应的Singleton实例,从上面获得的2个字段中拿到对应的IActivityManager。
  • 最后,使用Proxy.newProxyInstance()方法动态建立代理类IActivityManagerProxy,用IActivityManagerProxy来替换IActivityManager。

二、还原插件Activity:

  • 前面用占坑Activity经过了AMS的校验,可是咱们要启动的是插件TargetActivity,还须要用插件TargetActivity来替换占坑的SubActivity,替换时机为图中步骤2以后。
  • 在ActivityThread的H类中重写的handleMessage方法会对LAUNCH_ACTIVITY类型的消息进行处理,最终会调用Activity的onCreate方法。在Handler的dispatchMessage处理消息的这个方法中,看到若是Handelr的Callback类型的mCallBack不为null,就会执行mCallback的handleMessage方法,所以mCallback能够做为Hook点。咱们能够用自定义的Callback来替换mCallback。

自定义的Callback实现了Handler.Callback,并重写了handleMessage方法,当收到消息的类型为LAUNCH_ACTIVITY时,将启动SubActivity的Intent替换为启动TargetActivity的Intent。而后使用反射将Handler的mCallback替换为自定义的CallBack便可。使用时则在application的attachBaseContext方法中进行hook便可。

三、插件Activity的生命周期:

  • AMS和ActivityThread之间的通讯采用了token来对Activity进行标识,而且此后的Activity的生命周期处理也是根据token来对Activity进行标识的,由于咱们在Activity启动时用插件TargetActivity替换占坑SubActivity,这一过程在performLaunchActivity以前,所以performLaunchActivity的r.token就是TargetActivity。因此TargetActivity具备生命周期。

Hook Instrumentation:

Hook Instrumentation实现一样也须要用到占坑Activity,与Hook IActivity实现不一样的是,用占坑Activity替换插件Activity以及还原插件Activity的地方不一样。

分析:在Activity经过AMS校验前,会调用Activity的startActivityForResult方法,其中调用了Instrumentation的execStartActivity方法来激活Activity的生命周期。而且在ActivityThread的performLaunchActivity中使用了mInstrumentation的newActivity方法,其内部会用类加载器来建立Activity的实例。

方案:在Instrumentation的execStartActivity方法中用占坑SubActivity来经过AMS的验证,在Instrumentation的newActivity方法中还原TargetActivity,这两部操做都和Instrumentation有关,所以咱们能够用自定义的Instumentation来替换掉mInstrumentation。具体为:

  • 首先检查TargetActivity是否已经注册,若是没有则将TargetActivity的ClassName保存起来用于后面还原。接着把要启动的TargetActivity替换为StubActivity,最后经过反射调用execStartActivity方法,这样就能够用StubActivity经过AMS的验证。
  • 在newActivity方法中建立了此前保存的TargetActivity,完成了还原TargetActivity。最后使用反射用InstrumentationProxy替换mInstumentation。
资源插件化:

资源的插件化和热修复的资源修复都借助了AssetManager。

资源的插件化方案主要有两种:

  • 一、合并资源方案,将插件的资源所有添加到宿主的Resources中,这种方案插件能够访问宿主的资源。
  • 二、构建插件资源方案,每一个插件都构造出独立的Resources,这种方案插件不能够访问宿主资源。
so的插件化:

so的插件化方案和so热修复的第一种方案相似,就是将so插件插入到NativelibraryElement数组中,而且将存储so插件的文件添加到nativeLibraryDirectories集合中就能够了。

插件的加载机制方案:
  • 一、Hook ClassLoader。
  • 二、委托给系统的ClassLoader帮忙加载。

二、模块化和组件化

模块化的好处

www.jianshu.com/p/376ea8a19…

分析现有的组件化方案:

不少大厂的组件化方案是以 多工程 + 多 Module 的结构(微信, 美团等超级 App 更是以 多工程 + 多 Module + 多 P 工程(以页面为单元的代码隔离方式) 的三级工程结构), 使用 Git Submodule 建立多个子仓库管理各个模块的代码, 并将各个模块的代码打包成 AAR 上传至私有 Maven 仓库使用远程版本号依赖的方式进行模块间代码的隔离。

组件化开发的好处:

  • 避免重复造轮子,能够节省开发和维护的成本。
  • 能够经过组件和模块为业务基准合理地安排人力,提升开发效率。
  • 不一样的项目能够共用一个组件或模块,确保总体技术方案的统一性。
  • 为将来插件化共用同一套底层模型作准备。

跨组件通讯:

跨组件通讯场景:

  • 第一种是组件之间的页面跳转 (Activity 到 Activity, Fragment 到 Fragment, Activity 到 Fragment, Fragment 到 Activity) 以及跳转时的数据传递 (基础数据类型和可序列化的自定义类类型)。
  • 第二种是组件之间的自定义类和自定义方法的调用(组件向外提供服务)。

跨组件通讯方案分析:

  • 第一种组件之间的页面跳转不须要过多描述了, 算是 ARouter 中最基础的功能, API 也比较简单, 跳转时想传递不一样类型的数据也提供有相应的 API。
  • 第二种组件之间的自定义类和自定义方法的调用要稍微复杂点, 须要 ARouter 配合架构中的 公共服务(CommonService) 实现:
提供服务的业务模块:

在公共服务(CommonService) 中声明 Service 接口 (含有须要被调用的自定义方法), 而后在本身的模块中实现这个 Service 接口, 再经过 ARouter API 暴露实现类。

使用服务的业务模块:

经过 ARouter 的 API 拿到这个 Service 接口(多态持有, 实际持有实现类), 便可调用 Service 接口中声明的自定义方法, 这样就能够达到模块之间的交互。 此外,能够使用 AndroidEventBus 其独有的 Tag, 能够在开发时更容易定位发送事件和接受事件的代码, 若是以组件名来做为 Tag 的前缀进行分组, 也能够更好的统一管理和查看每一个组件的事件, 固然也不建议你们过多使用 EventBus。

如何管理过多的路由表?

RouterHub 存在于基础库, 能够被看做是全部组件都须要遵照的通信协议, 里面不只能够放路由地址常量, 还能够放跨组件传递数据时命名的各类 Key 值, 再配以适当注释, 任何组件开发人员不须要事先沟通只要依赖了这个协议, 就知道了各自该怎样协同工做, 既提升了效率又下降了出错风险, 约定的东西天然要比口头上说的强。

Tips: 若是您以为把每一个路由地址都写在基础库的 RouterHub 中, 太麻烦了, 也能够在每一个组件内部创建一个私有 RouterHub, 将不须要跨组件的路由地址放入私有 RouterHub 中管理, 只将须要跨组件的路由地址放入基础库的公有 RouterHub 中管理, 若是您不须要集中管理全部路由地址的话, 这也是比较推荐的一种方式。

ARouter路由原理:

ARouter维护了一个路由表Warehouse,其中保存着所有的模块跳转关系,ARouter路由跳转实际上仍是调用了startActivity的跳转,使用了原生的Framework机制,只是经过apt注解的形式制造出跳转规则,并人为地拦截跳转和设置跳转条件。

多模块开发的时候不一样的负责人可能会引入重复资源,相同的字符串,相同的icon等可是文件名并不同,怎样去重?

三、gradle

gradle熟悉么,自动打包知道么?

如何加快 Gradle 的编译速度?

Gradle的Flavor可否配置sourceset?

Gradle生命周期

四、编译插桩

谈谈你对AOP技术的理解?

说说你了解的编译插桩技术?

5、架构设计

MVC MVP MVVM原理和区别?

架构设计的目的

经过设计是模块程序化,从而作到高内聚低耦合,让开发者能更专一于功能实现自己,提供程序开发效率、更容易进行测试、维护和定位问题等等。并且,不一样的规模的项目应该选用不一样的架构设计。

MVC

MVC是模型(model)-视图(view)-控制器(controller)的缩写,其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的做用,来控制V层和M层通讯以此来达到分离视图显示和业务逻辑层。在Android中的MVC划分是这样的:

  • 视图层(View):通常采用XML文件进行界面的描述,也能够在界面中使用动态布局的方式。
  • 控制层(Controller):由Activity承担。
  • 模型层(Model):数据库的操做、对网络等的操做,复杂业务计算等等。
MVC缺点

在Android开发中,Activity并非一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户界面,并接受和处理来自用户的操做请求,进而做出响应。随着界面及其逻辑的复杂度不断提高,Activity类的职责不断增长,以至变得庞大臃肿。

MVP

MVP框架由3部分组成:View负责显示,Presenter负责逻辑处理,Model提供数据。

  • View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity)。
  • Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来下降耦合)。
  • Presenter:做为View与Model交互的中间纽带,处理与用户交互的逻辑。
  • View interface:须要View实现的接口,View经过View interface与Presenter进行交互,下降耦合,方便使用MOCK对Presenter进行单元测试。

MVP的Presenter是框架的控制者,承担了大量的逻辑操做,而MVC的Controller更多时候承担一种转发的做用。所以在App中引入MVP的缘由,是为了将此前在Activty中包含的大量逻辑操做放到控制层中,避免Activity的臃肿。

MVP与MVC的主要区别:
  • 一、(最主要区别)View与Model并不直接交互,而是经过与Presenter交互来与Model间接交互。而在MVC中View能够与Model直接交互。
  • 二、Presenter与View的交互是经过接口来进行的,更有利于添加单元测试。
MVP的优势
  • 一、模型与视图彻底分离,咱们能够修改视图而不影响模型。
  • 二、能够更高效地使用模型,由于全部的交互都发生在一个地方——Presenter内部。
  • 三、咱们能够将一个Presenter用于多个视图,而不须要改变Presenter的逻辑。这个特性很是的有用,由于视图的变化老是比模型的变化频繁。
  • 四、若是咱们把逻辑放在Presenter中,那么咱们就能够脱离用户接口来测试这些逻辑(单元测试)。

UI层通常包括Activity,Fragment,Adapter等直接和UI相关的类,UI层的Activity在启动以后实例化相应的Presenter,App的控制权后移,由UI转移到Presenter,二者之间的通讯经过BroadCast、Handler、事件总线机制或者接口完成,只传递事件和结果。

MVP的执行流程:首先V层通知P层用户发起了一个网络请求,P层会决定使用负责网络相关的M层去发起请求网络,最后,P层将完成的结果更新到V层。

MVP的变种:Passive View

View直接依赖Presenter,可是Presenter间接依赖View,它直接依赖的是View实现的接口。相对于View的被动,那Presenter就是主动的一方。对于Presenter的主动,有以下的理解:

  • Presenter是整个MVP体系的控制中心,而不是单纯的处理View请求的人。
  • View仅仅是用户交互请求的汇报者,对于响应用户交互相关的逻辑和流程,View不参与决策,真正的决策者是Presenter。
  • View向Presenter发送用户交互请求应该采用这样的口吻:“我如今将用户交互请求发送给你,你看着办,须要个人时候我会协助你”。
  • 对于绑定到View上的数据,不该该是View从Presenter上“拉”回来的,应该是Presenter主动“推”给View的。(这里借鉴了IOC作法)
  • View尽量不维护数据状态,由于其自己仅仅实现单纯的、独立的UI操做;Presenter才是整个体系的协调者,它根据处理用于交互的逻辑给View和Model安排工做。
MVP架构存在的问题与解决办法
  • 一、加入模板方法

将逻辑操做从V层转移到P层后,可能有一些Activity仍是比较膨胀,此时,能够经过继承BaseActivity的方式加入模板方法。注意,最好不要超过3层继承。

  • 二、Model内部分层

模型层(Model)中的总体代码量是最大的,此时能够进行模块的划分和接口隔离。

  • 三、使用中介者和代理

在UI层和Presenter之间设置中介者Mediator,将例如数据校验、组装在内的轻量级逻辑操做放在Mediator中;在Presenter和Model之间使用代理Proxy;经过上述二者分担一部分Presenter的逻辑操做,但总体框架的控制权仍是在Presenter手中。

MVVM

MVVM能够算是MVP的升级版,其中的VM是ViewModel的缩写,ViewModel能够理解成是View的数据模型和Presenter的合体,ViewModel和View之间的交互经过Data Binding完成,而Data Binding能够实现双向的交互,这就使得视图和控制层之间的耦合程度进一步下降,关注点分离更为完全,同时减轻了Activity的压力。

MVC->MVP->MVVM演进过程

MVC -> MVP -> MVVM 这几个软件设计模式是一步步演化发展的,MVVM 是从 MVP 的进一步发展与规范,MVP 隔离了MVC中的 M 与 V 的直接联系后,靠 Presenter 来中转,因此使用 MVP 时 P 是直接调用 View 的接口来实现对视图的操做的,这个 View 接口的东西通常来讲是 showData、showLoading等等。M 与 V已经隔离了,方便测试了,但代码还不够优雅简洁,因此 MVVM 就弥补了这些缺陷。在 MVVM 中就出现的 Data Binding 这个概念,意思就是 View 接口的 showData 这些实现方法能够不写了,经过 Binding 来实现。

三种模式的相同点

M层和V层的实现是同样的。

三种模式的不一样点

三者的差别在于如何粘合View和Model,实现用户的交互操做以及变动通知。

  • Controller:接收View的命令,对Model进行操做,一个Controller能够对应多个View。
  • Presenter:Presenter与Controller同样,接收View的命令,对Model进行操做;与Controller不一样的是Presenter会副作用于View,Model的变动通知首先被Presenter得到,而后Presenter再去更新View。一般一个Presenter只对应于一个View。据Presenter和View对逻辑代码分担的程度不一样,这种模式又有两种状况:普通的MVP模式和Passive View模式。
  • ViewModel:注意这里的“Model”指的是View的Model,跟MVVM中的一个Model不是一回事。所谓View的Model就是包含View的一些数据属性和操做的这么一个东东,这种模式的关键技术就是数据绑定(data binding),View的变化会直接影响ViewModel,ViewModel的变化或者内容也会直接体如今View上。这种模式其实是框架替应用开发者作了一些工做,开发者只须要较少的代码就能实现比较复杂的交互。
补充:基于AOP的架构设计

AOP(Aspect-Oriented Programming, 面向切面编程),诞生于上个世纪90年代,是对OOP(Object-Oriented Programming, 面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来创建一种从上道下的对象层次结构,用以模拟公共行为的一个集合。当咱们须要为分散的对象引入公共行为的时候,即定义从左到右的关系时,OOP则显得无能为力。例如日志功能。日志代码每每水平地散布在全部对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其余类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(Cross-Cutting)代码,在OOP设计中,它致使了大量代码的重复,而不利于各个模块的重用。而AOP技术则偏偏相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降模块间的耦合度,并有利于将来的可操做性和可维护性。

在Android App中的横切关注点有Http, SharedPreferences, Log, Json, Xml, File, Device, System, 格式转换等。Android App的需求差异很大,不一样的需求横切关注点必然是不同的。通常的App工程中应该有一个Util Package来存放相关的切面操做,在项目多了以后能够将其中使用较多的Util封装为一个Jar包/aar文件/远程依赖的方式供工程调用。

在使用MVP和AOP对App进行纵向和横向的切割以后,可以使得App总体的结构更清晰合理,避免局部的代码臃肿,方便开发、测试以及后续的维护。这样纵,横两次对于App代码的分割已经能使得程序不会过多堆积在一个Java文件里,但靠一次开发过程就写出高质量的代码是很困难的,趁着项目的间歇期,对代码进行重构颇有必要。

最后的建议

若是“从零开始”,用什么设计架构的问题属于想得太多作得太少的问题。 从零开始意味着一个项目的主要技术难点是基本功能实现。当每个功能都须要考虑如何作到的时候,我以为通常人都没办法考虑如何作好。 由于,全部的优化都是站在最上层进行统筹规划。在这以前,你必须对下层的每个模块都很是熟悉,进而提炼可复用的代码、规划逻辑流程。

MVC的状况下怎么把Activity的C和V抽离?

MVP 架构中 Presenter 定义为接口有什么好处;

MVP如何管理Presenter的生命周期,什么时候取消网络请求?

aop思想

Fragment若是在Adapter中使用应该如何解耦?

项目框架里有没有Base类,BaseActivity和BaseFragment这种封装致使的问题,以及解决方法?

设计一个音乐播放界面,你会如何实现,用到那些类,如何设计,如何定义接口,如何与后台交互,如何缓存与下载,如何优化(15分钟时间)

从0设计一款App总体架构,如何去作?

说一款你认为当前比较火的应用并设计(好比:直播APP,P2P金融,小视频等)

实现一个库,完成日志的实时上报和延迟上报两种功能,该从哪些方面考虑?

你最优秀的工程设计项目,是怎么设计和实现的;扩展,如何作成一个平台级产品?

6、其它高频面试题

一、如何保证一个后台服务不被杀死?(相同问题:如何保证service在后台不被kill?)比较省电的方式是什么?

保活方案

一、AIDL方式单进程、双进程方式保活Service。(基于onStartCommand() return START_STICKY)

START_STICKY 在运行onStartCommand后service进程被kill后,那将保留在开始状态,可是不保留那些传入的intent。不久后service就会再次尝试从新建立,由于保留在开始状态,在建立 service后将保证调用onstartCommand。若是没有传递任何开始命令给service,那将获取到null的intent。

除了华为此方案无效以及未更改底层的厂商不起做用外(START_STICKY字段就能够保持Service不被杀)。此方案能够与其余方案混合使用

二、下降oom_adj的值(提高service进程优先级):

Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:

  • 1.前台进程 (Foreground process)
  • 2.可见进程 (Visible process)
  • 3.服务进程 (Service process)
  • 4.后台进程 (Background process)
  • 5.空进程 (Empty process)

当service运行在低内存的环境时,将会kill掉一些存在的进程。所以进程的优先级将会很重要,能够使用startForeground 将service放到前台状态。这样在低内存时被kill的概率会低一些。

  • 常驻通知栏(可经过启动另一个服务关闭Notification,不对oom_adj值有影响)。

  • 使用”1像素“的Activity覆盖在getWindow()的view上。

此方案无效果

  • 循环播放无声音频(黑科技,7.0下杀不掉)。

成功对华为手机保活。小米8下也成功突破20分钟

  • 三、监听锁屏广播:使Activity始终保持前台。
  • 四、使用自定义锁屏界面:覆盖了系统锁屏界面。
  • 五、经过android:process属性来为Service建立一个进程。
  • 六、跳转到系统白名单界面让用户本身添加app进入白名单。

复活方案

一、onDestroy方法里重启service

service + broadcast 方式,就是当service走onDestory的时候,发送一个自定义的广播,当收到广播的时候,从新启动service。

二、JobScheduler:原理相似定时器,5.0,5.1,6.0做用很大,7.0时候有必定影响(能够在电源管理中给APP受权)。

只对5.0,5.一、6.0起做用。

三、推送互相唤醒复活:极光、友盟、以及各大厂商的推送。

四、同派系APP广播互相唤醒:好比今日头条系、阿里系。

此外还能够监听系统广播判断Service状态,经过系统的一些广播,好比:手机重启、界面唤醒、应用状态改变等等监听并捕获到,而后判断咱们的Service是否还存活。

结论:高版本状况下能够使用弹出通知栏、双进程、无声音乐提升后台服务的保活几率。

二、Android动画框架实现原理。

Animation 框架定义了透明度,旋转,缩放和位移几种常见的动画,并且控制的是整个View。实现原理:

每次绘制视图时,View 所在的 ViewGroup 中的 drawChild 函数获取该View 的 Animation 的 Transformation 值,而后调用canvas.concat(transformToApply.getMatrix()),经过矩阵运算完成动画帧,若是动画没有完成,继续调用 invalidate() 函数,启动下次绘制来驱动动画,动画过程当中的帧之间间隙时间是绘制函数所消耗的时间,可能会致使动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能响应事件。

三、Activity-Window-View三者的差异?

Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图) LayoutInflater像剪刀,Xml配置像窗花图纸。

在Activity中调用attach,建立了一个Window, 建立的window是其子类PhoneWindow,在attach中建立PhoneWindow。 在Activity中调用setContentView(R.layout.xxx), 其中其实是调用的getWindow().setContentView(), 内部调用了PhoneWindow中的setContentView方法。

建立ParentView:

做为ViewGroup的子类,实际是建立的DecorView(做为FramLayout的子类), 将指定的R.layout.xxx进行填充, 经过布局填充器进行填充【其中的parent指的就是DecorView】, 调用ViewGroup的removeAllView(),先将全部的view移除掉,添加新的view:addView()。

参考文章

四、低版本SDK如何实现高版本api?

  • 一、在使用了高版本API的方法前面加一个 @TargetApi(API号)。
  • 二、在代码上用版本判断来控制不一样版本使用不一样的代码。

五、说说你对Context的理解?

六、Android的生命周期和启动模式

由A启动B Activity,A为栈内复用模式,B为标准模式,而后再次启动A或者杀死B,说说A,B的生命周期变化,为何?

Activity的启动模式有哪些?栈里是A-B-C,先想直接到A,BC都清理掉,有几种方法能够作到?这几种方法产生的结果是有几个A的实例?

七、ListView和RecyclerView系列

RecyclerView和ListView有什么区别?局部刷新?前者使用时多重type场景下怎么避免滑动卡顿。懒加载怎么实现,怎么优化滑动体验。

ListView、RecyclerView区别?

1、使用方面:

ListView的基础使用:

  • 继承重写 BaseAdapter 类
  • 自定义 ViewHolder 和 convertView 一块儿完成复用优化工做

RecyclerView 基础使用关键点一样有两点:

  • 继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder
  • 设置布局管理器,控制布局效果

RecyclerView 相比 ListView 在基础使用上的区别主要有以下几点:

  • ViewHolder 的编写规范化了
  • RecyclerView 复用 Item 的工做 Google 全帮你搞定,再也不须要像 ListView 那样本身调用 setTag
  • RecyclerView 须要多出一步 LayoutManager 的设置工做

2、布局方面:

RecyclerView 支持 线性布局、网格布局、瀑布流布局 三种,并且同时还可以控制横向仍是纵向滚动。

3、API提供方面:

ListView 提供了 setEmptyView ,addFooterView 、 addHeaderView.

RecyclerView 供了 notifyItemChanged 用于更新单个 Item View 的刷新,咱们能够省去本身写局部更新的工做。

4、动画效果:

RecyclerView 在作局部刷新的时候有一个渐变的动画效果。继承 RecyclerView.ItemAnimator 类,并实现相应的方法,再调用 RecyclerView的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法设置完便可实现自定义的动画效果。

5、监听 Item 的事件:

ListView 提供了单击、长按、选中某个 Item 的监听设置。

RecyclerView与ListView缓存机制的不一样

想改变listview的高度,怎么作?

listview跟recyclerview上拉加载的时候分别应该如何处理?

如何本身实现RecyclerView的侧滑删除?

RecyclerView的ItemTouchHelper的实现原理

八、如何实现一个推送,消息推送原理?推送到达率的问题?

一:客户端不断的查询服务器,检索新内容,也就是所谓的pull 或者轮询方式。

二:客户端和服务器之间维持一个TCP/IP长链接,服务器向客户端push。

blog.csdn.net/clh604/arti…

www.jianshu.com/p/45202dcd5…

九、动态权限系列。

动态权限适配方案,权限组的概念

Runtime permission,如何把一个预置的app默认给它权限?不要受权。

十、自定义View系列。

Canvas的底层机制,绘制框架,硬件加速是什么原理,canvas lock的缓冲区是怎么回事?

双指缩放拖动大图

TabLayout中如何让当前标签永远位于屏幕中间

TabLayout如何设置指示器的宽度包裹内容?

自定义View如何考虑机型适配?

  • 合理使用warp_content,match_parent。
  • 尽量地使用RelativeLayout。
  • 针对不一样的机型,使用不一样的布局文件放在对应的目录下,android会自动匹配。
  • 尽可能使用点9图片。
  • 使用与密度无关的像素单位dp,sp。
  • 引入android的百分比布局。
  • 切图的时候切大分辨率的图,应用到布局当中,在小分辨率的手机上也会有很好的显示效果。

十一、对谷歌新推出的Room架构。

十二、没有给权限如何定位,特定机型定位失败,如何解决?

1三、Debug跟Release的APK的区别?

1四、android文件存储,各版本存储位置的权限控制的演进,外部存储,内部存储

1五、有什么提升编译速度的方法?

1六、Scroller原理。

Scroller执行流程里面的三个核心方法

mScroller.startScroll();
mScroller.computeScrollOffset();
view.computeScroll();
复制代码

一、在mScroller.startScroll()中为滑动作了一些初始化准备,好比:起始坐标,滑动的距离和方向以及持续时间(有默认值),动画开始时间等。

二、mScroller.computeScrollOffset()方法主要是根据当前已经消逝的时间来计算当前的坐标点。由于在mScroller.startScroll()中设置了动画时间,那么在computeScrollOffset()方法中依据已经消逝的时间就很容易获得当前时刻应该所处的位置并将其保存在变量mCurrX和mCurrY中。除此以外该方法还可判断动画是否已经结束。

1七、Hybrid系列。

webwiew了解?怎么实现和javascript的通讯?相互双方的通讯。@JavascriptInterface在?版本有bug,除了这个还有其余调用android方法的方案吗?

Android中Java和JavaScript交互
webView.addJavaScriptInterface(new Object(){xxx}, "xxx");
1
答案:能够使用WebView控件执行JavaScript脚本,而且能够在JavaScript中执行Java代码。要想让WebView控件执行JavaScript,须要调用WebSettings.setJavaScriptEnabled方法,代码以下:

WebView webView = (WebView)findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
//设置WebView支持JavaScript
webSettings.setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());

JavaScript调用Java方法须要使用WebView.addJavascriptInterface方法设置JavaScript调用的Java方法,代码以下:

webView.addJavascriptInterface(new Object()
{
    //JavaScript调用的方法
    public String process(String value)
    {
        //处理代码
        return result;
    }
}, "demo");       //demo是Java对象映射到JavaScript中的对象名

能够使用下面的JavaScript代码调用process方法,代码以下:

<script language="javascript">
    function search()
    {
        //调用searchWord方法
        result.innerHTML = "<font color='red'>" + window.demo.process('data') + "</font>";
    }
复制代码

1八、若是在当前线程内使用Handler postdelayed 两个消息,一个延迟5s,一个延迟10s,而后使当前线程sleep 5秒,以上消息的执行时间会如何变化?

答:照常执行

扩展:sleep时间<=5 对两个消息无影响,5< sleep时间 <=10 对第一个消息有影响,第一个消息会延迟到sleep后执行,sleep时间>10 对两个时间都有影响,都会延迟到sleep后执行。

1九、Android中进程内存的分配,能不能本身分配定额内存?

20、下拉状态栏是否是影响activity的生命周期,若是在onStop的时候作了网络请求,onResume的时候怎么恢复

2一、Android长链接,怎么处理心跳机制。

长链接:长链接是创建链接以后, 不主动断开. 双方互相发送数据, 发完了也不主动断开链接, 以后有须要发送的数据就继续经过这个链接发送.

心跳包:其实主要是为了防止NAT超时,客户端隔一段时间就主动发一个数据,探测链接是否断开。

服务器处理心跳包:假如客户端心跳间隔是固定的, 那么服务器在链接闲置超过这个时间还没收到心跳时, 能够认为对方掉线, 关闭链接. 若是客户端心跳会动态改变, 应当设置一个最大值, 超过这个最大值才认为对方掉线. 还有一种状况就是服务器经过TCP链接主动给客户端发消息出现写超时, 能够直接认为对方掉线.

2二、CrashHandler实现原理?

获取app crash的信息保存在本地而后在下一次打开app的时候发送到服务器。
复制代码

2三、SurfaceView和View的最本质的区别?

SurfaceView是在一个新起的单独线程中能够从新绘制画面,而view必须在UI的主线程中更新画面。

在UI的主线程中更新画面可能会引起问题,好比你更新的时间过长,那么你的主UI线程就会被你正在画的函数阻塞。那么将没法响应按键、触屏等消息。当使用SurfaceView因为是在新的线程中更新画面因此不会阻塞你的UI主线程。但这也带来了另一个问题,就是事件同步。好比你触屏了一下,你须要在SurfaceView中的thread处理,通常就须要有一个event queue的设计来保存touchevent,这会稍稍复杂一点,由于涉及到线程安全。

2四、Android程序运行时权限与文件系统权限

一、Linux 文件系统权限。不一样的用户对文件有不一样的读写执行权限。在android系统中,system和应用程序是分开的,system里的数据是不可更改的。

二、Android中有3种权限,进程权限UserID,签名,应用申明权限。每次安装时,系统根据包名为应用分配惟一的userID,不一样的userID运行在不一样的进程里,进程间的内存是独立的,不能够相互访问,除非经过特定的Binder机制。

Android提供了以下的一种机制,能够使两个apk打破前面讲的这种壁垒。

在AndroidManifest.xml中利用sharedUserId属性给不一样的package分配相同的userID,经过这样作,两个package能够被当作同一个程序,系统会分配给两个程序相同的UserID。固然,基于安全考虑,两个package须要有相同的签名,不然没有验证也就没有意义了。

2五、曲面屏的适配。

2六、TextView调用setText方法的内部执行流程。

2七、怎么控制另一个进程的View显示(RemoteView)?

2八、如何实现右滑finish activity?

2九、如何在整个系统层面实现界面的圆角效果。(即全部的APP打开界面都会是圆角)

30、非UI线程能够更新UI吗?

能够,当访问UI时,ViewRootImpl会调用checkThread方法去检查当前访问UI的线程是哪一个,若是不是UI线程则会抛出异常。执行onCreate方法的那个时候ViewRootImpl还没建立,没法去检查当前线程.ViewRootImpl的建立在onResume方法回调以后。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
复制代码

非UI线程是能够刷新UI的,前提是它要拥有本身的ViewRoot,即更新UI的线程和建立ViewRoot的线程是同一个,或者在执行checkThread()前更新UI。

3一、如何解决git冲突?

3二、单元测试有没有作过,说说熟悉的单元测试框架?

首先,Android测试主要分为三个方面:

  • 单元测试(Junit四、Mockito、PowerMockito、Robolectric)
  • UI测试(Espresso、UI Automator)
  • 压力测试(Monkey)

WanAndroid项目和XXX项目中使用用到了单元测试和部分自动化UI测试,其中单元测试使用的是Junit4+Mockito+PowerMockito+Robolectric。下面我分别简单介绍下这些测试框架:

一、Junit4:

使用@Test注解指定一个方法为一个测试方法,除此以外,还有以下经常使用注解@BeforeClass->@Before->@Test->@After->@AfterClass以及@Ignore。

Junit4的主要测试方法就是断言,即assertEquals()方法。而后,你能够经过实现TestRule接口的方式重写apply()方法去自定义Junit Rule,这样就能够在执行测试方法的先后作一些通用的初始化或释放资源等工做,接着在想要的测试类中使用@Rule注解声明使用JsonChaoRule便可。(注意被@Rule注解的变量必须是final的。最后,咱们直接运行对应的单元测试方法或类,若是你想要一键运行项目中全部的单元测试类,直接点击运行Gradle Projects下的app/Tasks/verification/test便可,它会在module下的build/reports/tests/下生成对应的index.html报告。

Junit4它的优势是速度快,支持代码覆盖率如jacoco等代码质量的检测工具。缺点就是没法单独对Android UI,一些类进行操做,与原生Java有一些差别。

二、Mockito:

能够使用mock()方法模拟各类各样的对象,以替代真正的对象作出但愿的响应。除此以外,它还有不少验证方法调用的方式如Mockit.when(调用方法).thenReturn(验证的返回值)、verfiy(模拟对象).验证方法等等。

这里有一点要补充下:简单的测试会使总体的代码更简洁,更可读、更可维护。若是你不能把测试写的很简单,那么请在测试时重构你的代码。

最后,对于Mockito来讲,它的优势是有各类各样的方式去验证"模仿对象"的互动或验证发生的某些行为。而它的缺点就是不支持mock匿名类、final类、static方法private方法。

三、PowerMockito:

所以,为了解决Mockito的缺陷,PoweMockito出现了,它扩展了Mockito,支持mock匿名类、final类、static方法、private方法。只要使用它提供的api如PowerMockito.mockStatic()去mock含静态方法或字段的类,PowerMockito.suppress(PowerMockito.method(类.class, 方法名)便可。

四、Robolectric

前面3种咱们说的都是Java相关的单元测试方法,若是想在Java单元测试里面进行Android单元测试,还得使用Robolectric,它提供了一套能运行在JVM的Android代码。它提供了一系列相似ShadowToast.getLatestToast()、ShadowApplication.getInstance()这种方式来获取Android平台对应的对象。能够看到它的优势就是支持大部分Android平台依赖类的底层引用与模拟。缺点就是在异步测试的状况下有些问题,这是能够结合Mockito来将异步转为同步便可解决。

最后,自动化UI测试项目中我使用的是Expresso,它提供了一系列相似onView().check().perform()的方式来实现点击、滑动、检测页面显示等自动化的UI测试效果,这里在个人WanAndroid项目下的BasePageTest基类里面封装了一系列通用的方法,有兴趣能够去看看。

3三、Jenkins持续集成。

3四、工做中有没有用过或者写过什么工具?脚本,插件等等;好比:多人协同开发可能对一些相同资源都各自放了一份,有没有方法自动检测这种重复之类的。

3五、如何绕过9.0限制?

如何限制?

  • 一、阻止java反射和JNI。
  • 二、当获取方法或Field时进行检测。
  • 三、怎么检测?

区分出是系统调用仍是开发者调用:

根据堆栈,回溯Class,查看ClassLoader是不是BootStrapClassLoader。

区分后,再区分是不是hidden api:

Method,Field都有access_flag,有一些备用字段,hidden信息存储其中。

如何绕过?

一、不用反射:

利用一个fakelib,例如写一个android.app.ActivityThread#currentActivityThread空实现,直接调用;

二、假装系统调用:

jni修改一个class的classloder为BootStrapClassLoader,麻烦。

利用系统方法去反射:

利用原反射,即:getDeclaredMethod这个方法是系统的方法,经过getDeclaredmethod反射去执行hidden api。

三、修改Method,Field中存储hidden信息的字段:

利用jni去修改。

3六、对文件描述符怎么理解?

3七、如何实现进程安全写文件?

其它面试题


其余扩展面试题

1、Kotlin (⭐⭐)

一、Kotlin 特性,和 Java 相比有什么不一样的地方?

  • 能直接与Java相互调用,能与Java工程共存
  • 大大减小样板代码
  • 能够将Kotlin代码编译为无需虚拟机就可运行的原生二进制文件
  • 支持协程
  • 支持高阶函数
  • 语言层面解决空指针问题
  • 对字符串格式化的处理($变量名)
  • 更像Python的语法
  • 对λ表达式支持更好

mp.weixin.qq.com/s/FqXLNz5p9…

二、Kotlin为何能和Java混编?

三、什么是协程?

2、大前端 (⭐⭐)

一、Hybrid通讯原理是什么,有作研究吗?

二、JS的交互理解吗?平时工做用的多吗,项目中是怎么与Web交互的?

Android经过WebView调用JS代码:

一、经过WebView的loadUrl():

  • 设置与Js交互的权限:

    webSettings.setJavaScriptEnabled(true)

  • 设置容许JS弹窗:

    webSettings.setJavaScriptCanOpenWindowsAutomatically(true)

  • 载入JS代码:

    mWebView.loadUrl("file:///android_asset/javascript.html")

  • webview只是载体,内容的渲染须要使用webviewChromClient类去实现,经过设置WebChromeClient对象处理JavaScript的对话框。

特别注意:

JS代码调用必定要在 onPageFinished() 回调以后才能调用,不然不会调用。

二、经过WebView的evaluateJavascript():

  • 该方法比第一种方法效率更高、使用更简洁,由于该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。
  • Android 4.4 后才可以使用。

只须要将第一种方法的loadUrl()换成evaluateJavascript()便可,经过onReceiveValue()回调接收返回值。

建议:两种方法混合使用,即Android 4.4如下使用方法1,Android 4.4以上方法2。

JS经过WebView调用 Android 代码:

一、经过 WebView的addJavascriptInterface()进行对象映射:

-定义一个与JS对象映射关系的Android类:AndroidtoJs:

  • 定义JS须要调用的方法,被JS调用的方法必须加入@JavascriptInterface注解。
  • 经过addJavascriptInterface()将Java对象映射到JS对象。

优势:使用简单,仅将Android对象和JS对象映射便可。

缺点:addJavascriptInterface 接口引发远程代码执行漏洞,漏洞产生缘由是:

当JS拿到Android这个对象后,就能够调用这个Android对象中全部的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。

二、经过 WebViewClient 的方法shouldOverrideUrlLoading ()回调拦截 url:

  • Android经过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url。

  • 解析该 url 的协议。

  • 若是检测到是预先约定好的协议,就调用相应方法。

    根据协议的参数,判断是不是所须要的url。 通常根据scheme(协议格式) & authority(协议名)判断(前两个参数)。

优势:不存在方式1的漏洞;

缺点:JS获取Android方法的返回值复杂,若是JS想要获得Android方法的返回值,只能经过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去。

三、经过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息:

原理:

Android经过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框 (警告框、确认框、输入框),获得他们的消息内容,而后解析便可。

经常使用的拦截是:拦截 JS的输入框(即prompt()方法),由于只有prompt()能够返回任意类型的值,操做最全面方便、更加灵活;而alert()对话框没有返回值;confirm()对话框只能返回两种状态(肯定 / 取消)两个值。

image

Android:你要的WebView与 JS 交互方式 都在这里了

三、react native有多少了解?讲一下原理。

四、weex了解吗?如何本身实现相似技术?

五、flutter了解吗?内部是如何实现跨平台的?如何实现多Native页面接入?如何实现对现有工程的flutter迁移?

六、Dart语言有研究过吗?

七、快应用了解吗?跟其她方式相比有什么优缺点?

八、说说你用过的混合开发技术有哪些?各有什么优缺点?

3、脚本语言 (⭐⭐)

一、脚本语言会吗?

二、Python会吗?

Python基础

人工智能了解

三、Gradle了解多少?groovy语法会吗?

非技术面试题

1、高频题集 (⭐⭐⭐)

一、你以为安卓开发最关键的技术在哪里?

技术是没有止境的,因此确定会不断有演进和难点。

一. 底层和框架如何更好地设计及优化以适应业务的高速增加。提及来很简单,低耦合高扩展,作起来是须要长期经验积累。

二. 我抛几个细节难点:

  • 插件化如何使插件的 Manifest 生效
  • H5 容器如何更好地优化和兼容
  • App 端优化,这是个没止境的话题,网络、图片、动画、内存、电量等等随着优化的加深,你会发现不能局限在客户端,服务端也须要深刻。
  • SPDY 的优势并入 HTTP 2.0 大家有在测试或用吗?
  • Fresco 出来前你是否是以为图片缓存已经到头了?
  • Android App 为何总体流畅性老是被诟病?……

三. 若是你以为没有难点或者难点在兼容、UI 之类问题上,那么可能两个缘由:

  • 公司业务发展过慢,对技术的需求不够迫切
  • 我的长时间在业务开发上,这个对于走技术路线的人来讲挺麻烦的,不主动去接触学习的话,n 年之后也仍是这个样子为了更好的我的成长,这两点都是须要注意和解决的问题。

二、你还要什么了解和要问的吗?

你在公司的一天是如何度过的?

可否给我简单介绍下贵公司业务与战略的将来发展?

贵公司最让你自豪的企业文化是什么?(适合大公司)

团队、公司如今面临的最大挑战是什么?

对于将来加入这个团队,你对个人指望是什么?

您以为我哪方面知识须要深刻学习或者个人不足在那些方面,从此我该注意什么*?

你还能够问下项目团队多少人,主要以什么方向为主,一年内的目标怎样,团队气氛怎样,等内容着手。

三、研究比较深刻的领域有哪些?

四、本身最擅长的技术点,最感兴趣的技术领域和技术?

五、项目中用了哪些开源库,如何避免由于引入开源库而致使的安全性和稳定性问题?

六、说下你都看过那些技术书籍,你是如何自学的。你以为本身的优点与弱点是什么。

七、说下项目中遇到的棘手问题,包括技术,交际和沟通。

八、说下你近几年的规划?

九、对加班怎么看(不要太浮夸,现实一点哦)?

十、介绍你作过的哪些项目。

十一、你并不是毕业于名牌院校?

十二、为何要离职?

1三、当你的开发任务很紧张,你怎么去作代码优化的?

2、次高频题集 (⭐⭐)

一、对业内信息的关注渠道有哪些?

二、最近都读哪些书?

三、给你一个项目,你怎么看待他的市场和技术的关系?

四、你以往的项目中,以你如今的眼光去评价项目的利弊?

五、对于非立项(KPI)项目,怎么推动?

六、都使用过哪些自定义控件?

七、除了简历上的工做经历,您还会去关注哪些领域?

八、评价下本身,评价下本身的技术水平,我的代码量如何?

九、你朋友对你的评价?

十、本身的优势和缺点是什么?并举例说明?

十一、你以为你个性上最大的优势是什么?

十二、说说你最大的缺点?

1三、最能归纳你本身的三个词是什么?

1四、说说你的家庭?

1五、除了本公司外,还应聘了哪些公司?(相似问题:当前的offer情况)

1六、经过哪些渠道了解的招聘信息?

1七、你的业余爱好是什么?

1八、你作过的哪件事最令本身感到骄傲?

1九、谈谈你对跳槽的见解?

20、怎样看待学历和能力?

2一、您跟您的主管或直接上司有没有针对以上离职缘由的这些问题沟经过?若是没有请说明缘由。若是有请说一下过程和结果?

2二、您以为你关注的这些领域跟您目前从事的职业有哪些利弊关系?若是有请说明利弊关系?

2三、您在选择工做中更看重的是什么?(多是成长空间、培训机会、发挥平台、薪酬等答案)

2四、您可不能够说说您在薪酬方面的内心预期?

2五、有人说挣将来比挣钱更为重要,您怎么理解?

2六、假设,某一天,在工做办公室走廊,您和一位同事正在抱怨上级陈某平时作事缺少公平性,恰巧被陈某听到,您会怎么办?

2七、怎么样处理工做和生活的关系?怎么处理在工做中遇到困难?请举例说明

2八、在您的现实生活中,您最不喜欢和什么样的人共事?为何?举例说明。

2九、在您认识的人中,有没有人不喜欢您?为何不喜欢您?请举例说明。

30、当老板/上司/同事/客户误会你,你会怎么办?

3一、当你发现其余部门的工做疏漏已经影响到您的工做绩效时,您怎么办?

3二、您但愿在什么样的领导下工做?

3三、咱们工做与生活历程并非一路顺风的,谈谈您的工做或生活中出现的挫折或低潮期,您如何克服?

3四、假如您的上司是一个很是严厉、领导手腕强硬,时常给您巨大压力的人,您以为这种领导方式对您有何利、弊?

3五、您的领导给您布置了一项您之前从未触及过的任务,您打算如何去完成它?(若是有相似的经历说说完成的经历。)

3六、谈谈您以往职业生涯中最有压力的1、两件事,并说说是如何克服的。

3七、谈谈您以往职业生涯中令您有成就感的1、两件事,并说说它给您的启示。

3八、请您举一个例子,说明在完成一项重要任务时,您是怎样和他人进行有效合做的。

3九、当你要牺牲本身的某些方面与他人共事时,你会怎么办?

40、有时团队成员不能有效共事,当遇到这种问题时你是怎么处理的?你又是如何改善这类状况的?

4一、咱们有时不得不与本身不喜欢的人在一个团队工做,若是遇到这样的状况你会怎么办?

4二、您对委任的任务完成不了时如何处理?

4三、说说您对下属布置的任务在时间方面是如何要求的?

4四、说说您在完成上司布置的任务时,在时间方面是如何要求本身的?

4五、您以往在领导岗位中,一个月内分别有哪些主要的工做任务?

4六、当您发现您的部属目前士气较低沉,您通常从哪些方面去调动?

4七、说说您在以往领导岗位中出现管理失控的事例及过后的缘由分析。您的部属在一个专业的问题上跟您发生争议,您如何对待这种事件?

4八、你对某某某互联网发生事情的见解?(直播答题等等)

4九、怎么看待前端和后端?

结语

其实,在面试中,不少题目并无真正的答案,你能回答到什么样的深度,仍是得靠本身真正的去使用和研究。知识的深度和广度都很重要,因此都须要花相应的时间去学习。这样,就算被问到本身不熟悉的领域也能够同面试官唠嗑两句,而后能够在适当的时机引向本身有深刻研究的领域,让面试官以为你是这个领域的专家。

赞扬

若是这个库对您有很大帮助,您愿意支持这个项目的进一步开发和这个项目的持续维护。你能够扫描下面的二维码,让我喝一杯咖啡或啤酒。很是感谢您的捐赠。谢谢!


Contanct Me

● 微信 && 微信群:

欢迎关注个人微信:bcce5360。因为微信群人数太多没法生成群邀二维码,因此麻烦你们想进微信群的朋友们,加我微信拉你进群(PS:微信群的学习氛围与各项福利将会超乎你的想象)

● QQ群:

2千人QQ群,Awesome-Android学习交流群,QQ群号:959936182, 欢迎你们加入~

About me

很感谢您阅读这篇文章,但愿您能将它分享给您的朋友或技术群,这对我意义重大。

但愿咱们能成为朋友,在 Github掘金上一块儿分享知识。