货拉拉Android笔试题深度解析:从组件到Binder原理

货拉拉Android笔试题深度解析:从组件到Binder原理 1. 一份笔试题背后的技术栈货拉拉到底在考什么2018年货拉拉的秋招Android笔试题放在今天回头看依然有很强的参考价值。不是说题目本身有多前沿而是它代表了互联网二线头部公司对Android工程师的基础能力预期——不玩偏门不炫技就是把Android日常开发中最核心、最容易出问题的那一层拎出来考。当时货拉拉正处于业务快速扩张期物流调度、司机端、用户端多线并行对Android工程师的需求很明确你能独立负责模块你对性能敏感你对Android底层机制有真正理解而不是只会调接口。笔试题目自然就围绕这些能力展开。Android这一端说实话面试题库翻来覆去就是那些东西四大组件、Handler、Binder、View绘制、进程与线程、内存管理、网络与数据存储。但同样的知识点不同公司考出来的深度和角度完全不同。有的公司喜欢考源码细节有的公司喜欢考场景设计货拉拉的题目更偏向后者——给你一个具体场景看你怎么用Android的机制去解决问题。这道卷子对准备面试的人有很高的参考价值原因有三点第一它代表了物流O2O类业务场景下的Android技术关切点。这类业务对定位、地图、消息推送、进程保活、弱网处理有更高的要求这些都会反映在题目偏好上。第二它涵盖了Android面试中最常被追问的高频考点。你不必怀疑这些知识点到底重不重要出题人已经用题目帮你划了重点。第三它考察的是会不会和为什么不是知不知道。很多题看起来简单做对容易做好难。这也是大部分笔试的筛选逻辑——用看似基础的题目拉开真正理解和背答案之间的距离。接下来我会按笔试中常见的知识板块逐一拆解把每个考点背后的原理、容易踩的坑、以及实战中真正会遇到的场景都讲明白。这篇文章不是简单地罗列答案而是告诉你遇到这类题你的思考路径应该是什么。2. 四大组件与启动模式笔试里最稳拿分却也最容易被追问的部分Android的四大组件——Activity、Service、BroadcastReceiver、ContentProvider是任何一份Android笔试题都绕不开的内容。货拉拉的卷二也不例外。为什么必考因为四大组件是Android应用与系统交互的基石你对它们的理解深度直接反映你对Android系统的认知水平。2.1 Activity启动模式不能只背四种模式的定义关于Activity的启动模式标准答案是standard、singleTop、singleTask、singleInstance四种。但笔试中真正有区分度的问法是如果一个Activity所在的Task栈发生改变onNewIntent的调用时机是什么栈里的Activity实例会怎么变化——这就需要你真正理解启动模式背后的Task和栈机制。我记得当时做这道题时的思考过程是这样的standard模式每次启动都会创建新实例这没什么好说的。singleTop需要注意的是只有当目标Activity已经在栈顶时才复用否则还是创建新实例。singleTask保证全局只有一个实例并且在它复用时它上面的所有Activity都会被清掉然后回调onNewIntent。singleInstance则更特殊它单独占一个Task适合那种需要被全局共享且不希望被其他Activity包围的场景比如来电界面。如果仅仅答到这里在笔试里拿不到满分。要拿满分你必须知道一个容易被忽略的点不同启动模式下的Task归属问题。比如设置了singleTask的Activity它启动后会先找自己想要的Task是否存在这个Task是根据taskAffinity属性来确定的。默认情况下taskAffinity等于包名但如果你在AndroidManifest里手动改了这个属性行为就会完全不一样。另外还有flag的问题——Intent.FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TOP、FLAG_ACTIVITY_SINGLE_TOP这些flag和XML里配置的launchMode是叠加生效的XML里配置是静态的默认值flag是动态的覆盖值。遇到冲突时flag优先于XML配置。这个细节才是真正拉开差距的地方。2.2 Service的两种启动方式以及bindService的隐藏细节Service这道题大多数人都能答出startService和bindService的区别一个与启动者无关一个与启动者绑定。但货拉拉的题目会往下深挖一层——startService和bindService混用时Service的生命周期怎么走实际项目中音乐播放器类的App最常遇到这种情况先startService让音乐在后台播放再bindService来获取Binder对象从而控制播放。此时Service的生命周期是onCreate - onStartCommand - onBind。注意onStartCommand只会在第一次startService时调用之后再次startService不会重复调用。而onBind在整个Service存活期间只会被调用一次之后再次bindService不会再回调onBind而是直接复用已有的Binder对象。还有一点很关键unbindService时如果之前调用过startServiceService不会销毁只会回调onUnbind。只有当startService启动且没有对应的stopService同时所有bind都已解绑时Service才会在onUnbind之后走onDestroy。笔试里经常用一个多选来描述这种状态变化选错的同学往往是忽略了startService开启后必须显式stopService才能销毁这个最基础却也最容易被绕晕的规则。另外补充一个常见的坑onUnbind返回true表示下次bindService时会调用onRebind。很多资料把这个讲得很玄乎其实本质是系统帮你做了Binder对象的复用与回调通知。正常业务开发中很少用到但笔试如果出现了你要能说清楚这个返回值的目的以及在什么场景下onRebind会被调。2.3 BroadcastReceiver与ContentProvider容易被忽视但笔试必考BroadcastReceiver在2018年那会儿是进程间通信和全局事件通知的重要手段虽然现在很多场景已经被EventBus、LiveData替代但系统广播和静态注册的知识依然高频出现在笔试中。笔试常考的点有两个。一是静态注册与动态注册的区别——静态注册在Manifest中声明开机自启、APK安装等系统事件必须用静态注册才能收到动态注册在代码中registerReceiver必须在onDestroy中unregisterReceiver否则会泄漏。二是Android 8.0之后静态注册隐式广播的限制——很多自定义的隐式广播不再允许静态注册接收这一变化在2018年的面试中已经出现当时很多同学都栽在这上面。ContentProvider这块考的通常是它的作用与实现方式以及它与Binder的联系。这里需要注意的是ContentProvider是Android提供的一种跨进程数据共享机制底层同样是Binder实现的。它的好处是系统帮忙管理了Binder连接和线程模型你不需要手动处理Binder调用只需要实现query、insert、update、delete四个方法系统会自动通过Binder跨进程调度到ContentProvider所在的进程。如果你在笔试题里看到ContentProvider的onCreate和Application的onCreate谁先执行这道题正确答案是ContentProvider的onCreate先执行。原因是ContentProvider的实例化发生在Application.attachBaseContext之后、Application.onCreate之前这涉及AMS在启动应用时的初始化顺序属于源码级的知识点。能答到这个深度就说明你的基本功不是背出来的。3. Handler与消息机制看似送分题实则暗藏杀机Handler机制是Android笔试的必考题几乎到了逢面必问的程度。这道题表面上是考Handler的用法——在子线程中发消息在主线程中处理消息从而实现线程切换。但真正拉开差距的问法集中在三个地方Looper在主线程中是怎么创建的、Handler为什么会造成内存泄漏、MessageQueue的阻塞与唤醒是怎么实现的。3.1 Looper、MessageQueue与Handler的三者关系先理清核心结构。Looper负责从MessageQueue中循环取消息Handler负责把消息放入MessageQueue以及在消息被取出后处理它。一个线程只有一个Looper也只有一个MessageQueue但可以有多个Handler。面试中常问的第一个问题主线程中的Looper是怎么创建的答案是在ActivityThread的main方法里调用了Looper.prepareMainLooper()。这个方法创建了主线程的Looper并将其设置为当前线程的Looper。之后Looper.loop()开始循环取消息整个主线程就进入了消息循环状态。主线程之所以能一直运行不退出就是因为这个无限循环——loop里的for循环如果没有收到退出消息就会一直阻塞在MessageQueue.next()方法上。第二个问题子线程中使用Handler时为什么必须先调用Looper.prepare()原因是Handler构造时会通过Looper.myLooper()获取当前线程的Looper如果当前线程没有调用preparemyLooper()返回nullHandler构造时就会抛出RuntimeException。这就是标准的Cant create handler inside thread that has not called Looper.prepare()异常。第三个问题MessageQueue的阻塞与唤醒机制。next()方法在没有消息时会调用nativePollOnce进入阻塞态此时线程不会消耗CPU当有消息入队时通过nativeWake唤醒。这套机制依赖Linux的epoll实现底层是Pipe或eventfd。笔试中如果能答出这一层就足以说明你真正看过源码而不是只会背结论。3.2 Handler内存泄漏不光是答用静态类弱引用这么简单Handler内存泄漏是高频问点但大多数人的回答流于表面因为Handler持有Activity的引用当消息延迟处理时Activity已经销毁但Handler仍被Looper中的Message持有导致Activity无法被回收。这个答案对但不够完整。真正要理解的是Message持有HandlerHandler持有ActivityLooper持有MessageQueueMessageQueue持有Message而Looper又是线程级别的单例生命周期与线程一样长。这个引用链决定了只要Looper还在消息队列里的消息就会持有Activity的引用。如果消息延迟5分钟才处理Activity就无法在这5分钟内被回收。解决思路也不只是静态类弱引用。正确做法是在onDestroy时移除未处理的消息——handler.removeCallbacksAndMessages(null)以及将Handler声明为静态类或独立类不持有外部类的隐式引用。但要注意即使改成静态类如果你在Handler内部调用了外部Activity的方法还是要通过弱引用来获取否则绕了一圈还是泄漏。3.3 主线程的Looper.loop()为什么不会导致ANR这是一个极容易被问倒的追问。很多人的理解是主线程被阻塞了所以ANR但实际上主线程的Looper.loop()就是一个无限循环ANR的原因不是循环本身而是循环里某个任务执行时间过长。理解的关键在于主线程不是死等某个事件而是循环处理排队的事件。loop()方法每取到一个Message就交给Handler的dispatchMessage处理处理完继续取下一条。如果某一条消息处理时间超过了设定阈值系统才判定为ANR。所以消息机制的阻塞是合理的、是设计如此——阻塞时线程进入休眠状态释放CPU有消息时通过epoll机制唤醒。真正要避免的是在消息处理中做过重的耗时任务。我把这个知识点称为送分题里最凶险的一问。能完整把阻塞、唤醒、ANR边界讲清楚的人在笔试和面试中都会被高看一眼因为这代表你对Android的并发模型有系统级的理解而不是停留在能跑就行的层面。4. Binder与进程通信Android面试的功力试金石如果说Handler是Android面试的基础关那Binder就是进阶关。货拉拉的卷二里Binder相关题目出现在多个板块——可能是直接问你Binder的原理也可能是通过AIDL、ContentProvider、Messenger这些具体实现来间接考察。无论哪种形式它考察的都是你对跨进程通信这件事本质的理解。4.1 为什么Android要使用Binder而不是其他IPC方案这道题在业内几乎成了标答性能高、安全好、传输数据方便。但你要能把它说透而不是抛结论。Binder基于内核的Binder驱动实现每次数据拷贝只需要一次而传统IPC如管道、Socket通常需要两次拷贝。性能上的优势是实打实的。安全性上Binder在内核层为每个进程分配了UID传递数据时不需要依赖应用层校验天然具备身份识别能力。对比Socket的匿名通信Binder的安全模型从内核层面就杜绝了恶意进程伪造身份的可能。还有一个容易被忽略的点Binder是Android系统全面使用的IPC机制——AMS、PMS、WMS这些系统服务都是通过Binder向应用提供能力的。选择Binder作为主要IPC方案意味着开发者只需要学一套技术栈就能跟系统服务交互这才是Android选择Binder作为默认方案的真正战略考量。Android不是没有别的IPC方案而是让Binder成为所有上层服务的统一底座。4.2 一次完整的Binder调用数据是怎么流转的笔试如果深入追问一般会集中在一次Binder调用经历了几次拷贝和Binder的线程模型这两个点上。先看一次调用的完整链路。以应用进程调用系统服务为例应用进程的Binder代理Proxy将数据打包通过Binder驱动发送到目标进程Binder驱动在内核空间完成数据拷贝与线程唤醒目标进程的Binder线程池收到请求后将数据交给Stub对象的对应方法执行执行结果再通过同样的路径返回。这里需要理清几个角色。Proxy端是Binder对象在客户端的代表它实现了同样的接口但对应用层透明——应用层调用的公共方法其实是在Proxy中完成了数据的序列化与传输。Stub端是服务端的Binder对象它通过onTransact方法解析客户端传过来的数据调用真正的业务方法。Binder驱动则负责数据在内核态与用户态之间的搬运以及线程间的唤醒调度。关于拷贝次数标准答案是一次。客户端调用transact时数据从用户空间复制到内核空间Binder驱动再将数据从内核空间映射到服务进程的用户空间这个过程只发生一次真正的数据拷贝其余通过内存映射完成。而传统Socket方案需要从用户空间复制到内核空间、再复制到用户空间两次拷贝性能差距明显。线程模型方面Binder通信的服务端运行在Binder线程池中——这个线程池由系统自动管理默认大小根据设备不同有差异通常不会超过16个。这意味着你编写的AIDL接口方法可能会被Binder线程池中的任意线程调用如果同时有多个客户端并发请求服务端的代码必须考虑线程安全。这是很多从没做过跨进程开发的同学在笔试中容易忽略的实战细节。4.3 AIDL的写法和使用场景笔试会怎么考AIDL是Binder的最常见上层实现。笔试中关于AIDL的考察一般从两个维度切入一是代码层面的给你一段残缺的AIDL实现让你补全二是原理层面的问你AIDL生成的Proxy和Stub分别在做什么。代码层面常见的一个考点是——AIDL支持的参数类型有哪些标准答案是Java基本类型、String、CharSequence、List、Map、Parcelable对象。这里有个隐形的坑List和Map能作为参数传递但它们内部的元素类型必须也是AIDL支持的类型否则编译报错。另一个考点是方向标志——in、out、inout的区别in表示客户端传入服务端只能读取不能修改out表示服务端可以写入客户端传入的值会被忽略inout则是双向读写。使用inout会带来额外的序列化开销所以实际开发中能选in就不选inout能选out就不选inout。这个优化意识在笔试和面试中都能加分。场景层面的考察通常是给你一个业务场景让你说该用什么IPC方案。比如你需要在一个后台Service里持续获取位置信息并推送给多个客户端正确答案是AIDL——因为Binder支持多客户端同时绑定并回调数据。又比如你只想在App内跨进程传递一个轻量级事件可能Messenger就够用了不必上AIDL这种重量级方案。方案选型的能力比写代码的能力更能反映工程经验。4.4 匿名Binder与实名Binder这是一个冷门但能拉开差距的考点Binder通信有两种注册方式实名Binder和匿名Binder。实名Binder面向系统服务——系统服务启动时会在ServiceManager中注册客户端通过名称获取Binder引用比如getSystemService获取的就是实名Binder。匿名Binder则是普通应用间通信时服务端通过bindService或AIDL的asInterface方法将Binder对象传给客户端这个Binder对象不经过ServiceManager直接以文件描述符的形式在进程间传递因此叫匿名。笔试中如果出现bindService返回的IBinder对象是如何跨进程传递的这类问题你要能答出Service的onBind方法返回的IBinder对象通过Binder驱动的映射机制传递到客户端进程客户端拿到这个代理对象后就能与服务端的真实对象通信。整个过程没有名字只有文件描述符的传递因此是匿名Binder。这几个知识点串起来你对Binder的认知就从会写AIDL代码升级到理解Android进程通信的本质了。在货拉拉这类公司的笔试里能答到这一层的人不多但答出来的人一定会在综合评分上占优势。5. 自定义View与事件分发笔试里的性价比之王自定义View和事件分发在Android面试中的比重一直很高。原因很简单这是我们日常开发中最频繁接触、也最容易出问题的部分。列表滑动冲突、点击穿透、布局卡顿每一件都跟View机制直接相关。货拉拉的卷二里这部分题目既有理论也有实操——理论考的是绘制流程和事件分发规则实操考的可能是给你一段自定义View代码让你分析它的性能问题或是让你写出一个支持双指缩放效果的View实现思路。5.1 View的绘制流程measure/layout/draw到底在干什么View的绘制流程是自定义View的基础。Android的UI体系遵循先测量、再布局、后绘制的顺序由ViewRootImpl调度执行。measure阶段的产物是View的宽高测量结果。onMeasure方法接收两个MeasureSpec参数分别表示父View给子View的尺寸要求和模式。MeasureSpec的三种模式——UNSPECIFIED、EXACTLY、AT_MOST——分别对应不限定尺寸、精确匹配、最大不超过某个值的场景。笔试常见的坑题是wrap_content对应什么模式答案是AT_MOST—父容器给你一个最大尺寸约束你可以在约束内自由决定实际大小。所以如果你自定义View时没有在onMeasure中处理wrap_content那么wrap_content会被当作match_parent来处理因为默认实现会直接接受MeasureSpec的尺寸。这个坑在日常开发中经常遇到笔试也常作为考察点出现。layout阶段的作用是确定View在父容器中的位置。onLayout方法需要你根据测量结果为每个子View计算left、top、right、bottom四个坐标。对于只有单个子View或者不包含子View的自定义Viewlayout阶段的逻辑比较简单但如果你实现的是ViewGrouponLayout就是核心逻辑所在需要遍历所有子View调用其layout方法。draw阶段的核心是先画背景、再画自身内容、再画子View、最后画前景和滚动条。onDraw方法中通过Canvas进行绘制这涉及到Paint的设置和性能优化。笔试中高频出现的考察点是invalidate和postInvalidate的区别——invalidate必须在主线程调用postInvalidate可以在子线程调用它会通过ViewRootImpl发布一个异步消息到主线程来触发重绘。5.2 事件分发机制三个方法的调用顺序和返回值的意义事件分发机制是另一个必考点。核心方法有三个dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。调用顺序是Activity - ViewGroup - View 逐层向下传递然后再从View向上返回。以一次点击为例事件先到达Activity的dispatchTouchEvent转发给DecorView再分发到根ViewGroup。ViewGroup的dispatchTouchEvent会先调用onInterceptTouchEvent判断是否拦截如果不拦截就遍历子View找到目标View调用其dispatchTouchEvent。如果子View的onTouchEvent返回true事件消费成功后续事件直接交给该View处理如果返回false事件会上抛给父View的onTouchEvent处理。笔试中最常出现的问题是事件分发中的DOWN事件和MOVE事件是由哪个方法返回值决定的。这里有个决定性的细节事件序列以DOWN事件为起点DOWN事件的返回值决定了整个事件序列的走向。如果某个View在DOWN事件中返回false那么后续的MOVE和UP事件都不会再传给这个View。所以处理事件分发时DOWN事件的处理要格外慎重如果在DOWN事件里做了错误判断后面的所有事件都会走错分支。还有一个高频考点是onTouchEvent和OnClickListener的调用顺序。如果View的onTouchEvent返回true则事件被View消费不会再回调OnClickListener只有onTouchEvent返回false事件没有被消费向上传递后可能由父View处理此时也不会触发ClickListener。所以Click事件能触发的前提是onTouchEvent执行完毕且返回true。这个逻辑绕吗绕但笔试就是要考你绕不绕得清楚。5.3 实际开发中的事件冲突举例滑动冲突的解决思路事件分发笔试不会只问原理它一定会结合场景。最常见的场景就是滑动冲突——比如一个垂直滑动的ScrollView里嵌套了一个水平滑动的ViewPager或者是一个RecyclerView嵌套另一个RecyclerView。滑动冲突的通用解决方案是外部拦截法和内部拦截法。外部拦截法的核心是在父View的onInterceptTouchEvent中判断是否需要拦截如果当前事件是MOVE事件且父View需要滑动就返回true拦截如果是DOWN事件则返回false不拦截。内部拦截法的核心是在子View的dispatchTouchEvent中通过requestDisallowInterceptTouchEvent阻止父View拦截配合父View的onInterceptTouchEvent实现。我推荐优先使用外部拦截法因为它的逻辑更清晰、更可控。判断的条件一般是当前滑动的方向和父View允许滑动的方向一致时拦截。比如竖向ScrollView嵌套横向RecyclerView的场景判断依据是事件在水平和垂直方向上的位移差——如果水平位移大于垂直位移说明是横向滑动子View处理否则父View拦截。笔试中如果能手动写出这个判断逻辑比单纯背诵拦截法的方法名要加分很多。因为这说明你真的遇到过滑动冲突并且知道怎么解决。5.4 性能相关的考察避免过度绘制与View层级优化View性能相关的题目在笔试中也占一定比重。最常见的考察点有三个过度绘制、View层级过深、以及自定义View的测量和绘制是否高效。过度绘制是指同一块区域被绘制了多次。系统通过打开显示过度绘制区域的开发者选项可以看到颜色标记——蓝色代表绘制1次绿色2次粉色3次红色4次及以上。优化的手法包括移除不必要的背景、使用merge标签减少层级、用ConstraintLayout替代嵌套布局、在RecyclerView中使用setBackground优化列表项的显示等。View层级优化的核心是减少层级数。Android的布局渲染是按层级从上到下逐个遍历的层级越深measure和layout的耗时越长。笔试中会考察merge标签的作用——它可以在布局的根节点使用起到减少一个层级的效果也会考察ViewStub的作用——懒加载一个布局只有在你调用inflate时才执行布局的创建从而减少页面初始化的耗时。自定义View的绘制性能优化方面常考的点是onDraw方法中避免创建对象、避免执行耗时操作因为onDraw在每次重绘时都会执行。另一个常考点是invalidate和requestLayout的区别——invalidate只会触发onDraw而requestLayout会触发measure和layout开销更大。如果在不改变尺寸和位置的场景下调用requestLayout就属于性能浪费。6. 数据存储与网络层笔试里最能体现工程经验的板块Android的数据存储和网络层是笔试中既考理论又考经验的板块。货拉拉的卷二在这一块的出题风格非常务实——不考冷门API考的是你在真实业务中怎么选型、怎么处理边界问题。数据存储至少包含SharedPreferences、SQLite、Room和文件存储网络层则围绕HttpURLConnection、OkHttp、Retrofit以及数据解析展开。6.1 SharedPreferences的坑writeToFile的原子性与apply/commit之争SharedPreferences在2018年前后的Android面试题里几乎是常客因为它的坑实在太多了。笔试最常考的是apply和commit的区别commit是同步提交会阻塞调用线程返回boolean值apply是异步提交返回void立即写入内存然后由系统在合适的时机写入磁盘。理解这条区别的进阶问题是apply会不会导致ANR答案是在某些场景下会——虽然apply是异步的但Activity的onStop和onPause中系统会等待apply的写入完成如果磁盘IO很慢就可能导致ANR。另一个容易考的坑是SharedPreferences的mode——MODE_PRIVATE、MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE。其中后两种在Android 7.0之后被废弃了原因是安全问题。MODE_MULTI_PROCESS在API 23之后也被废弃了官方明确不建议用SharedPreferences做跨进程通信因为它的读写不是线程安全的更不是多进程安全的。还有一个更隐蔽的坑SharedPreferences在写入时不是原子性的——它会先写临时文件再rename成正式文件。如果写入过程中进程被杀掉临时文件就会残留数据可能丢失。2018年之后业界流行的替代方案有DataStore和MMKV但在当时这笔题目里更多是想考察你对apply/commit的机制理解以及你知道什么时候该用哪种方式。6.2 SQLite与ORM框架数据库连接池与事务处理SQLite在Android面试中通常考察三个层面一是SQL语句和Cursor的正确使用二是SQLiteOpenHelper的升级与降级逻辑三是ORM框架如Room、GreenDAO与原生SQLite的关系。升级逻辑是笔试中容易出错的地方。SQLiteOpenHelper的onUpgrade方法在数据库版本号增大时被调用你需要在这里执行ALTER TABLE或CREATE TABLE等迁移语句。版本升级不只是加一张表那么简单还要考虑已有数据的保留和兼容性。一个常见的出题场景是旧表有3个字段新版需要加1个字段你不能直接DROP TABLE重建因为会丢失用户数据正确做法是用ALTER TABLE ADD COLUMN。事务处理也是不常被关注但笔试偶尔会出现的点。SQLite的事务是原子性的——要么全部成功要么全部失败使用beginTransaction和setTransactionSuccessful配合实现。如果事务过程中抛异常没有调用setTransactionSuccessful系统会回滚所有修改。这一点在处理批量插入时尤其重要不使用事务批量插入1000条数据可能要几秒钟使用事务可能只需要几十毫秒因为事务减少了磁盘IO的次数。这个性能差在实际项目中非常明显笔试中如果出现如何快速批量插入大量数据的题目答案的核心就是开启事务、使用预编译语句、关闭自动提交。数据库连接池方面Room框架内置了连接池它解决的是多线程并发访问数据库时的效率问题。笔试中如果有Room为什么比原生SQLite好用的题目除了语法简洁以外线程安全、编译期校验、生命周期感知这三个答案点都要说到少一个都不够完整。6.3 网络层OkHttp和Retrofit的核心机制网络层是Android开发中绕不开的部分。2018年的时候OkHttp和Retrofit已经成了事实标准。笔试对它的考察重点从怎么用升级到了底层原理是什么。OkHttp的核心机制包括连接池复用、请求队列调度、缓存策略、拦截器链。连接池复用的目的是减少TCP握手和TLS握手的次数——同一主机的多个请求会复用同一个连接连接池默认最多保持5个空闲连接每个空闲连接存活5分钟。这个参数的优化对频繁访问同一服务器的App影响极大。拦截器链是OkHttp最灵活的设计。它有应用拦截器和网络拦截器两种应用拦截器在请求发出前和响应返回后都能处理网络拦截器在连接建立后、请求发出前处理。通过拦截器可以实现日志打印、统一加参数、统一签名、重试机制这些能力。笔试中如果让你设计一个统一的网络层方案大概率要围绕拦截器展开。Retrofit的本质是一个基于OkHttp的RESTful API封装库它通过动态代理机制把接口方法转化为对应的HTTP请求。核心原理是Retrofit.create创建了一个动态代理对象你在调用接口方法时代理对象会解析方法上的注解GET、POST、Path等生成一个OkHttp的Request然后通过OkHttpClient发起请求最后通过Converter把ResponseBody转换成你的返回类型。笔试中常见的一个追问是Retrofit和OkHttp的关系是什么答案很简单Retrofit的底层还是OkHttpRetrofit解决的是怎么把接口定义映射为HTTP请求的问题OkHttp解决的是HTTP请求怎么高效发出去的问题。这两个定位不同面试官让你选型时你要能说清楚什么时候该只用OkHttp什么时候该上Retrofit。6.4 弱网处理与请求重试物流业务场景下的核心能力物流调度类App有一个非常典型的特征司机端网络环境极不稳定经常进出隧道、地库、偏远地区弱网处理能力直接决定产品可用性。货拉拉的笔试如果结合自身业务场景大概率会在这上面做文章。弱网处理的核心思路有三个超时时间设置、重试策略、数据缓存。超时时间不能是全局统一的固定值要根据接口性质区分——查询类接口可以设置相对短的超时时间上传类接口需要更长的超时时间。重试策略要避免无限重试和立即重试正确做法是指数退避——第一次失败等1秒重试第二次失败等2秒第三次等4秒同时最多重试3~5次避免雪崩式请求。数据缓存方面列表页和详情页必须做本地缓存或磁盘缓存保证弱网甚至无网状态下用户依然能看到之前加载过的内容。OkHttp的拦截器非常适合做统一的重试机制。在自定义拦截器中判断响应码和异常类型当网络超时或者服务器返回5xx时自动进行重试并配合指数退避策略控制重试间隔。这套方案不是笔试的延伸而是真实项目中每天都在跑的逻辑。7. 那些年我答错的Android笔试题整理出来的实战心得我在准备Android面试的过程中踩过不少坑。很多题看起来简单但真正上手做的时候才发现自己理解得不够深。这里我把复习过程中常见的高频易错点整理成一个具体的清单方便你对照自查。7.1 高频易错点清单第一梯队是Handler与内存相关的陷阱。Handler持有Activity导致内存泄漏这个问题几乎必考但很多人答不出引用链的完整路径。LruCache的线程安全性也常被问——LruCache内部是用LinkedHashMap实现的但它不是线程安全的多线程访问需要加锁Java中可以通过Collections.synchronizedMap包一层来解决。第二梯队是View绘制与触控相关的细节。事件分发中UP事件的处理容易被忽略——如果一个View的DOWN事件返回了true但UP事件处理不正确可能导致后续的点击事件无法被正确触发。自定义View中wrap_content的处理很多人默认不处理导致自定义View在XML中设置wrap_content时表现异常这在面试中被问到自定义View需要注意什么时是标配答案之一。第三梯队是网络与数据存储相关的边界条件。SQLite版本升级的老数据处理、OkHttp的拦截器顺序、SharedPreferences的apply与commit在ANR场景中的表现这些都属于你知道原理但没踩过坑就说不清楚的知识点。7.2 高效准备Android笔试的方法论如果你正在准备Android笔试面试我的经验是可以按照知识体系梳理 - 高频题目训练 - 场景设计复盘三个步骤来做。知识体系梳理阶段不要直接刷题先把Android官方的Training文档和核心源码过一遍。重点是Activity、Service、BroadcastReceiver、ContentProvider这四大组件——它们的生命周期、启动模式、与AMS的交互、以及它们对应的跨进程通信机制。这一轮的目的是建立完整的地图而不是零散地记知识点。高频题目训练阶段针对Handler、Binder、自定义View、进程通信、网络框架、性能优化这几个大板块做专项训练。每道题不能只看答案就过要尝试自己画出链路图、写出伪代码然后对照优质解析找差距。比如一道Handler消息延迟5分钟Activity被销毁了会发生什么的题目你要能画出Message-Handler-Activity的引用链并说出解决方案而不是只背结论。场景设计复盘阶段把自己代入真实业务需求——比如如何设计一个支持断点续传的文件上传模块、如何优化一个卡片式信息流列表的卡顿问题、如何保证App退到后台时消息能及时送达。这些题目没有标准答案考的就是你在前面两个阶段积累的知识能不能灵活组合应用。这个阶段最有价值的地方在于它能帮你把零散的知识点串成一条线形成真正的工程思维。回头看这份货拉拉的笔试题它给我的最大启发不是某道题的答案而是整份试卷所代表的出题思路——不追求偏难怪而是把Android开发中最基础、最常用、最容易被忽略的知识点做成筛子筛掉那些只会写代码但不理解原理的人筛掉那些只会背诵但不理解场景的人留下来的是真正能把技术玩明白的人。如果你正在准备Android面试我的建议是不要只盯着题库背答案而是静下心来把Handler、Binder、View绘制、事件分发这几条主线彻底吃透。你可能觉得这些基础知识离业务很远但实际上你遇到的每一个卡顿、每一个闪退、每一个诡异的交互问题最终都要回到这些底层的原理上来。基本功扎实的人遇到问题的时候不用猜直接用知识推导出答案。这种放心感才是准备面试真正值得追求的东西。