Android中高级开发工程师-面试记录-长期更新(二)
由于篇幅原因,接上面的一篇继续:Android中高级开发工程师-面试记录-长期更新
JD一面 2021-05-26 20:30 电话面试
1、Kotlin和Java的运算符可以重载么?协程和线程的区别
2、自定义View和自定义ViewGroup的区别
3、onMeasure、onLayout、onDraw方法的先后顺序,有没有哪些方法可以触发执行,invalidate、layout、postinvalidate方法,还有没有其他方法经常需要重写?
4、性能调优方面的理解
5、SVG可以修改图片的颜色么?webp的使用有哪些限制?
6、同一个图片放到不同文件夹下加载到内存的大小是一样的么?
7、MVVM和MVP的一些区别,讲讲你理解的MVVM
8、头条适配方案的原理讲讲,是怎么动态修改density的
9、AIDL简单讲解一下。底层实现的是什么机制?
10、Binder机制了解
11、Handler的机制,详细的
12、关于Handler有一个需求,一个消息要立刻执行,要怎么做?
13、Handler同步屏障异步消息的概念的理解
14、平时工作有处理过OOM的问题么?系统抛出OOM的原因有哪些
15、Android系统在内存不足时,依据什么来杀掉一些应用的?
1、Kotlin的运算符可以重载么?协程和线程的区别?
答:
- 可以的,Java中不支持运算符重载,但是Kotlin中支持运算符重载,运算符重载是对已有的运算符赋予新的含义,使一个运算符作用于不同类型的数据会有对应这个类型的行为。使用operator关键字来修饰的,主要也是对比如plus、times等等一些函数的重载。
- 协程其实就是一个线程框架吧,网络上有说线程是阻塞式的,协程是非阻塞式的,我感觉其实不大对,所有代码都是阻塞式的,单线程下阻塞式,多线程下切换了一个线程,原线程就非阻塞式了,协程其实只是内部悄悄的帮你做了一个切线程的一个操作而已,所以可以说协程是一个阻塞式写法的非阻塞式。我记得官方文档有举了一个例子说开十万个协程和开十万个线程的对比来说明协程是一种轻量级的线程,我感觉这个说法是官方的一个误导性的说法,代码里好像是直接new了十万个Thread,来对比的,实际上协程的对比应该是和Executor线程池来对比,比如使用Executors.newSingleThreadScheduledExecutor的话,其实效率就没有很大的差别了。
- 协程方便的地方就是在同一个代码块中进行线程切换,Kotlin的协程可以用同步的方式写出异步的代码,可以消除Java带来的回调式的一些写法,比如有两个请求,需要并发执行,然后将两个结果进行合并后更新到页面上,用Java来实现可能就不好操作了,因为你不知道哪一个接口先请求完成,所以就可能会选择串行的方式去做了,这样性能上可能就多损耗了一倍了,而Kotlin可以使用挂起函数来做,分别执行两个请求,然后进行合并,没有了回调也提高了一些性能。协程会使用Dispatchers调度器在挂起函数执行完成后自动切回到原先执行的线程,也就是挂起和恢复。实际上挂起的操作并不是suspend关键字的作用,真正的挂起操作是Dispatchers来做的。suspend关键字的作用只是一个提醒的作用,告诉调用者说这个函数是一个耗时方法,要在协程里调用。
2、自定义View和自定义ViewGroup的区别
答:自定义View和自定义ViewGroup的区别主要在与onLayout方法的重写吧,自定义ViewGroup需要对子View 进行布局的一个操作,所以需要重写onLayout方法。自定义View和自定义ViewGroup,他们都需要进行测量和绘制,ViewGroup不仅需要测量自身,还要调用measureChild方法去循环子View并对子View进行测量,同时他们也都需要重写onDraw方法,onDraw方法会经常被调用,所以需要尽量避免在onDraw方法中创建对象。
3、onMeasure、onLayout、onDraw方法的先后顺序,有没有哪些方法可以触发执行,invalidate、layout、postinvalidate方法,还有没有其他方法经常需要重写?
答:他们的先后顺序是onMeasure、onLayout、onDraw,也就是先测量,才知道应该布局在哪里,之后才会进行绘制的操作。postinvalidate和invalidate的主要区别就是postinvalidate方法实现了Handler消息机制,可以让开发者在非UI线程也能调用刷新View的方法,最终还是调用到了invalidate方法。invalidate方法最终会回调到onDraw方法,而要回调onMeasure方法和onLayout方法的话可以使用requestLayout。也就是说,如果View只是需要重新绘制而没有改变位置的话,可以使用invalidate(非UI线程可以使用postinvalidate方法),如果View的位置发生了变化的话,可以使用RequestLayout方法。自定义View/ViewGroup的过程中,还有onSizeChanged()、onFinishInflate()这两个方法也是经常重写的方法吧,onFinishInflate方法只有在布局文件中加载View实例的时候会回调这个方法,如果使用new的方式不会回调这个方法,这个方法会在onMeasure方法之前调用。onSizeChanged方法一般是在视图大小发生改变的时候回调,之后回调onlayout方法进行重新布局。
总结一下:自定义View时,最主要重写onMeasure()、onDraw方法,还有经常重写的还有onSizeChanged(),自定义ViewGroup时,比自定义View多一个onLayout方法,onFinishInflate()重写的不多倒是,要了解一下invalidate()、postinvalidate()、requestLayout()方法的作用,还有onDraw方法中的paint、path、canves等类
4、你做过哪些性能调优方面的优化,或者你没做过,了解过的也可以讲讲。
答:内存优化方面主要是有几个方面的吧,APP端给用户最为直观的感受就是APP启动快慢、使用时页面流畅度、下载的时候安装包的体积、使用过程中的稳定性(奔溃/ANR)、然后现在用户比较关心的就是耗电量(这个就和个人的一些代码习惯上有一些关系了,包括对内存、算法等等)
我在项目中做过的有:
- APP启动优化:在application中的onCreate()会经常做一些SDK的初始化操作,就会对一些非必要及时初始化的SDK做一些异步初始化的操作(阿里有一个alpha框架),尽量不做或者异步去做一些计算来加快启动速度,同时启动页布局尽量简单。同时,我们可以将APP的主题背景设置为透明,这样虽然会有一点卡顿的现象,但是至少不会白屏。
- 页面流程度上的优化:主要就是减少层级的嵌套,手机开发者模式中有一个可以看过渡绘制的,可以用来分析哪些地方过渡绘制。使用include、merge来重用布局,减少一个层级的嵌套,利用ViewStub来防止提前加载某些不需要及时展示的View等。Android也推出了一个新的布局叫约束布局(ConstraintLayout),用来减少布局的层级嵌套。还有就是因为Android页面刷新机制是16ms刷新一次,所以不要在UI线程去做一些计算,超过16ms的就会造成一些卡顿现象。
- APK的瘦身优化:用户在下载的时候就会关注这个APK的大小,在用户体验上就需要对APK大小来进行一些优化了。这个主要就是针对一些图片压缩、SVG、webp等占用资源较小的图片。尽量删除一些无用资源以及一些不需要的so包,还有就是混淆也可以减少一部分的APK大小。
- 稳定性的优化主要在监控及平时的代码习惯吧,可以用Thread.UncaughtExceptionHandler来捕获全局异常,然后上传异常信息到自己的服务器,也可以使用第三方平台来统计这些错误信息,当然第三方平台可能增加APK文件的大小。然后稳定性主要还有一方面在于ANR的处理吧,也有一些收集ANR的手段,我主要做的就是在平时开发过程中尽量避免,如果出现也可以通过去查看data/anr目录下的trace.txt文件,从文件中也可以分析出哪些地方出现的ANR异常。
- 电量网络优化最主要就是减少对计算以及网络请求时做一些不必要请求的缓存之类的,当然很多方面的优化都可以减少电量的使用,渲染、内存啊等等,那些优化了,其实也是对电量优化方面做了一些贡献。特别耗电的APP,用户也不大喜欢用,毕竟现在的人最怕的就是手机没电了。
- 还有用户可能不那么直观感受到的就是内存方面的优化,这个主要就是在内存泄漏方面以及图片的使用上做一些优化吧,内存泄漏可以利用一些工具来分析,AndroidStudio上就有一个工具叫profiler,可以看电量、网络、内存等等一些信息。然后图片的使用是Android中内存占用最大的一块吧,非必要的一些图片文件尽量放到xxxhdp目录下,因为同一张图片不同文件夹目录下加载到内存中占用内存大小是不一样的,越大占用内存越小。
优化上的东西我了解的大概就是这些了吧,之前也分析过一些,可以看上篇文章,这里就再说说。
5、SVG可以修改图片的颜色么?webp的使用有哪些限制?
答:SVG可以修改图片颜色,SVG是一个xml格式的,可以手动修改里面的一个fillColor属性,或者也可以使用代码动态的修改颜色,主要是通过setTint方法来修改的。SVG使用上,在5.0以下需要在gradle里面配置允许使用。在Android 4.0(API level 14)中支持有损的WebP图像,在Android 4.3(API level 18)和更高版本中支持无损和透明的WebP图像,但是目前来看 6.0以下的手机应该都比较少了吧,我感觉甚至可以放弃支持4.0的适配了。
6、同一个图片放到不同文件夹下加载到内存的大小是一样的么?
这个请看我的另外一篇文章,里面详细解释了图片加载到内存中是怎么计算内存大小以及放到不同文件夹下加载到内存中的大小是怎么样的。Bitmap相关
这里给一个结论吧:
- 在同一个设备上,图片放在依次放在由低到高的分辨率目录中(mdpi~xxxhdpi),图片的 Bitmap 内存的大小不断减小。
- 在同一个分辨率目录中,依次运行在由低到高的分辨率设备上,图片的 Bitmap 的大小不断增加。
7、MVVM和MVP的一些区别,讲讲你理解的MVVM
这里其实主要说一下自己的见解,也没什么标准答案吧
答:MVVM主要是基于数据的双向绑定及观察者模式来实现的,MVP主要是通过共同实现一个View接口来实现的,MVP模式下处理不妥当的话,有可能造成内存的泄漏,因为都持有了View的一个引用。MVVM比MVP来讲更加的解耦,同时写法上也感觉更加的简便。感觉要是简单的项目,就用MVC来做来,复杂点的还是MVVM的优势较大,这个是我个人的一个感受。对于这个问题来讲,其实没有好坏之分,只有合适与不合适的说法,就是存在即有意义。
8、头条适配方案的原理讲讲,它是怎么动态修改density的
答:我们常用的 px 转 dp 的公式 dp = px / density,头条的适配方案其实就是动态计算density的值,根据设计稿的dp值,来动态计算在每个手机上的density的值,也就能得到相应的px的值。比如一个540dp的设计稿,在1080px手机上时,算出来的density值为 1080/540 = 2,在540px手机上时,算出来的density值为:540/540 = 1,假如一个View的宽度为50dp,那在1080px手机上所占用的像素就为50dp = 100px,在540px手机上所占像素为50dp= 50px,则它们的 百分比值是相等的 100/1080 = 50/540。
这个density是DisplayMetrics类中的属性,实际上是在要修改的页面中的onCreate方法setContentView方法前进行修改或者通过application中进行修改,application中修改就是全局的,其他页面不需要适配的话就可以在其他页面上重新进行修改这个值就好。
9、AIDL简单讲解一下。底层实现的是什么机制?
答:AIDL是进程间通信的一种方式,底层实现是通过Binder机制来实现的。Binder机制底层又是通过mmap内存映射原理来实现的,内存分为用户空间和内核空间,Binder机制通过一次一次的数据拷贝来传递数据。
10、Binder机制了解
答:每个进程的内存空间都分为用户空间和内核空间,用户空间和内核空间之间的通信需要native层的方法来实现,方法叫copy_form_user()。进程A的内核空间和进程B的用户空间通过Linux的一个mmap方法来创建出一个共享的物理内存地址,然后进程A的用户空间和进程B的用户空间进行通信的话,就只需要进程A的用户空间往进程A的内核空间进行一次数据拷贝(copy_from_user),进程B的用户空间与进程A的内核空间通过mmap内存映射的方式,就能在共享的物理能存中获取到进程A拷贝的数据,就实现了通信。
11、Handler的机制
Handler机制之前有说过,这里不再重复了,但是要详细的了解内容还是非常多的,看到这里的话,几乎每个大厂都会问Handler机制,所以还是需要去多了解一些。
12、关于Handler有一个需求,消息队列里还有很多没有执行的消息,有一个消息要立刻执行,要怎么做?
答:这个问题当时真不知道怎么处理,其实是第13个问题的答案,Handler发送消息其实是同步执行的,发送的消息都在消息队列中进行排队的。要做到立刻执行,可以通过异步消息的方式,这里就需要了解同步屏障和异步消息机制了。直接到第13问吧
13、Handler同步屏障异步消息的概念的理解
答:同步屏障,让异步消息不用排队等候处理。可以理解为同步屏障是一堵墙,把同步消息队列拦住,先处理异步消息,等异步消息处理完了,这堵墙就会取消,然后继续处理同步消息。
- 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
- 屏障消息和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
- postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
- postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
- 插入普通消息会唤醒消息队列,但是插入屏障不会。
在ViewRootImpl.scheduleTraversals()UI刷新的源码中中见过同步屏障异步消息的用法。
public void postSyncBarrier() {
Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
token = (int) method.invoke(Looper.getMainLooper().getQueue());
}
public void removeSyncBarrier() {
Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier", int.class);
method.invoke(Looper.getMainLooper().getQueue(), token);
}
发送异步消息比较简单,调用Message的setAsynchronous(boolean flag)方法即可,甚至可以直接创建一个异步Handler。
14、平时工作有处理过OOM的问题么?系统抛出OOM的原因有哪些?
答:OOM就是内存溢出,现在加载图片也都用Glide等图片加载库了,就比较少出现加载图片的OOM。OOM出现的原因归结到底其实就是因为程序进程需要申请一块内存,但是系统无法提供需要的内存大小,就会抛出OOM异常。内存泄漏是OOM的一个很大原因,还有就是图片,资源对象未关闭、注册的服务未及时反注册、图片太大等等造成OOM异常。
15、Android系统在内存不足时,依据什么来杀掉一些应用的?
答:通常在一部Android手机里同时运行着多个应用(app),每个app对应一个系统进程,当系统需要更多的资源(如内存)而空闲资源不足时,Android系统就会选择杀掉一些“低优先级”的进程以便释放所需资源。如果一个app正在与用户交互,那么它所在的进程具有最高优先级;其次,如果一个app是可见的,例如被一个对话框部分遮挡,它所在进程具有第二高的优先级;再次,如果app当前是不可见的,也就是被切换到了后台,则它所在进程具有第三高的优先级;这里要补充一点,如果这个后台app启动了一个service,则它比一般的后台app优先级高一些。最后,如果一个进程里没有包含任何app(空进程),这个进程的优先级是最低的。