# Android FrameWork面试点集合 站点:答源(https://www.dandroid.cn/) 原文:https://www.dandroid.cn/archives/21146 Canonical:https://www.dandroid.cn/archives/21146 作者:admin 发布日期:2023-09-25 最后更新:2023-09-25 15:49 分类:Android系统开发 / Framework源码 内容类型:post 说明:本页是原文的纯文本镜像,供大模型与检索器完整读取;不含导航、广告、付费框。引用请以上方「原文」链接为准。 ## 正文 1. 怎么跨进程传递大图片 考察点 了解各种跨进程传输数据的方式及各自优缺点 了解TransactionTooLargeException的触发原因和底层机制 了解Bitmap传输底层原理 跨进程传大图有哪些方案 给图片保存到固定的地方,传key值(路径)给对方 问题:性能差,把图片写到文件需要耗时,对方读取文件也需要耗时 通过IPC的方式转发图片数据 不经过文件系统,但是需要多次拷贝 IPC方式传图 Binder:性能好,使用方便,但是大小有限制 Socket、管道:两次拷贝,也有大小限制 共享内存:性能不错 主要注意点 1.性能,减少拷贝次数 2.内存泄漏,资源及时关闭 TransactionTooLargeException 发出去的或者返回的数据量太大 Binder缓存用于该进程所有正在进行中的Binder事务 进程弃用binder机制会映射一块内存,大小是1M 跨进程通信申请的缓冲区大小是不能超过1M的 所有binder事务共享这1M内存空间,应该尽量避免同时跑多个事务,尤其是数据量很大的事务 大数据量打碎分批发,或按需发(官方建议) 总结 跨进程传递大图片的方式 图片写到文件,路径传到另一个进程,再读出来 intent传图,但是容易抛异常,原因是什么 binder调用传图,底层ashmem机制 图115 2. ThreadLocal原理 考察点 ThreadLocal的适用于什么场景? ThreadLocal的使用方式是怎样的? ThreadLocal的实现原理是怎样的? ThreadLocal在FrameWork中的使用 Looper 图116 Choreorgapher 图117 原理 每一个线程都有一张表,其实就是一个数组,key和value都存储在数组中 key存储在weakReference中,value对应对象如Looper、Choreorgapher等 一个应用里可以定义多个ThreadLocal,ThreadLocal都有自己的哈希值,哈希值根据hashCounter计算 算出hash值之后,对表的size取余数,就能算出key:value在表中的index值 如果发生了hash冲突,就会从当前index开始向下遍历数组,放在空闲的位置上 代码如下: 图119 总结 同一个ThreadLocal对象,在不同线程get返回不同value Thread对象里有张表,保存ThreadLocal到value的映射关系 这张表是怎么实现的?(见上面的原理分析) 是如何解决hash冲突的?(见上面的原理分析) 3. 说说Looper的副业(待完善) Looper里可以监听其它描述符 创建管道,跨进程传数据,用looper监听描述符事件 4. 怎么检查线程有耗时任务 两种情况 正常的,轻微阻塞 不正常的,严重阻塞 检测机制 WatchDog: Framework自带,检查system_server中系统服务是否正常 用于检查死锁或者线程异常 BlockCanary 开源框架,用于检查线程是否有耗时任务 WatchDog WatchDog的作用上面说过:一是检查是否发生了死锁,二是检查线程是否被任务blocked WatchDog是一个线程 看下WatchDog的关键变量: mHandlerCheckers是核心列表,添加了多个HandlerChecker 每一个Checker都对应了一个线程,比如主线程、前台线程、IO线程等,这几个线程都是system_server中非常重要的工作线程 每一个HandlerChecker下都有一个Monitor列表 addMonitor实质上是向mMonitorChecker加了一个BinderThreadMonitor,用于检测binder线程是否正常 图121 看下下图 第一个MonitorChecker用户检查系统服务是否发生了死锁,在单独的线程中检查, 原理就是在另外的线程中去尝试拿到锁,拿到了就正常返回 如果一直拿不到,就可能是产生了死锁问题 如图AMS中代码细节 图122 同时服务还会把自己的工作线程new一个HandlerChecker,也注册到watchDog,每一个handlerChecker都会对应一个线程,在线程中派发handlerChecker BlockCanary 原理就是通过messageLogging在消息分发前后的时间戳打印,利用时间戳的差算出消息执行耗时 5. 怎么同步处理消息 「同步处理消息」:我们发了一个消息到另外一个线程去处理,并没有直接返回,而是挂在那里等待消息处理完毕,有些时候还需要拿到消息处理结果,这就是同步处理消息 适用场景如图124 应用要访问另一个进程的服务,请求会调到服务进程的binder线程池中,binder线程切换到工作线程处理请求,工作线程工作的时候binder线程会一直等待,应用端也会一直等着,等到处理完成后返回结果给给应用 6. 你去了解framework是为了解决一个什么样的问题,怎么解决的? 考察点 你有没有解决复杂问题的经验 你有没有深入研究底层原理的习惯 你的知识体系是否有一定深度 7. 应用组件相关题目 为什么Activity在onResume之后才会显示出来 ActivityThread handleResumeActivity时WindowManager才会addView并makeVisible bindService的时候Rebind总是调不到,研究原理 TODO 广播onReceive的context可否启动Activity, 显示Dialog? TODO 发现provider的onCreate比Application还早,研究一下 Application 构造方法 –> Application.attachBaseContext –> ContentProvider.onCreate –> Application.onCreate –> Activity.onCreate 看下 ActivityThread.java 的 handleBindApplication方法: 图130 8. 消息通信相关题目 intent带的数据量大了会异常,研究原因 binder机制 1m限制 需要跨进程传大图,研究Bitmap传输原理,Ashmem机制 想知道Handler消息延时的精度怎么样,去了解原理 延迟处理不是延迟发送,精度不太准确 为什么有时候IdleHandler调不到,去了解原理 主线程繁忙,一直在处理消息 比如: 在View的onDraw方法里面无限制的直接或者间接调用View的invalidate方法。 做一个无限轮询的View动画。 9.性能优化相关题目 ANR了,看主线程是否有耗时任务 卡顿掉帧,了解屏幕刷新机制,研究Choregrapher 启动速度优化,了解应用启动原理 内存优化,清理不必要的资源 10. Android FrameWork用到了哪些设计模式?请举例说明 单例模式 Framework中:SingleTon类,应用IAM 图126 线程内: 线程间/进程内:Choreographer,ThreadLocal线程私有,不同线程获取不同的实例,同一线程获取同一实例 图125 进程间: 进程间的内存是相互隔离的,如何保证在进程间的单例呢,这个时候就需要一个中间人,所谓的中间人,就是他可以和所有进程进行通信,由其确保这个对象是单例的 下面的例子中,单例是ServiceManager,中间人是binder驱动 对所有的进程来说,ServiceManager对应的binder句柄都是一个即0,这样binder驱动在转发请求的时候都会送往同一个目标,即ServiceManager所在的进程,这样就能保证ServiceManager是跨进程单例的 图127 观察者模式 分为进程内和跨进程两种 应用: 广播(同时支持进程内和进程间) 推送(基于网络) 应用层 Rxjava RecyclerView:Adapter的notifyChanged、notifyItemChanged等 代理模式 静态代理 ActivityManagerProxy IActivityManager的接口调用都会转交给mRemote 128 动态代理 11.Framework中有什么你觉得设计很巧妙的地方,请举例说明 Binder调用,模糊进程边界,让IPC调用像在一个进程中一样简单,使开发者不必过分关注跨进程通信的事,将重点放在业务上 请求的转发:请求由应用端发起,通过binder驱动转发给目标进程去处理,目标进程处理完毕之后,会把返回结果通过binder驱动传回Client端 binder对象的传递:包含实体对象和引用对象,这两种实体在经过binder驱动层的时候会自动转换,比如binder实体对象跨进程之后就变成了代理对象,代理对象回到实体对象所在的进程之后又会还原为实体对象;不论实体对象还是代理对象,应用层拿到的都是统一的接口对象,方便调用 Bitmap大图传输,高性能 传递匿名共享内存的句柄,到了目标进程之后映射内存,这样目标进程就能获取bitmap的像素数据 Zygote预加载资源创建应用进程,共享资源,这样应用进程就不必重复加载资源 init进程启动后,会启动一些系统服务和Zygote进程zygote预加载资源(系统主题资源,常用的类等 )并启动system_server,并开启循环等待socket请求,孵化应用进程 图129 Intent解耦,模糊进程边界,把调用者和组件之间进行解耦 应用只需要表达自己的意图,由AMS选择处理意图的组件 调用者和组件之间可能是同一进程的,也可能不是同一进程的 比如广播,发广播的时候带intent,由AMS解析intent,匹配接收的receiver,receiver可能是同一进程的,也可能是不同进程的,只要用intent表达意图就可以了 1. Android设备开机之后发生了什么 1.Bootloader引导开机 长按开机键后,主板通电,引导芯片开始执行预设代码,加载引导程序到内存中,主要做内存检查和硬件初始化工作 2.Linux kernel 加载硬件驱动 如硬件驱动/显示驱动/输入驱动/媒体驱动 硬件抽象层解决并适配不同驱动标准,为上层应用提供统一的api 3.C++ framework 创建linux中的第一个用户进程,init(pid=1) 孵化adbd进程(断点调试)和logd进程(日志打印) 孵化第一个java进程->zygote进程(连接java世界和native c++世界) 4.Android framework zygote进程 ZygoteInit.java会启动systemserver系统服务进程 所有app进程都由zygote进程孵化而来 app进程创建完成之后,都会由ZygoteInit反射调用ActivityThread.java(main函数),使得app得以启动 ZygoteInit.java是由C++代码通过类加载机制加载的,最终调用到了ZygoteInit.java的实例方法 ZygoteInit.java中main方法会启动SystemServer系统服务进程 SystemServer进程创建之后会执行入口类SystemServer.java,创建ActivityManagerService/WindowManagerService/PowerManagerService/InputManagerService 5.Apps 所有的系统服务都创建成功之后,会执行Launcher启动流程 2. ZygoteInit.java做了那些事情 预加载资源 启动SystemServer进程,三类,当所有系统服务都启动完成之后,会通知AMS调用systemReady启动Launcher应用 引导服务 17个 AMS 四大组建调度服务(安卓10之后Activity调度交给ATMS) Activity Task Manager Service 安卓10之后Activity调度服务 PKMS 安装,卸载,扫描管理等 PowerManagerService 电源管理服务 Dex字节码优化服务等 启动核心服务 9个 BatteryService 电池状态管理 WebviewUpdateService Webview升级服务 GpuService Gpu驱动加载服务 启动其他服务 90+ AlarmManagerService 时钟服务 NetworkStatsService 网络状态服务 WindowManagerService 窗口管理服务 AudioService 音频服务 创建Socket服务 进入阻塞状态,等待连接,用以来处理来自AMS申请进程创建的请求 3. Launcher相关 如何定制一款Launcher 启动Activity添加category.HOME和category.DEFAULT 系统如何识别已安装应用那个是Launher应用 Intent.category_home属性 4. ActivityThread main方法 ①初始化主线程looper ③主线程开始轮训 ②attach()向AMS注册自己,以便AMS调度自己 attachApplication:应用进程向AMS注册本地应用进程,方便AMS调度四大组件及生命周期,从此应用与Framework建立关联,有了生命力 bindApplication:ActivityThread反射创建Application对象,调用onCreate函数 通过AMS(ATMS)再次启动Activity,进程创建时未启动的那个Activity会恢复继续创建 在ActivityThread中以反射形式创建Activity实例对象,接着会调度Activity的核心方法` activity.attach() activity.onCreate() 5. View树相关 如何在Activity的onCreate和onResume获取view宽高? 由于setContentView之后view的测绘工作还没有开始,正确获取view宽高的方式是使用view.post或者view.viewTreeObserver.addOnGlobalLayoutListener监听器获取 viewTree绘制流程是从哪里开始的? 在Activity的onCreate、onStart、onResume都没有view绘制相关的代码,说明view绘制在onResume之后 查看ActivityThread源码可以发现,handleResumeActivity()函数调用performResumeActivity之后会调用WindowManager.addView(...)函数,windowManager的具体实现在WindowManagerGlobal中,这里创建了ViewRootImpl,ViewRootImpl关联了decorView,然后便开始了测量、布局、绘制的流程 ViewRootImpl 特点 WindowSession将Window添加到WindowManagerService 页面View树的顶层节点,关联Window和View Choreographer接收Vsync同步信号触发View的三大流程 WindowInputEventReceiver接收屏幕输入事件,分发手势 接收Vsync同步信号,触发View的动画重绘 viewRootImpl中对UI测量布局绘制的消息是一条异步消息 页面上所有view的测量Measure工作都由ViewRootImpl的performMeasure()来实现,同理layout->performLayout()和draw->performDraw() 小结:View三大流程 ①ActivityThread的handleResumeActivity()函数调用了WindowManagerGlobal的addView函数 ②在WindowManagerGlobal的addView函数中,实例化了ViewRootImpl,并且将传入的decorView绑定 ③在ViewRootImpl的setView函数中,调用了requestLayout,requesetLayout会先对线程进行检查(checkThread),检查通过之后,就会通过Cherographer注册一个垂直同步信号监听,屏幕上每一帧刷新都会执行PerformTraversals函数,PerformTraversals函数中会调用performMeasure、performLayout、performDraw,进而调用到了viewGroup中,viewGroup会遍历所有子view,调用子view的measure、layout、draw函数 6. Activity之手势分发 手势的接收在ViewRootImpl内部类WindowInputEventReceiver中接收的 doProcessInputEvents分发了手势(输入)事件,从队列头部开始派发 事件的分发采用了责任链设计模式,依次为ViewpostImeInputStage->NativePostImeInputStage->EarlyPostImeInputStage->ImeInputStage->ViewPreImeInputStage->NativePreImeInputStage 点击事件在ViewpostImeInputStage的onProcess中处理 小结:如上图 在ViewRootImpl中,使用WindowInputEventReceiver接收手势事件,向系统注册了一个输入事件的监听,一旦有输入事件就会派发给Receiver,在一个输入事件被派发给应用之前,会经过几个阶段的预处理 对于一个屏幕点击事件而言,最终会调用ViewRootImpl的processPointerEvents方法,进而调用decorView的dispatchTouchEvent方法,这个方法又通过window.callback接口转发到Activity中 为什么不转发到viewGroup中呢,这样做是为了使开发者能够在Activity中监听或者自定义手势的拦截,多了一次机会 在Activity的dispatchTouchEvent中,又把手势分发给了phoneWindow,在phonewindow中兜兜转转又把手势转给了DecorView,最后就到达了view或者view 文章来源于互联网:Android FrameWork面试点集合 --- 由 答源 DAN 底座 1.4.1 生成 · 2026-11-11T03:13:06+00:00