2024年Android面试总结
1.动画类型有哪些?插值器原理?
答:1. 帧动画(Frame Animation)
- 特点:通过连续播放一系列图像帧来创建动画效果。
- 原理:使用
AnimationDrawable
类加载XML定义的帧图片,通过定时更新当前显示的帧来实现动画。 - 使用场景:适合简单的动画效果,如GIF动画。
2. 补间动画(Tween Animation)/ 视图动画(View Animation)
- 子类型:平移(Translate)、旋转(Rotate)、缩放(Scale)、透明度(Alpha)。
- 特点:通过指定动画开始和结束的属性值,系统自动计算变化过程。
- 原理:基于View的动画,通过修改Canvas的绘制矩阵来实现动画效果,但不改变View的实际属性。
- 使用场景:适合简单的视图动画效果,如页面切换时的淡入淡出。
- 底层解析:
- AnimationUtils.loadAnimation()开始加载解析,通过xmlParse解析相应字段节点根据不同动画类型创建动画;
- View.startAnimation(xxx)触发invalidate()重绘,draw()根据动画标志位计算得到animation,并更新transformation,触发下一次绘制;
- 结果返回给View在绘制时对画布Canvas做矩阵变换从而实现动画。
- 这是在draw层面实现,只有视觉变化,实际属性不变。
3. 属性动画(Property Animation)
- 特点:通过动态修改对象的属性值来实现动画效果,功能强大且灵活。
- 原理:执行
animator.start()
后,动画作为回调注册到AnimationHandler
,并通过Choreographer
实现精确时间控制和插值计算。 - 使用场景:适用于需要改变对象实际属性的复杂动画效果,如非线性动画。
- 实现非线性动画: 通过ValueAnimator或者ObjectAnimator结合Interpolator(插值器)来实现非线性动画。
- 其中,插值器(Interpolator)定义了动画值随时间变化的速率。 Android提供了多种内置的插值器,如AccelerateInterpolator(加速插值器)、DecelerateInterpolator(减速插值器)、AccelerateDecelerateInterpolator(先加速后减速插值器)等。此外,你还可以自定义插值器来满足特定的需求。
2.StringBuffer和StringBuilder区别?
答:
-
1.线程安全:
- StringBuffer:线程安全
- StringBuffer是线程安全的,它的所有公开方法都是通过内部的synchronized修饰来实现同步的,从而保证了多线程环境下的数据一致性。因此,在多线程环境下,如果有多个线程同时访问和修改同一个StringBuffer对象,不会出现数据不一致的问题。
- StringBuilder:线程不安全
- 与StringBuffer不同,StringBuilder并没有使用synchronized修饰其方法,因此它是线程不安全的。在单线程环境下,StringBuilder的性能要优于StringBuffer,因为它避免了不必要的同步开销。但是,在多线程环境下,如果多个线程同时访问和修改同一个StringBuilder对象,就可能导致数据不一致的问题。
-
2.缓冲区优化:
- StringBuffer的缓存区优化
- StringBuffer每次获取toString都会直接使用缓存区的toStringCache值来构造一个字符串,而不需要像StringBuilder那样每次都需要复制一次字符数组,再构造一个字符串。这样的设计使得StringBuffer在频繁调用toString方法时具有较好的性能表现。
- StringBuilder的字符数组复制
- 与StringBuffer不同,StringBuilder每次调用toString方法时都需要复制一次字符数组,并构造一个新的字符串。这样的设计使得StringBuilder在单次操作中具有较好的性能表现,但在频繁调用toString方法时可能会产生较多的内存开销。
-
3.性能:
- StringBuilder 的性能要远大于 StringBuffer,因为StringBuffer的所有公开方法都是同步的,线程更安全,但是会导致性能大大降低,StringBuilder的所有方法都不是同步的,所以StringBuilder相比较StringBuffer性能更好
3.jvm内存模型?
JVM内存模型的工作原理
JVM通过类加载器将Java代码编译成的字节码加载到内存中的运行时数据区,然后通过执行引擎将字节码翻译成底层系统的指令执行。在这个过程中,JVM还通过本地库接口调用其他语言编写的本地库来实现程序的功能。
-
堆内存:是JVM中最大的一块,由新生代和老年代组成。默认情况下新生代按照8:1:1的比例来分配;
-
方法区:存储类信息、常量、静态变量等数据,是线程共享的区域;
-
栈:分为虚拟机栈和本地方法栈,主要用于方法的执行;
-
线程共享:方法区+堆;
-
线程独享:虚拟机栈+本地方法栈+程序计数器;
-
**程序计数器**:这是线程私有的内存区域,用于记录当前线程执行的字节码行号。当线程执行Java方法时,程序计数器记录虚拟机字节码的地址;当执行Native方法时,程序计数器的值为空。
-
**Java虚拟机栈**:每个线程都有自己的Java虚拟机栈,用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法执行时都会创建一个栈帧,方法执行完毕后,栈帧出栈。
-
**本地方法栈**:用于存储本地方法的调用信息,也是线程私有的。本地方法通常由C语言实现。
-
**堆**:这是JVM管理的最大一块内存区域,用于存储几乎所有的对象实例和数组数据。堆内存是垃圾收集器(GC)的主要工作区域,可以划分为新生代和老年代,以便更有效地管理内存。
-
**方法区**:在JDK 1.8及以后版本中称为元空间,用于存储类信息、常量、静态变量和编译器编译后的代码等数据。所有线程共享这一区域。
4.线程池7大核心参数及原理?
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:最大空闲时间
- unit:最大空闲时间单位
- workQueue:任务队列
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。
- handler:拒绝策略,
- 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
- 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
- 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
- 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
- 如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
- 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,则线程池会抛出异常RejectExecutionException。
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
5.Android多进程通信方式有哪些?各自的优缺点?
- AIDL:功能强大,支持进程间一对多的实时并发通信,并可实现 RPC (远程过程调用);
- Messenger:支持一对多的串行实时通信, AIDL 的简化版本;
- Bundle:四大组件的进程通信方式,只能传输 Bundle 支持的数据类型;
- ContentProvider:强大的数据源访问支持,主要支持 CRUD 操作,一对多的进程间数据共享,例如我们的应用访问系统的通讯录数据;
- BroadcastReceiver:即广播,但只能单向通信,接收者只能被动的接收消息;
- 文件共享:在非高并发情况下共享简单的数据;
- Socket:通过网络传输数据;
6.Binder机制原理?
Binder原理
Binder是Android提供的一种效率更高、更安全的基于C/S架构的IPC通信机制,其本质也是调用系统底层的内存共享实现。
从进程角度来看Binder进程间通信原理:
在Android系统中
- 用户空间彼此不能共享
- 内核空间可以共享
- 用户空间进程通过open/ioctl等方式与内核空间通信
- Client与Server通信的实现,是由Binder驱动在内核空间完成
在android中,有很多Service都是通过binder来通信的。这里要引入另一个重要的角色:ServiceManager。ServiceManager负责管理Server所提供的服务,同时响应Client的请求并为之分配相应的服务。
可以把ServiceManager(以下简称SM)比作通讯站,Client和Server是电话用户,Server首先要向通讯站注册号码,当Client拨打号码时,通讯站首先检索是否有该号码,如果有则转接给Client,否则不响应。
需要注意的是,Client一般认为是数据发送方,Server是数据接收方,两者并非固定不变的。如果Server在收到数据后主动向Client方发送数据,则两者身份互换。
Binder通信的完整流程如下:
- Server向ServiceManager注册服务
- Client向ServiceManager申请服务
- SM作为守护进程,处理Client端请求,并管理所有Server端服务
- BinderDriver位于Kernel层,是一切运作的基础
7.App启动流程?
- 点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;
- system_server进程接收到请求后,向zygote进程发送创建进程的请求;
- Zygote进程fork出新的子进程,即App进程;
- App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
- system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;
- App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
- 主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。
- App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。
8.Handler机制原理?
Handler是Android系统中用于实现线程间通信和任务调度的一种机制。它基于Android的Looper、MessageQueue和Message三个核心组件实现。主要工作原理如下:
1.Message(消息):Message是一个包含要传递的数据和指令的对象。它可以携带整数、字符串、Bundle等不同类型的数据。当需要在不同线程之间传递数据或执行任务时,通常会创建一个Message并将其发送给Handler。
2.Handler(处理程序):Handler是用于处理Message的对象。它通常与一个特定的线程(通常是主线程)关联。通过Handler,您可以将Message发送到与其关联的线程的消息队列中,以便在那个线程中执行处理。
3.Looper(消息循环器):Looper是一个用于管理线程的消息队列的对象。每个线程都可以有一个Looper,它会在线程上创建一个消息队列,允许该线程接收并处理Message。主线程通常已经具有一个默认的Looper,而后台线程需要显式创建一个Looper。
4.MessageQueue(消息队列):MessageQueue是一个FIFO(先进先出)队列,用于存储待处理的Message。每个Looper都有一个关联的MessageQueue,Handler将Message发送到这个队列中,然后由Looper依次处理队列中的Message。
Handler机制的工作流程:
1.在主线程(或其他线程)上创建一个Handler对象,这个Handler会关联到当前线程的Looper。
2.在后台线程中,创建一个Message对象,可以将一些数据和处理指令放入这个Message。
3.使用Handler的sendMessage方法将Message发送到与Handler关联的Looper的MessageQueue中。
4.Looper在后台线程中不断轮询MessageQueue,当有新的Message到达时,将Message取出并交给Handler处理。
5.Handler收到Message后,可以根据Message中的指令执行相应的操作,通常是在主线程中更新UI。
6.如果需要定时任务或循环执行,可以使用Handler的postDelayed方法。
Handler的阻塞唤醒机制是怎么回事?
Handler的阻塞唤醒机制是指在Looper的MessageQueue中,当Message数量过多时,Looper会进入阻塞状态,直到有新的Message到来。当有新的Message到来时,Looper会唤醒并处理新的Message。
具体实现如下:
- Looper在MessageQueue中维护了一个阻塞队列,当阻塞队列中的Message数量达到一定阈值时,Looper会进入阻塞状态。
- 当有新的Message到来时,Looper会从阻塞队列中移除一个Message,并将其插入到MessageQueue中,从而唤醒Looper。
- Looper唤醒后,会从MessageQueue中取出新的Message并处理
9.子线程可以更新ui吗?在Activity那个生命周期?
Andoird是不允许直接在子线程中更新UI的。原因是子线程中更新UI会引起线程不安全问题,导致界面卡顿掉帧。
在子线程中run方法中,通过handler.post或其他方式将更新UI的任务消息发送到UI线程,由UI线程更新UI。
Thread和Runnable的区别
- Thread代表线程类。start()开启子线程,执行体为run()方法
- Runnable只是一个接口,直接调用其run()方法,并不会开启子线程
下面介绍几种子线程更新UI的方法
方式一:Handler和Message
*方式二:在子线程中调用view.post*
方式三:在子线程中调用runOnUIThread
方式四:Handler.post()方法
10.Activity生命周期?A跳转B执行生命周期?弹出Dialog时Activity生命周期?
Activity的生命周期可以概括为以下几个阶段:
创建(Created):当Activity被创建时,系统会调用onCreate()方法。
启动(Started):当Activity变为用户可见时,系统会调用onStart()方法。
响应(Resumed):当Activity开始与用户交互时,系统会调用onResume()方法。此时,Activity处于Activity栈的顶部,是活跃的Activity。
暂停(Paused):当另一个Activity部分或完全覆盖当前Activity时,系统会调用onPause()方法。
停止(Stopped):当Activity不再可见时,系统会调用onStop()方法。
重新启动(Restarted):当Activity从停止状态恢复时,系统会调用onRestart()方法,然后继续生命周期的启动阶段。
销毁(Destroyed):当Activity需要被销毁时,系统会调用onDestroy()方法。
当在Activity A中弹出Dialog时,Activity A的生命周期不会发生变化,Dialog是Activity的一部分,不会影响Activity的生命周期。Dialog的显示和消失不会触发Activity的任何生命周期回调方法。
注: 当AActivity切换BActivity的所执行的方法:
AActivity:onCreate()->onStart()->onResume()->onPouse()
BActivity:onCreate()->onStart()->onResume()
AActivity:onStop()->onDestory()
当AActivity切换BActivity(此activity是以dialog形式存在的)所执行的方法:
AActivity:onCreate()->onStart()->onResume()->onPouse()
BActivity:onCreate()->onStart()->onResume()
11.屏幕旋转生命周期?
屏幕旋转时,Activity的生命周期变化如下:
-
屏幕旋转导致Activity重新创建:当屏幕旋转时,当前的Activity会被销毁并重新创建。这是因为屏幕旋转会导致布局的改变,需要重新加载适配新屏幕方向的布局资源12。
-
生命周期方法调用顺序:
**onPause()**:当前Activity即将失去焦点时调用,应停止处理耗时操作、保存用户数据或释放资源3。
**onStop()**:当前Activity完全不可见时调用,应取消注册监听器、停止动画或释放其他资源3。
**onDestroy()**:当前Activity被销毁时调用,可以在此方法中释放所有资源3。
**onCreate()**:创建新的Activity时调用,用于初始化Activity的状态和布局3。
**onStart()**:Activity即将变为可见状态时调用3。
**onResume()**:Activity变为可见状态时调用,通常在此方法中注册监听器、启动动画或获取位置更新等操作3。
12.Activity启动模式及应用场景?
Activity的四种启动模式
- standard(标准模式)
- 行为:默认的启动模式,每次启动Activity时都会创建一个新的实例,并将其放入任务栈中。所以,任务栈中可能同时存在该Activity的多个实例。
- 应用场景:每次打开都是新内容或新页面。比如新闻内容列表,每次打开都是新页面。
- singleTop(单顶模式,or栈顶复用模式)
- 行为:如果任务栈的栈顶已经是该Activity的实例,则不会创建新的实例,而是直接复用栈顶的Activity实例。如果栈顶不是该Activity的实例,则会创建新的实例并放入栈中。
- 应用场景:适用于那些不需要多个实例的Activity,比如通知栏消息点击打开应用
- singleTask(单任务模式,or栈内复用模式)
- 行为:如果任务栈中已经存在该Activity的实例,则不会创建新的实例,而是将任务栈中该实例以上的所有Activity实例都移除,让该实例位于栈顶。如果任务栈中不存在该Activity的实例,则创建新的实例。
- 应用场景:适用于作为应用程序入口的Activity,如APP的主页或主界面!!!确保无论从哪里进入应用,都回到主页面而不是在主页面上方叠加新的页面。
- singleInstance(单实例模式)
- 行为:该模式下的Activity会单独位于一个任务栈中。无论从哪里启动该Activity,都会重用这个实例,并且该Activity会单独占据一个任务栈。
- 生命周期:走(onPause) -> onNewIntent() -> onResume
- 应用场景: 适用于与其他完全隔离的Activity,如来电显示页面; 适用于那些需要全局唯一实例的Activity,比如系统级的服务Activity或者需要全局共享数据的Activity。
主Activity一般用哪种启动模式
对于主Activity(即应用的首页或主界面),一般推荐使用singleTop
或singleTask
启动模式。
- 使用
singleTop
模式:如果主Activity已经位于任务栈的栈顶,再次启动它时不会创建新的实例,而是直接复用现有的实例。 这可以避免不必要的Activity创建和销毁,提高应用的性能和用户体验。 - 使用
singleTask
模式:如果主Activity在任务栈中存在,无论它位于栈的哪个位置,都会将它之上的所有Activity实例移除, 并将其置于栈顶。这可以保证用户无论通过哪种方式回到应用,都会直接看到主Activity,并且 主Activity上方不会有其他Activity遮挡。
最终选择哪种模式,需要根据应用的具体需求和用户体验来决定。如果应用结构相对简单,用户行为也比较单一,使用singleTop
模式可能就足够了。如果应用结构复杂,需要处理多种用户场景和跳转逻辑,那么使用singleTask
模式可能更加合适。
5、使用方式:
(1)在AndroidManifest.xml,指定android:launchMode=“singleInstance”;
(2)通过Intent设置标志位,addFlags(NEW_TASK、CLEAR_TOP、SINGLE_TOP)
13.java中extends和super的区别?
**Java泛型中的extends
和super
关键字用于限定泛型类型的边界,但它们的作用和用法有所不同。**
extends和super的区别
- 使用场景:
- extends用于指定泛型的上界,表示泛型参数可以是某个类的子类或本身,类似于“is-a”的关系。例如,
T extends Number
表示类型T必须是Number类或其子类。 - super用于指定泛型的下界,表示泛型参数可以是某个类的父类或本身,类似于“has-a”的关系。例如,
T super Integer
表示类型T必须是Integer类或其父类。
- extends用于指定泛型的上界,表示泛型参数可以是某个类的子类或本身,类似于“is-a”的关系。例如,
- 使用效果:
- extends限制的类型参数可以读取但不能写入,因为使用extends限制后,编译器无法确定泛型类型的具体类型,所以只能让读操作成立,而写操作不成立,否则会存在类型不安全的风险。
- super限制的类型参数可以写入但不能读取,因为使用super限制后,泛型类型一定是某个父类或本身,所以只能让写操作成立,而读操作不成立,否则会导致类型转换错误。
14.String s1 = new String(“abc”) 创建了几个字符串对象?
**在Java中,表达式 String s1 = new String("abc")
创建的字符串对象数量取决于字符串常量池中是否已经存在字符串 “abc”。**
- 如果字符串常量池中不存在字符串 “abc”,则创建两个对象:
- 一个是在堆空间中通过
new
关键字创建的新对象。 - 另一个是在字符串常量池中创建的字符串常量 "abc"。
- 一个是在堆空间中通过
- 如果字符串常量池中已经存在字符串 “abc”,则只创建一个对象:
- 这种情况下,
new String("abc")
会在堆空间中创建一个新的对象,但由于 “abc” 已经在常量池中存在,所以不会在常量池中重复创建。
- 这种情况下,
字符串常量池的作用
字符串常量池是Java中的一个机制,用于存储唯一的字符串常量,以节省内存。当创建一个字符串时,JVM会首先检查常量池中是否已经存在相同内容的字符串。如果存在,则直接返回常量池中的引用;如果不存在,则创建一个新的字符串对象并放入常量池中。
15.Android中自定义view的流程以及onMeaure()方法调用时机?
在 Android 中,自定义 View 的绘制流程主要包括测量、布局、绘制三个关键步骤。具体来说,[自定义 View](https://so.csdn.net/so/search?q=自定义 View&spm=1001.2101.3001.7020) 的绘制涉及重写系统的 onMeasure()
、onLayout()
和 onDraw()
方法
自定义控件步骤:
1、创建View
2、处理View的布局
3、绘制View
4、与用户进行交互
5、优化已定义的View
在Android中,自定义View时,onMeasure()
方法是在视图层次结构中的父视图和子视图之间协商布局时调用的。它的主要职责是设置视图的宽度和高度。
onMeasure()
方法的调用时机通常如下:
- 当父视图对自己的子视图进行布局时,会先调用子视图的
onMeasure()
方法来确定子视图的尺寸。 - 当你将一个自定义视图添加到布局文件中,或者在代码中动态创建并添加到容器中时。
- 当父视图的尺寸发生改变时,也会重新对子视图进行尺寸测量。
16. View的绘制流程?
1). View系统的绘制流程会从ViewRootImpl的performTraversals()方法中开始,performTraversals()的意思是:执行遍历,Traversals:遍历的意思
performTraversals会分别调用 performMeasure, performLayout,performDraw
而这三个方法,我想你应该能猜到,他们会启动onMesure,onLayout,onDraw方法
2). ViewRoot中包含了窗口的总容器DecorView,ViewRoot中的performTraversal()方法会依次调用decorView的measure、layout、draw方法,从而完成view树的绘制。
3). measure()方法,layout(),draw()三个方法主要存放了一些标识符,来判断每个View是否需要再重新测量,布局或者绘制
View树的绘制是一个递归的过程,从ViewGroup一直向下遍历,直到所有的子view都完成绘制
总结: View的整个绘制流程可以分为以下三个阶段:
- measure: 判断是否需要重新计算View的大小,需要的话则计算;每个View的控件的实际宽高都是由父视图和本身视图决定的
- layout: 判断是否需要重新计算View的位置,需要的话则计算;
- draw: 判断是否需要重新绘制View,需要的话则重绘制。
- measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。
17.为什么okhttp中核心线程数是0?
OkHttp的线程池设计为核心线程数为0,最大线程数为Integer.MAX_VALUE
,并且使用SynchronousQueue
作为任务队列。这种设计的主要目的是为了快速响应网络请求,避免线程的闲置和资源的浪费。具体来说:
- 快速响应请求:由于核心线程数为0,当有新的请求到来时,线程池会立即创建一个新线程来处理该请求,而不需要等待空闲线程。这样可以确保每个请求都能立即得到处理,从而提高响应速度。
- 资源利用效率:由于线程池的最大线程数设置为
Integer.MAX_VALUE
,理论上可以创建无限多的线程来处理请求。但实际上,OkHttp通过内部机制控制了请求的最大并发数,通常不会达到这个上限。这种设计可以在高并发场景下提供足够的处理能力,同时避免了过多的线程占用系统资源。 - SynchronousQueue的使用:
SynchronousQueue
是一个无缓冲的阻塞队列,每个put
操作必须等待一个take
操作,反之亦然。这意味着每当有一个任务提交时,必须有一个线程在等待执行这个任务,从而确保了任务的即时处理。 - 线程的回收:空闲线程在60秒后会被终止,这样可以避免长时间闲置的线程占用系统资源。这种设计使得线程池能够根据实际需求动态调整线程数量,提高资源利用率。
综上所述,OkHttp的线程池设计通过核心线程数为0、最大线程数为Integer.MAX_VALUE
以及使用SynchronousQueue
,实现了高并发处理和资源的高效利用,从而提升了网络请求的处理速度和系统的整体性能。
18.okhttp拦截器原理?
OkHttp最核心的工作是在 getResponseWithInterceptorChain() 中进行,在进入这个方法分析之前,我们先来了 解什么是责任链模式,因为此方法就是利用的责任链模式完成一步步的请求。责任链顾名思义就是由一系列的负责者构成的一个链条,类似于工厂流水线
一、责任链设计模式
①定义:
它为请求创建了一个接收者对象的链。为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链,当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。(责任链模式也叫职责链模式)
在责任链模式中,每一个对象对其下家的引用而接起来形成一条链。请求在这个链上传递,直到链上的某一个对象 决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的 情况下动态的重 新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象 所接受。
②为什么要使用责任链模式
原因如下:
- 解耦
- 实现单依职责原则
二、默认的5大拦截器有哪些?
- RetryAndFollowUpInterceptor(重试和重定向拦截器)
- 第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求
- BridgeInterceptor(桥接拦截器)
- 补全请求,并对响应进行额外处理
- CacheInterceptor(缓存拦截器)
- 请求前查询缓存,获得响应并判断是否需要缓存
- ConnectInterceptor(链接拦截器)
- 与服务器完成TCP连接 (Socket)
- CallServerInterceptor(请求服务拦截器)
- 与服务器通信;封装请求数据与解析响应数据(如:HTTP报文)
三、okhttp工作的大致流程
1.整体流程
(1)、当我们通过**OkhttpClient创立一个okHttpClient 、Request 、**Call,并发起同步或者异步请求时;
(2)、okhttp会通过Dispatcher对我们所有的Call(*Real*Call实现类)进行统一管理,并通过execute()及enqueue()方法对同步或者异步请求进行执行;
(3)、execute()及enqueue()这两个方法会最终调用RealCall中的getResponseWithInterceptorChain()方法,从阻拦器链中获取返回结果;**
(4)、拦截器链中,依次通**过ApplicationInterceptor**(*应用拦截器*)****、RetryAndFollowUpInterceptor(重定向阻拦器)、BridgeInterceptor(桥接阻拦器)、CacheInterceptor(缓存阻拦器)、ConnectInterceptor(连接阻拦器)、NetwrokInterceptor(网络拦截器)、CallServerInterceptor(请求阻拦器)对请求依次处理,与服务的建立连接后,获取返回数据,再经过上述阻拦器依次解决后,最后将结果返回给调用方。
2.细化分析:
- ApplicationInterceptor: 应用拦截器,通过addInterceptor添加,拿到的是原始请求,可以添加一些自定义header、通用参数、参数加密、网关接入等等。
- RetryAndFollowUpInterceptor:重试和重定向拦截器,处理错误重试和重定向
- BridgeInterceptor:桥接拦截器,主要工作是为请求添加cookie、添加固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,然后保存响应结果的cookie,如果响应使用gzip压缩过,则还需要进行解压。
- CacheInterceptor:缓存拦截器,如果命中缓存则不会发起网络请求。
- ConnectInterceptor:连接拦截器,内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。
- NetworkInterceptors:网络拦截器,用户自定义,通常用于监控网络层的数据传输。
- CallServerInterceptor:网络请求拦截器,在前置准备工作完成后,真正发起了网络请求。
3.总结:
整个OkHttp功能的实现就在这五个默认的拦截器中,所以先理解拦截器模式的工作机制是先决条件。这五个拦截 器分别为: 重试拦截器、桥接拦截器、缓存拦截器、连接拦截器、请求服务拦截器。每一个拦截器负责的工作不一 样,就好像工厂流水线,最终经过这五道工序,就完成了最终的产品。
但是与流水线不同的是,OkHttp中的拦截器每次发起请求都会在交给下一个拦截器之前干一些事情,在获得了结果之后又干一些事情。整个过程在请求向是顺序的,而响应向则是逆序。
当用户发起一个请求后,会由任务分发起 Dispatcher 将请求包装并交给重试拦截器处理。
1、重试拦截器在交出(交给下一个拦截器)之前,负责判断用户是否取消了请求;在获得了结果之后,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器。
2、桥接拦截器在交出之前,负责将HTTP协议必备的请求头加入其中(如:Host)并添加一些默认的行为(如:GZIP 压缩);在获得了结果后,调用保存cookie接口并解析GZIP数据。
3、缓存拦截器顾名思义,交出之前读取并判断是否使用缓存;获得结果后判断是否缓存。
4、连接拦截器在交出之前,负责找到或者新建一个连接,并获得对应的socket流;在获得结果后不进行额外的处
理。
5、请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的响应数据。 在经过了这一系列的流程后,就完成了一次HTTP请求!
19.okhttp中 应用层拦截器和网络层拦截器区别?
20.synchronized关键字使用场景?
22.ThreadLocal原理?
23.jvm垃圾回收机制?回收算法?
24.RecyclerView缓存机制及原理?各自调用时机?
25.TV开发焦点问题?如何记忆焦点?
26.屏幕适配原理?AutoSize,今日头条原理?
27.Android中内存优化?
28.什么是ANR?ANR类型及发生原因和解决办法?
29.内存泄漏是什么?发生原因?如何排查?解决方法?
30.ArrayList和LiskedList区别?
31.HashMap底层实现原理?扩容原理?
32.Android中开启多线程的方式?优缺点?
33.如何让多个线程按顺序执行?
34.OKHTTP使用了哪些设计模式及优缺点?
35.setContentView的绘制流程?
36.App打包流程?
37.Apk安装过程?
38.Android常用的设计模式有哪些?说说你的理解?
39.retrofit原理?
40.rxjava原理?如何切换线程?map操作符和flatmap区别?背压?
41.线程池核心参数有哪些?使用流程?拒绝策略?
42.jvm内存模型?
43.GC回收机制?如何判断一个对象是否能被回收?gc回收算法?
44.synchronized和volatile区别?
45.同步锁?重入锁?可重入锁?
46.java多线程用法?如何让多个线程按顺序执行?
47.LiveDate原理?使用过程中遇到的问题?解决方法?
48.viewmodel原理?
49.lifecycle原理?
50.协程原理?优缺点?
51.Java封装、多态、继承是什么?
52.Java中抽象和接口的区别?
53.Java中引用类型有哪些?概念?
54.Java中数组、树、链表有啥区别?HashMap原理?
答:1.数组:定义:数组是用于储存多个相同类型数据的[集合](https://so.csdn.net/so/search?q=集合&spm=1001.2101.3001.7020),是有序的元素序列。 特点:数组就是在[内存](https://so.csdn.net/so/search?q=内存&spm=1001.2101.3001.7020)中开辟一块连续的、大小相同的空间,用来存储数据. 可以通过下标访问的方式访问成员,查询效率高 增删操作会给系统带来性能消耗[保证数据下标越界的问题,需要动态扩容] 2.树:定义:一棵树(tree)是由n(n>0)个元素组成的[有限集合](https://baike.baidu.com/item/有限集合), 每个元素称为[结点](https://baike.baidu.com/item/结点)(node); 有一个特定的结点,称为[根结点](https://baike.baidu.com/item/根结点/9795570)或根(root); 除根结点外,其余结点被分成m(m>=0)个互不相交的有限集合,而每个[子集](https://baike.baidu.com/item/子集)又都是一棵树(称为原树的子树)3.链表:定义:链表是一种物理[存储单元](https://blog.csdn.net/qq_39151085/article/details/109669228)上非连续、非顺序的[存储结构](https://blog.csdn.net/qq_39151085/article/details/109669228),[数据元素](https://blog.csdn.net/qq_39151085/article/details/109669228)的逻辑顺序是通过链表中的[指针](https://baike.baidu.com/item/指针/2878304)链接次序实现的,每一个链表都包含多个节点,节点又包含两个部分,一个是数据域(储存节点含有的信息)分为单向链表和双向链表添加:添加时只需要修改指针的指向地址就可以,无需要像数组那样开辟新的内存空间删除:删除时同样修改指针的指向地址就可以s特点:> 灵活的空间要求,存储空间不要求连续 > > 不支持下标的访问.支持顺序的遍历搜索 > > 针对增删操作找到对应的节点改变链表的头尾指向即可,无需移动元素存储位置