Handler
handler在android应用
1.在卡顿监测会用到消息机制;主要是发送一个延时消息来监测是否,在执行时间内没有remove该消息就代码APP发生卡顿;
2.ANR监测也是通过发送一个延时消息来监测是否发生ANR;ANR是APP卡顿的极端情况;
3.View监测事件是否长按也用到消息机制,在发生Down的时候会发送一个延时消息,在Up的时候会将该消息Remove掉,如果指定的时间没有发生UP就会触发长按事件;
4.Activity生命周期的控制也是在ActivityThread发送不同的消息来切换Activity生命周期;
5.消息机制可以将一个任务切换到其它指定的线程,如AsyncTask;
6.Android动画每一次刷新也需要Handler;
Android是靠消息驱动的,没有消息机制手机根本无法工作,以上这些场景都用到Android消息机制,还有很多其他未知的场景可能也会用到Android消息机制,所以消息机制在Android中具有很重要的地位;
handler原理
Looper:所有的人机交互代码,都得有一个死循环去持续处理人机交互代码。总不能一个main跑到底就结束了。所以Looper就出现了。Looper存在第一理由就是提供死循环。Looper.loop()后会让当前线程变成looper线程。那APP主线程总不能死循环了什么都不做吧?所以,Looper存在的第二个理由就是有M(Message)则处理,无则阻塞。(pipe/epoll机制,IO多路复用,阻塞在IO的读端,不耗费CPU资源。)
MessageQueue:它存在的目的很简单。提供消息接收的并发功能。一般被Looper持有引用。
Handler:google工程师是为什么设计它的呢?首先得说一下原理,A线程怎么跟UI(looper)线程通信呢?我们知道linux进程内资源是共享的,所以线程之间资源也是共享的。所以A线程只要拿到主线程的MQ实例,就能跟主线程通信了。所以Handler存在的第一个目的就是同一进程的线程间通信。第二个功能是消息回调;Handler对象既自己发消息,又自己处理消息。 AB线程只要拿到handler引用,A就可以发消息,B就可以处理消息。Handler在哪里创建,就获得对应线程的Loooper,这是前提。
Message:消息载体。
主线程Loop是死循环,为什么还能跑生命周期
死循环Looper.loop()前,一定要做点什么,留个入口,这样别人才能有途径通知我做事。就是那一句thread.attch(false);这句话会创建一个binder线程(ApplicationThread),该线程不做什么,就是system_server中AMS的bp端。通知方式是什么?就是本题主题Handler。Binder线程内外都持有该mH的引用。
Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?
looper.loop本身不会导致应用卡死。主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
Activity生命周期,比如暂停Activity,流程如下:
1.线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)
2.线程2通过binder传输到App进程的线程4;
3.线程4通过handler消息机制,将暂停Activity的消息发送给主线程;
4.主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给 ActivityThread.H.handleMessage()方法,再经过方法的调用,
5.最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。
Handler 是如何能够线程切换
Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。那么Handler内部如何获取到当前线程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
能不能让一个Message加急被处理?/ 什么是Handler同步屏障?
可以 / 一种使得异步消息可以被更快处理的机制。
如果向主线程发送了一个UI更新的操作Message,而此时消息队列中的消息非常多,那么这个Message的处理就会变得缓慢,造成界面卡顿。所以通过同步屏障,可以使得UI绘制的Message更快被执行。
什么是同步屏障?这个“屏障”其实是一个Message,插入在MessageQueue的链表头,且其target==null。Message入队的时候不是判断了target不能为null吗?不不不,添加同步屏障是另一个方法:
HandlerThread
HandlerThread继承Thread,它是一种可以使用Handler的Thread,它的实现也很简单,在run方法中也是通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环(与我们手动创建方法基本一致),这样在实际的使用中就允许在HandlerThread中创建Handler了。
由于HandlerThread的run方法是一个无限循环,因此当不需要使用的时候通过quit或者quitSafely方法来终止线程的执行。
Handler.postDelayed的原理
message会按最终执行的时间顺序插入到messagequeue的指定位置。如果延迟消息消费到队头,looper发现消息延迟时间未到会继续休眠等待执行。
Activity.runOnUIThread、Handler、View.post
最终底层都是用的handler机制
runOnUiThread:如果在主线程调用,直接执行。如果在子线程调用就用主线程handler往主线程消息队列里发送消息。
Handler:
View.post:需要在view attach 到 Window 后,通过ViewRootImpl的ViewRootHandler执行请求。如果view没有attach到window则不会执行。
Activity启动模式
默认启动模式standard
栈顶复用模式singleTop
栈内复用模式singleTask
全局唯一模式singleInstance
https://blog.csdn.net/zy_jibai/article/details/80587083
Android的进程管理
前台进程:正在屏幕上显示的进程和一些系统进程
可见进程:不在前台,但用户依然可见的进程,举个例来说:widget、输入法等
服务进程:通过 startService() 方法启动的进程,但不属于前台进程和可见进程。例如,在后台播放音乐或者在后台下载就是服务进程。
后台进程:目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。入A应用目前为前台进程,按下Home键回到桌面,A应用就变为了后台进程
空进程:没有任何东西在内运行的进程。保留这种进程的的唯一目的是用作缓存,以缩短该应用下次在其中运行组件所需的启动时间。
https://zhuanlan.zhihu.com/p/145245419
冷启动热启动
冷启动热启动区别
启动方式分为两种:冷启动和热启动。
冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,
冷启动过程
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。
1、点击桌面图标,Launcher会启动程序默认的Acticity,之后再按照程序的逻辑启动各种Activity
2、启动Activity都需要借助应用程序框架层的ActivityManagerService服务进程(Service也是由ActivityManagerService进程来启动的);在Android应用程序框架层中,ActivityManagerService是一个非常重要的接口,它不但负责启动Activity和Service,还负责管理Activity和Service。
Step 1. 无论是通过Launcher来启动Activity,还是通过Activity内部调用startActivity接口来启动新的Activity,都通过Binder进程间通信进入 到ActivityManagerService进程中,并且调用ActivityManagerService.startActivity接口;
Step 2. ActivityManagerService调用ActivityStack.startActivityMayWait来做准备要启动的Activity的相关信息;
Step 3. ActivityStack通知ApplicationThread要进行Activity启动调度了,这里的ApplicationThread代表的是调用 ActivityManagerService.startActivity接口的进程,对于通过点击应用程序图标的情景来说,这个进程就是Launcher了,
Step 4. ApplicationThread不执行真正的启动操作,它通过调用ActivityManagerService.activityPaused接口进入到ActivityManagerService 进程中,看看是否需要创建新的进程来启动Activity;
Step 5. 对于通过点击应用程序图标来启动Activity的情景来说,ActivityManagerService在这一步中,会调用startProcessLocked来创建一个 新的进程,而对于通过在Activity内部调用startActivity来启动新的Activity来说,这一步是不需要执行的,
Step 6. ActivityManagerServic调用ApplicationThread.scheduleLaunchActivity接口,通知相应的进程执行启动Activity的操作;
Step 7. ApplicationThread把这个启动Activity的操作转发给ActivityThread,ActivityThread通过ClassLoader导入相应的Activity类,然后把 它启动起来。
冷启动优化
白屏问题:设置透明背景
冷启动时间优化:减少在Application和第一个Activity的onCreate()方法的工作量;不要让Application参与业务的操作;不要在Application进行耗时操作;不要以静态变量的方式在Application中保存数据;减少布局的复杂性和深度;
View的绘制流程
最终performTraversals()方法触发了View的绘制。该方法内部,依次调用了performMeasure(),performLayout(),performDraw(),将View的measure,layout,draw过程,从顶层View分发了下去。
Dialog,PopupWindow中View的绘制过程也是一样的,只是触发的方式不同。例如Dialog中,是调用dialog.show()时,触发了WindowManagerImpl的addView(),后面的流程就一样了。
自定义View的几种方式
1.继承自View,重写onDraw方法
场景:显示的内容需要高度定制,如图表
2.继承自ViewGroup
场景:流式布局等,对于内容布局由特殊要求的。通过重写onLayout方法达到目的。
3.继承自特定的View(如TextView,ImageView)
场景:需要扩展系统控件功能,如圆角ImageView
4.继承自特定的ViewGroup(如LinearLayout)
Recyclerview
recyclerview缓存机制
关于RecyclerView的缓存分为四级,Scrap、Cache、ViewCacheExtension和RecycledViewPool。Scrap是屏幕内的缓存一般我们不怎么需要特别注意;Cache可直接拿来复用的缓存,性能高效;ViewCacheExtension需要开发者自定义的缓存,API设计比较奇怪,慎用;RecycledViewPool四级缓存,可以避免用户调用onCreateViewHolder()方法,提高性能,在ViewPager+RecyclerView的应用场景下可以大有作为。
recyclerview和listview比较
ListView有两级缓存,分别是Active View和Scrap View,缓存的对象仅仅是ItemView,viewholder每次还是需要重新绑定数据到ItemView;而RecyclerView有四级缓存,分别是Scrap、Cache、ViewCacheExtension和RecycledViewPool,缓存的对象是ViewHolder。Scrap和Cache分别是通过position去找ViewHolder可以直接复用;ViewCacheExtension自定义缓存,目前来说应用场景比较少却需慎用;RecycledViewPool通过type来获取ViewHolder,获取的ViewHolder是个全新,需要重新绑定数据。
Window
windowmanagerservice 统一管理window,activity中的phonewindow也是一种window,phonewindow的view就是decorview,decorview内包括了contentview(activity里setContentView对应)。dialog,popupwindow,toast等都是window。自定义悬浮窗也是window(type层级较高的显示在最上面的window),如果需要在全系统上显示6.0版本开始要打开权限。
Window:屏幕上的某块显示区域,用来承载 View。
WindowManagerService(WMS):Android 框架层的一个服务进程,用来管理 Window。
Surface:对应一块屏幕缓冲区,每个 window 对应一个 Surface。
Canvas:提供了一系列绘图接口,用来在 Surface 上进行绘制操作。
SurfaceFlinger:Android 的一个服务进程,负责管理 Surface。
window的type属性:决定了window的显示次序。window是有分类的,不同类别的显示高度范围不同,例如我把1-1000m高度称为低空,1001-2000m高度称为中空,2000以上称为高空。window也是一样按照高度范围进行分类,他也有一个变量Z-Order,决定了window的高度。window一共可分为三类:应用级window、子window(dialog)、系统级window(Toast)
Window的flags参数:flag标志控制window的东西比较多,很多资料的描述是“控制window的显示”,但我觉得不够准确。flag控制的范围包括了:各种情景下的显示逻辑(锁屏,游戏等)还有触控事件的处理逻辑。控制显示确实是他的很大部分功能,但是并不是全部。
window的solfInputMode属性:这一部分就是当软件盘弹起来的时候,window的处理逻辑,这在日常中也经常遇到,如:我们在微信聊天的时候,点击输入框,当软键盘弹起来的时候输入框也会被顶上去。如果你不想被顶上去,也可以设置为被软键盘覆盖。
事件分发机制
主要分事件分发和事件处理,
事件分发:Activity(不处理)-> 根View -> 一层一层ViewGroup(如果有的话) -> 子View
事件处理 :Activity(不处理则丢弃) <- 根View <- 一层一层ViewGroup(如果有的话) <- 子View
具体在传递事件的时候,是由以下三个方法来控制的:
dispatchTouchEvent : 分发事件
onInterceptTouchEvent : 拦截事件
onTouchEvent : 消费事件
viewgroup才有onInterceptTouchEvent拦截事件,有view消费了action_down事件,后续的action_move和action_up事件都会传给他。
Android虚拟机
Dalvik虚拟机、Art虚拟机
4.4版本前,使用的是Dalvik虚拟机
5.0版本以后,使用的是Art虚拟机
Dalvik虚拟机
1.Dalvik是基于寄存器的虚拟机,读取和保存数据会比基于栈的JVM在运行时快很多
2.基于寄存器的虚拟机允许更快的执行时间,但代价是编译后的程序更大
4.新的Dex字节码格式:合并多个class字节码文件、减少常量池大小、减少文件的IO操作,提高类的查找速度、减少文件大小
4.dex的优化格式odex:在App安装的过程和DexClassloader加载过程中会对Dex文件进行优化成odex
Dalvik虚拟机中使用JIT编译
在运行时对dex的指令进行intercept,解释成机器码
虚拟机根据函数调用的次数,来决定热点代码
以函数为维度将热点代码的机器码进行缓存,在下一次调用时直接调用该机器码
JIT编译的优点与缺点
优点:安装速度超快、存储空间小
缺点:Multidex加载的时候会非常慢,因为在dex加载时会进行dexopt。JIT中需要解释器,解释器解释的字节码会带来CPU和时间的消耗。由于热点代码的Monitor一直在运行,也会带来电量的损耗
5.0-7.0的Art虚拟机
在5.0-7.0之间,Android使用ART虚拟机,使用AOT编译,运行的文件格式也从odex转换成了oat格式。
在APK安装的时候,PackageManagerService会调用dex2oat通过静态编译的方式,来将所有的dex文件(包括Multidex)编译oat文件。
在运行的时候,就直接运行oat的代码。而其中的Dex文件的内容也就是为了DexClassLoader在动态加载其他的Dex文件时,在链接的过程中可以找到对应的meta-data,正确的链接到引用的类文件与函数。
AOT编译得优点与缺点
优点:运行时会超级快、在运行时省电,也节省各种资源
缺点:在系统更新的时候,所有app都需要进行dex2oat的操作,耗费的时间太长。在app安装的过程中,所耗费的时间也越来越长,因为apk包越来越大。由于oat文件中包含dex文件与编译后的Native Code,导致占用空间也越来越大。
7.0至今的Art虚拟机
由于之前版本ART的缺点,7.0之后的采用了Hybrid Mode的ART虚拟机:解释器、JIT、OAT,将这三种方案进行混合编译,来从运行时的性能、存储、安装、加载时间进行平衡。
在第一次启动的时候,系统会以JIT的方式来运行App,在系统空闲的时候会在后台对App进行AOT静态编译,并且会根据JIT运行时所收集的运行时函数调用的信息生成的Profile文件来进行参考 。
glide的缓存机制
从glide缓存的lruCache出发让我讲一下自己怎么实现,一开始只说到了链表,后来面试官提醒效率,于是回答到了linkedHashmap
glide缓存的弱引用说到安卓四大引用还有在项目中的使用
Bitmap图片处理,下采样、编码、超大图片保证原图加载且如何防止OOM?
安卓 ANR原因?如何判断?
数据持久化&对比:SP、SQLite、File
App上线后用户使用时卡顿怎么查看是什么原因
看视频的时候网络请求很慢怎么优化
SP的内部实现
SP多进程不安全要怎么解决?(这个当时答的ContentProvider,但是面试官不满意,后面引导我mmap,然鹅我只知道个大概,没跟上思路,后面查了下发现腾讯的 MMKV 框架茅塞顿开
Android相关优化(如内存优化、网络优化、布局优化、电量优化、业务优化)
热修复的原理