必看!2025 年 Android 面试高频题全解析,助你轻松拿 offer

引言:部分开发者认为面试题难度过高,面试问题总是答不出来或者答的没条理,其实根源在于基础薄弱或缺乏深度研究。本文专门收集了网络上大部分面试题并进行整理,具备以下特点:

  • 覆盖面广:涵盖Android核心技术及扩展知识点。

  • 答案可靠:每道题目均经作者亲自验证并附解答。

  • 互动性强:支持通过评论区或私信交流疑问,倡导共同探讨进步。

结尾处附了作者多年来收集网络上关于Android学习资料库及资深人员求职面试技巧,欢迎大家联系作者领取

1、如何优化自定义View

为了加速你的view,对于频繁调用的方法,需要尽量减少不必要的代码。先从onDraw开始,需要特别注意不应该在这里做内存分配的事情,因为它会导致GC,从而导致卡顿。在初始化或者动画间隙期间做分配内存的动作。不要在动画正在执行的时候做内存分配的事情。

你还需要尽可能的减少onDraw被调用的次数,大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate()的次数。如果可能的话,尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view

另外一个非常耗时的操作是请求layout。任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助。

如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小。

2View的绘制流程

  • DecorView被加载到Window

  • Activity的startActivity开始,最终调用到ActivityThreadhandleLaunchActivity方法来创建 Activity,首先,会调用performLaunchActivity方法,内部会执行ActivityonCreate方法,从而完成DecorViewActivity的创建。然后,会调用handleResumeActivity,里面首先会调用 performResumeActivity去执行ActivityonResume()方法,执行完后会得到一个 ActivityClientRecord对象,然后通过r.window.getDecorView()的方式得到DecorView,然后会通a.getWindowManager()得到WindowManager,最终调用其addView()方法将DecorView加进去。

  • WindowManager的的实现类是WindowManagerImpl,它内部会将addView的逻辑委托给 WindowManagerGlobal,可见这里使用了接口隔离和委托模式将实现和抽象充分解耦。在 WindowManagerGlobaladdView()方法中不仅会将DecorView添加到Window中,同时会创建 ViewRootImpl对象,并将ViewRootImpl对象和DecorView通过root.setView()DecorView加载到Window中。这里的ViewRootImplViewRoot的实现类,是连接WindowManagerDecorView的纽带。View的三大流程均是通过ViewRoot来完成的。

了解绘制的整体流程

绘制会从根视图ViewRootperformTraversals()方法开始,从上到下遍历整个视图树,每个View控件负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作。

理解MeasureSpec

MeasureSpec表示的是一个32位的整形值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSizeMeasureSpecView类的一个静态内部类,用来说明应该如何测量这个 View。它由三种测量模式,如下:

  • EACTLY:精确测量模式,视图宽高指定为match_parent或具体数值时生效,表示父视图已经决定了子视图的精确大小,这种模式下View的测量值就是SpecSize的值。

  • AT_MOST:最大值测量模式,当视图的宽高指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸

  • UNSPECIFIED:不指定测量模式, 父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到。

MeasureSpec通过将SpecModeSpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包的方法,打包方法为makeMeasureSpec,解包方法为getModegetSize

普通ViewMeasureSpec的创建规则如下:

对于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的

View,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。

  View绘制流程之Measure

  • 首先,ViewGroup中的measureChildren()方法中会遍历测量ViewGroup中所有的View,当View的可见性处于GONE状态时,不对其进行测量。

  • 然后,测量某个指定View时,根据父容器的MeasureSpec和子ViewLayoutParams等信息计算子ViewMeasureSpec

  • 最后,将计算出的MeasureSpec传入Viewmeasure方法,这里ViewGroup没有定义测量的具体过程,因为ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去实现。不同的ViewGroup子类有不同的布局特性,这导致它们的测量细节各不相同,如果需要自定义测量过程,则子类可以重写这个方法。(setMeasureDimension方法用于设置View的测量宽高,如果 View没有重写onMeasure方法,则会默认调用getDefaultSize来获得View的宽高)

getSuggestMinimumWidth分析

如果View没有设置背景,那么返回android:minWidth这个属性所指定的值,这个值可以为0;如果

View设置了背景,则返回android:minWidth和背景的最小宽度这两者中的最大值。

自定义View时手动处理wrap_content时的情形

