自我介绍省略
问:导航服务厂商的方式是提供apk还是sdk
答:两种方案都有
问:贵司的导航方案对比高德百度或国外方案有什么优势吗
答:国内高德百度方案不是很了解,我们是做海外的,数据用的here,引擎是自研的
复盘:没有说优势,比如引擎自研,不需要天天催引擎去修复问题,功能支持定制化,迭代速度快
问:你们导航apk被用于服务各家车厂厂商,每个应该有差异化定制的,你觉得各个厂商的最大差异在哪
答:一个是界面差异,另外是功能方面,比如某厂商有独有功能组队出行,另外厂商需要路口放大图功能
复盘:没有提与车厂自有硬件和生态的整合。比如各个车厂车辆信号,接口都是不同的
问:你们apk的整体架构方案
答:code基线一样,不同厂商不同flavor
问:apk整体架构
答:两代引擎架构不同,老引擎MVC,新引擎mvvm,新引擎使用新技术多,如dagger解耦,navigation界面跳转,livedata+databinding做界面动态刷新,老引擎用JAVA开发,新引擎使用Kotlin开发
复盘:没有说为什么有两代引擎,为什么要转框架,为什么使用新技术。比如MVC耦合性过高,代码维护困难,Navigation统一了跳转逻辑,并且自动处理和保存返回栈状态,不再使用原先那套晦涩难懂的flowmanager,老引擎MVC中,数据变化后需要手动findViewById然后setText;LiveData感知生命周期自动派发更新,DataBinding把布局和数据绑定声明式写在一起,减少很多UI模板代码
问:你们的老引擎用MVC架构,那么界面和业务逻辑会耦合,你们是如何解决的
答:xml界面也是按flavor切分出去
问:这样会不会工作量太大
答:项目初期是的
问:你们遇到的项目问题可能是项目共有的,你们如何去在各个厂商里面都解决
答:我们用jira管理问题,如果问题是各个平台都有,那么会有public标签,解决后,其他项目组要么cherry pick要么rebase
复盘:没有回答到点子上,公共问题我们只改基类。基类放在公共代码区,不分厂商。改一次,所有厂商自动生效,不需要每个厂商单独去改。
问:你们的项目分flavor,应该无法cherry pick吧?
答:通过继承解决,出问题的代码一般写在基类,基类不分flavor
复盘:这里回答其实有问题,之所以cherry pick是因为项目后期其实切出主分支了 在SOP上只能cherry pick其他项目共性问题
问:你们项目是只有一个activity多个fragment吗,如何管理fragment
答:是的,我们用自己开发的一套flowmanager管理fragment,fragment对应page,page也有page start,stop,create,destroy等生命周期,flowmanager内部维护一个stack,界面跳转就是入栈出栈
问:如果fragment已经显示了,你们的入栈效果是什么样的,会弹出其他页面吗
答:不会出现你这样的问题,如果界面已经显示,flowID判断界面一样就不会跳转
问:如果不做界面跳转,那么会做弹栈吗
答:有的方法会,有的不会
问:有用到Android原生fragmentmanager管理fragment
答:没有
复盘:这块需要再研究一下代码
问:你对反射的理解
答:通过找方法找属性违规地调用系统方法去实现一个功能,项目中只有一个地方用到反射,是替换系统字体的相关代码。不推荐使用,会影响性能,除非没有其他解决方案。例如用反射和annotation可以实现IOC注解,但是最新的dagger已经不使用反射实现注解了,已经改为使用编译时生成代码,不在运行时运行反射,可以提高性能。
问:说说Kotlin的协程
答:轻量级线程,对CPU资源开销小,一个线程消耗资源大概1m,协程大概只有十分之一或更少,语法糖很厉害,能够轻松实现多线程操作
复盘:回答不够精确
Kotlin协程本质上是一个轻量级的线程框架,它的核心是挂起与恢复机制。
和传统线程相比:
资源开销小:一个线程通常占用~1MB栈空间,而协程可以小到几十字节,所以一个进程可以轻松创建成千上万个协程。
切换成本低:协程的挂起是用户态操作,不涉及操作系统内核调度,比线程切换高效得多。
语法糖:用suspend关键字标记挂起函数,配合launch、async等作用域构建器,可以用同步的代码风格写出异步的逻辑,避免传统的回调地狱。
但要注意,协程并不是为了取代线程,而是更好地利用线程。它会把协程派发到有限的线程池上执行,实现高效的并发。
问:协程里面加的日志没有打印,有哪些可能情况
答:忘了
复盘:
- 协程根本没有被启动或执行
launch或async的作用域被取消了(比如在ViewModel中,页面销毁时viewModelScope自动取消)。
协程被延迟启动(start = CoroutineStart.LAZY)且没有被start()或await()触发。
- 日志所在的代码没有运行到
协程内部有个空判断或条件分支,日志所在的那条分支没进去。
协程在到达日志前抛出了异常(且异常被静默吞掉了,没有打印堆栈)。
- 日志所在的协程被取消或挂起未恢复
在日志打印前调用了yield()或delay(),而协程在此过程中被外部取消了。
协程被挂起后,一直没有恢复(比如等待一个永远不会完成的CompletableDeferred)。
- 日志本身的问题
日志的Tag或级别被过滤了(比如设置的是Log.d(VERBOSE),但手机当前日志级别是WARN)。
在非UI线程打印日志,但使用的日志工具只绑定了主线程的日志输出(较少见,但有可能)。
问:JAVA创建多线程的方式有哪两种,他们有什么区别
答:只记得new thread+runable
问:其实是一个是用runable一个是继承thread方式
复盘:基础知识忘了
继承 Thread 类:创建一个类继承 Thread,重写 run() 方法,然后创建该类的实例并调用 start()。
实现 Runnable 接口:创建一个类实现 Runnable 接口,实现 run() 方法,然后将该实例作为参数传给 Thread 对象,再调用 Thread 的 start()。
核心区别(3点):
扩展性:Java是单继承,继承Thread后就不能再继承其他类了;而实现Runnable还可以继承其他类,更灵活。
资源共享:多个Thread对象可以执行同一个Runnable实例,天然适合共享同一份资源(比如卖票系统);继承Thread方式则需要用static变量,更麻烦。
代码分离:Runnable方式把"任务本身"和"线程运行"分离,更符合面向对象设计。
问:说说你项目中遇到的困难
答:举一个oom的例子,截图投屏功能OOM。导航通过AIDL传输5Hz的图片给中间件,期间发生OOM。调查下来发现allocateDirect方法会申请连续内存,改用allocate可以申请非连续内存以避免OOM,但是后续仍有OOM,继续调查发现是因为我创建图片存储对象byte数组和Bitmap时,每次都会new对象,这个会造成内存抖动,如果GC来不及释放内存,那么最终会导致OOM。改法是做缓存,不是每次都创建对象,而是只有对象为空的时候才创建新的对象
问:你缓存的时候,用到特殊对象么?
答:没有,缓存的时候就是将变量的声明从方法内移动到类里面,比如创建的byte数组和bitmap对象的生命都是从方法内移动到类里面
复盘:这个回答没有理解面试官的意图。问"特殊对象"是在暗示:直接缓存Bitmap和byte数组,可能会持续占用大量内存,导致其他问题。你应该回答的是如何控制缓存的大小和生命周期(比如LRU缓存、软引用等)。
///TODO
问:你刚刚说的这个投屏功能和导航进程应该不是同一个进程吧,你们是如何通信的,为什么采用这个通信方式
答:不是同一个进程,但是我们没有直接和副屏进程进行通信,在系统框架中,我们是将图片通过AIDL传输给中间件的,这个方案是系统框架决定的,客户要求我们用这样的方式进行通信。
问:你刚刚提到了Aidl 你认为Aidl发消息,其内部是多线程还是单线程?
答:单线程
问:其实是多线程,Aidl内部会使用binder线程池进行消息的发送,所以你如果不做处理,消息可能乱序,比如你发送的消息是1234,收到的消息可能是3214,你有做相关处理么?
答:我们属于客户端,单线程阻塞调用AIDL的sendIamge接口,可以确保发送顺序和接收顺序
问:图片发送的时候需要进行序列化和反序列化,你直到这是为什么么
答:可能是因为序列化后图片尺寸会变小吧?
问:你对平台化的理解?
答:底层通用,比如地图引擎,搜索,求路等,上层界面分flavor,通过继承关系,各个厂商特殊的定制部分为子类部分,公共逻辑为基类。另外有特殊接口也可以通过adapter适配特殊接口
问:如何分析ANR
答:主要看ANR的日志在哪,如果logcat日志没有,就去anr下的trace文件看看,实在没有就尝试复现,混淆代码打印的堆栈信息可能会被加密,这需要找到版本号对应的mapping文件,去解析加密的方法具体是哪个方法。
问:ANR有哪一类?这个ANR发生的条件是什么?
答:activity fragment 应该是5s超时,service是不是更长 10s或20s?记不清了,我们比较关注这个ANR如何产生和修复,不是很关系具体需要花多久会造成ANR
问:你在ANR日志看到的main线程 最多的堆栈对应的哪一行日志对应到哪个方法?
答:没有理解问题
问:单例如何写
答:Kotlin有object来写单例,Java的话一般使用双重校验的单例,使用前判断一次instance是否为空,如果不为空,那么返回实例,如果为空,那么加锁,再判断一次然后创建实例
问:这种实现,如果我用反射能不能绕开这个单例
答:不是很清楚
问:如果我不想外部用反射绕过这个单例,应该怎么做?
答:我们不是给外部提供sdk的,所以这样的场景考虑地比较少
问:导航相当于一个服务,可能连接很多客户端,这时需要创建一个注册服务和一个反注册服务的接口,这样的接口应该如何实现?
答:不清楚
问:很多client端需要用到你的导航服务,现在有一个request,客户端注册了一个监听,我们希望request不要阻塞原先的请求,我希望拿到注册的监听随时给他一个返回
答:没有懂
问:正常的内存抖动不会造成OOM,为什么你的之前AIDL传图片的例子会造成OOM
答:因为我创建的对象是图片,图片没有压缩时,尺寸比较大,压缩后也有0.1M左右,帧率又有5Hz,如果内存紧张,资源来不及释放,就可能OOM
问:针对图片这种对象,JVM是如何做回收的?
答:在方法里面定义的对象,在方法体结束后,就相当于图里一个孤立的节点,JVM在检测到对象没有可达路径时就会将对象回收
问:如何实现生产者消费者模式?你会考虑什么方面
答:我不清楚生产者消费者如何实现,但是我知道handler有用到生产者消费者模式,例如hander的sendmsg相当于生产了一个对象,looper在message队列中loop到msg然后执行handle msg时就是消费对象
问:你如何使用AI辅助工具
答:简单的基础知识,有遗忘的问网页的AI,这样回答比较迅速;复杂的问题,用GitHub copilot,他能读取源码,分析地不够到位,但是AI不够智能,可以AI和人工结对编程,一起分析问题,印证AI说的对不对
问:你有用AI工具做需求么,如何做的?
答:用提示词的方式做,但是AI做可能有问题,需要后期代码review
问:有什么问题么 略