
这套题我在开发者社区见过不少回帖也听当年参加笔试的朋友复盘过整体风格很典型——不考偏门API不追新框架重点全在JVM语言功底、Android消息机制、事件分发、进程通信这些“底层硬功”上最后再加一道编程题和一题开放性的架构设计。说白了校招看重的是基础扎不扎实、看没看过源码、有没有自己折腾过性能问题而不是你背了多少框架API。这篇文章我就按这份卷子的知识点板块来拆每一题都给到参考答案和踩坑点顺便聊聊2017年那会儿Android面试为什么偏爱这些方向希望对正在准备面试的朋友有点参考价值。1. 整体考情与题目结构1.1 这份B卷的整体印象欢聚时代2017校招的Android工程师B卷整体题量中等偏上考试时间在90分钟左右。和A卷偏重基础语法不同B卷明显向Android系统原理方向倾斜尤其是Handler、Binder、事件分发这三块分值占比相当高。题型构成大致是这样的单选题8道左右每题2分覆盖Java语法、集合类、进程通信基础。多选题4道左右每题4分覆盖Android生命周期、Handler机制、内存优化。简答题4道左右每题8分覆盖自定义View、消息机制、网络协议。程序题2道一道是手写单例或算法题另一道是读代码说输出或找Bug。压轴设计题1道10分左右要求设计一个图片加载框架或IM消息架构。从这个结构能看出单选和多选主要筛基础牢不牢简答题看理解深不深程序题看动手能力最后的设计题是拉开差距用的看候选人有没有真正写过项目、有没有架构意识。1.2 校招笔试为什么偏爱这些考点2017年正好是Android开发从会写界面就行转向底层能力要求提升的分水岭。那时候插件化、热修复、组件化开始流行面试官不再满足于你会用Activity而是会追问Activity启动过程中发生了什么事。B卷这些考点的选择逻辑其实是Java基础Android开发绕不开Java集合类在项目里天天用但很多人只停留在会用HashMap不知道它和Hashtable的区别不知道HashMap在Java 8里引入了红黑树。消息机制Handler是Android应用与主线程交互的核心也是ANR问题的根源之一几乎每家公司的笔试题都会涉及。进程通信欢聚时代做直播、IM类产品音视频数据和消息推送都涉及多进程场景Binder自然是必考。性能优化直播产品对内存、流畅度要求极高内存泄漏、布局优化、ANR排查都是实际工作中天天面对的问题。这套题不是故意难为你而是以用定考——笔试题目背后对应的都是工程师入职后马上要面对的日常工作面。2. Java与Android基础题精讲2.1 静态方法、重载与重写的辨析B卷单选题里有一道很经典的题以下关于Java中static关键字的说法正确的是 A. static方法可以被重写 B. static方法只能访问static成员 C. static方法不能定义在接口中 D. static方法不能是抽象方法这题正确答案是D。当年不少人栽在B选项上因为这句话看起来没毛病但细想就不对——static方法里确实不能直接访问非static的实例成员因为没有this但它完全可以通过对象引用来访问实例成员。static方法的本质是属于类的方法它在类加载时就分配了内存不依赖对象而存在。因此public class Parent { public static void staticMethod() { System.out.println(Parent static); } public void instanceMethod() { System.out.println(Parent instance); } } public class Child extends Parent { // 这不是重写是隐藏 public static void staticMethod() { System.out.println(Child static); } Override public void instanceMethod() { System.out.println(Child instance); } }调用时的区别是核心考点Parent p new Child(); p.staticMethod(); // 输出 Parent static静态方法看左侧类型 p.instanceMethod(); // 输出 Child instance实例方法看右侧对象这就是静态方法跟着类型走实例方法跟着对象走的规则。面试时如果只是背结论很容易在变种题上翻车。比如把变量声明成Child再调用结果就不一样了。这类题考查的不是会不会用static而是是否理解JVM在编译期和运行期分别做了什么决定。另外B选项的陷阱在于static方法内部如果先创建了一个对象当然可以通过这个对象的引用来访问它的实例成员。所以只能访问static成员这种绝对化的说法基本都是错的。2.2 HashMap与Hashtable的异同这题2017年出现的频率极高几乎每套Android校招题里都有。B卷的考法是给出一组说法让你选正确的关于HashMap和Hashtable下列说法错误的是 A. Hashtable的方法基本都加了synchronized线程安全 B. HashMap允许key为nullHashtable不允许 C. HashMap的初始容量是16Hashtable是11 D. HashMap的遍历顺序是确定的答案是D。HashMap的遍历顺序由哈希值决定而哈希值又跟hashCode有关所以不保证顺序稳定。这个知识点在Android开发里很实用。比如做数据缓存频繁遍历一个Map去更新UI如果依赖了顺序就会出Bug。我在实际项目里就遇到过同一个Map在Android 6.0上遍历顺序是A、B、C换到Android 8.0上变成了C、A、B排查了半天才意识到是hashCode实现和系统版本差异导致的。延伸一下如果面试官再往下问为什么HashMap是线程不安全的你要能说出这三个层面多线程put时如果两个线程同时触发了resize可能导致链表成环JDK 1.7里会引发死循环。多线程同时put且hash碰撞时数据会互相覆盖。modCount被多线程修改后遍历会抛出ConcurrentModificationException。JDK 1.8之后HashMap引入了红黑树优化链表长度超过8且数组长度超过64时转为红黑树。这个优化主要是为了防DDoS攻击——人为构造大量hash碰撞把所有数据塞进同一个桶让查找从O(1)退化到O(n)。但是要注意转红黑树有个前提数组长度必须大于等于64。如果数组长度不够即使某个桶链表超过8也不会转树而是先扩容。2.3 sleep和wait的底层区别Java基础题里sleep和wait的区别也是高频考点。B卷以简答题形式出现简述Thread.sleep()和Object.wait()的区别。标准答法分四点所属类不同sleep是Thread的静态方法wait是Object的成员方法。锁的释放sleep不释放锁wait释放锁。这是最核心的区别。sleep执行时其他线程依然进不了同步块wait执行后当前线程会进入等待队列其他线程可以抢到锁。使用位置sleep可以在任何地方调用wait必须在synchronized代码块或方法中调用否则抛IllegalMonitorStateException。唤醒机制sleep的线程到时间自动醒来wait必须依赖notify/notifyAll唤醒或者设置超时时间。从底层实现来看sleep最终会调用到JVM的线程调度让出CPU但保留对象监视器的持有权。wait则会把当前线程挂到ObjectMonitor的_WaitSet队列中同时释放监视器。这也是为什么wait之前必须先拿到锁——你要释放的前提是你得有。Android开发里wait和sleep遇到的高频场景是在自定义消息队列或者生产者-消费者模式中。比如我有一个后台线程在轮询任务队列如果没有任务就让线程进入wait状态有任务时notify唤醒它。如果用sleep就会白白空转固定时长既不及时又费电。提示在Android主线程里调用Thread.sleep会导致界面卡顿甚至ANR因为主线程被阻塞了。补充一点Android 4.0之后主线程不能做网络请求sleep虽然没被明令禁止但只要是耗时操作都应该放到子线程。3. Android核心机制深度拆解3.1 Handler消息机制完整链路Handler是Android面试的钉子户B卷简答题中出现的概率几乎是100%。常见问法是简述Handler、Looper、MessageQueue三者之间的关系以及消息从发送到执行的完整流程。标准流程是这样的Looper.prepare()在子线程中创建Looper内部会创建一个MessageQueue并通过ThreadLocal把Looper绑定到当前线程。Looper.loop()开启无限循环不断从MessageQueue中取消息。Handler在构造时通过Looper.myLooper()获取当前线程的Looper从而持有对应的MessageQueue引用。调用handler.sendMessage()时enqueueMessage方法把消息插入MessageQueue按时间戳排序延迟消息会插到合适位置。Looper从队列中取出消息后回调dispatchMessage最终交给handleMessage处理。这个机制的核心设计是一个线程只有一个Looper和一个MessageQueue但可以有多个Handler。所以同一个线程中不管创建多少个Handler最终消息都是串行执行的。Android面试喜欢追问的问题是为什么不能在子线程中new Handler因为Handler构造时要通过Looper.myLooper()获取当前线程的Looper如果当前线程没有调用Looper.prepare()Looper.myLooper()返回null直接抛RuntimeException。除了主线程之外主线程在ActivityThread的main方法中已自动创建Looper其他线程需要先手动调用Looper.prepare()和Looper.loop()。MessageQueue的next()方法在没有消息时会阻塞吗不会。next()方法内部调用了nativePollOnce这是一个Native层的epoll机制会进入休眠状态等待唤醒而不是忙轮询所以不消耗CPU。这也是Android对省电的一个经典优化。当下一条消息还没到时间时它也会进入休眠直到消息时间到达。Handler调度机制和Java的ScheduledExecutorService比有什么不同Handler消息运行在绑定线程中天然线程安全且不需要考虑并发同步特别适合线程间通信场景而ScheduledExecutorService是线程池任务是并发执行的。Android中更新UI只能在主线程所以Handler的主线程模型更加契合。3.2 Activity启动模式与onNewIntentActivity启动模式是笔试中的必考点B卷以选择题简答题的形式反复出现。四种启动模式要记住的不只是定义而是要能说出应用场景启动模式核心行为典型场景standard每次都新建实例普通页面一般页面singleTop栈顶复用复用会回调onNewIntent接收推送通知跳转的页面singleTask栈内唯一清空其上所有ActivityApp主页、WebView容器singleInstance独占一个任务栈来电页面、闹钟提醒笔试时最常见的坑是singleTask启动时如果目标Activity已经在栈中且不在栈顶会把它上面的Activity全部出栈这个过程会回调onNewIntent同时不会调用onCreate。还有低频考法singleTop与singleTask的区别。singleTop只判断栈顶是不是自己如果不是就新建singleTask则搜索整个任务栈里有没有自己有就把自己提到栈顶并把上面的页面全部销毁。2017年时还常考taskAffinity的概念这题稍难。taskAffinity用于指定Activity的任务栈名称默认是包名。singleTask配合taskAffinity可以指定Activity在哪个任务栈启动比如从应用A调起应用B的某个页面可以指定taskAffinity为B的包名这样就能复用B的任务栈。再扩展一个知识点启动模式是通过intent.setFlags()动态指定的它优先于Manifest中的静态配置。这个在代码中动态跳转、处理三方SDK跳转场景中很实用。3.3 Service两种启动方式的生命周期对比Service的启动方式也是校招必考。B卷一般会以多选题出现下列哪些方式可以启动一个Service A. startService() B. bindService() C. startActivity() D. sendBroadcast()答案是AB。startService和bindService可以同时存在Service的onCreate只会执行一次但会同时处于已启动和已绑定两个状态。生命周期差异是关键启动方式启动方法停止方法生命周期非绑定启动startService()stopService()或stopSelf()onCreate - onStartCommand - onDestroy绑定启动bindService()unbindService()onCreate - onBind - onUnbind - onDestroy混合启动startServicebindServicestopServiceunbindService需要两者都停止才走onDestroy有经验的面试者会补充onStartCommand有四种返回值分别对应不同场景START_STICKYService被系统杀掉后系统会尝试重新创建它并传入null的Intent。适合后台播放器这类需要长期存活的服务。START_NOT_STICKY被杀后不重建适合不重要的任务。START_REDELIVER_INTENT被杀后重建并把上次的Intent重新传进来。适合需要立即恢复的任务。START_STICKY_COMPATIBILITY兼容版本实际效果模糊。笔试最常见的陷阱是bindService时如果ServiceConnection.onServiceDisconnected被回调通常是Service所在进程意外死亡被系统回收或者Crash不代表正常解绑。正常解绑走的是onUnbind回调这一点经常被搞混。另外提一嘴2017年上下文背景限制还没那么严格到了Android 8.0之后后台启动Service就受限了需要配合startForegroundService用前台服务才能保活。这个知识点如果写在卷子里面试官会觉得你是有项目实战的。4. 自定义View与事件分发4.1 View的测量流程MeasureSpec的三种模式B卷简答题中有一道经典的描述View的onMeasure流程以及MeasureSpec的三种模式分别代表什么含义。MeasureSpec由32位int组成高2位代表SpecMode测量模式低30位代表SpecSize具体尺寸大小。三种模式EXACTLY精确模式layout_width或layout_height指定了精确值或者使用了match_parent。父容器已经确定了子View的尺寸。AT_MOST最大模式layout_width或layout_height使用了wrap_content。父容器只限制最大尺寸子View可以根据内容自行决定最终大小。UNSPECIFIED未指定模式父容器对子View没有任何限制。这个模式在ScrollView、ListView的item测量中很常见也是自定义View最容易踩坑的地方。实际项目里最经典的坑是自定义View时onMeasure里直接super.onMeasure()导致wrap_content失效View会占满父容器。原因就是super.onMeasure里对wrap_content的处理等同于match_parent。正确写法是Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode MeasureSpec.getMode(widthMeasureSpec); int widthSize MeasureSpec.getSize(widthMeasureSpec); if (widthMode MeasureSpec.AT_MOST) { // wrap_content根据内容计算一个合理宽度 widthSize getPaddingLeft() getSuggestedMinimumWidth() getPaddingRight(); } setMeasuredDimension(widthSize, resolveHeight(heightMeasureSpec)); }别小看这几行代码我在实际项目中遇到过自定义标签流控件没有重写onMeasure结果每个item都占满一行等宽变成全宽后来定位到问题就出在AT_MOST模式下没做处理。4.2 事件分发机制的精髓三个方法一条链事件分发的题目在B卷中通常是一道简答题有时甚至会结合代码让考生分析输出顺序。核心是这三个方法dispatchTouchEvent负责事件分发决定事件交给谁处理。onInterceptTouchEvent负责事件拦截仅ViewGroup有View没有。onTouchEvent负责事件消费返回true表示处理了事件。以一次ACTION_DOWN为例从Activity到ViewGroup再到View的传递顺序是Activity.dispatchTouchEvent - ViewGroup.dispatchTouchEvent - ViewGroup.onInterceptTouchEvent - View.dispatchTouchEvent - View.onTouchEvent事件被消费后后续的ACTION_MOVE、ACTION_UP会沿着同样的链路传给消费事件的View如果中间层拦截了则事件在拦截层处理。笔试的难点在几个细节onTouchEvent和onClick的调用顺序是如果View的onTouchEvent返回true即消费了事件在ACTION_UP时会触发performClick进而回调onClick。所以Click事件是建立在onTouchEvent返回true的基础上的。requestDisallowInterceptTouchEvent的作用是什么子View可以调用这个方法阻止父View拦截事件常用于解决ViewPager和侧滑删除的滑动冲突问题。但父View在onInterceptTouchEvent中也可以通过特殊方式绕过这个限制比如处理ACTION_DOWN时优先拦截。onInterceptTouchEvent默认返回什么ViewGroup默认返回false但有一个例外——如果子View处理了ACTION_DOWN事件父View的ACTION_MOVE默认不会拦截。要改变这个行为需要重写onInterceptTouchEvent或在子View中使用requestDisallowInterceptTouchEvent阻止父View拦截。4.3 invalidate、requestLayout与postInvalidate这道题在B卷简答题中出现频率很高经常和自定义View一起考简述invalidate和requestLayout的区别。一张表说清楚方法触发流程调用时机invalidate只重绘不重新测量布局。View会调用onDraw重新绘制内容变了尺寸没变requestLayout触发onMeasure、onLayout、onDraw完整流程尺寸、位置变了postInvalidate线程安全的invalidate可在子线程调用子线程中更新UI核心区别是invalidate只走onDrawrequestLayout会重新触发onMeasure、onLayout、onDraw。这个知识点在性能优化中非常重要——如果只是颜色变了调用requestLayout白白多做一次测量和布局极其浪费。还要注意invalidate必须在主线程调用如果从子线程更新UI需要使用postInvalidate。但实际开发中postInvalidate用得并不多因为大多数情况下你还是用Handler或者runOnUiThread切回主线程再操作。5. 进程通信与网络协议5.1 Binder机制Android IPC的唯一真神Binder在Android系统里地位特殊。面试常问为什么Android选用Binder而不是传统的共享内存、Socket、管道标准答法是三次拷贝 vs 两次拷贝以及安全性两个角度性能方面共享内存虽然性能好但需要自行处理并发同步Socket基于内核网络协议栈需要拷贝四次性能差Binder一次拷贝就能完成数据传输通过mmap映射性能介于两者之间。安全方面Binder为每个应用分配了UID/PID内核级可以做身份校验不需要应用层手动处理安全而传统IPC没有这个能力。易用性Binder的接口定义类似AIDL开发者通过接口代理调用远端服务使用体验接近本地方法调用。AIDL的工作原理是B卷简答题的另一个考法。它本质上是把接口调用转换为Parcel对象的写入和读取。客户端通过代理对象transact()向服务端发送数据服务端的Binder线程池接收后回调onTransact()。一个容易忽略的知识点是AIDL的方法是在Binder线程池中执行的所以如果实现了耗时操作要注意线程切换否则会阻塞Binder线程池影响系统响应。同理服务端onBind返回的Binder对象的onTransact调用也不在UI线程。这个坑我在使用system service API时经常遇到也导致过java.lang.RuntimeException: Cant create handler inside thread that has not called Looper.prepare()的崩溃。5.2 HTTPS与HTTP的区别网络协议题在B卷中一般占8分左右以简答题形式出现简述HTTPS的加密过程。标准答法是四步握手客户端向服务端发起HTTPS请求携带客户端支持的加密协议版本、随机数。服务端回复证书包含公钥、服务端随机数、确认的加密套件。客户端验证证书合法性通过CA证书链验证通过后生成一个预主密钥pre-master secret用服务端公钥加密后发过去。两端分别用客户端随机数服务端随机数预主密钥生成会话密钥之后用对称加密传输数据。这个流程的精妙之处在于非对称加密用来传输密钥对称加密用来传输数据。因为非对称加密性能差不适合大批量数据对称加密性能高但需要双方先协商出密钥。笔试最容易漏答的点是CA证书链验证这一步。很多考生只说用公钥加密但没说如何确认收到的公钥就是服务器的。这里的答案核心是证书链验证客户端信任根证书通过根证书依次验证二级证书、服务端证书确保服务端身份可信。Android开发中的网络库OkHttp默认对HTTPS做了全套校验但经常遇到的问题是开发环境自己签的证书无法通过校验。这时候有些同学直接忽略证书校验这块要提醒一下调试期可以这么做但上线前务必恢复。如果面试能提到OkHttp的自定义TrustManager和HostnameVerifier的坑加分效果不错。5.3 TCP的三次握手与四次挥手网络协议基础题B卷出现过选择题TCP连接建立时为什么需要三次握手断开时为什么需要四次挥手三次握手的目的是确认双方的收发能力第一次客户端发送SYN服务端确认客户端发送能力正常自己接收能力正常。第二次服务端发送SYNACK客户端确认自己发送能力正常、服务端接收正常服务端发送能力正常、客户端接收正常。第三次客户端发送ACK服务端确认客户端接收能力正常、自己发送能力正常。这其实是双向确认机制。如果是两次握手服务端无法确认客户端的接收能力是否正常。四次挥手的核心在于TCP是全双工通信两个方向需要独立关闭客户端发送FIN表示客户端数据已发完。服务端回复ACK表示收到关闭请求。服务端发送FIN表示服务端数据也发完了。客户端回复ACK连接关闭。为什么不是三次挥手因为服务端可能在收到FIN时还有数据要发送不能立即关闭。所以要等数据发完再单独发送FIN。Android面试中这个知识点通常不会单独出现而是和OkHttp、Socket编程一起考。比如为什么HTTP/1.1的连接复用依赖TCP长连接因为三次握手开销大频繁新建连接会拖慢请求速度而Keep-Alive机制能让TCP连接保持一定时间。6. 编程题与开放设计题6.1 手写单例模式B卷编程题几乎必考手写单例。2017年最常见的考法是请用Java实现一个线程安全的单例模式。标准答案肯定是双重检查锁Double-Checked LockingDCLpublic class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }注意这个volatile关键字它是DCL的灵魂。如果没有volatileinstance new Singleton()这一步在JVM层面会被拆成三条指令分配内存空间。初始化对象。将instance引用指向这块内存。JVM的重排序可能把第2步和第3步调换顺序。当线程A执行完第3步引用赋值但没执行第2步对象初始化时线程B判断instance ! null直接返回了未初始化的对象这就出现了问题。volatile关键字禁止了重排序保证了引用赋值一定发生在对象初始化之后。这是面试中必须说透的点否则面试官会认为你只是背了模板。另一个重要考点是静态内部类实现public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }这种写法既能实现懒加载只有getInstance被调用时才初始化SingletonHolder又是线程安全的类加载机制的初始化阶段保证可见性还没有同步开销在Android开发中应用很广。6.2 最高频的算法题反转单链表B卷算法题还出现过反转单链表public ListNode reverseList(ListNode head) { ListNode prev null; ListNode curr head; while (curr ! null) { ListNode next curr.next; curr.next prev; prev curr; curr next; } return prev; }这道题有两个容易错的地方一是保存下一个节点的引用next必须在改变curr.next之前否则链表会断开二是终止条件是curr ! null不是curr.next ! null。如果面试官加问递归实现代码是public ListNode reverseList(ListNode head) { if (head null || head.next null) { return head; } ListNode newHead reverseList(head.next); head.next.next head; head.next null; return newHead; }递归实现的关键是理解head.next.next head这一步——把当前节点的下一个节点指向自己相当于完成了反转。6.3 压轴设计题图片加载框架设计B卷压轴题出现过请设计一个图片加载框架需要考虑内存缓存、磁盘缓存、网络加载和线程调度。这道题考察的不是背诵某个开源库而是工程架构能力。我当时给出的大致方案是整体分层加载入口load(url) - 返回一个Request对象支持设置占位图、错误图、回调。三级缓存先查内存缓存再查磁盘缓存最后走网络。LRU内存缓存使用LruCache大小为进程可用内存的1/8通过Activity级别的TrimMemory回调调整缓存大小。磁盘缓存使用DiskLruCache限制文件个数或总大小推荐10MB~50MB。线程调度主线程发请求线程池加载图片。需要区分网络请求线程池和本地IO线程池网络请求线程池数量可以多一些如4~6个本地IO线程池2~3个即可。回调切回主线程保证图片设置到ImageView时一定在主线程。关键细节图片压缩加载时先读图片文件头获取尺寸如果大于ImageView所需尺寸通过inSampleSize采样压缩避免OOM。ReusePool设置inBitmap为true从复用池中取出可用的Bitamp减少内存分配和GC压力。加载中的去重对于同一个URL同时发起的多个加载请求应该合并为一个加载任务避免重复网络请求。这个方案如果面试时能画出架构图把缓存策略、线程模型、内存优化都有理有据地讲出来基本稳过高分。7. 易错题排查与面试方法论7.1 笔试现场最容易丢掉的分根据对历年校招笔试的分析以下几点是最容易丢分的地方第一类是读题不细。单选题下列说法错误的是很多人扫一眼觉得A正确就选了没注意题目问的是错误。建议做题时把正确/错误圈出来养成条件反射。第二类是概念混用。比如把HashMap和Hashtable、sleep和wait、startService和bindService搞混。面试官出这些对比题的目的就是考察是否建立了准确的概念边界建议平时复习时就把容易混淆的知识点整理成表格来对比记忆。第三类是代码细节缺失。手写单例时漏掉volatile手写反转链表时忘存next引用都是致命的低级错误。代码题千万不要跳步把每一步都要写全。第四类是开放题没有结构。设计题其实没有标准答案但答题要有层次感。一个可行的框架是分层架构 缓存策略 线程模型 异常处理 扩展性五个维度先在草稿纸上列出骨架再填充细节。7.2 Android校招考点速查表我把这份B卷覆盖的考点整理成一张表方便后续复习快速定位考点模块核心题目高频追问推荐阅读Java基础static/重写重载/集合类HashMap在JDK 1.8的改动《Java编程思想》消息机制Handler/Looper/MessageQueue主线程为什么不死源码阅读组件Activity启动模式/Service生命周期onNewIntent触发时机《Android开发艺术探索》自定义ViewonMeasure/事件分发滑动冲突解决方案《Android开发艺术探索》第4章进程通信Binder/AIDLBinder线程池与主线程切换老罗的Binder分析性能优化内存泄漏/ANR/布局优化常见泄漏场景Android官方文档网络HTTPS/TCP/UDPOkHttp的连接复用机制《图解HTTP》算法单例/链表/二叉树/字符串时间空间复杂度分析剑指Offer7.3 拿到这套题后我的复习建议如果正在准备Android校招我的建议是按照底层优先、源码为主、项目验证的思路来安排时间底层优先Handler、Binder、View体系是Android的三大支柱必须做到能画流程图、能讲出每一步的原理而不是只背结论。源码为主LAUNCHER启动流程、ActivityThread的main方法、Handler的enqueueMessage、ViewRootImpl的performTraversals这些源码看一遍比刷十套题都管用。项目验证算法和设计题只有在项目中真正用过才能在笔试时写出有深度的答案。比如做项目时遇到OOM你排查的过程就是最好的面试素材。最后再分享一个实用建议笔试时如果遇到不会的题千万不要空着。比如设计题即使没有完整方案也可以把你知道的相关知识点缓存、线程、压缩写上去至少能拿一些步骤分。面试官看的是分析思路而不只是最终答案。