直接继承View的控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用  wrap_content就相当于使用match_parent。此时,可以在wrap_content的情况下(对应 MeasureSpec.AT_MOST)指定内部宽/(mWidthmHeight)

LinearLayoutonMeasure方法实现解析(这里仅分析measureVertical核心源码

系统会遍历子元素并对每个子元素执行measureChildBeforeLayout方法,这个方法内部会调用子元素measure方法,这样各个子元素就开始依次进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方向的初步高度。每测量一个子元素,mTotalLength就会增加,增加的部分主要包括了子元素的高度以及子元素在竖直方向上的margin等。

Activity中获取某个View的宽高

由于Viewmeasure过程和Activity的生命周期方法不是同步执行的,如果View还没有测量完毕,那么获得的宽/高就是0。所以在onCreateonStartonResume中均无法正确得到某个View的宽高信息。解决方式如下:

  • Activity/View#onWindowFocusChanged:此时View已经初始化完毕,当Activity的窗口得到焦点和失去焦点时均会被调用一次,如果频繁地进行onResume和onPause,那么 onWindowFocusChanged也会被频繁地调用。

  • view.post(runnable):   通过post可以将一个runnable投递到消息队列的尾部,始化好了然后等待 Looper调用次runnable的时候,View也已经初始化好了。

  • ViewTreeObserver#addOnGlobalLayoutListener:当View树的状态发生改变或者View树内部的 View的可见性发生改变时,onGlobalLayout方法将被回调。

  • View.measure(int widthMeasureSpec, int heightMeasureSpec):match_parent时不知道 parentSize的大小,测不出;具体数值时,直接makeMeasureSpec固定值,然后调用 view..measure就可以了;wrap_content时,在最大化模式下,用View理论上能支持的最大值去构造MeasureSpec是合理的。

View的绘制流程之Layout

首先,会通过setFrame方法来设定View的四个顶点的位置,即View在父容器中的位置。然后,会执行到onLayout空方法,子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有View控件布局流程。

LinearLayoutonLayout方法实现解析(layoutVertical核心源码

其中会遍历调用每个子ViewsetChildFrame方法为子元素确定对应的位置。其中的childTop会逐渐增大,意味着后面的子元素会被放置在靠下的位置。

注意:在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于Viewmeasure过程,而最终宽/高形成于Viewlayout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。在一些特殊的情况下则两者不相等:

  • 重写View的layout方法,使最终宽度总是比测量宽/高大100px。

  • View需要多次measure才能确定自己的测量宽/高,在前几次测量的过程中,其得出的测量宽/高有可能和最终宽/高不一致,但最终来说,测量宽/高还是和最终宽/高相同。

View的绘制流程之Draw

Draw的基本流程

绘制基本上可以分为六个步骤:

  • 首先绘制View的背景;

  • 如果需要的话,保持canvas的图层,为fading做准备;

  • 然后,绘制View的内容;

  • 接着,绘制View的子View;

  • 如果需要的话,绘制View的fading边缘并恢复图层;

  • 最后,绘制View的装饰(例如滚动条等等)。

setWillNotDraw的作用

如果一个View不需要绘制任何内容,那么设置这个标记位为true以后,系统会进行相应的优化。

  • 默认情况下,View没有启用这个优化标记位,但是ViewGroup会默认启用这个优化标记位。

  • 当我们的自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。

  • 当明确知道一个ViewGroup需要通过onDraw来绘制内容时,我们需要显示地关闭 WILL_NOT_DRAW这个标记位。

Requestlayout,onlayout,onDraw,DrawChild区别与联系?

  • requestLayout()方法 :会导致调用 measure()过程 和 layout()过程,将会根据标志位判断是否需要 ondraw。

  • onLayout()方法:如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局。

  • onDraw()方法:绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)。

  • drawChild():去重新回调每个子视图的draw()方法。

invalidate() 和 postInvalidate()的区别 ?

invalidate()与postInvalidate()都用于刷新View,主要区别是invalidate()在主线程中调用,若在子线程中使用需要配合handler;而postInvalidate()可在子线程中直接调用。

3、Binder机制的作用和原理

Linux系统将一个进程分为用户空间和内核空间。对于进程之间来说,用户空间的数据不可共享,内核空间的数据可共享,为了保证安全性和独立性,一个进程不能直接操作或者访问另一个进程,即 Android的进程是相互独立、隔离的,这就需要跨进程之间的数据通信方式。普通的跨进程通信方式一般需要2次内存拷贝,如下图所示:

一次完整的 Binder IPC 通信过程通常是这样:

  • 首先 Binder 驱动在内核空间创建一个数据接收缓存区。

  • 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系。

  • 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

Binder框架中ServiceManager的作用?

Binder框架 是基于 C/S 架构的。由一系列的组件组成,包括 ClientServerServiceManagerBinder驱动,其中 ClientServerService Manager 运行在用户空间,Binder 驱动运行在内核空间。如下图所示:

  • Server&Client:服务器&客户端。在Binder驱动和Service Manager提供的基础设施上,进行 Client-Server之间的通信。

  • ServiceManager(如同DNS域名服务器)服务的管理者,将Binder名字转换为Client中对该  Binder的引用,使得Client可以通过Binder名字获得ServerBinder实体的引用。

  • Binder驱动(如同路由器):负责进程之间binder通信的建立,计数管理以及数据的传递交互等底层支持。

4、AMS家族重要术语解释。

  1. ActivityManagerServices,简称AMS,服务端对象,负责系统中所有Activity的生命周期。
  2. ActivityThread,App的真正入口。当开启App之后,调用main()开始运行,开启消息循环队列,这就是传说的UI线程或者叫主线程。与ActivityManagerService一起完成Activity的管理工作。
  3. ApplicationThread,用来实现ActivityManagerServie与ActivityThread之间的交互。在 ActivityManagerSevice需要管理相关Application中的Activity的生命周期时,通过ApplicationThread的代理对象与ActivityThread通信。
  4. ApplicationThreadProxy,是ApplicationThread在服务器端的代理,负责和客户端的 ApplicationThread通信。AMS就是通过该代理与ActivityThread进行通信的。
  5. Instrumentation,每一个应用程序只有一个Instrumetation对象,每个Activity内都有一个对该对象的引用,Instrumentation可以理解为应用进程的管家,ActivityThread要创建或暂停某个Activity时,都需要通过Instrumentation来进行具体的操作。
  6. ActivityStack,Activity在AMS的栈管理,用来记录经启动的Activity的先后关系,状态信息等。通过  ActivtyStack决定是否需要启动新的进程。
  7. ActivityRecord,ActivityStack的管理对象,每个Acivity在AMS对应一个ActivityRecord,来记录  Activity状态以及其他的管理信息。其实就是服务器端的Activit对象的映像。
  8. TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个  ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种  launchMode,那么对这概念应该不陌生。

5、App启动流程(Activity的冷启动流程)。

点击应用图标后会去启动应用的Launcher Activity,如果Launcer Activity所在的进程没有创建,还会创建新进程,整体的流程就是一个Activity的启动流程。
Activity的启动流程图(放大可查看)如下所示:


整个流程涉及的主要角色有:

  • Instrumentation: 监控应用与系统相关的交互行为。
  • AMS:组件管理调度中心,什么都不干,但是什么都管。
  • ActivityStarter:Activity启动的控制器,处理Intent与Flag对Activity启动的影响,具体说来有:1寻找符合启动条件的Activity,如果有多个,让用户选择;2 校验启动参数的合法性;3 返回int参数,代表Activity是否启动成功。
  • ActivityStackSupervisior:这个类的作用你从它的名字就可以看出来,它用来管理任务栈。
  • ActivityStack:用来管理任务栈里的Activity。
  • ActivityThread:最终干活的人,Activity、Service、BroadcastReceiver的启动、切换、调度等各种操作都在这个类里完成。

注:这里单独提一下ActivityStackSupervisior,这是高版本才有的类,它用来管理多个ActivityStack,早期的版本只有一个ActivityStack对应着手机屏幕,后来高版本支持多屏以后,就有了多个 ActivityStack,于是就引入了ActivityStackSupervisior用来管理多个ActivityStack。
整个流程主要涉及四个进程:

  • 调用者进程,如果是在桌面启动应用就是Launcher应用进程。
  • ActivityManagerService等待所在的System  Server进程,该进程主要运行着系统服务组件。
  • Zygote进程,该进程主要用来fork新进程。
  • 新启动的应用进程,该进程就是用来承载应用运行的进程了,它也是应用的主线程(新创建的进程就是主线程),处理组件生命周期、界面绘制等相关事情。

有了以上的理解,整个流程可以概括如下:

  1. 点击桌面应用图标,Launcher进程将启动Activity(MainActivity)的请求以Binder的方式发送给了AMS。
  2. AMS接收到启动请求后,交付ActivityStarter处理Intent和Flag等信息,然后再交给 ActivityStackSupervisior/ActivityStack 处理Activity进栈相关流程。同时以Socket方式请求Zygote进程fork新进程。
  3. Zygote接收到新进程创建请求后fork出新进程。
  4. 在新进程里创建ActivityThread对象,新创建的进程就是应用的主线程,在主线程里开启  Looper消息循环,开始处理创建Activity。
  5. ActivityThread利用ClassLoader去加载Activity、创建Activity实例,并回调Activity的 onCreate()方法,这样便完成了Activity的启动。

最后,再看看另一幅启动流程图来加深理解:


6、Android的打包流程?(即描述清点击 Android Studio 的
build 按钮后发生了什么?)apk里有哪些东西?签名算法的原理?
apk打包流程


Android的包文件APK分为两个部分:代码和资源,所以打包方面也分为资源打包和代码打包两个方面,下面就来分析资源和代码的编译打包原理。
APK整体的的打包流程如下图所示:

具体说来:

  • 通过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java文件。
  • 通过AIDL工具处理AIDL文件,生成相应的Java文件。
  • 通过Java Compiler编译R.java、Java接口文件、Java源文件,生成.class文件。
  • 通过dex命令,将.class文件和第三方库中的.class文件处理生成classes.dex,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
  • 通过ApkBuilder工具将资源文件、DEX文件打包生成APK文件。
  • 通过Jarsigner工具,利用KeyStore对生成的APK文件进行签名。
  • 如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件距离文件的起始距位置都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度会更快,并且会减少其在设备上运行时的内存占用。

apk组成

  • dex:最终生成的Dalvik字节码。
  • res:存放资源文件的目录。
  • asserts:额外建立的资源文件夹。
  • lib:如果存在的话,存放的是ndk编出来的so库。
  • META-INF:存放签名信息

MANIFEST.MF(清单文件):其中每一个资源文件都有一个SHA-256-Digest签名,MANIFEST.MF文件的SHA256(SHA1)并base64编码的结果即为CERT.SF中的SHA256-Digest-Manifest值。
CERT.SF(待签名文件):除了开头处定义的SHA256(SHA1)-Digest-Manifest值,后面几项的值是对MANIFEST.MF文件中的每项再次SHA256并base64编码后的值。
CERT.RSA(签名结果文件):其中包含了公钥、加密算法等信息。首先对前一步生成的MANIFEST.MF使用了SHA256(SHA1)-RSA算法,用开发者私钥签名,然后在安装时使用公钥解密。最后,将其与未加密的摘要信息(MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被修改。

  • androidManifest:程序的全局清单配置文件。
  • resources.arsc:编译后的二进制资源文件。

7、Android系统启动流程是什么?(提示:init进程 -> Zygote进程 –> SystemServer进程 –> 各种系统服务 –> 应用进程)


Android系统启动的核心流程如下:

  • 启动电源以及系统启动:当电源按下时引导芯片从预定义的地方(固化在ROM)开始执行,加载引导程序BootLoader到RAM,然后执行。
  • 引导程序BootLoader:BootLoader是在Android系统开始运行前的一个小程序,主要用于把系统OS拉起来并运行。
  • Linux内核启动:当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当其完成系统设置时,会先在系统文件中寻找init.rc文件,并启动init进程。
  • init进程启动:初始化和启动属性服务,并且启动Zygote进程。
  • Zygote进程启动:创建JVM并为其注册JNI方法,创建服务器端Socket,启动SystemServer进程。
  • SystemServer进程启动:启动Binder线程池和SystemServiceManager,并且启动各种系统服务。
  • Launcher启动:被SystemServer进程启动的AMS会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到系统桌面上。

 结束语:

在竞争白热化的就业浪潮中,每一次跳槽机会都如同千军万马过独木桥!如果你正怀揣着职业转型的梦想,想要在激烈的求职战场脱颖而出,那精心筹备面试,就是你打开理想 offer 大门的金钥匙!我熬夜数月,精心打磨了这份超全面试资料库,不仅有 Android 从入门到大神的全阶段学习秘籍,还有无数资深面试官私藏的通关技巧视频,助你逆风翻盘,拿下心动岗位!

面试资料:

Android教学资料  (600G+)资料

【!!以上全部资料扫描下方二维码领取!!】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值