引子
2022 年 3 月辞职,没多久上海爆发疫情,蜗居在家准备面试。在阅历 1 个月的闭关和 40+ 场 Android 面试后,拿到一些 offer。
总体上说,有如下几种面试题型:
- 基础常识
- 算法题
- 项目阅历
- 场景题
场景题,即“就事务场景给出处理方案”,考察运用常识处理问题的才干。这类题取决于临场应变、长期积累、命运。
项目阅历题取决于对作业内容的总结提炼、拔高升华、命运:
- 争取到什么样的资源
- 安排了怎样样的分工
- 搭建了什么样的架构
- 运用了什么办法
- 做了什么样的取舍
- 选用了什么战略
- 做了什么样的优化
- 处理了什么问题
力争把默默无闻的“拧螺丝”说成惊天动地的“造火箭”。(这是一门技能活)
但也不行防止地会产生“有些人觉得这是高大上的火箭,有些人觉得不过是低矮下的零件”。面试就比方相亲,甲之蜜糖乙之砒霜是常有的事。除非你优异到处理了某个业界的难题。
算法题取决于刷题,命运,相较于前两类题,算法题可“突击”的成分就更多了。只需刷题满足多,胜算就满足大。大量刷,反复刷。
基础常识题是一切题型中最能“突击”的,它取决于对“考纲”的整理复习、概括总结、背诵、命运。Android 的常识体系是庞杂的,关于有限的个人精力来说,考纲是无穷大的。
这不是一篇面经,把面试题公布是不讲武德的。但能够同享整个复习稿,它是我依照自己划定的考纲整理出的全部答案,由于篇幅太长,决定把全部内容分红两篇同享给大家,这一篇的内容是 Android 和 Java & Kotlin。(必有遗漏,欢迎弥补~)
整个复习稿分为如下几大部分:
- Android
- Java & Kotlin
- 规划办法 & 架构
- 多线程
- 网络
- OkHttp & Retrofit
- Glide
Android
制作流程
- 画多大?(丈量measure)
- 画在哪?(定位layout)
- 画什么?(制作draw)
- 丈量、定位、制作都是从View树的根结点开端自顶向下进行地,即都是由父控件驱动子控件进行地。父控件的丈量在子控件件丈量之后,但父控件的定位和制作都在子控件之前。
- 父控件丈量进程中ViewGroup.onMeasure(),会遍历一切子控件并驱动它们丈量自己View.measure()。父控件还会将父控件的布局办法与子控件布局参数相结合构成一个MeasureSpec方针传递给子控件以指导其丈量自己(3*3的表格,假如孩子是wrapcontent,依据measureChildWithMargin,则孩子的办法是AtMost)。View.setMeasuredDimension()是丈量进程的结尾,它表明View巨细有了确认值。
- 榜首次加载onMeasure()至少调用两次,最多调用5次(都是有ViewRootImpl调用performMeasure()),会进行多次丈量测验,总是期望以更小的窗口巨细进行制作,假如不行则扩展
- 经过 MEASURED_DIMENSION_SET 强制指定需求为 measuredDimension赋值,不然抛反常
- 父控件在完结自己定位之后,会调用ViewGroup.onLayout()遍历一切子控件并驱动它们定位自己View.layout()。子控件总是相关于父控件左上角定位。View.setFrame()是定位进程的结尾,它表明视图矩形区域以及相关于父控件的方位现已确认。
- 控件依照制作布景,制作自身,制作孩子的次序进行。重写onDraw()界说制作自身的逻辑,父控件在完结制作自身之后,会调用ViewGroup.dispatchDraw()遍历一切子控件并驱动他们制作自己View.draw()
- 为什么只能在主线程制作界面:由于重绘指令会有子视图一层层传递给父视图,终究传递到ViewRootImpl,它在每次触发View树遍历时都会调用ViewRootImpl.checkThread()
MeasureSpec
MeasureSpec用于在View丈量进程中描绘尺度,它是一个包括了布局办法和布局尺度的int值(32位),其间最高的2位代表布局办法,后30位代表布局尺度。它包括三种布局办法分别是
- UNSPECIFIED:父亲没有规定你多大
- EXACTLY:父亲给你设定了一个死尺度
- AT_MOST:父亲规定了你的最大尺度
同步音讯屏障
- ViewRootImpl 将遍历view树包装成一个Runnable并抛到Choreographer, 在抛之前会向主线程音讯行列中抛同步屏障
- 同步屏障也是一个Message,只不过 target 等于null
- 取下一条message的算法中,若遇到同步屏障,则会越过同步音讯,向后遍历找榜首条异步音讯找到则回来(Choreographer抛的异步音讯),若没有找到则会履行epoll挂起
- 当履行到遍历View树的 runnable时,ViewRootImpl会移除同步屏障
Choreographer
- 将和ui相关的使命与vsync同步的一个类。
- 每个使命被笼统成CallbackRecord,同类使命按时刻先后次序组成一条使命链CallbackQueue。四条使命链寄存在mCallbackQueues[]数组结构中
- 接触事情,动画,View树遍历都会被抛到编舞者,并被包装成CallbackRecord并存入链式数组结构,当Choreographer收到一个Vsync就会顺次从输入,动画,制作这些链中取出使命履行
- 当vsync到来时,会向主线程抛异步音讯(履行doFrame)并带上音讯生成时刻,当异步音讯被履行时,从使命链上摘取一切以前的使命,并按时刻先后次序逐一履行。
关于 Choreographer 的详细剖析能够点击读源码长常识 | Android卡顿真的是由于”掉帧“?
制作模型
- 1.软件制作 2.硬件加速制作
- 软件制作便是调用ViewRootImpl持有的 surface.lockCanvas(),获取和SurfaceFlinger同享的匿名内存,并往里边填充像素数据。这些操作产生在主线程。
- 硬件加速和软件制作的分歧点是在ViewRootImpl中,假如敞开了硬件加速则调用mHardwareRenderer.draw,不然drawSoftware。
- 硬件加速制作分为两个进程
- 在主线程构建DrawOp树:从根视图触发,自顶向下地为每一个视图构建DrawOp。(运用DisplayListCanvas缓存DrawOp,终究存储到RenderNode)
- 向RenderThread发一个DrawFrameTask,唤醒它进行烘托
- 进行DrawOp兼并
- 调用gpu命令进行制作,gpu向匿名同享内存写内容
- 终究将填充好的raw Buffer提交给SurfaceFlinger组成显现。
- RenderThread 是单例,每个进程只需一个。
- cpu 不拿手图形核算,硬件加速便是把这些核算交由 GPU 完结(图形核算转成 gpu指令),由此加入了RenderNode(相当于View),和 DisplayList(相当于view的制作内容)。当重绘恳求主张时,只更新 displyList
Android 运用冷发动流程
冷发动便是在 Launcher 进程中敞开另一个引证 Activity 的进程。这是一个 Launcher 进程和 AMS,运用进程和 AMS,WMS 双向通讯的进程:
-
Launcher 进程和 AMS 说:“我要发动Activity1”
-
AMS创立出 Activity1 对应的 ActivityRecord 以及 TaskRecord,告知 Launcher 进程履行 onPause()
-
Launcher 履行 onPause(),并奉告 AMS
-
发动一个 starting window,AMS 恳求 zygote 进程 fork一个新进程
-
在新进程中,构建ActivityThread,并调用main(),在其间敞开主线程音讯循环。
-
AMS 开端回调Activity1的各种生命周期办法。
-
当履行到 Activity.onAttch()时,PhoneWindow 被构建。
-
当履行到 Activity.onCreate()时,setContentView()会被托付给 PhoneWindow,并在其间构建DecorView,再依据主题解析体系预界说文件,作为 DecorView 的孩子,布局文件中肯定有一个 id 为 content 的容器控件,他将成为 setContentView 的父亲。
-
当履行到 Activity.onResume()时,DecorView 先被设置为 invisible,然后将其增加到窗口,此进程中会构建 ViewRootImpl 方针,它是 app 进行和 WMS 双向通讯的纽带。ViewRootImpl.requestLayout()会被调用,以触发View树自顶向下的制作。
-
View 树遍历,会被包装成一个使命抛给 Choreographer。在此之前 ViewRootImpl 会向主线程音讯行列抛一个同步音讯屏障。以抵达优先遍历异步音讯的效果。
-
Choreographer 将使命暂存在链式数组结构中,然后注册监听下一个 vsync 信号。
-
待下一个 vsync 信号到来之时,Choreographer 会从链上摘取一切比当不时刻更早的使命,并将他们包装成一个异步音讯抛到主线程履行。
-
异步音讯的履行,便是从顶层视图开端,自顶向下,逐一视图进行 measure,layout,draw的进程。
-
ViewRootImpl 持有一个 surface,它是原始图形缓冲区的一个句柄,原始图形缓冲区是一块寄存像素数据的内存地址,这块内存地址由app进程和SurfaceFlinger同享。当 app进程履行完上述进程时,就意味着像素数据现已填入该块内存,于是 app 告知 SurfaceFlinger 像素数据现已就绪,能够进行组成并烘托到屏幕了。
-
当 DecorView 完结烘托后,就会被设置为 visible,界面展示出来。
Surface
- 它是原始图画缓冲区的一个句柄。即raw buffer的内存地址,raw buffer是保存像素数据的内存区域,经过Surface的canvas 能够将图画数据写入这个缓冲区
- Surface类是运用一种称为双缓冲的技能来烘托
- 这种双缓冲技能需求两个图形缓冲区GraphicBuffer,其间一个称为前端缓冲区frontBuffer,别的一个称为后端缓冲区backBuffer。前端缓冲区是正在烘托的图形缓冲区,而后端缓冲区是接下来要烘托的图形缓冲区,当vsync到来时,交流前后缓冲区的指针
- 部分改写是经过前端缓冲区复制像素到后端缓冲区,而且兼并脏区以缩小它。
- 每个ViewRootImpl都持有一个Surface。
SurfaceFlinger
- SurfaceFlinger 是由 init 进程发动的运转在底层的一个体系进程,它的首要职责是组成和烘托多个Surface,并向方针进程发送笔直同步信号 VSync,并在 vsync 产生时组成帧到frame buffer
- SurfaceFlinger持有BufferQueue顾客指针,用于从BufferQueue中取出图形数据进行组成后送到显现器
- View.draw()制作的数据是怎样流入SurfaceFlinger进行组成的?
- Surface.lockCanvas()从BufferQueue中取出图形缓冲区并确认
- View.draw()将内容制作到Canvas中的Bitmap,便是往图形缓冲区填充数据
- Surface.unlockCanvasAndPost()解锁缓冲区并将其入队BufferQueue,然后告知SurfaceFlinger进行组成,鄙人一个vsync到来时进行组成(app直接喝surfaceFlinger通讯)
- 运用进程经过Anonymous Shared Memory将数据传输给SurfaceFlinger,由于Binder通讯数据量太小
Surfaceview
- 一个嵌入View树的独立制作外表,他坐落宿主Window的下方,经过在宿主canvas上制作透明区域来显现自己
- 虽然它在界面上隶归于view hierarchy,但在WMS及SurfaceFlinger中都是和宿主窗口分离的,它具有独立的制作外表,制作外表在app进程中表现为Surface方针,在体系进程中表现为在WMS中具有独立的WindowState,在SurfaceFlinger中具有独立的Layer,而普通view和其窗口具有同一个制作外表
- 由于它具有独立与宿主窗口的制作外表,所以它独立于主线程的改写机制。
- 它的布景是归于宿主窗口的制作外表,所以假如布景不透明则会盖住它的制作内容
TextureView
- SurfaceView 具有独立的制作外表,而TextureView和View树同享制作外表
- TextureView经过调查BufferQueue中新的SurfaceTexture到来,然后调用invalidate触发View树重绘,假如有View叠加在TextureView上面,它们的脏区有交集,则会触发不必要的重绘,所以他的改写操作比SurfaceView更重
- TextureView 持有 SurfaceTexture,它是一个GPU纹路
- SurfaceView 有双缓冲机制,制作更加流通
- TextureView 在5.0之前在主线程制作,5.0之后在RenderThread制作。
界面卡顿
- 改写率是屏幕每秒钟改写次数,即每秒钟去buffer中拿帧数据的次数, 帧率是GPU每秒准备帧的速度,即gpu每秒向buffer写数据的速度,
- 卡顿是由于掉帧,掉帧是由于写buffer的速度慢于取buffer的速度。关于60HZ的屏幕,每隔16.6ms显现设备就会去buffer中取下一帧的内容,没有取到就掉帧了
- 当扫描完一个屏幕后,设备需求从头回到榜首行以进入下一次的循环,此刻有一段时刻空地,称为VerticalBlanking Interval(VBI), Vsync便是 VBI 产生时产生的笔直脉冲,这是最好的交流双缓冲的时刻点,交流双缓冲是交流内存地址,瞬间就完结了
- 一帧的显现要阅历如下进程:cpu核算画多大,画在哪里,画什么,然后gpu烘托核算成果存到buffer,显现器每隔一段时刻从buffer取帧。若没有取到帧,只能持续显现上一帧。
- VSYNC=Vertical Synchronization笔直同步,它便是为了确保CPU、GPU生成帧的速度和display改写的速度保持共同
- VSYNC信号到来意味着,交流双缓冲的内存地址,font buffer 和 back buffer 交流,这个瞬间 back font就供下一帧运用, project butter(4.1)今后, Vsync一到就 GPU和cpu就开端烘托下一帧的数据
- 双缓冲机制:用两个buffer存储帧内容,屏幕一直去font buffer取显现内容,GPU一直向back buffer寄存准备好的下一帧,每个Vsync产生吃便是他们交流之时,若下一帧没有准备好,则就错过一次交流,产生掉帧
- 三缓冲机制:为了防止耗时的制作使命浪费一个vsync周期,用三个buffer存储帧。当时显现buffer a中内容,正在烘托的下一帧寄存在buffer b中,当VSYNC触发时,buffer b中内容还没有烘托好,此刻buffer a不能铲除,由于下一帧需求持续显现buffer a,假如没有第三个buffer,cpu和gpu要白白等候到下一个VSYNC才有或许能够持续烘托后序帧
- View的重绘恳求最少要等候两个vsync 才干显现到屏幕上:重绘恳求首要会包装成一个runnable,寄存在Choreographer中,下一个Vsync到来之时,便是它履行之时,履行完毕,构成的帧数据会放在back buffer中,比及下一个Vsync到来时才会显现到屏幕上
requestLayout() vs invalidate()
- 其实两个函数都会自底向上传递到顶层视图ViewRootImpl中
- requestLayout()会增加两个符号位PFLAG_FORCE_LAYOUT,PFLAG_INVALIDATED,而invalidate()只会增加PFLAG_INVALIDATED(所以不会丈量和布局)
- invalidate(),会查看主线程音讯行列中是否现已有遍历view树使命,经过ViewRootImpl.mWillDrawSoon是否为true,若有则不再抛
- invalidate表明当时控件需求重绘,会符号PFLAG_INVALIDATED,重绘恳求会逐级上传到根视图(但只需这个view会被重绘,由于其他的父类没有PFLAG_INVALIDATED,而且带着脏区域.初始脏区是主张view的上下左右,逐级向上传递后每次都会换到父亲的坐标系(平移 left,top)。
- View.measure()和View.layout()会先查看是否有PFLAG_FORCE_LAYOUT符号,假如有则进行丈量和定位
- View.requestLayout()中设置了PFLAG_FORCE_LAYOUT符号,所以丈量,布局,有或许触发onDraw是由于在在layout进程中发现上下左右和之前不相同,那就会触发一次invalidate,所以触发了onDraw。
- postInvalidate 向主线程发送了一个INVALIDATE的音讯
Binder
-
Linux内存空间 = 内核空间(操作体系+驱动)+用户空间(运用程序)。为了确保内核安全,它们是阻隔的。内核空间可拜访一切内存空间,而用户空间不能拜访内核空间。
-
用户程序只能经过体系调用陷入内核态,然后拜访内核空间。体系调用首要经过 copy_to_user() 和 copy_from_user() 完结,copy_to_user() 用于将数据从内核空间复制到用户空间,copy_from_user() 将数据从用户空间复制到内核空间。
-
Android 将 Binder driver 挂载为动态内存(LKM:Loadable Kernel Module),经过它以 mmap 办法将内核空间与接纳方用户空间进行内存映射(用户空间一块虚拟内存地址和内核空间虚拟内存地址指向同一块物理地址),这样就只需发送方将数据复制到内核就等价于复制到了接纳方的用户空间。
-
Binder 通讯优点:1. 安全性好:为发送方增加UID/PID身份信息。2. 功能更佳:传输进程只需一次数据复制,而Socket、管道等传统IPC手段都至少需求两次数据复制。
-
经过Binder完结的跨进程通讯是c/s办法的,客户端经过长途服务的本地署理像服务端发恳求,服务端处理恳求后将成果回来给客户端
-
binder通讯的巨细束缚是1mb-8kb(Binder transaction buffer),这是mmap内存映射的巨细束缚(单个进程),其间异步传输oneway传输束缚是同步的一半(1mb-8kb)/2,内核答应Binder传输的最大束缚是4M(mmap的最大空间4mb)
-
在生成的stub中有一个asInterface():它用于将服务端的IBinder方针转换成服务接口,这种转化是区别进程的,假如客户端和服务端处于同一个进程中,此办法回来的是服务端Stub方针自身,不然新建一个长途服务的本地署理
-
aidl 中的 oneway 表明异步调用,主张RPC之前不会构建parcel reply
-
aidl 中的 in 表明客户端向服务端传送值并不关怀值的改动,out表明服务端向客户端回来值
进程通讯办法(IPC)
- Messenger:不支撑RPC(Remote Procedure Call) ,低并发串行通讯,并发高或许等候时刻长。
- 它是除了aidl之外的创立Remote bound service的办法
- 它能够完结客户端和服务器的双向串行通讯(来信和回信)
- 服务器和客户端本质上是经过拿到对方的Handler像对方发送音讯。但Handler不能跨进程传递,所以在外面包了一层Messenger,它继承与IBinder。服务端和客户端分别将界说在自己进程中的Messenger传递给对方,经过Messenger彼此发送音讯,其实是Messenger中的Handler发送的,服务端将自己的Messenger经过onServiceConnected()回来,客户端经过将Messenger放在Message.replyTo字段发送给服务器
- AIDL:支撑RPC 一对多并发通讯
- 存在多线程问题:跨进程通讯是不同进程之间线程的通讯,假如有多个客户端主张恳求,则服务端binder线程池就会有多个线程响应。即服务端接口存在多线程并发安全问题。
- RemoteCallbackList用于办理跨进程回调:其内部有一个map成果保存回调,键是IBinder方针,值是回调,服务端持有的接口有必要是RemoteCallbackList类型的
- 长途服务运转在binder线程池,客户端宣布恳求后被挂起,假如服务耗时长,客户端或许会产生anr,所以需求新启线程恳求服务
- AIDL 进程通讯流程:
- 经过IInterface界说服务后会自动生成stub和proxy
- 服务器经过完结stub来完结服务逻辑并将其以IBinder办法回来给客户端
- 客户端拿到IBinder后生成一个本地署理方针(经过asInterface()),经过署理方针恳求服务,署理睬构建两个Parcel方针,一个data用来传递参数,另一个reply用来存储服务端处理的成果,并调用BinderProxy的transact()主张RPC(Remote Procedure Call),一起将当时进程挂起。所以假如长途调用很费时,不能在UI线程中恳求服务
- 这个恳求经过Binder驱动传递到长途的Stub.onTransact()调用了服务真实的完结
- 回来成果:将回来值写入reply parcel并回来
- 文件:经过读写同一个文件完结数据传递。(复杂方针需求序列化),并发读或许产生数据不是最新的状况,并发写能够破坏数据,适用于对同步要求低的场景
- ContentProvider:一对多进程的数据同享,支撑增删改查
- Bundle:仅限于跨进程的四大组件间传递数据,且只能传递Bundle支撑的数据类型
Bundle
- 运用ArrayMap存储结构,省内存,查询速度稍慢,由于是二分查找,适用于小数据量
- 运用Parcelable接口完结序列化,而hashmap运用serializable
Parcel
-
将各种类型的数据或方针的引证进行序列化和反序列化,经过mmap直接写入内核空间。另一个进程能够直接读取这个内核空间(由于做了mmap,不需求另一次copy_to_user())
-
运用复用池(是一个Parcel数组),获取Parcel方针
-
parcel 存取数据次序需求保持共同,由于parcel在一块接连的内存地址,经过首地址+偏移量完结存取
耐久化办法
- SharedPreference
- 以xml文件办法存储在磁盘
- 读写速度慢,需求解析xml文件
- 文明存储,安全性差
- 是线程安全的,但不是进程安全的
- 调用写操作后会先写入内存中的map结构,调用commit或许apply才会履行写文件操作
- 或许构成 anr:
- Activity或许service onstop的时分,sp会等候写使命完毕,假如使命迟迟没有完毕则会构成anr
- getSharedPreference会敞开线程读文件,调用getString会调用wait()等候读文件完毕,假如文件大,则会构成主线程堵塞。
- sp一共有三个锁:写文件锁,读内存map的锁,写editor中map的锁
- SQLite
- 文件
- DataStore
-
根据Flow,供给了挂起办法而不是堵塞办法。
-
根据事物更新数据
-
支撑protocol buffer
-
不支撑部分改写
-
Service
- 分类
-
started service
- 生命周期和发动他的组件无关,有必要显现调用stopservice()才干中止
-
bound service
- 生命周期和发动他的组件绑定,组件都毁掉了 他也毁掉
- Local Bound Service :为自己运用程序的组件供给服务
- Remote Bound Service:为其他运用的组件供给服务
- 1.aidl
- 2.Messenger
- bound service怎样和绑定组件生命周期联动:在绑定的时分会将serviceConnection保存在LoadedApk的ArrayMap结构中,当Activity finish的时分,会遍历这个结构逐一解绑
-
- service默许是后台进程
- Service和Activity通讯
- 经过 Intent 传入startService
- 发送播送,或许本地播送
- bound service,经过办法调用通讯
IntentService
- 他是Service和音讯机制的结合,它适用于后台串行处理一连串使命,使命履行完毕后会自毁掉。
- 它发动时会创立HandlerThread和Handler,并将HandlerThread的Looper和Handler绑定,每次调用startService()时,经过handler发送音讯到新线程履行
- 可是它没有将处理成果回来到主线程,需求自己完结(能够经过本地播送)
播送
- 是一种用调查者办法完结的异步通讯
- 有静态注册和动态注册两种办法,静态注册生命周期比动态注册长(在运用装置后就处于监听状况)
- 静态注册播送接纳器时只需界说了intent-filter 则android:exported特点为true 表明该播送接纳器是跨进程的
- 静态注册播送简单被进犯:其他App或许会针对性的宣布与当时App intent-filter相匹配的播送,由此导致当时App不断接纳到播送并处理;
- 静态注册播送简单被劫持:其他App能够注册与当时App共同的intent-filter用于接纳播送,获取播送详细信息。
- 经过manifest注册的播送是静态播送
- 静态播送是常驻播送,常驻播送在运用退出后依然能够收到
- 动态注册的不是常驻播送,它的生命周期痛注册组件共同
- 动态注册要比及发动他的组件发动不时才注册
- 动态播送优先级比静态高
- 播送接纳者BroadcastReceiver经过Binder机制向AMS进行注册,播送发送者经过binder机制向AMS发送播送,播送的Intent和Receiver会被包装在BroadcastRecord中,多个BroadcastRecord组成行列
- onReceive()回调履行时刻超越10s 会产生anr,假如有耗时操作需求运用IntentService处理,不主张新建线程
- 分为有序播送和无序播送
- 无序播送:一切播送接纳器承受播送的先后次序不确认,播送接纳者无法阻止播送发送给其他接纳者。
- 有序播送:播送接纳器收到播送的次序按预先界说的优先级从高到低摆放 接纳完了假如没有丢掉,就下传给下一个次高优先级别的播送接纳器进行处理,顺次类推,直到终究
- 自界说Intent并经过sendOrderBroadcast()发送
- 能够经过在intent-filter中设置android:priority特点来设置receiver的优先级,优先级相同的receiver其履行次序不确认,假如BroadcastReceiver是代码中注册的话,且其intent-filter具有相同android:priority特点的话,先注册的将先收到播送
- 运用setResult系列函数来成果传给下一个BroadcastReceiver
- getResult系列函数来获得上个BroadcastReceiver回来的成果
- abort系列函数来让体系丢掉该播送,运用该播送不再传送到别的BroadcastReceiver
- 还能够分为本地播送和跨进程播送
- 本地播送仅限于在运用内发送播送,向LocalBroadCastManager注册的接纳器都寄存在本地内存中,跨进程播送都注册到system_server进程
音讯机制
- Android 主线程存在一个无限循环,该循环在不断地从音讯行列中取音讯。音讯是按时刻先后次序刺进到链表结构的音讯行列中,最旧的音讯在队头,最新的音讯在队尾。
- Looper经过无限循环从音讯行列中取音讯并分发给其对应的Handler,并收回音讯
- Handler 是用于串行通讯的工具类。
- 音讯池:链式结构,静态,一切音讯共用,取音讯时从头部取,音讯分发完毕后头部刺进
- Android音讯机制共有三种音讯处理办法,它们是互斥的,优先级从高到低分别是1. Runnable.run() 2. Handler.callback 3. 重载Handler.handleMessage()
- 若音讯行列中音讯分发完毕,则调用natviePollOnce()堵塞当时线程并开释cpu资源,当有新音讯刺进时或许超不时刻到时线程被唤醒
- idleHandler 是在音讯行列闲暇时会被履行的逻辑,每拿取一次音讯有且仅有一次时机履行.经过queueIdle()回来true表明每次取音讯时都会履行,不然履行一次就会被移出
- 同步音讯屏障是一种特别的同步音讯,他的target为null, 在 MessageQueue.next()中遇到该音讯,则会遍历音讯行列优先履行一切异步音讯,若遍历到行列尾部仍是没有异步音讯,则堵塞会调用epoll,直到异步音讯到来或许同步屏障被移出
- 运用epoll完结音讯行列的堵塞和唤醒,Message.next()是个无限循环,若当时无音讯可处理睬堵塞在nativePollOnce(),若有推迟音讯,则设置超时,没有音讯时主线程休眠不会占用cpu
- epoll是一个IO事情告知机制 监听多个文件描绘符上的事情 epoll 经过运用红黑树搜索被监控的文件描绘符(内核保护的文件打开表的索引值)
- epoll终究是在epoll_wait上堵塞
- nativeWake() 唤醒是经过往管道中写入数据,epoll监听写入事情,epoll_wait()就回来了
接触事情
- 接触事情的传递是从根视图自顶向下“递”的进程,接触事情的消费是自下而上“归”的进程。
- 接触事情由ViewRootImpl经过ViewRootHandler接纳到,然后存取一个链式行列,再逐一分发给Activity接纳到接触事情后,会传递给PhoneWindow,再传递给DecorView,由DecorView调用ViewGroup.dispatchTouchEvent()自顶向下分发ACTION_DOWN接触事情。
- ACTION_DOWN事情经过ViewGroup.dispatchTouchEvent()从DecorView经过若干个ViewGroup层层传递下去,终究抵达View。View.dispatchTouchEvent()被调用。
- View.dispatchTouchEvent()是传递事情的结尾,消费事情的起点。它会调用onTouchEvent()或OnTouchListener.onTouch()来消费事情。
- 每个层次都能够经过在onTouchEvent()或OnTouchListener.onTouch()回来true,来告知自己的父控件接触事情被消费。只需当基层控件不消费接触事情时,其父控件才有时机自己消费。
- ACTION_MOVE和ACTION_UP会沿着方才ACTION_DOWN的传递路径,传递给消费了ACTION_DOWN的控件,假如该控件没有声明消费这些后序事情,则它们也像ACTION_DOWN相同会向上回溯让其父控件消费。
- 父控件能够经过在onInterceptTouchEvent()回来true来阻拦事情向其孩子传递。假如在孩子现已消费了ACTION_DOWN事情后才进行阻拦,父控件会发送ACTION_CANCEL给孩子。
滑动抵触
父子都能够消费滑动事情时会产生滑动抵触:
- 父控件自动:父控件只阻拦自己滑动方向上的事情(经过在onInterceptTouchEvent中回来true完结),其他事情不阻拦持续传递给子控件
- 子控件自动:子控件要求父控件进行阻拦或许不阻拦(经过getParent().requestDisallowInterceptTouchEvent(true)完结)
ArrayMap & HashMap
- 存储结构:arrayMap用一个数组存储key的哈希值,用另一个数组存储key和value(挨着i和i+1),而HashMap用一个Entry结构包裹key,value,所以HashMap更加占用空间。
- 拜访办法:arrayMap经过二分查找key数组,时刻复杂度是o(log2n),HashMap经过散列定位办法,时刻复杂度是o(n),
- ArrayMap 删去键值对时分会进行数组平移以紧缩数组
- ArrayMap 刺进键值对时或许产生数组全体平移以腾出刺进方位
SparseArray & HashMap
- SparseArray用于寄存键值对,键是int,值是Object。
- SparseArray用两个长度持平的数组分别存储键和值,同一个键值对地点两个数组中的索引持平。
- SparseArray比HashMap拜访速度更慢,由于二分查找速度慢于散列定位。
- SparseArray比HashMap更节省空间,由于不需求创立额定的Entry寄存键值对。
- SparseArray中寄存键的数组是递加序列。
- SparseArray删去元素时并不会真实删去,而是符号为待删去元素,在合适的时分会将后边的元素往前挪掩盖掉待删去元素。待删去元素在没有被掩盖前有或许被复用。
recyclerview
-
Recycler有4个层次用于缓存ViewHolder方针,优先级从高究竟顺次为
ArrayList<ViewHolder> mAttachedScrap
、ArrayList<ViewHolder> mCachedViews
、ViewCacheExtension mViewCacheExtension
、RecycledViewPool mRecyclerPool
。假如四层缓存都未命中,则从头创立并绑定ViewHolder方针 -
缓存功能:
缓存 从头创立 ViewHolder
从头绑定数据 mAttachedScrap false false mCachedViews false false mRecyclerPool false true -
缓存容量:
- mAttachedScrap:没有巨细束缚,但最多包括屏幕可见表项。
- mCachedViews:默许巨细束缚为2,放不下时,依照先进先出准则将最早进入的
ViewHolder
存入收回池以腾出空间。 - mRecyclerPool:对
ViewHolder
按viewType
分类存储(经过SparseArray
),同类ViewHolder
存储在默许巨细为5的ArrayList
中。
-
缓存用途:
- mAttachedScrap:用于布局进程中屏幕可见表项的收回和复用。
- mCachedViews:用于移出屏幕表项的收回和复用,且只能用于指定方位的表项,有点像“收回池预备行列”,即总是先收回到
mCachedViews
,当它放不下的时分,依照先进先出准则将最早进入的ViewHolder
存入收回池。 - mRecyclerPool:用于移出屏幕表项的收回和复用,且只能用于指定
viewType
的表项
-
缓存结构:
- mAttachedScrap:ArrayList
- mCachedViews:ArrayList
- mRecyclerPool:对ViewHolder按viewType分类存储在SparseArray中,同类ViewHolder存储在ScrapData中的ArrayList中
ItemDecoration
- 用于制作ItemView以外的内容,有两个回调onDraw和onDrawOver,区别是制作的次序有先后,onDraw()会在RecyclerView.onDraw()中调用,表明制作RecyclerView自身的内容,会在制作孩子之前,所以出现在孩子下面,而onDrawOver() 是在 RecyclerView.draw()中调用,在制作孩子之后调用,所以会出现在孩子上方
view生命周期
结构View –> onFinishInflate –> onAttachedToWindow –> onMeasure –> onSizeChanged –> onLayout –> onDraw –> onDetachedFromWindow
Bitmap
- raw和drawable和sdcard,这些不带dpi的文件夹中的图片被解析时不会进行缩放(inDensity默许为160)
- 获取bitmap宽高:若inJustDecodeBounds为true,则不会把bitmap图片的像素加载到内存(实践是在Native层解码了图片,可是没有生成Java层的Bitmap),仅仅获取该bitmap的原始宽(outWidth)和高(outHeight)
- 在4.4之前假如复用bitmap则不支撑在native层缩放,缩放放到java层,将本来bimap基础上新建bitmap,毁掉本来的,这样效率低, 4.4今后支撑在native层做缩放
- 8.0,bitmap像素数据在native,6.0以前finalize开释native的bitmap,之后经过注册NativeAllocationRegistry(简化了Cleaner的运用),将native资源的巨细计入GC触发的战略之中。即便java堆增长缓慢,而native堆增长快速也同样会触发gc
- Cleaner用于收回native堆中的像素数据:Cleaner继承自虚引证,虚引证指向方针被收回时,虚引证方针会进入ReferenceQueue,异步线程ReferenceQueueDaemon会在ReferenceQueue.wait()上等候,只需方针被收回,然后遍历引证行列,若存在Cleaner则调用clean办法开释native内存
- Bitmap巨细=长* 宽 * 像素巨细
- BitmapFactory.Options
- 复用bitmap:加载时设置inBitmap表明运用之前bitmap方针运用过的内存 而不是从头拓荒新内存(假如被复用的Bitmap == 回来的被加载的Bitmap,那么阐明复用成功了)。复用条件是,图画是可变的isMutable为true
- inPreferredConfig
- 只需当图片是webp 或许png24的时分,inPreferredConfig才会有效果。
- ALPHA_8 : 图片只需alpha值,没有RGB值,一个像素占用一个字节
- RGB_565:2字节
- ARGB_8888 : 一个像素占用4个字节
- inSampleSize:为2的幂次,表明紧缩宽高为本来的1/2,像素密度不变,按需加载,按ImageView巨细加载,核算inSampleSize的算法是,原始宽高不断除2,inSampleSize不断乘2,直到原始宽高小于需求宽高。
- 收回bitmap(Bitmap.recycle()):仅仅开释图片native方针的内存,而且去除图片像素数据的引证,让图片像素数据能够被废物收回
- Bitmap占用内存巨细因素:
- 图片的原始宽高(即咱们在图片修正软件中看到的宽高)
- 解码图片时的Config装备(即每个像素占用几个字节)
- 解码图片时的缩放因子(即inTargetDensity/inDensity)
- BitmapRegionDecoder用于图片分块加载
- jpg 色彩丰富,没有兼容性问题,不支撑透明度,和动画,适用于摄影作品。jpg在高对比度的场景下效果欠好,比方黑色文字在白色的布景上
- png包括透明度适用于图标,由于大面积的重复色彩,适用于无损紧缩
- webp包括有损和无损两个办法,webp的浏览器支撑不佳,webp支撑动画和透明度,之前动画只能用gif,透明度只能选png,有损紧缩后的webp的解码速度慢,比gif慢2倍
- 图片缩放份额 scale = 设备分辨率 / 资源目录分辨率 如:1080×1920的图片显现xhdpi中的图片,scale = 480 / 320 = 1.5,图片的宽高会乘以scale
ANR
- KeyDispatchTimeout:View的点击事情或许接触事情在特定的时刻(5s)内无法得到响应。
- BroadcastTimeout:播送onReceive()函数运转在主线程中,在特定的时刻(10s)内无法完结处理。
- ServiceTimeout:Service的各个生命周期函数在特定时刻(20s)内无法完结处理
Lifecycle
- 让任何组件能够作为调查者调查界面生命周期
- 经过LifecycleRegistry,它持有一切调查者,经过注册ActivityLifecycleCallbacks 完结生命周期的分发,假如是29 以下则将ReportFragment增加到activity中
- 监听运用前后台切换:经过registerActivityLifecycleCallbacks,然后在保护一个活泼activity的数量,ProcessLifecycleOwner为咱们做了这件事情 用于监听运用前后台切换,ProcessLifecycleOwner的初始化经过ContentProvider完结
康复数据
- onSaveInstanceState()+onRestoreInstanceState():会进行序列化到磁盘,耗时,杀进程依然存在
- Fragment+setRetainInstance():数据保存在内存,装备产生改动时数据依然存在,但杀进程后数据不存在
- onRetainNonConfigurationInstance() + getLastNonConfigurationInstance():数据保存在内存,装备产生改动时数据依然存在,但杀进程后数据不存在
进程优先级
一共有五个进程优先级
- 前台进程(Foreground process):该进程中有前台组件正在运转,oom_adj:FOREGROUND_APP_ADJ=0
- 正在交互的Activity,Activity.onResume()
- 前台服务
- Service.onCreate() onStart()正在履行
- Receiver.onReceive()正在履行
- 可见进程(Visible process) VISIBLE_APP_ADJ = 1
- 正在交互的Activity,Activity.onPause()
- 服务进程(Service process)
- 后台服务
- 后台进程(Background process) BACKUP_APP_ADJ = 3
- 不行见Activity Activity.onStop()
- 后台进程优先级等于后台服务,所以长时刻后台使命终究其服务
- 空进程(Empty process):不包括任何组件的进程,Activity 在退出的时分进程不会毁掉, 会保留一个空进程便利今后发动. 但在内存不足时进程会被毁掉
按下回来键退出运用,此刻运用进程变成缓存进程,随时或许被杀掉
按下home键退出运用,此刻运用是不行见进程
LruCache
- ~是内存缓存,持有一个 LinkedHashMap 实例
- ~用LinkedHashMap作为存储结构,且LinkedHashMap按拜访次序排序,最新的结点在尾部,最老的结点在头部
发动优化
- 视觉优化:windowBackground设置一张图片(成为StartingWindow的Decorview的布景)
- 初始化使命优化:能够异步初始化的,放异步线程初始化,有必要在主线程但能够推迟初始化的,放在IdleHandler中,
- ContentProvider 优化:去掉没有必要的contentProvider
- 缩小main dex:MultidexTransform 解析一切manifest中声明的组件生成manifest_keep.txt,再查找manifest_keep.txt中一切类的直接引证类,将其保存在maindexlist.txt中,终究将maindexlist.txt中的一切class编译进main.dex。multiDex优化,自行解析AndroidManifest,自界说main.dex生成逻辑,将和发动页相关的代码分在主dex中,减小主dex巨细,加速加载速度。
- multiDex.install 异步化,在4.4以下的机型MultiDex.install()耗时。它会先解压apk,遍历其间的dex文件,然后紧缩成对应的zip文件(这是榜首次的逻辑,第二次发动时现已有zip文件则直接读取。)然后经过反射,将其他的dex追加到DexPathList的尾部。这个进程中的紧缩成zip能够免除,以进步速度。能够将这个进程放在独自的一个进程中做(在attachBaseContext中,敞开一个死循环等候multidex完结),而且该进程有一个activity界面展示loading,但加载完毕后告知主进程,在跳转到闪屏页
LiveData
- LiveData 的数据调查者在内部被包装成另一个方针(完结了 LifecycleEventObserver 接口),它一起具有了数据调查才干和生命周期调查才干
- LiveData 内部会将数据调查者进行封装,使其具有生命周期感知才干。当生命周期状况为 DESTROYED 时,自动移除调查者
- LiveData 的调查者会保护一个“值的版别号”,用于判别前次分发的值是否是最新值,“新调查者”被“老值”告知的现象叫“粘性”。由于新调查者的版别号总是小于最新版号,且增加调查者时会触发一次老值的分发
- 粘性不应该成为一个问题,网上有很多关于粘性的处理方案,详见LiveData 面试题库、回答、源码剖析
- 在高频数据更新的场景下运用 LiveData.postValue() 时,会构成数据丢失。由于“设值”和“分发值”是分隔履行的,之间存在推迟。值先被缓存在变量中,再向主线程抛一个分发值的使命。若在这推迟之间再一次调用 postValue(),则变量中缓存的值被更新,之前的值在没有被分发之前就被擦除了。
ViewModel
-
ViewModel 实例被存储在ViewModelStore的map中, 在装备产生改动时onRetainNonConfigurationInstance会被调用(ViewModelStore的map方针会被存储在NonConfigurationInstances中),并会回来这个方针.在康复ViewModel时再getLastNonConfigurationInstance中再次获取
-
Activity 完结了LifecycleOwner,等onDestroy时会测验(非装备改动时)调用 store的 clear(遍历了 viewmodel的clear)
-
ViewModel 在 Fragment 中不会因装备改动而毁掉的原因其实是由于其声明的 ViewModel 是存储在 FragmentManagerViewModel 中的,而 FragmentManagerViewModel 是存储在宿主 Activity 中的 ViewModelStore 中,又因 Activity 中 ViewModelStore不会因装备改动而毁掉,故 Fragment 中 ViewModel 也不会因装备改动而毁掉。
DiskLruCache
- 内部有一个线程池(只需一个线程)用于整理缓存
- 内部有一个LinkedHashMap结构代表内存中的缓存,键是key,值是Entry实体(key+file文件)
- ~有一个journal文件缓存操作的日志文件,结构时会读取日志文件并将其转换成LinkedHashMap存储在内存
- 取缓存时,先读取内存中的Entry,然后将其转换成Snapshot方针,能够从Snapshot中拿到输入流
- 写缓存时,新建Entry实体并存在LinkedHashMap中,将其转换成Editor方针,能够从中拿到输出流
- 经过LinkedHashMap完结LRU替换
- 每一个Cache项有四个文件,两个状况(DIRTY,CLEAN),每个状况对应两个文件:一个文件存储Cache meta数据,一个文件存储Cache内容数据
编译打包流程
- 打包资源,res下文件转换成二进制,asset目录
- 编译java文件为class文件
- 将class文件转换为dex文件
- 将资源和dex打包到apk
- 签名
launch mode
taskAffinity与allowTaskReparenting配合:咱们能够在AndroidManifest.xml为Activity装备android:allowTaskReparenting特点,表明答应此Activity替换其从属的使命栈。设置此特点的Activity一但当时Task切换到了后台,就会回到它“倾向”的使命栈中
-
singleTask
大局仅有,先查看是否有和待发动Activity的taskAffinity相同的task(若未显现指定,则默许和app的榜首个activity具有相同的taskAffinity,为包名),若无则新建task并新建Activity压栈,若有则把该task移到前台并在该task中寻觅待发动activity,若找到则将该task中该activity之上的一切activity弹出,让其成为栈顶(此刻onCreate()不被调,onNewIntent()被调),若没有找到则新建Activity实例并压栈 -
singleInstance
大局仅有,假如不存在待发动Activity,则新建task来容纳待发动activity,而且该task不能放入其他activity,若存在,则将对应的task移到前台
该类型只能在manifest中指定,不能经过Intent.setFlag()
该类型只能效果于Task栈底的Activity -
standard
大局不只需,每次都在当时task中新建一个实例(待发动activity.onCreate()每次都会被调用) -
singleTop
大局不只需,只需当Activity坐落task的栈顶时,该activity实例才会被重复利用(onNewIntent()被调用而不是onCreate()),不然都会新建实例
ActivityManagerService
- 在SystemServer中被发动,经过SystemServiceManager.startService(ActivityManagerService.Lifecycle.class)发动
- 担任四大组件的发动切换调度
- AMS 结构的时分
- 创立了两个线程,一个是作业线程,一个是和进程发动相关的线程
- 发动了低内存检测LowMemDetector,经过epoll机制获取低内存告知
- 构建ActiveServices办理service
- 构建ProviderMap办理contentProvider
- 初始化ActivityTaskManager
- 敞开Watchdog,监听线程堵塞
- 创立OomAdjuster用于调整进程的优先级等级
- 创立BatteryStatsService和ProcessStatsService用于办理电量状况和进程状况
- 获取AMS方针,是经过 ServiceManager.getService() 获取一个 IBinder 方针,然后经过asInterface()获取本地方针或许长途方针的本地署理(Android 10中一切体系服务都经过AIDL接口来获取,在Android10以前获取服务不是经过直接经过AIDL接口的,而是经过ActivityManagerNative来转发,本质仍是经过AIDL生成类Stub来获取)
- 它担任办理Activity,它经过ActivityStackSupervisor办理Activity调度,ActivityStackSupervisor实例在它结构函数中被创立
- 它会和运用进程双向通讯以完结发动Activity,
- 它保护一切进程信息,包括体系进程,它经过ProcessRecord来保护进程运转时的状况信息,需求将运用进程绑定到ProcessRecord才干开端一个Application的构建
动画
- view animation
- 也称为补间动画:界说关键帧,其他帧由体系补齐,有点像导航
- 局限于view 局限于位移 旋转 透明度 缩放
- 用父控件的Transformation中的matrix,将matrix运用到View 上,每次制作的时分会查看动画是否完结,若没完结则调用invalidate(),ViewRootImpl向编舞者跑了一个遍历View树的使命,会有同步音讯屏障
- 不能监听动画改动的进程
- 还能响应原有方位的接触事情,是由于会将 matrix 反向运算
- property animation
- 构建值改动的完整序列 并将其运用到视图特点上
- 不只仅适用于view 可用于任何供给了getter和setter的方针的任何特点上
- 经过向 Choreographer 不断post 一个动画类型使命(在制作使命之前履行), 当时帧完毕后若动画未完毕持续post 没有同步音讯屏障
- 可监听动画改动进程
- Interpolator决定值改动的速度(依据时刻流逝的百分比核算出当时特点值改动的百分比)
- 若是硬件加速,则直接修正RenderNode中的相关特点,不需求从头构建DisplayList,若是软件制作,则会触发invalidate
- 帧动画:就像连环画相同,一张张图片接连播映。AnimationDrawable 会在动画播映之前将一切帧都加在到内存,耗内存。
关于帧动画的功能优化能够点击Android功能优化 | 帧动画OOM?优化帧动画之SurfaceView逐帧解析
ConstraintLayout 功能
- 额定的封装,会将容器控件包装成ConstraintWidgetContainer,子控件包装成ConstraintWidget
- 在丈量布局的时分,会想把一切的ConstraintWidget移除,然后遍历一切子控件并从头增加ConstraintWidget,假如子控件有束缚则将其衔接到锚点,构建依赖图,深度遍历依赖图,进行求解(Cassowary 算法),终究得到相关于父亲的上下左右。
Java & Kotlin
string
string是final类型的char数组,表明引证不会改动
final
表明引证指向不能变,但其指向的变量是可变的
泛型
- 泛型的目的是类型参数化,即用变量表明类型
- 进步安全性:泛型能够把运用Object的错误提前到编译后,而不是运转后,进步安全性
- 消除强转:没有泛型的时分,都用Object替代,由于Object能够强转成任何类型
- PECS是在运用泛型时为了遵守里氏替换准则有必要准守的准则,运用泛型增加代码适用性时确保了类型安全。
- PE:Producer extends 完结协变效果:泛型类和类型参数的笼统程度具有相同的改动方向。泛型类只出产泛型,即泛型只会出现在类办法的回来值方位,kotlin顶用out表明,java用extend表明
- CS:consumer super 完结逆变效果:泛型类只消费泛型,即泛型只出现在类办法的参数位,kotlin顶用in表明,java用super表明。
- 类型参数的父子联系是否会接连到外部类上,若接连的叫协变,不然父子联系转向了,这叫逆变,若没有父子联系则叫不变型 ,泛型是不变型
- 类型擦除:为了兼容1.5以前的代码,即编译后的实参类型是Object或许上界
- 当子类掩盖或许完结父类办法时,办法的形参要比父类办法的更为宽松;
- 当子类掩盖或许完结父类办法时,办法的回来值要比父类的更严格。
- 假如在编译的时分就保存了泛型类型到字节码中,那么在运转时咱们就能够经过反射获取到,假如在运转时传入实践的泛型类型,这个时分就会被擦除,反射获取不到当时传入的泛型实践类型
- Kotlin reified 可防止类型擦除,它会将办法内联到履行的地方,而且对泛型方针进行instanceof 的分类讨论。
关于泛型的详细剖析能够点击Kotlin 进阶 | 不变型、协变、逆变
java方针生命周期
- 创立:为方针分配内存,结构方针
- 运用:至少一个强引证指向它
- 不行见:不再持有强引证(程序履行超出了方针的效果域)
- 不行达:没有强引证指向
- 搜集:准备gc,会履行 finalize()
- 终结:等候废物收回
- Deallocated:收回完结
类加载
- 编译:javac 命令把 .java 文件编译成字节码(.class 文件)
- 运转:jvm履行.class
- 类加载进程:加载—链接—初始化
- 类加载:jvm把.class作为二进制流读入内存,并实例化一个Class方针,jvm 并不是一次性把一切类都加在到内存,而是履行进程中遇到没有加载的才加载,并只加载一次(Android加载的dex)
- 验证:二进制合法性校验
- 准备:为类变量在办法区赋初始值
- 解析:将类名,办法名,字段名替换为内存地址
- 初始化:对类的自动引证,包括new 调用静态办法,运用静态字段
- 运用:
- 卸载:
核算类加载耗时:反射BaseDexClassLoader的 pathList,写入自界说的 PathClassLoader(装饰者增加耗时核算)
- 类加载器 PathClassLoader:只能加载运用包内的dex
- 类加载器 DexClassLoader:能够加载恣意方位的 dex
- 类加载器 BaseDexClassLoader:持有 DexPathList,结构如下, BaseDexClassLoader(DexPathList(Element数组(DexFile(多个Class))))
- DexPathList: 将 dex 文件转换成 element 存入数组(dexElements),findClass()是遍历Elements并进行类名匹配。
- Android 类加载进程:Dex 文件在类加载器中被包装成 Element,Element以数组办法被DexPathList 持有,加载类时经过遍历Element数组进行类名匹配查找,只需把新的Dex文件刺进到 Element头部即可完结热修(反射)。
- 双亲托付:类加载器加载类时,将加载恳求逐级向上托付,直到BootStrapClassloader,真实的加载从顶层开端,逐级向下查找。防止了类重复加载,以及安全,由于体系类总是由上层类加载器加载,无法经过自界说篡改
类结构次序
- 先父亲,再孩子
- 先静态再非静态
- 先字段,后结构器(字段先后有界说次序决定)
- 先代码块 后结构办法
HashMap
- 存储结构是开散列表:地址向量+近义词子表=数组+单链表。
- 处理哈希抵触的办法是拉链法:将相同散列地址的键值寄存在近义词子表中。
- capacity为啥要为2的幂次,是为了用位与运算替代取模运算,进步功能。
- 为啥loadFactor 是0.75,由于中庸,若为1,则频频抵触,若为更小值,则会频频扩容。
- 结构~时,并没有初始化地址向量,而是要比及put操作是才结构
- 遍历HashMap的次序是从地址向量的榜首个开端,先早年到后遍历近义词子表,然后下一个近义词子表
- HashMap经过hash算法先定位到地址向量中对应的方位,然后遍历近义词子表
- HashMap不是线程安全的,当~扩容的时分要进行迁移,多线程并发put会导致迁移出环。主张运用Hashtable或许ConcurrentHashMap。Hashtable将put和get办法都加上了synchronized,功能较差
WeakHashMap
- 用于寄存键值对,当产生gc时,其间的键值对或许被收回。适用于对内存灵敏的缓存
- 寄存键值对的Entry继承自WeakReference。当产生gc时,Entry被收回并加入到ReferenceQueue中
- 拜访~时,会将现已gc的键值对从中删去(经过遍历ReferenceQueue)
LinkedHashMap
- 是一个有序 map,能够按刺进次序或许拜访次序摆放
- 在 hashMap 基础上增加了头尾指针构成双向链表,继承 Node 增加前后结点的指针,每次构建结点时会将他链接到链尾。
- 若是按拜访次序排序,存取键值对的时分会将其拆下刺进到链尾,链头是最老的结点,满时会被移出
- 按拜访次序来排序是LRU缓存的一种完结。
ThreadLocal
- 用于将方针和当时线程绑定(将方针存储在当时线程的ThreadLocalMap结构中)
- ThreadLocalMap是一个相似HashMap的存储结构,键是ThreadLocal方针的弱引证,值是要保存的方针
- set()办法会获取当时线程的ThreadLocalMap方针
- threadLocal内存走漏:key是弱引证,gc后被收回,value 被entry持有,再被ThreadLocalMap持有,再被线程持有,假如线程没有完毕,则value无法拜访到,也无法收回,方案是及时remove掉不必的value
- threadlocal 会自动整理key为null 的entry
内存走漏
内存走漏是由于堆内存无法开释
android内存走漏便是生命周期长的方针持有了生命周期较短方针的引证
- 静态成员变量(单例)
- 静态成员变量的生命周期和整个app相同,假如它持有短生命周期的方针则会导致这些方针内存走漏
- 静态变量在不运用时需求置空
- 静态变量运用弱引证持有Activity
- 单例持有App context而不是Activity Contex
- 静态办法能够被子类隐藏,而不是重写
- 非静态内部类
- 匿名内部类持有外部类引证
- handler是典型的匿名内部类,handler中的音讯持有handler引证,假如有未处理完的音讯,则会导致handler外层类内存走漏,Looper -> MessageQueue -> Message -> Handler -> Activity,处理办法是静态内部类+Activity弱引证,而且在activity退出时铲除一切音讯
- new Thread()是典型的匿名内部类,假如Activity退出后Thread还在履行则会引起Activity内存走漏
- 调集类
- 调集方针会持有孩子的引证,需求及时铲除且置空
- webview内存走漏
- WebView在加载网页后会长期占用内存而不能被开释,因而咱们在Activity毁掉后要调用它的destory()办法来毁掉它以开释内存
引证
- 强引证
- 经过=显式的将方针A赋值给变量a,则A就存在一个强引证a
- 强引证需求显式的置null 以告知gc该方针能够被收回
- 在一个办法的内部有一个强引证,这个引证保存在栈中,而真实的引证内容(Object)保存在堆中。当这个办法运转完结后就会退出办法栈,则引证内容的引证不存在,这个Object会被收回。可是假如这个object是大局的变量时,就需求在不必这个方针时赋值为null,由于强引证不会被废物收回
- 清空list时需求遍历一切元素将其置null
- 软引证
- 假如一个方针只具有软引证,则内存空间满足,废物收回器就不会收回它;假如内存空间不足了,就会收回这些方针的内存。只需废物收回器没有收回它,该方针就能够被程序运用。软引证可用来完结内存灵敏的高速缓存。
- 弱引证
- 弱引证与软引证的区别在于:只具有弱引证的方针具有更时刻短的生命周期。在废物收回器线程扫描它所管辖的内存区域的进程中,一旦发现了只具有弱引证的方针,不管当时内存空间满足与否,都会收回它的内存。不过,由于废物收回器是一个优先级很低的线程,因而不一定会很快发现那些只具有弱引证的方针。
- 虚引证
- 虚引证首要用来跟踪方针被废物收回器收回的活动,当废物收回器准备收回一个方针时,假如发现它还有虚引证,就会在收回方针的内存之前,把这个虚引证加入到与之 相关的引证行列中。用于在方针被收回时做一些事情
软引证、弱引证、虚引证的结构办法均能够传入一个ReferenceQueue与之相关。在引证所指的方针被收回后,引证(reference)自身将会被加入到ReferenceQueue之中,此刻引证所引证的方针reference.get()已被收回 (reference此刻不为null,reference.get()此刻为null)。在一个非强引证所引证的方针收回时,假如引证reference没有被加入到被相关的ReferenceQueue中,则表明还有引证所引证的方针还没有被收回。假如判别一个方针的非强引证本该出现在ReferenceQueue中,实践上却没有出现,则表明该方针产生内存走漏。
接口和笼统类
- 类能够完结很多个接口,可是只能继承一个笼统类
- 类假如要完结一个接口,它有必要要完结接口声明的一切办法。可是,类能够不完结笼统类声明的一切办法,当然,在这种状况下,类也有必要得声明成是笼统的。
字符串常量池
- JVM为了削减字符串方针的重复创立,其保护了一个特别的内存,这段内存被成为字符串常量池
- 字符串常量池完结的前提条件是java中的String方针是不行变的,不然多个引证指向同一个变量的时分并改动了String方针,就会产生紊乱
- 字符串常量池是用时刻换空间,cpu需求在常量池中寻觅是否有相同字符串
- 字符串结构办法
- 字面量办法:String str = “droid” 运用这种办法创立字符串时,JVM首要会对这个字面量进行查看,假如字符串常量池中存在相同内容的字符串方针的引证,则将这个引证回来,不然新的字符串方针被创立,然后将这个引证放入字符串常量池,并回来该引证
- 新建方针办法:String str = new String(“droid”); 运用这种办法创立字符串时,不管字符串常量池中是否有相同内容,新的字符串总是会被创立。 关于上面运用new创立的字符串方针,假如想将这个方针的引证加入到字符串常量池,能够运用intern办法。调用intern后,首要查看字符串常量池中是否有该方针的引证,假如存在,则将这个引证回来给变量,不然将引证加入并回来给变量。
String str4 = str3.intern();
反常
- Exception和Error都继承于Throwable
- Exception是程序错误
- Exception又分为checked Exception(编译时反常)和unchecked Exception(运转时)。checked Exception在代码里有必要显式的进行捕获,这是编译器查看的一部分。unchecked Exception也便是运转时反常,相似空指针反常、数组越界等,通常是能够防止的逻辑错误
- Error是比程序更加低层的错误, 包括虚拟机错误OutOfMemoryError,StackOverFlowError
注解
注解为代码增加一些额定的信息,以便稍后能够读取这些信息。这些信息能够协助代码查看,编译时生成代码以削减模板代码
- 元注解
- @Retention:界说注解生命周期
- RetentionPoicy.SOURCE注解只保留在源文件,当Java文件编译成class文件的时分,注解被遗弃;用于做一些查看性的操作,比方 @Override
- RetentionPoicy.CLASS:注解被保留到class文件,但jvm加载class文件时分被遗弃,这是默许的生命周期;用于在编译时进行一些预处理操作,比方生成一些辅助代码(编译时注解便是编写生成代码的代码),ButterKnife 运用编译时注解,即在编译时经过自界说注释解析器AbstractProcessor读取注解并由此生成java文件(在里边调用了 findViewById)
- RetentionPoicy.RUNTIME:注解不只被保存到class文件中,jvm加载class文件之后,依然存在;用于在运转时去动态获取注解信息
- @Target:界说了Annotation所修饰的方针规模
内存模型
dalvik虚拟机内存空间被划分红多个区域 = 虚拟机栈+ 程序计数器+ 办法区+ 堆+ 本地办法栈
- 办法区:办法区首要是存储现已被 JVM 加载的类信息(版别、字段、办法、接口)、常量、静态变量、即时编译器编译后的代码和数据。该区域被各个线程同享的内存区域。
- 堆区:又称动态内存分配,寄存一切用经过new创立的类方针(包括该方针其间的一切成员变量),也便是方针的实例。这部本分存在不运用时将会由 Java 废物收回器来担任收回
- 堆内存没有可用的空间存储生成的方针,JVM会抛出java.lang.OutOfMemoryError
- 堆内存分为新生代和老年代和永生代,新生代又分为Eden、From Survivor、To Survivor三个区域
- 永生代用于寄存class信息
- 虚拟机栈 :虚拟机栈是线程私有的数据结构,它用来描绘 Java 办法履行的内存模型,每个办法被履行的时分,JVM 都会在虚拟机栈中创立一个栈帧
- 栈帧(Stack Frame)
- 一个线程包括多个栈帧,而每个栈帧内部包括部分变量表、操作数栈、动态衔接、回来地址等
- 部分变量表是变量值的存储空间,咱们调用办法时传递的参数,以及在办法内部创立的部分变量都保存在部分变量表中。在 Java 编译成 class 文件的时分,就会在办法的 Code 特点表中的 max_locals 数据项中,确认该办法需求分配的最大部分变量表的容量。
- 在办法退出后都需求回来到办法被调用的方位,程序才干持续履行。而虚拟机栈中的“回来地址”便是用来协助当时办法康复它的上层办法履行状况。
- 假如栈内存没有可用的空间存储办法调用和部分变量,JVM会抛出java.lang.StackOverFlowError
- 栈帧(Stack Frame)
- 本地办法栈和虚拟机栈相似,只不过用于履行native办法
- 程序计数器:每个线程都需求一个程序计数器,用于记录正在履行指令的地址
GC
- 废物界说:有两种界说废物的办法
- 引证计数:给方针中增加一个引证计数器,每当有一个地方引证它时,计数器值就加1;当引证失效时,计数器值就减1;计数器为0的方针便是废物
- 可抵达性:从GC Roots作为起点,向下搜索它们引证的方针,能够生成一棵引证树,树的节点视为可达方针,反之视为不行达。不行抵达的方针是废物,被界说为废物的方针不代表马上会被收回,还会去查看是否要履行finalize办法
- GC种类:Minor GC、Full GC ( 或称为 Major GC )
- 废物收收回回不行抵达的方针,即没有引证的方针,可抵达的方针一定被根引证
- Minor GC 是产生在新生代中的废物搜集动作,所选用的是copy and sweep(经过6次gc还存活的方针会被放到老年代)
- Full GC 是产生在老年代的废物搜集动作,所选用的是mark and sweep
- 分代收回(generational collection):每个方针记录有它的代代(generation)信息。所谓的代代,是指该方针所阅历的废物收回的次数。代代越长远的方针,在内存中存活的时刻越久
- GC收回算法
- copy and sweep:内存被分为两个区域。方针总存活于两个区域中的一个。当废物收回发动时,Java程序暂停运转。JVM从根动身,找到可抵达方针,将可抵达方针复制到空白区域中并严密摆放,修正由于方针移动所构成的引证地址的改动。终究,直接清空方针原先存活的整个区域,使其成为新的空白区域。适用于存活方针少,废物方针多的场景
- mark and sweep:每个方针将有符号信息,用于表明该方针是否可抵达。当废物收回发动时,Java程序暂停运转。JVM从根动身,找到一切的可抵达方针,并符号(mark)。随后,JVM需求扫描整个堆,找到剩余的方针,并清空这些方针所占据的内存堆,缺点是简单产生内存碎片。适用于存活方针多,废物方针少的场景
- 分代收回算法:老年代每次gc只需少量方针被收回,而新生代有大量方针被收回,关于新生代选用copy and sweep,对老年代选用mark and sweep。
OOM类型
- 堆内存不足
- 无满足的接连内存空间
- 文件描绘符超越数量束缚
- 线程数量超越束缚
- 虚拟内存不足
内存优化
- 运用内存友好的数据结构 SpareseArray,ArrayMap
- 防止内存走漏,防止长生命周期方针持有短生命周期方针
- 运用池结构,复用方针防止内存抖动。
- 依据手机内存巨细,设置内存缓存的巨细。
- 多进程,扩展可运用内存。
- 经过ComponentCallback2 监听内存吃紧,进行内存缓存的开释。
LeakCanary
- 经过ActivityLifecycleCallbacks监听Activity生命周期,在onActivityDestroy时获取Activity实例,并为其构建弱引证并相关引证行列。
- 起异步线程,调查ReferenceQueue是否有Activity的弱引证,假如有则阐明收回成功,不然收回失利
- 收回失利后会手动触发一次gc,再监听ReferenceQueue,假如仍是收回失利,则dump内存
- LeakCanary 经过contentProvider装置
- 当一个Activity的onDestory办法被履行后,阐明该Activity的生命周期现已走完,鄙人次GC产生时,该Activity方针应将被收回
equals()
- equals() 界说在JDK的Object.java中。能够界说两个方针是否持平的逻辑
- “==”持平判别符用于比较根本数据类型和引证类型数据。 当比较根本数据类型的时分比较的是数值,当比较引证类型数据时比较的是引证(指针)即指向堆内存的地址
- ==的语义是固定的,而equals()的语义是自界说的
hashCode()
- hashCode() 的效果是获取哈希码,它实践上是回来一个int整数。仅仅当创立并某个“类的散列表”(关于“散列表”见下面阐明)时,该类的hashCode() 才有用,效果是:确认该类的每一个方针在散列表中的方位,Java调集中本质是散列表的类,如HashMap,Hashtable,HashSet
- HashMap 假如运用equals判别key是否重复,需求逐一比较,时刻复杂度为O(n),但假如运用hashCode(),由于它是一个int值。所以能够直接作为数组结构的某个索引值,假如该索引方位没有内容则表明key没有重复,复杂度为O(1)
- 假如两个方针持平,那么它们的hashCode()值一定相同。这里的持平是指,经过equals()比较两个方针时回来true。
- 假如两个方针hashCode()持平,它们并不一定持平。由于在散列表中,hashCode()持平,即两个键值对的哈希值持平。然而哈希值持平,并不一定能得出键值对持平。弥补说一句:“两个不同的键值对,哈希值持平”,这便是哈希抵触。
sealed class
- 是一个继承结构固定的笼统类,即在编译时现已确认了子类数量,不能在运转时动态新增
- 它的子类只能声明在同一个包名下
- 是一个笼统类,且结构办法是私有的,它的孩子是final类,而且孩子声明有必要嵌套在sealed class内部。
- 枚举的局限性
束缚枚举每个类型只答应有一个实例
束缚一切枚举常量运用相同的类型的值
crossinline
在具有inline 特性的一起,防止非部分回来,由于直接return掉函数会影响原有功能,crossinline的lambda内部有必要运用部分回来,比方return@foo
sequence
- ~是慵懒的:中心操作不会被履行,只需终端操作才会(toList())
- ~的核算次序和迭代器不同:~是对一个元素运用全部的操作,然后第二个元素运用全部操作,而迭代器是对列表一切元素运用榜首个操作,然后对列表一切元素运用第二个操作
虚拟内存
- 每个运用拜访的地址空间是虚拟地址空间,所以能够无穷大,Linux担任将虚拟内存转换为物理地址。
- 为了便利将虚拟内存地址和物理地址进行映射,内存空间被切割成若干个页(通常是4kb巨细)
- Memory Management Unit(MMU)这个硬件专门用于将虚拟地址转换为物理地址。它经过查询映射表得到物理地址
- 虚拟地址分为高4位的页号,和后边的偏移量,每次经过页号查询映射表得到物理地址的页号,然后再将偏移量拼在后边得到物理地址
kotlin 空安全
- 编译成java后是经过if判空完结空安全的
- kotlin和java交互的时分空安全被破坏,能够经过在java代码增加@NotNull注解进行非空束缚
Channel
- 是一个挂起行列,和java 中 blocking queue 相似
- 出产者叫 SendChannel,顾客叫 ReceiveChannel
- 出产者和顾客之间有一条缓冲通道
- 履行多线程一起出产,多线程一起消费
- flow 只需订阅才出产数据,Channel 发送数据和订阅无关,所以是暖流
协程
- 是建立在线程之上的,更轻量级的(用户态),更简单操控生命周期(结构化并发)的核算单元。
- 借助于suspend办法完结用户态非抢占式的并发调度,协程经过挂起自动让出履行权(普通的线程映射为内核线程,内核线程的调度是抢占cpu时刻片)
- 比运用线程池更简单撤销异步操作,享受结构化并发,反常处理
- 挂起办法并不会挂起线程,由于就像调用一个带回调的办法相同,它挂起的是协程剩下的代码。
结构化并发
java 线程间的并发是没有级联联系的,所以对错结构的
- 完毕一个线程时,怎样一起完毕这个线程中创立的子线程?
- 当某个子线程在履行时需求完毕兄弟线程要做怎样做?
- 怎样等候一切子线程都履行完了再完毕父线程?
这些问题都能够经过同享符号位、CountDownLatch 等办法完结。但这两个比如让咱们意识到,线程间没有级联联系;一切线程履行的上下文都是整个进程,多个线程的并发是相对整个进程的,而不是相对某一个父线程。
CPS
- Continuation Passing Style,传递剩余的核算,将剩余的核算作为一个回调传递给办法。
suspend
- cps+状况机: 每个suspend 办法都构建一个continuation不经济,一个协程块中的suspend办法会共用一个 continuation(持有一个label)。将原先不同的continuation写在了不同的 switch case 分支内,以挂起点为切割点。每履行一个分支点,label 就+1,表明进入下一个分支。挂起办法会被多次调用(invokeSuspend),由于label值不同每次都会走不同的分支
- suspend 的回来值标志着挂起办法有没有被挂起
Dispatcher
- 调度器CoroutineDispatcher是一个ContinuationInterceptor。经过interceptContinuation()将continuation包装成DispatchedContinuation
- 不同的调度器经过重写 dispatch办法完结不同的线程调度。有些是经过handler抛一个runnable,有些是向线程池抛一个
- default 归于cpu运算密集型:线程被堵塞时,cpu是在忙着运算
- io 归于io型:线程被堵塞时,cpu是闲着。
Job
- 能够被撤销的使命
- 他有六种状况:new-active-completing-completed-cancelling-canceled
- 只需推迟发动的协程的job才会处于new 状况,其他都处于active状况,completing意味着自己的活干完了,在等子协程。 cancelling 是在撤销协程之前终究的整理资源的时机。
- 新建的协程会继承父亲的CoroutineContext,除了其间的job,新协程会新建job并成为父job的子job
背压
- 出产速度大于消费速度
- 运用缓冲区,堵塞行列
- 缓冲区巨细 & 缓冲区满之后的战略(丢掉最新,最久,挂起)
反常处理
- 在 coroutineScope中,反常是向上传达的,只需恣意一个子协程产生反常,整个scope都会履行失利,而且其他的一切子协程都会被撤销掉;
- 在 supervisorScope中,反常是向下传达的,一个子协程的反常不会影响整个 scope的履行,也不会影响其他子协程的履行;(重写了childCancelled并回来false)
CancellationException 反常总是被疏忽
撤销协程
-
每个发动的协程都会回来一个job,调用 job.cancel()会让job处于canceling状况,然后鄙人一个挂起点抛出CancellationException ,假如协程中没有挂起点,则协程不能被撤销。由于每个suspend 办法都会查看job是否活泼,若不活泼则抛出CancellationException ,这个反常仅仅给上层一次封闭资源的时机,能够经过try-catch 捕获
-
关于没有挂起的协程,需求经过while(isActive)来查看job是否被撤销 或许 yield()
-
当协程抛出CancellationException 后,在发动协程将会被疏忽
我正在参加技能社区创作者签约方案招募活动,点击链接报名投稿。