

一个类的行为或者算法可以在运行时更改,策略模式改变对象的执行算法。属于行为型模式。
1 | public class StrategyPattern { |
1.可以防止硬编码,if_else构成的多种case的行为;
2.系统需要动态的在几种算法中选择一种;
3.不希望提供具体的算法逻辑。
1.算法可以灵活切换;
2.避免复杂的条件判断;
3.算法扩展性好。
1.具体策略类会越来越多;
实际应用场景:
三方应用的初始化,有友盟(区分线上线下版本内部会日志打印上报时机)、BlockCanary;
源码中RecyclerView或者LisView的Adapter,每个具体的列表就是一种策略,构建列表时使用具体的策略来完成展示。

反射就是在程序运行时动态加载类、方法或属性,在coding阶段是直接不知道对象是谁
普通场景创建一个类的过程是先判断类的Class对象是否加载到了内存中,Class对象已经加载到内存(字节码已经加载)就为实例对象分配内存,根据不同的垃圾收集器分配内存,GC使用复制算法或者标记整理算法内存规整场景下,只需要将指针向空闲的一边移动,这种分配方式称指针碰撞,另外一种内存存在碎片,碎片内存会保存在一个“空闲列表”中,从空闲列表中取出一块可容纳目标对象的内存区域来存储对象。如果Clas对象没有加载到内存中,就执行类的加载过程,经过加载、连接和初始化完成类的加载,再为实例对象分配内存。
反射通过Class对象创建实例对象,并获取类的属性和方法。
通过new对象,经过编译器安全校验,确认具体对象创建叫静态编译;反射这种方式叫做动态编译。
1 | //通过forName方法获得(带有包名的真实类路径) |
1 | //直接通过类.class |
1 | //通过对象.getClass() |
1 | //得到所有构造函数 |
1 | //获得特定构造函数 |
1 | //得到共有的public方法 包括继承的方法 |
1 | //得到类所有方法 包括public private protect |
1 | //得到特定对象,并执行实例该方法 |
1 | //获取所有共有的public变量,包括继承的属性 |
1 | //获取所有申明的变量 public private protect |
1 | //设置变量值 |
1 | //通过反射得到的构造函数创建ArrayList实例,添加了两个元素ok和12 |
反射忽略了权限检查、语法检查(上面ArrayList变量l可以添加ok字符串也可以添加整型数12),破坏了封装性。
1.用在IOC容器,用来创建对象;
2.编译阶段跨模块无法调用的场景;
3.Gson中变量复制(不需要get set方法的原因)。
JAVA中泛型简单讲就是将类型参数化,可以用在类、接口和方法。
泛型类:
1 | public class ClassA<T> { |
泛型接口:
1 | interface IA<T> { |
泛型方法:
1 | public <T> void print(T t) { |
泛型只在编译期有效,编译后生成的字节码中不包含泛型信息,也就是在编译结束后泛型被去除了。即泛型擦除。泛型被擦除后变成了什么呢?
1 | ArrayList<Integer> listA = new ArrayList<>(); |
运行结果:
listA type isclass java.lang.Integer
listA编译后泛型转化成了实际对象类型Integer,也就是自动类型转换。
MyClass<? extends Number> 有上限,须继承Number类;
MyClass<? super String>有下限,须是String类的父类,如Object
1.泛型会经过先类型检查再编译,类型检查针对的是引用;
1 | ArrayList<String> list = new ArrayList<>(); |
2.引用传递;1
2
3
4ArrayList<String> list = new ArrayList<>();
t1(list);//编译不通过,类型检测ArrayList<String>与ArrayList<Object>数组结构不一样
private void t1(ArrayList<Object> l) {
}
泛型类型继承规则与普通类继承关系不一样的一点。
3.类型擦除与多态的冲突,如在父类中泛型方法,子类实现的对应方法泛型换作具体类型,在该过程中系统为我们生成了桥方法;
1 | //创建接口IFanSheDuoTai,申明方法,方法参数类型是泛型T; |
反编译FanSheDuoTai.class
javap -p FanSheDuoTai.class
1 | public class com.litchi.demo.FanSheDuoTai implements com.litchi.demo.IFanSheDuoTai<java.lang.String> { |
FanSheDuoTai反编译得到的方法可以看到有test(String)和test(Object),test(Object)就是在泛型编译完成后的桥方法
4.泛型不能是基本数据类型;
5.Gson在运行时使用常量池中泛型标签信息解析数据到泛型;
1 | Type componentType = ((GenericArrayType)type).getGenericComponentType(); |
6.静态方法不能使用类定义的泛型,因为在静态方法调用时,类对象可能还没有实例化,类定义泛型是在类实例化时执行,所在的内存区域也是不一样的。
https://www.cnblogs.com/huansky/p/8043149.html
https://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java-class-files
控制反转(Inversion Of Control):
控制和反转,连个问题谁控制谁?谁反转了谁?
对象控制它内部创建的对象,反转是创建对象的方式被反转,传统是对象直接new一个需要对象,这种形式可以叫正转或不转,控制反转就是将依赖对象的创建交给三方完成,而不需要直接new所需对象。依赖注入就是IOC的一种具体表现。
依赖注入:
对象A依赖对象B,对象B不会在对象A中直接创建,而是将对象B引用传给对象A,这种形式就叫依赖注入。也就是对象A对对象B的依赖,通过注入形式获得持有。
1 | class A{ |
依赖注入在实际开发中非常常见,比如设置事件监听、网络回调等,策略模式也是一种依赖注入的思想。
]]>
OkHttp默认支持
1共享一个scoket完成相同主机的请求,支持Http2.0,头部压缩、连接复用、服务端push,同一主机所有请求共用一个scoket连接;
2.连接池,减少请求延迟(https http协议下)
3.透明gzip压缩
4.响应缓冲,减少不必要的网络请求。
网络请求调用流程大致是:
首先创建request对象,设置请求地址,方法,header信息,接着OkHttp调用newCall方法传入创建好的request对象,newCall返回RealCall,RealCall执行enqueue执行异步请求,同时传入回调, 请求完成拿到数据,同步请求执行execute方法并返回response对象。
1 | public synchronized ExecutorService executorService() { |
ThreadPoolExecutor参数含义:
corePoolSize:0 –核心线程数 即一直保持在线程池中的线程数,即使它们闲置也不会被回收,除非设置了核心线程数超时时间allowCoreThreadTimeOut,设置allowCoreThreadTimeOut后核心线程在闲置时达到超时时间就会被回收。
maximumPoolSize:Integer.MAX_VALUE–线程数池中允许的最大线程数;
keepAliveTime:60–线程池中非核心线程闲置等待任务时的超时时间;
unit:TimeUnit.SECONDS– keepAliveTime的时间单位;
workQueue:AsynchronousQueue –工作队列是任务在执行前的容器,任务通过execute方法提交;
threadFactory:Util.threadFactory(“OkHttp Dispatcher”, false) –该工厂在创建线程时被使用;
所以OkHttp中的线程池创建时没有核心线程,不限制线程的数量,线程在闲置60s后会被回收,那么是不是说线程池中的线程并行是不做限制的增长,显然不是,Dispatcher还维护了异步请求的两个队列(runningAsyncCalls和readyAsyncCalls),在向runningAsyncCalls队列中添加call时最大请求数是64,这样保证同一时刻最多只有64个请求正在执行,使线程池中线程的增量是可控的。另外还可以自定义线程池配置设置给Dispatcher。
Dispatcher(分发器)主要负责什么?
Dispatcher内部维护一个线程池,和三个请求队列(runningAsyncCalls、readyAsyncCalls and runningSyncCalls),负责请求的执行和管理。
拦截器(Interceptor)主要负责什么?拦截器的好处?
拦截器是OkHttp中最重要的部分,它负责网络重试、重定向、网络监控、缓冲等功能。拦截器的调用流程是从getResponseWithInterceptorChain方法开始,getResponseWithInterceptorChain方法中创建ApplicatinInterceptorChain,接着调用chain.proceed(request),proceed方法中继续创建ApplicatipnInterceptorChain,将chain传入拦截器interceptor方法中,该方法中会执行chain.proceed(),接着每个拦截器依次都会被调用到,最后在CallServerIntercptor拦截器中返回请求服务器得到的response对象。拦截器调用过程中我们可以自定义应用类拦截器和网络类拦截器,应用类拦截器最先被执行,网络类拦截器会在建立连接后被执行,也就是ConnectInterceptor拦截器被调用之后。 拦截器的这种设计使复杂的网络请求操作分层完成,每一层的拦截器完成自身对请求和响应的任务(各司其职)。
OkHttp内置哪些拦截器,具体负责哪个任务?
内置拦截器包括:
RetryAndFollowupInterceptor:负责网络重试和重定向;
BridgeInterceptor:负责将应用层码转化为网络层码,将header中缺失的网络层特有header补充完整(例如User-Agent,keep-alive);
CacheInterceptor:管理缓冲,缓冲读取和更新;
ConnectInterceptor:建立网络连接,是请求服务的基本;
CallServerInterceptor:真正发送请求和获取响应数据(将请求写入IO流中,从IO流中读取响应);
实际业务中用自定义拦截器可以做什么?
自定义应用类拦截器:
添加请求header 签名(Id 参数 md5) 版本 ;
验签失败重试(返回内容中会有重试策略);
自定义日志打印。
自定义网络层拦截器:
如果需要打印网络重试、重定向等信息可以使用网络
OkHttp中用到了哪些设计模式?
1.建造者模式(Builder模式)适合构造函数入参多而杂的情况下使用:
说明:使用一个Builder类一步步构建最终的对象。目标对象是由多个“配置”组合构造出来的。
OkHttpClient、Request、Response、Headers、HttpUrl都是用了构造者模式。
2.工厂模式:
说明:创建目标对象不需要对对象使用者暴露创建逻辑,对外屏蔽对象具体实现,使用者只需关注其接口。
CacheStrategy.Factory、ThreadFactory、EventListener.Factory(调用create()根据call创建EventListener)。
3.外观模式:
说明:统一一个类对调用者提供一系列接口,隐藏内部系统的复杂性。
OkHttpClient类 调用者的调用接口都是通过OkHttpClient完成。
4.责任链模式:
说明:一个请求需要经过多个处理者进行责任处理才能完成。没有处理者都有一个特点:前一个处理者对象会持有下一个处理者对象的引用形成一条链,请求发生时,沿着这条链传递。
拦截器链。
5.单例模式:
说明:自己创建自己的唯一实例。
OkHttpClient在项目中使用需要用单例。
OkHttp在项目中做了哪些封装?
OkHttpClient做了单例
感谢:
https://www.jianshu.com/p/fa0dcbfe05cd
https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html

本文主要以源码形式解读OkHttp内部实现,源码基于okhttp:3.10.0。
先看一个异步请求的例子:
1 | //(1) builder模式配置参数构建request对象 |
上面是一个OkHttp异步请求的代码,先构建一个Request对象设置请求地址、请求方式、header以及非GET请求还可设置body,然后创建OkHttpClient对象调用newCall设置request对象得到RealCall,RealCall调用enqueue发起异步请求并设置请求回调完成了一个简单的异步请求,OkHttpClient在实际开发中需要单例,原因会在后面的内容中有答案。
1 | /** |
Request包含请求的参数url请求地址、method请求方法、header请求头数据、请求body以及tag标签。
1 | OkHttpClient(Builder builder) { |
(12)OkHttpClient中创建了连接池,还维护了线程池(dispatcher中)和响应缓冲,所以在使用过程中要用单例。
1 | /** |
newCall方法内部调用RealCall.newRealCall方法并返回Call对象:
1 | static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { |
(1)创建RealCall对象;RealCall对象持有OkHttpClient和Request,构造方法中还创建了重试/重定向拦截器RetryAndFollowInterceptor;
(2)从OkHttpClient得到evenListener对象,
1 | public void enqueue(Callback responseCallback) { |
(1)如果call已经被执行,抛出异常;
(2) 捕获RealCall类的栈轨迹;
(3)触发监听方法callStart(),表示请求开始;
(4)执行dispatcher分发器enqueue方法,创建了AsyncCall类,AsyncCall传入responseCallback。
下面查看dispatcher的enqueue方法:
1 | synchronized void enqueue(AsyncCall call) { |
(1)把call添加到正在运行的队列的判断依据:如果正在执行的异步请求数小于最大请求数(默认64),并且同一个主机执行的异步请求小于单个主机运行的最大请求数(默认5)否则添加到准备队列;
(2)根据(1)把call添加到正在运行的队列;
(3)将call交线程池执行;
(4)不满足(1)把call添加到准备队列。
AsyncCall是Runnable实现类,execute方法完成请求和返回的执行。
AsyncCall. execute():
1 | protected void execute() { |
(1)getResponseWithInterceptorChain方法得到response对象,getResponseWithInterceptorChain是核心实现,后边专门展开说明;
(2)请求如果取消返回,调用responseCallback.onFailure通知处理请求失败;
(3)否则正常请求返回,调用responseCallback.onResponse返回response对象,调用方就可以拿到请求的数据返回,做具体业务处理;
(4)(5)回调执行eventListener.callFailed和responseCallback.onFailure;
(6)dispatcher执行finished方法,finish内部会调用promoteCalls方法从readyAsyncCalls队列中取出call 添加到runningAsyncCalls中,executorService().execute(call)加入线程池中执行call。添加到runningAsyncCalls中的条件是小于运行runningAsyncCalls最大call数并且同一主机call数小于maxRequestsPerHost(即同一主机最大请求数)。
1 | Response getResponseWithInterceptorChain() throws IOException { |
(1)构建全部拦截器list,先添加自定义应用层拦截器;
(2)添加重试/重定向拦截器;
(3)添加桥接拦截器;
(4)添加缓冲拦截器;
(5)添加连接拦截器;
(6)如果不是websocket,添加自定义网络拦截器;
(7)添加请求服务拦截器;
(8)传入拦截器list,请求,call对象,事件监听,连接超时时间以及读写超时时间生成Interceptor.Chain链对象,执行chain.proceed(originalRequest)。
chain.proceed(originalRequest):
1 | public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, |
(1)创建下一个RealInterceptorChain,将chain传入interceptor.intercept方法,intercept中会执行nextchain.proceed方法,然后再继续创建下一个RealInterceptorChain,intercept再执行下一个nextchain.proceed方法,这样循环调用所有拦截器,到最后一个拦截器CallServerInterceptor停止遍历,返回response,遍历循环流程如下图:

接下来阅读自带的拦截器代码:
1 | public Response intercept(Chain chain) throws IOException { |
(1)StreamAllocation用来协调连接(Connections)、流(Screams)和请求队列(Calls);
(2)如果请求取消,调用screamAllocation.release()。release方法会关闭socket,并回调 eventListener.connectionReleased。
(3)执行realChain.proceed方法,proceed内部会创建下一个chain,再传入下一个拦截器,拦截器intercept继续执行负责的工作,并调用chain.proceed()。
(4)如果realChain.proceed方法抛出RouteException,那么调用recover方法,recover方法返回false则不会重试连接,抛出IOException异常,异常会在Call.execute方法中捕获执行eventListener.callFailed和onFailure方法,返回false的条件如下:
1.应用层禁止重试 ;
2.定义了不可重复发送的请求body ;
3.捕获的异常严重等级属于致命 ;
4.没有更多的路由可重意重试;
如果上述的四种场景,请求会被发起重试。
(5)IOException,同样调用recover方法,按照(4)中逻辑判断是否重连;
(6)如果抛出没有catch的异常则执行StreamAllocation.screamFailed()和StreamAllocation.screamFailed;
(7)priorResponse是先前得到的响应数据,如果已经先前响应不为空,response会结合先前响应;
(8)根据响应码确认请求是否需要重定向,返回null表示不需要;
(9)不需要重定向就streamAllocation.release()释放连接并返回response,否则执行下面逻辑;
(10)释放response.body对象;
(11)当前重定向数大于最大可重定向数,则释放连接,抛出异常;
(12)请求不允许重复连接,则释放连接,抛出异常;
(13)检查是否是相同的连接,不是就释放当前连接,重新创建ScreamAllocation;
(14)codec为空抛出异常;
(15)重定向request赋值request,准备执行while循环;
(16)保存当前的response。
1 | public Response intercept(Chain chain) throws IOException { |
桥接拦截器主要功能:
1.将应用码转为网络码;
2.用户请求转为网络请求
1 | public Response intercept(Chain chain) throws IOException { |
(1)获取缓冲策略;
(2)缓冲策略不为空,缓冲响应为空时,关闭缓冲策略;
(3)网络被禁止,缓冲不存在时,返回失败;
(4)不需要网络,返回缓冲响应,缓冲生效;
(5)执行chain.proceed方法抛出异常时,关闭缓冲;
(6)有缓冲时根据条件使用缓冲响应;
(7)使用网络响应;
(8)给予本请求缓冲(添加到缓冲中);
1 | public Response intercept(Chain chain) throws IOException { |
(1)获得RealConnection对象,调用下一个chain.proceed。
1 | public Response intercept(Chain chain) throws IOException { |
(1)写入请求头数据;
(2)写入请求body数据;
(3)读取响应头数据;
(4)读取响应body数据。


Android应用达到一定规模后业务会越来越多,所有业务模块在同一module下耦合性会越来越强,这种强耦合带来开发维护成本增加,开发调试效率也会变低,项目组件化就势在必行。
1.项目模块清晰,组件向内高聚合,对外低耦合;
2.项目成员可以专注于具体组件内业务逻辑,降低开发成本;
3.可以独立调试,提升开发调试效率;
4.可以产出全局功能组件,服务于整个团队,快速响应新项目,达到功能重用。
1.组件间页面如何跳转?
2.组件间如何实现功能调用?
3.如何独立组件或随意组合组件进行调试?
4.如何在各个组件间获取Application实例?
5.如何实现组件间代码隔离?
针对以上五点问题找到以下五个解决方式:
1.利用路由框架实现页面也跳转,如ARoute,或者自行定义的路有跳转协议;
2.利用依赖注入实现组件间通信,调用方利用接口申明实现调用;
3.Android gradle提供两种插件,com.android.application表示App Module,com.android.library表示库Module,通过在gradle.properties中设置参数控制Module的插件类型制定App module,同时配置module的Application和manifest文件,这样就可以独立运行需要调整的module。
4.设计module_common组件创建BaseApplication,App壳module工程Application继承至BaseApplication,其他组件依赖module_common这样就拿到了Application实例。
5.组件之间不存在相互依赖,不能直接调用,它们都依赖到App壳module,这样就完成了组件间的隔离。


本篇分为三个部分记录Handler消息机制,第一部分逐一对“四件套”源码(基于Android-29)进行解读;第二部分根据源码总结它们之间的关联;第三部分对Handler涉及到的问题进行解答。
Message的作用是消息载体本身,Message类内部主要是关键参数和创建方法。官方推荐使用Mesage.obtain()或者Handler.obtainMessage()来创建Message对象,利用缓冲中Message,避免了重复创建对象。这两个方法的内法调用:
1 | /** |
1 | /** |
Handler.obtainMessage()其实是调用到了Message中的obtain()区别只是入参handler重新给message的target赋值,所以只需要关注Message.obtain(),sPool以链表形式存储了缓冲的Message对象,缓冲Message的链表不为空时就从尾部取出一个Message,为空直接创建。至于这里sPool中的Message是在什么时机缓冲起来,它其实是在looper的loop方法处理Message后调用msg.recycleUnchecked()加入到sPool缓冲池。
接下来说明Message中几个关键变量。
what是Message的唯一标示,来区分发送的Message,what值在不同handler是不需要考虑code冲突。
1 | /** |
arg1和arg2用来传递数据是int类型的数据,obj是Object类型,可以传递所有继承Object的数据类型,如果Message用在跨进程通信时obj需要进行序列化;
1 | /** |
when字段表示发送消息的时间字段,基准时间是SystemClock.uptimeMillis(),如果设置了发送延时时间when的值就是基准时间加延时时间;
1 | /** |
data类型同样用来传递数据,data是Bundle类型,可以像Activity间传递消息一样设置key-values键值对交换数据;
1 | Bundle data; |
target是handler的引用, target的主要作用是在Looper的loop方法中用来分发Message;
1 | Runnable callback; |
Message还可以设置callback参数,在消息被Looper处理时调用Message的Runnable.run(),用在Message需要一一对应不同的Runnable。
MessageQueue是以链表形式存储Message对象,enqueueMessage()插入对象并返回操作结果。next()主要作用是从Queue读取Message并从链表中移除,postSyncBarrier()用于在Queue中添加同步屏障。
MessageQueue.enqueueMessage() :
1 | boolean enqueueMessage(Message msg, long when) { |
(1)Looper调用quit()后停止Message插入。
接下来的执行流程主要是链表的插入操作还有是否需要唤醒Looper轮询,判断依据是mBlocked(阻塞)和p.isAsynchronous()两个因素。
MessageQueue.next():
1 | Message next() { |
(1)Looper调用quit()后停止执行。
(2)nativePollOnce()用于“等待”消息,知道下一条Message可用为止。
(3)这里是一种同步屏障机制,target==null作为同步屏障开启的标志循环找到一个异步消息,下面的操作中优先执行异步消息。同步屏障具体深入会在后面部分说明。
(4)链表中删除目标Message并返回。
(5)MessageQueue中没有Message时执行Idlehandler(闲时机制),Idlehandler.queueIdle()返回true,Idlehandler回调会在mIdleHandlers中保持存在,false回调执行完成后从mIdleHandlers中移除。
MessageQueue.postSyncBarrier():
1 | private int postSyncBarrier(long when) { |
方法内部就是创建一个target为null的Message,在next方法中利用target等于null作为依据来判断是否设置同步屏障。同样对应removeSyncBarrier就是利用Message的token字段找到设置的同步屏障Message移除。
Looper主要职责是创建MessageQueue和处理Message。prepare方法创建looper对象添加到sThreadLocal中,Looper构造方法中同时创建MessageQueue。loop方法通过调用MessageQueue.next方法取出Message,调用msg.target的handleMessage方法交给对应handler处理Message。
prepare()和构造方法:
1 | /** Initialize the current thread as a looper. |
(1)quitAllowed 为true表示允许停止,这里构造方法是私有的,public修饰的无参构造调用这里时传入true,所以我们在日常使用中,在工作线程创建的looper都是允许停止的,只有UI线程的looper是不允许停止。也就是我们不能通过Looper.quit()来停止主线程的looper,可以这样理解,如果主线的looper停止也就相当于应用程序停止工作。
loop():
1 | /** |
(1)无限循环中queue调用next读取Message对象;
(2)调用hanlder.dispachMessage方法将Message交给handler处理;
(3)回收Message缓冲池未满时重置数据将Message添加到缓冲池。
Hndler负责发送Message和处理回调的各种Message,enqueueMessage方法发送消息,将消息添加到queue中,handleMessage方法处理回调回来Message,Handler中的方法在开发中经常会直接使用。removeMessages方法移除当前handler下的Message,内部调用MessageQueue.removeMessages,同样的方法还有removeCallbacks()和
1 | /** |
1 | /** |
hanlder中的发送Message和移除Message都是调用MessageQueue对链表做操作。
上面对源码的解读,大致对各自的职责有了了解,这里对它们之间的联系做说明。
MessageQueue是以Message作为内容的链表结构,可以将Message插入到MessageQueue中,也可以将Message从MessageQueue中删除。
Looper负责循环读取MessageQueue中的Message,将Message调用给handler完成Message消息传递。
Handler负责将创建好的Message发送给MessageQueue,MessageQueue将其插入到链表中,同时Handler最终接收Looper从MessageQueue中读取的Message执行它的处理方法handleMessage。

1 | void scheduleTraversals() { |
mTraversalRunnable是一个Runnable对象,run方法中会调用performTraversals()执行UI绘制。

1 | private void test() { |
test()是一个类的方法,Runnable是它的匿名内部类,局部变量str用了final修饰符,如果没有final修饰符将会报错,为什么呢?
为了保证数据的一致。
从虚拟机运行内存角度:
test()方法对应虚拟机栈中的一个栈帧,当该栈帧出栈后其局部变量也就销毁,然这时存在内部类的方法需要访问str,就出现了生命周期不一致的情况,所以需要使用final将str变为常量来保证一致性。
匿名内部类编译后会另外单独生成一个Class,外部参数会以构造函数参数传入,传入参数可能在内部被修改,这会导致了数据的不一致。
]]>2020年对于社会这个大环境来说是艰难的一年,对于我个人来说更是变动的一年,离开了从实习到如今四年的老东家,开始了一年的漂泊,到了年底还没有稳定下来,不过一年过来庆幸自己没有妥协,一直还在不断调整和思考。
2020就这样写下了记号,工作上这一年是没有成果的,物质和精神都没有,当然这些也不是年初计划太过于关注的点。2020如果说有一点点收获的话那是在生活状态和心理变化上,之前一直唯利,如今停下这个脚步也该看看走的稳不稳,走的累不累,当然也要感谢那几年的累积,才能让现在看的更明了一些,如果比作爬山,我觉得过去的四年我是前两年用力很猛,后两年有些疲软,这一年停下了脚步歇歇脚,有点冷热不均的意思,接下来该怎么走呢?平衡,生活与工作、休息与忙碌、亲情和还没来的爱情,2021年我要尝试去平衡这些,当前生命健康是第一。
过去决定现在,现在影响将来,2021我的生活会是不同以往的,预感会有很大的变化,且拭目以待。
2021伊始,感谢将出现在我生命里的可爱的人们。3q!
]]>View的绘制是从ViewRootImpl类的performTraversals方法开始经过measure、layout、draw三个过程将View绘制出来的,measure方法用来测量ViewGroup/View的宽高,layout用来确定View的最终宽高和在容器内的位置,draw绘制View到屏幕上。

performTraversals方法会依次调用perfomrMeasure,performLayout和performDraw,这三个方法依次调用底层View的绘制流程,也就是调用onMeasure、onLayout和onDraw,三个方法通过递归方式完成整个布局的绘制。
1 |
|
代码里可以看到MeasureSpec将SpecMode和SpecSize打包成一个int值,这样可以避免过多的内存分配,getMode和getSize方法利用MODE_MASK与操作来得到需要的mode和size值。
SpecMode有三种模式:
这三个值的设置是站在父容器的角度衡量view的,UNSPECIFIED是父容器没有对view做限制,EXACTLY是父容器给出了一个明确限制值(最大边界)来约束view,AT_MOST则是按照view的想要的来展示,但也有指定值来限定。
对于一个普通的View,它的MeasureSpec是由父容器的MeasureSpec和自身设置的LayoutParam参数来决定的,比如View设置了固定的宽或者高那么它在宽或者高方向上的SpecMode就是EXCATLY,无论它的父容器设置什么测量模式
View的绘制流程和activity的生命周期是不同步的,所以在onresume中直接获得view宽高是错误的,可以痛殴VIewTreeObser监听绘制过程或者通过View.post方法投递一个消息队列到尾部,等待looper调用该runnable时view已经初始化
postscript:getMeasureWidth和getWidth的区别
]]>public boolean dispatchTouchEvent(MotionEvent event)public boolean onInterceptTouchEvent(MotionEvent event)public boolean onTouchEvent(MotionEvent event)

说明:
Android采用分层架构,从上到下分别为应用程序层(apps、System apps)、应用程序框架层(Java API Framework)、系统运行库和运行环境层(Libraries+Android Runtime)和Linux核心层(HAL+Linux Kernel)。

System apps
可以理解为内置的系统应用,可以像调用Java APIFramework一样区调用系统应用,例如我们调用日历区添加一个日常提醒。
Java ApI Framewor
android中常用的组件
Native c/c++ Libraries
本地库比Java API Framework更加偏低层,这里包含OpenGl、多媒体框架等内容。
Android Runtime
Android运行时环境,也就是Android虚拟机。Android5.0之前是使用Dalvik虚拟机,Dalvik虚拟机是基于JIT(Just in TIme)及时编译的引擎。Android5.0之后采用ART虚拟机,ART虚拟机是基于AOT(Ahead Of Time)作为编译引擎
Hardware Abstrction Layer
硬件抽象层主要是媒体、蓝牙、传感器的库模块。
Linux Kernel
Android平台的最底层,直接与硬件交互,负责硬件驱动、进程管理、内存管理、网络管理等功能。
什么是JIT和AOT?
在Android中Java代码会被转换成DEX字节码文件,DEX字节码文件是Android虚拟机可以识别的,Android虚拟机把字节码在转化为机器能识别的机器码。
Dalvik虚拟机给予JIT编译,JIT也叫及时编译器,JIT工作原理是在应用运行时,首先将一部分DEX字节码转化为机器码,在程序执行的过程中再陆续将更多代码编译并缓冲,这样做的好处是内存占用少,但CPU再应用运行期间相对消耗大。
AOT叫做提前编译器,它是在应用的安装期间就将DEX字节码转化为了机器码,并将其存储在设备上。这样做的好处是在应用运行时占用CPu资源少一些,因为已经转化成了机器码,相对内存占用上多一些。
内存中Bitmap大小的计算公式:
长占用的像素 宽占用的像素 每个像素占用的内存
避免OOM也就是要减小图片在内存中的大小,有两种方式等比缩小长宽和减少每个像素占用的内存。
等比缩小长宽:
Bitmap的创建是通过BitmapFactory的工厂方法decodeFile()、decodeStream()、decodeByteArray()、decodeResource()。这些方法在创建Bitmap使都一个参数Options,Options中的属性inSampleSize用来对图片进行长宽的设置,inSampleSize的值是2的幂次方,通过设置合适的值来对图片进行缩放操作。
减少像素内存:
Options中的属性inPreferredConfig,通过调整这个值的属性来改变每个像素所占的内存,默认值是ARGB_8888,修改为RGB_565或ARGB_4444可以减少一半的内存。
| 值 | 含义 |
|---|---|
| ARGB_8888 | A(Alpha)、R(Red)、G(Green)、B(Blue)各占8位的精度,加起来32位的精度(即4个字节),也就是一个像素占4个字节的长度 |
| ARGB_4444 | A(Alpha)、R(Red)、G(Green)、B(Blue)各占4位的精度,加起来16位的精度(即2个字节),也就是一个像素占2个字节的长度 |
| RGB_565 | R(Red)占5位精度、G(Green)占6位精度、B(Blue)占5位精度,一共16位精度,也就是2个字节,不携带透明度信息 |
| ALPHA_8 | 每个像素占位(即一个字节),只办函透明度值,不携带颜色信息 |
什么是IPC?
IPC全称Inter-Process Communication,即进程间通信。一般应用采用多进程有是为了使应用能够获取更多的内存空间。由于一个进程对应分配一个虚拟机,进程与虚拟机的一一对应,造成我们对同一个类的对象在不同进程中就产生了对个副本,例如有processOne和processTwo两个进程,它们都有一个类A.class,那么在两个进程中的A.class是互不干扰的。总结起来对进程会造成一下几个问题:
1. 静态成员和单例模式失效;
2. 线程同步机制生效;
3. SharedPreferences可靠性降低(进程并发写可能异常)
4. Application多次创建(可以理解成Application的创建和进程的创建是一致的)
Binder机制
Binder是android进程间通信的方式,Binder时基于C/S架构,主要有四部分组成:
1. Client(客户端进程)
2. Server(服务端进程)
3. ServiceManager(提供注册、查询和返回代理服务对象的功能)
4. Binder驱动(主要进程间的连接,进程间数据交互等基础底层操作)
服务器通过Binder驱动在ServiceManager中注册服务
客户端通过Binder驱动查询Servicemanager中注册的服务
SreviceManager通过Binder驱动返回服务器代理对象
客户端拿到服务器的代理对象后即可进行进程间的通信
IPC方式比较
| 优 | 缺 | 适用场景 | |
|---|---|---|---|
| Bundle | 使用简单 | 传输Bundle支持的数据类型 | 四大组件间的数据传递 |
| 文件共享 | 使用简单 | 不适合高并发,无法进行即时通信 | 用于实时性不高的场景 |
| AIDL | 功能强大 支持一对多并发下的通信,支持实时 | 使用较复杂,需要做好线程同步问题 | 一对多通信且有RPC需求 |
| Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不支持RPC、不能很好处理高并发、数据通过Message进行传输,只能传输Bundle支持的数据类型 | 低并发的一对多即时通信 |
| ContentProvider | 在数据源访问上功能强大,支持一对多的并发数据共享,可通过call操作扩展其他操作 | 提供数据源的CRUD操作 | 进程一对多的数据共享 |
| Socket | 功能强大,支持网络传输数据流、支持一对多的实时通信 | 实现较繁琐 | 网络数据共享 |
Android的性能优化主要从四方面入手,内存优化、布局优化、网络优化和安装包优化。
常用检查工具
LeakCanary是一个三方检测内存泄漏的工具库 ,集成后会自动检测应用运行期间的内存泄漏,并直观的输出。
Android自带的Android Profiler ,可以检测CPU、MEMERY、NETWORK三方的性能。
BlockCanary是一个三方用来检测UI卡顿的工具库,像LeakCanary一样集成后当发生UI卡顿现象时会输出卡顿的信息,通过输出的信息可以很方便的来定位导致卡顿的原因。
内存优化
内存优化的方式时避免内存泄漏(节流)、增加内存(开源)。
常见的内存泄露:
增加内存方案一般会在Application下添加largeHeap=”true”,活着新开进程来时应用的内存总空间增大。
根据spk的构成做一下几方面的优化:
属性动画
属性动画目前使用场景最多,分类两种ViewPropertyAnimator和ObjectAnimator。前者是通用的动画,例如旋转、透明度、位移和缩放,简单通过View.animator()就可得到ViewPropertyAnimator。后者通过ObjectAnimator.of属性()来返回一个ObjectAnimator,ObjectAnimator需要重绘View所以要调用invalidate()来刷新绘制,最后通过start()方法启动动画。
补间动画和属性动画的区别?
补间动画只是在父VIew层利用Matrix不多绘制View,达到移动的效果,其实View并没有发生变化,还在动画之前的位置。
属性动画是真正改变了View的属性值,真正的改变View的具体属性值的。
TCP协议是面向连接、可靠的字节流传输服务。TCP协议在C/S间数据交换前,需要先在上方建立一个TCP连接,之后才开始传输数据,并提供超时、重发、丢弃重复数据、数据校验和流量控制等功能。
特点:面向连接、可靠通信、面向字节流
应用层协议:HTTP、HTTPS、SSH、FTP、SMTP
UDP是一个面向数据报的传输层协议,不具有可靠性,只是把数据发出去,不保证数据是否能到达S端。因为UDP在传输数据前不需要建立一个连接,所以它的传输效率很快,不能保证数据的可靠。
特点:无连接、不可靠、面向数据报
应用层协议:DHCP、DNS

第一次握手(请求建立连接)C端发送建立连接请求,携带序列号seq=x 标示SYN=1,此时C端处于SYN_SEND状态;
第二次握手(确认请求)S端收到后,发出确认信息,确认信息ACK=x+1,同时携带自己的序列号SYN=1,seq=y;
第三次握手(建立连接)C端收到S端的确认请求后,向S端发送确认ACK=y+1,S端收到请求后两端都处于Established状态,表示当前的一次TCP连接成功。

第一次挥手(请求释放)C端发送释放连接的请求信号FIN=1,seq=u,此时C端处于FIN WAIT状态,不再发送数据给S端;
第二次挥手(确认请求)S端收到释放请求后,发送确认收到请求释放,ACK=1,ack=u+1,此时S度啊处于CLOSE EAIT状态,不再接收C端数据,但是需要发送给C端的数据可继续发送;
第三次挥手(确认释放)当S端不再有数据需要发送给C端时,发送却是释放连接,携带FIN=1,seq=w,ACK=1,ack=u+1,此时S端处于LAST ACK状态,等待C端的最终确认;
第四次挥手(最终释放确认)当C端收到了确认释放后,随即发送最终释放确认,ACK=1,ack=w+1,seq=u+1;此时C端在等待2MSL后关闭连接,S端收到请求后同样关闭。
为什么需要三次握手?
如果是两次握手,服务端确认请求后,不知道客户端是否能收到了消息,服务端的消息得不到确认。(服务端消息等不到确认)
如果客户端发送的请求网络延迟了,超时后有客户端重新发起请求,倘若在重发请求正常进行完毕后,再收到之前网络拥塞的请求,再和服务端建立连接,这个时候就可能服务端一直等待,导致服务端连接资源浪费。
为什么第三次握手是seq=x+1,而不是x+2?
从握手的规律可以看出来seq在确认请求中会变做确认表示即ack,ack会在seq值的基础上加1,同时TCP规定,SYN不携带数据,但会消耗掉一个序列号,SYN=1时会消耗seq的一个值(即加1),第三次握手ACK=1、SYN不等于1,而ACK=1不消耗seq所以seq=x+1而不是x+2,挥手中FIN也和SYN一样
TCP建立连接后客户端出现故障会怎样?
服务端有个计数器,一般两小时,两小时如果没有收到任何数据,会发送探测报文段,发了几个报文段如果还是没反应,服务端就会关闭连接。
HTTP(HyperText Transfer Protocol)中文全名超文本传输协议,HTTP是用于客户端与服务器间请求响应的协议。HTTP是应用层的协议,和其他该层协议一样,它是服务于某一类具体应用的协议。
HTTPS是在HTTP的基础上加上了SSL/TLS层
TLS(Transport Layer Security,传输层安全协议)、SSL(Secure Sockets Layer 安全套接层)
SSL由NetScape公司设计,共有三个版本1、2、3。SSL 3.0得到大规模使用,而TLS是SSL标准化后的升级版
SSL/TLS握手阶段工作流程:

客户端向服务端发出加密通信请求(ClientHello)
携带支持的协议,例如TLS 1.0版
客户端生成的随机数random1,后面用于生成“对话密钥”
支持的加密方法,比如RSA公钥加密
支持的压缩的方法
服务端收到请求,做出响应(ServerHello)
确认加密通信协议,例如TLS 1.0版。如果协议与服务端支持的不一致,则关闭加密通信。
服务端生成一个随机数random2,后面用于生成“对话密钥”
确认加密方法,例如RSA公钥加密
携带服务端证书给客户端
客服端做证书验证和公钥对随机数加密发送给服务端(ClientResponse)
验证证书的安全性
验证通过后,客户端生成随机数pre-master secret,然后使用证书中的公钥进行加密,发送给服务端
服务端私钥解密获得随机数(ServerResponse)
服务端收到公钥加密的内容,在服务端使用私钥解密后得到pre-master secret,然后根据random1、random2和pre-master secret通过一定的算法得出对称加密的秘钥,作为后面交互过程中的对称密钥。同时客户端也使用random1、random2和pre-master secret,同样的算法生成对称密钥。
如何保证公钥不被篡改?
将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。
什么是对称加密?
加密和解密用的都是相同的密钥,优点是速度快,缺点是安全性低,常见的对称加密DES、AES等
什么是非对称加密?
非对称加密由一对密钥对,分为公钥和私钥。一般私钥自己持有,公钥可以公开给对方,优点是安全性比对称密钥高,缺点是数据传输效率比对称密钥低。采用公钥加密的数据只能由对应的私钥来解密。常见的非对称加密算法RSA。
HTTPS在传输过程中采用了对称加密和非对称加密结合使用,使用非对称加密传递密钥,然后使用对称密钥进行数据传输的加密和解密。二者的结合既保证了传输的安全性,也保证了数据传输的效率