以下主要针对往期收录的面试题进行一个分类归纳整理,方便大家统一回顾和参考。本篇是第四集~
强调一下:因篇幅问题:文中只放部分内容,全部面试开发文档需要的可在公众号<Android苦做舟>获取或找作者领取。
第一篇面试题在这: Android中高级进阶开发面试题冲刺合集(一)
第二篇面试题在这: Android中高级进阶开发面试题冲刺合集(二)
第三篇面试题在这: Android中高级进阶开发面试题冲刺合集(三)
Android 性能调优相关
1.谈谈你对Android性能优化方面的了解?
参考答案:
启动优化: application中不要做大量耗时操作,如果必须的话,建议异步做耗时操作
布局优化:使用合理的控件选择,少嵌套。(合理使用include,merge,viewStub等使用)
apk优化(资源文件优化,代码优化,lint检查,.9.png,合理使用shape替代图片,webp等)
性能优化,网络优化,电量优化
避免轮询,尽量使用推送。应用处于后台时,禁用某些数据传输限制访问频率,失败后不要无限重连选用合适的定位服务(GPS定位,网络定位,被动定位)使用缓存startActivityForResult替代发送广播
内存优化
循环尽量不使用局部变量避免在onDraw中创建对象,onDraw会被频繁调用,容易造成内存抖动。循环中创建大的对象,也是如此。不用的对象及时释放数据库的cursor及时关闭adapter使用缓存注册广播后,在生命周期结束时反注册及时关闭流操作图片尽量使用软引用,较大的图片可以通过bitmapFactory缩放后再使用,并及时recycler。另外加载巨图时不要 使用setImageBitmap或setImageResourse或BitmapFactory.decodeResource,这些方法拿到的都是bitmap的对象,占用内存较大。可以用BitmapFactory.decodeStream方法配合BitmapFactory.Options进行缩放避免static成员变量引用资源耗费过多实例避免静态内部类的引用
2.一般什么情况下会导致内存泄漏问题?如何解决
参考答案:
资源对象没关闭造成的内存泄漏(如:Cursor
、File
等)Bitmap
对象不在使用时调用recycle()释放内存集合中对象没清理造成的内存泄漏(特别是static
修饰的集合)接收器、监听器注册没取消造成的内存泄漏Activity
的Context
造成的泄漏,可以使用ApplicationContext
Handler
造成的内存泄漏问题(一般由于Handler
生命周期比其外部类的生命周期长引起的)
3.自定义 Handler 时如何有效地避免内存泄漏问题?
参考答案:
1.自定义的静态handler 2.可以加一个弱引用 3.还有一个主意的就是当你activity被销毁的时候如果还有消息没有发出去 就remove掉吧 4.removecallbacksandmessages去清除Message和Runnable 加null 写在生命周的ondestroy()就行
4.哪些情况下会导致OOM问题?如何解决?
参考答案:
1,过多的内存泄漏会导致内存溢出 2,加载大的图片 3,创建过多的线程
内存优化的解决方法: 1.申请更大的内存,比如多进程、设置manifest中的largeHeap=true等。 2.减少内存使用 ①使用优化后的集合对象,分场景使用SpaseArray和HashMap; ②使用微信的mmkv替代sharedpreference; ③使用StringBuilder替代String拼接 ④统一带有缓存的基础库,特别是图片库,如果用了两套不一样的图片加载库就会出现2个图片各自维护一套图片缓存 ⑤给ImageView设置合适尺寸的图片,列表页显示缩略图,查看大图显示原图 ⑥优化业务架构设计,比如省市区数据分批加载,需要加载省就加载省,需要加载市就加载失去,避免一下子加载所有数据 3.避免内存泄漏
5.ANR 出现的场景以及解决方案?
参考答案:
场景: 1、触摸无响应5s 2、BroadCastReciver 前台处理超过10s 后台超过60s 3、Server 前台处理超过20s 后台超过200s
ANR出现的类型有两种 1、主线程耗时导致 2、CPU、内存、IO 占用过高资源耗尽(其他进程也可以导致)
如何避免: 1、不要在主线程中做耗时的操作 2、避免CPU占用过高,简化方法,减少执行时间 3、避免内存占用过高,防止内存泄漏
6.谈谈 Android 中内存优化的方式?
参考答案:
关于内存泄漏,一般像单例模式的使用不当啊、集合的操作不当啊、资源的缺乏有效的回收机制啊、Handler、线程的使用不当等等都有可能引发内存泄漏。
单例模式引发的内存泄漏: 原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用 优化:改为持有Application的引用,或者不持有使用的时候传递。
集合操作不当引发的内存泄漏: 原因:集合只增不减 优化:有对应的删除或卸载操作
线程的操作不当引发的内存泄漏: 原因:线程持有对象的引用在后台执行,与对象的生命周期不一致 优化:静态实例+弱引用(WeakReference)方式,使其生命周期一致
匿名内部类/非静态内部类操作不当引发的内存泄漏: 原因:内部类持有对象引用,导致无法释放,比如各种回调 优化:保持生命周期一致,改为静态实例+对象的弱引用方式(WeakReference)
常用的资源未关闭回收引发的内存泄漏: 原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭 优化:使用后有对应的关闭和卸载机制
Handler使用不当造成的内存泄漏: 原因:Handler持有Activity的引用,其发送的Message中持有Handler的引用,当队列处理Message的时间过长会导致Handler无法被回收 优化:静态实例+弱引用(WeakReference)方式 内存溢出: 原因: 1.内存泄漏长时间的积累 2.业务操作使用超大内存 优化: 1.调整图像大小后再放入内存、及时回收 2.不要过多的创建静态变量
7.谈谈布局优化的技巧?
参考答案:
1.减少过度绘制,减少不必要的背景绘制,线性,相对,约束层级一样的用线性,层级多的用相对或者约束来控制,减少层级 2.减少布局深层次嵌套,配合merge,include,viewStub,约束布局这些来减少布局的开销 3.尽量减少控件个数,对 TextView 左边或者右边有图片可是试用 drawableLeft,drawableRight 4.熟悉API尽量借助系统现有的属性来实现一些UI效果 5.对于 ImageVIew 通过imageDrawable方法进行设置避免ImageView的background和imageDrawable重叠 6.使用Canvas的clipRect和clipPath方法限制View的绘制区域
8.对于 Android 中图片资源的优化方案你知道哪些?
参考答案:
首先我们可以对图片进行二次采样,从本质上减少图片的内存占用。就是将大图片缩小之后放入到内存中,以实现减小内存的目的 2.其次就是采用三层缓存架构,提高图片的访问速度。三层缓存架构是内存-文件-网络。
内存是访问速度最快的部分但是分配的空间有限,所以不可能占用太多。其中内存缓存可以采用LRU算法(最近最少使用算法),来确定要删除内存中的那些图片,保存那
些图片。
文件就是将图片保存到本地,可以使SD卡中,也可以是手机内部存储中。
网络就是访问网络下载图片,进行图片的加载。 3.常见的png,JPG,webp等格式的图片在设置到UI上之前需要经过解码过程,而图片采用不同的码率,也会造成对内存的占用不同。 4.最后一点,也是图片优化最重要的一点。重用Bitmap. 5.不使用bitmap要记得实时回收,减小内存的开销
9.Android Native Crash 问题如何分析定位?
参考答案:
日志解析命令
ndk-stack
此命令即可以即时定位也可以将日志文件输出到某个路径后再定位,两者的区别在于后者比较灵活,可以再次分析,可查看文件名、方法名和行号
addr2line
根据名称的理解,意思是将地址转换成行号,没错,确实是这样的,日志文件中只有内存地址,对我们来说毫无意义,因此,我们要使用此命令将内存地址转换成我们能够看得懂的信息,如文件名、行号,缺陷是此命令不提供方法名
objdump
将so文件转换成asm(汇编)文件,在asm文件中通过内存地址即可找到发生问题的方法名
线上分析
上面的这些命令只能够进行本地分析,如果说用户的手机发生了native crash怎么办呢?这种情况我们需要集成收集BUG的相关SDK ,如Testin,集成后,如果有客户发生错误,我们就可以在Testin后台查看定位信息了
两种方式
1、单个查找
在日志文件中根据包名查找指针地址使用addr2line将地址解析成具体的行号
2、全部查找
直接使用ndk-stack命令即可
10.该如何给 Apk 瘦身?
参考答案:
代码优化混淆,资源过滤没有引用的,xml 文件名混淆,so最低兼容,图片使用webp或svg,删除打包后多屏幕支持的资源,apk解压再zip压缩签名
11.说一下你是如何优化 App 启动过程的?
参考答案:
1.把application oncreate 中要执行的方法 分为同步和异步,尽量去延迟执行 或者使用空闲线程 去初始化一些方法 2.配置一个启动背景,避免白屏或者黑屏,然后做一个空的Activity这个Activity只做一件事,就是跳转到真的Activity,因为 启动速度 和application oncreate的耗时和第一个Activity的绘制有关, 上面都是easy的做法
利用 redex 工具 优化 dex , 因为 class字节码 分布在不同的dex中,所以启动的时候必须逐个查找一些文件,他们散列分布在不同的dex中,查找起来耗时又不方便,利用redex 把相关的class 放在同一个dex包下,避免 同一个dex包被多次查找
4.在attachedbaseContext中 新起一个进程 去加载 mutildex 可以加速App启动页的打开(可能在启动页中会等待,但是加速了从launcher到启动页的速度)
12.谈谈代码混淆的步骤?
参考答案:
代码混淆的目的: 1、代码混淆之后会随机生成难易理解的类名、方法名以及属性名,加大反编译的难度; 2、可以对apk进行优化,缩小包的体积;
代码混淆的步骤: 1、在gradle文件里面打开配置:minifyEnabled true; 2、在proguard-rules.pro 文件里面配置混淆规则; 在配置混淆的时候,需要注意:1、四大组件以及view可以不进行混淆;2、实体类不进行混淆;3、第三方库根据各自的要求配置混淆规则;
13.说说 App 的电量优化?
参考答案:
避免轮循。可以利用推送。如果非要轮循,合理的设置频率。应用处于后台时,避免某些数据的传输,比如感应器,定位,视频缓存。页面销毁时,取消掉网络请求。限制访问频率,失败后不要无限的重连。合理的选择定位精度和频率。使用缓存。如果数据变化周期比较长,可以出一个配置接口,用于记录那些接口有变化。没变化的直接用缓存。减少广播的使用频率。可以用观察者,startActivityForResult等代替。
14.谈谈如何对 WebView 进行优化?
参考答案:
可以试下这些方法进行优化:
单/多进程化:webView在独立的进程里面,那么WebView的进程崩溃不会影响到主进程运行;同时WebView的安 全漏洞也很难影响到主进程;如果是多进程的话,可以使用WebView的容器池,有二次秒开的作用;不过缺点就是需要你做好和WebView的跨进程通讯了网络优化:我们可以让WebView的host和客户端的host保持一致,那么就达到复用DNS缓存的效果;如果客户端有针对网络请求进行了优化,那么可以让WebView的全部网络请求托管给客户端H5离线包:这个是手Q的H5方案之一,让客户端提前去下载离线的H5数据包,WebView只需要加载本地H5数据包即可,这么做不仅可以避免一些http的劫持,而且跳过了WebView的建立TCP连接和H5、CCS等数据下载的过程,直接开始UI渲染,大大提高了WebView的效率
15.如何处理大图的加载?
参考答案:
1、首先确定大图的用途,精度需求: a)完整显示,对精度要求不高,图片本身就很大 b)对精度需求比较高,不需要完整显示 2、解决方案 a)针对第一种的处理图片本身,按需加载(根据显示设备本身大小进行缩放),降低精度加载(改变图片模式,如将ARGB8888改成RGB565,ARGB4444),修改图片格式(png改成webp,jpg) b)第二种的一般采用局部加载,主要要用到的是BitmapRegionDecoder这个类decodeRegion的方法,读取图片指定大小的数据,然后通过移动来动态改变显示区域的图片
16.谈谈如何对网络请求进行优化?
参考答案:
1.为避免DNS解析异常问题,可以直接使用 IP 建立连接; 2.使用 Gzip 压缩 Response 减少数据传输量;使用 Protocol Buffer 代替 JSON; 3.请求图片的 url 中可以添加 格式、质量、宽高等参数;使用缩略图、使用 WebP格式图片,大图分片传输; 4.使用网络缓存,使用图片加载框架; 5.监听设备网络状态,根据不同网络状态选择对应情况下的网络请求策略:网络良好和弱网、离线等情况下分别设计不同的请求策略,比如 WIFI 下一个请求可以获取几十个数据,甚至可以一次性执行多个请求;而弱网下一个请求获取几个数据,且文本类型优先,富文本其次,除文本数据外其它类型的数据一开始只显示占位符;离线下事先保存请求数据到磁盘,在离线时从磁盘加载数据。
17.请谈谈如何加载Bitmap并防止内存溢出?
参考答案:
首先我们 要知道bitmap内存是怎么计算的例子:
手机屏幕大小 1080 x 1920(inTarget = 420),加载 xhdpi (inDensity = 320)中的图片 1920 x 1080,scale = 420 / 320, 最总我们可以得知 他的占用内存 1418 * 2520 * 4 很明显 被放大了。 防止内存溢出:1对图片进行内存压缩; 2.高分辨率的图片放入对应文件夹; 3.内存复用 4.及时回收
Android 中的 IPC
1.请回答一下 Android 中进程间通信有哪些方式?
参考答案:
2.请谈谈你对 Binder 机制的理解?
参考答案:
Binder机制:1.为了保证进程空间不被其他进程破坏或干扰,Linux中的进程是相互独立或相互隔离的。 2.进程空间分为用户空间和内核空间。用户空间不可以进行数据交互;内核空间可以进行数据交互,所有进程共用一个内核空间。 3.Binder机制相对于Linux内传统的进程间通信方式:(1)性能更好;Binder机制只需要拷贝数据一次,管道、消息队列、Socket等都需要拷贝数据两次;而共享内存虽然不需要拷贝,但实现复杂度高。(2)安全性更高;Binder机制通过UID/PID在内核空间添加了身份标识,安全性更高。 4.Binder跨进程通信机制:基于C/S架构,由Client、Server、Server Manager和Binder驱动组成。 5.Binder驱动实现的原理:通过内存映射,即系统调用了mmap()函数。 6.Server Manager的作用:管理Service的注册和查询。 7.Binder驱动的作用:(1)传递进程间的数据,通过系统调用mmap()函数;(2)实现线程的控制,通过Binder驱动的线程池,并由Binder驱动自身进行管理。 8.Server进程会创建很多线程处理Binder请求,这些线程采用Binder驱动的线程池,由Binder驱动自身进行管理。一个进程的Binder线程池默认最大是16个,超过的请求会阻塞等待空闲的线程。 9.Android中进行进程间通信主要通过Binder类(已经实现了IBinder接口),即具备了跨进程通信的能力。
3.什么是 AIDL?它的使用场景是什么?
参考答案:
AIDL:AIDL是用于进程间的通迅 能够实现进程间的一对多实时并发通迅 通过定义AIDL接口 创建一个服务将接口暴露给客服端 客户端把定义的ALDL文件复制到项目中 通过bindService绑定服务端服务 并通过 定义AIDL.Stub.asInterface(service)将服务端返回的Binder代理对象转换成AIDL接口所属的类型 调用方法即可
Android 系统 SDK 相关
1.请简要谈谈 Android 系统的架构组成?
参考答案:
android系统分为四部分,从高到低分别是:
1、Android应用层 Android会同一系列核心应用程序包一起发布,该应用程序包包括email客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等。所有的应用程序都是使用JAVA语言编写的。
2、Android应用框架层 开发人员也可以完全访问核心应用程序所使用的API框架。该应用程序的架构设计简化了组件的重用;任何一个应用程序都可以发布它的功能块并且任何其它的应用程序都可以使用其所发布的功能块(不过得遵循框架的安全性限制)。同样,该应用程序重用机制也使用户可以方便的替换程序组件。
3、Android系统运行层 Android 包含一些C/C++库,这些库能被Android系统中不同的组件使用。它们通过 Android 应用程序框架为开发者提供服务。 4、Linux内核层 Android 的核心系统服务依赖于 Linux 2.6 内核,如安全性,内存管理,进程管理, 网络协议栈和驱动模型。 Linux 内核也同时作为硬件和软件栈之间的抽象层。
2.SharedPreferences 是线程安全的吗?它的 commit 和 apply 方法有什么区别?
参考答案:
1.SharePreferences是线程安全的 里面的方法有大量的synchronized来保障。 2.SharePreferences不是进程安全的 即使你用了MODE_MULTI_PROCESS 。 3.第一次getSharePreference会读取磁盘文件,异步读取,写入到内存中,后续的getSharePreference都是从内存中拿了。 4.第一次读取完毕之前 所有的get/set请求都会被卡住 等待读取完毕后再执行,所以第一次读取会有ANR风险。 5.所有的get都是从内存中读取。 6.提交都是 写入到内存和磁盘中 。apply跟commit的区别在于 apply 是内存同步 然后磁盘异步写入任务放到一个单线程队列中 等待调用。方法无返回 即void commit 内存同步 只不过要等待磁盘写入结束才返回 直接返回写入成功状态 true or false 7.从 Android N 开始, 不再支持 MODE_WORLD_READABLE & MODE_WORLD_WRITEABLE. 一旦指定, 会抛异常 。也不要用MODE_MULTI_PROCESS 迟早被放弃。 8.每次commit/apply都会把全部数据一次性写入到磁盘,即没有增量写入的概念 。 所以单个文件千万不要太大 否则会严重影响性能。 建议用微信的第三方MMKV来替代SharePreference
3.Serializable 和 Parcelable 有哪些区别?
参考答案:
Parcelable 的读写顺序必须一致, 为什么要这样呢?Intent 传值有大小限制;
4.请说一下 Android 7.0 的新特性?
参考答案:
特性: 1,新的Notification 2,多窗体支持(分屏模式),并行运行两个应用 3,VR支持
性能和效率: 1,随时随地的低耗电模式,省电 2,高速的应用安装路径,提高安装应用和系统更新速度
安全: 1)私有文件夹,限制访问 2)应用间共享文件,需要通过FileProvider 3)Https的抓包,网络安全性配置,将粒度缩小到应用级别
5.谈谈 ArrayMap 和 HashMap 的区别?
参考答案:
1.查找效率 HashMap因为其根据hashcode的值直接算出index,所以其查找效率是随着数组长度增大而增加的。 ArrayMap使用的是二分法查找,所以当数组长度每增加一倍时,就需要多进行一次判断,效率下降
2.扩容数量 HashMap初始值16个长度,每次扩容的时候,直接申请双倍的数组空间。 ArrayMap每次扩容的时候,如果size长度大于8时申请size*1.5个长度,大于4小于8时申请8个,小于4时申请4个。这样比较ArrayMap其实是申请了更少的内存空间,但是扩容的频率会更高。因此,如果数据量比较大的时候,还是使用HashMap更合适,因为其扩容的次数要比ArrayMap少很多。
3.扩容效率 HashMap每次扩容的时候重新计算每个数组成员的位置,然后放到新的位置。 ArrayMap则是直接使用System.arraycopy,所以效率上肯定是ArrayMap更占优势。
4.内存消耗 以ArrayMap采用了一种独特的方式,能够重复的利用因为数据扩容而遗留下来的数组空间,方便下一个ArrayMap的使用。而HashMap没有这种设计。 由于ArrayMap之缓存了长度是4和8的时候,所以如果频繁的使用到Map,而且数据量都比较小的时候,ArrayMap无疑是相当的是节省内存的。
5.总结 综上所述,数据量比较小,并且需要频繁的使用Map存储数据的时候,推荐使用ArrayMap。 而数据量比较大的时候,则推荐使用HashMap。
6.简要说说 LruCache 的原理?
参考答案:
androidx.collection.LruCache 初始化的时候, 会限制缓存占据内存空间的总容量 maxSize; 底层维护的是 LinkedHashMap, 使用 LruCache 最好要重写 sizeOf 方法, 用于计算每个被缓存的对象, 在内存中存储时, 占用多少空间;
在 put 操作时, 首先计算新的缓存对象, 占多少空间, 再根据 key, 移除老的对象, 占用内存大小 = 之前占用的内存大小 + 新对象的大小 - 老对象的大小; put 操作最后总会根据 maxSize, 在拿到 LinkedHashMap.EntrySet 中链表的头节点, 循环判断, 只要当前缓存对象占据内存超出 maxSize, 就移除一个头节点, 一直到符合要求;
lruCache 和 LinkedHashMap 的关系: LinkedHashMap 中维护一个双向链表, 并维护 head 和 tail 指针, lruCache 使用了 LinkedHashMap accessOrder 为 true 的属性, 只要访问了某个 key, 包括 get 和 put, 就把当前这个 entry 放在链表的位节点, 所以链表的头节点, 是最老访问的节点, 尾节点是最新访问的节点, 所以, lruCache 就很巧妙的利用了这个特点, 完成了 Least Recently Used 的需求;
7.Android 中为什么推荐用 SparseArray 代替 HashMap?
参考答案:
首先并不能说完全用SparseArray替代。在key是int并且数据量小于1000的情况下,用SparseArray替代确实在空间上性能要好,但是在时间上只能接近HashMap而已。 SparseArray的key固定是int所以减少了装箱和拆箱的操作 SpareArray内部是运用两个数组进行维护一个是keys存储key的,一个是values存储value的。由于key是int所以没有hash碰撞,在查找位置时用二分查找的方式,时间上比较有优势。 存储方法由于SparseArray存储的结构比HashMap简单,不需要维护链表等。所以存储上比HashMap好
8.PathClassLoader 和 DexClassLoader 有何区别?
参考答案:
8.0前DexClassLoader多了一个optimizedDirectory,用来存dex2oat的目录,8.0后被废弃,两个完全一样
9.说说 HttpClient 与 HttpUrlConnection 的区别?为何前者会被替代?
参考答案:
区别: 1,android2.3之前,HttpUrlConnection具有一些bug,例如关闭输入流时可能导致连接池关闭。 2,android2.3之后,HttpUrlConnection才相对成熟。特点是,轻量、api少 3,HttpClient一直很强大,支持get、post、delete等其他协议。 具体可参考表格: /spec-dog/p/3792616.html
被替代原因: 太重了,api太多;针对Android系统,不便于向后维护
10.什么是Lifecycle?请分析其内部原理和使用场景?
参考答案:
Jetpack 的 Lifecycle 库:它可以有效的避免内存泄漏,解决 Android 生命周期的常见难题。 内部原理:ComponentActivity 的 onCreate 方法中注入了 ReportFragment,通过 Fragment 来实现生命周期监听。 使用场景:给 RecyclerView 的 ViewHolder 添加 Lifecycle 的能力。自己实现 LifecycleHandler,在 Activity 销毁的时候,自动移除 Handler 的消息避免 Handler 导致的内存泄漏。
11.谈一谈 Android 的签名机制?不同版本下的签名有什么不同?
参考答案:
Android 7.0中引入了APK Signature Scheme v2,v1是jar Signature来自JDK。 V1:应该是通过ZIP条目进行验证,这样APK 签署后可进行许多修改 - 可以移动甚至重新压缩文件。
V2:验证压缩文件的所有字节,而不是单个 ZIP 条目,因此,在签名后无法再更改(包括 zipalign)。正因如此,现在在编译过程中,我们将压缩、调整和签署合并成一步完成。好处显而易见,更安全而且新的签名可缩短在设备上进行验证的时间(不需要费时地解压缩然后验证),从而加快应用安装速度。
v1和v2的签名使用 1)只勾选v1签名并不会影响什么,但是在7.0上不会使用更安全的验证方式 2)只勾选V2签名7.0以下会直接安装完显示未安装,7.0以上则使用了V2的方式验证 3)同时勾选V1和V2则所有机型都没问题
12.谈谈安卓 Apk 构建的流程?
参考答案:
1) aapt 为res 目录下资源生成R.java 文件,同时为AndroidMainfest.xml 生成Mainfeset.java文件 2)aidl 将项目中自定义的aidl 生成相应的Java文件 3) javac 将项目中所有java 代码编译成class 文件,包括 业务逻辑代码, aapt 生成 java文件,aidl 生成java 文件 4) proguard , 混淆同时生成mapping.txt ,这步可选 \5) 将所有class 文件(包括第三方class 文件) 转换为dex 文件 6)aapt 打包, 将res资源,assets资源,打包成一个.ap的文件 7)apkbuilder ,将所有dex 文件,.ap文件, AndroidMainfest.xml打包为.apk 文件,这是一个未签名的apk包 8)jarsigner 对apk 进行签名 9)ziplign 对apk 进行对齐操作,以便运行时节约内存。
13.简述一下 Android 8.0、9.0 分别增加了哪些新特性?
参考答案:
10.0
ExternalStrorage文件沙盒后台限制启动activity
9.0
刘海模式,手机可以直接设计刘海模式
夜间模式
默认使用https
非 SDK 接口的限制
全面屏
后台应用:
您的应用不能访问麦克风或摄像头。 使用连续报告模式的传感器(例如加速度计和陀螺仪)不会接收事件。 使用变化或一次性报告模式的传感器不会接收事件。 如果您的应用需要在运行 Android 9 的设备上检测传感器事件,请使用前台服务。
电话信息现在依赖设备位置设置 如果用户在运行 Android 9 的设备上停用设备定位,则以下函数不提供结果:
TelephonyManager.getAllCellInfo()
TelephonyManager.listen()
TelephonyManager.getCellLocation()
TelephonyManager.getNeighboringCellInfo()
Build.SERIAL 始终设置为 “UNKNOWN” 以保护用户的隐私,如果您的应用需要访问设备的硬件序列号,您应改为请求 READ_PHONE_STATE权限,然后调用 getSerial()。
多进程 webview 信息访问限制
对使用非 SDK 接口的限制:NoSuchMethodError/NoSuchFieldException
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // Android P or above } else { // below Android P }
检测是否使用了非SDK接口 工具veridex
Apache HTTP 客户端弃用,需要自定义classloader
针对 Android 9 或更高版本并使用前台服务的应用必须请求FOREGROUND_SERVICE权限。 这是普通权限,因此,系统会自动为请求权限的应用授予此权限。
8.0
未知来源应用通知渠道应用无法使用其清单注册大部分隐式广播(即,并非专门针对此应用的广播)
14.谈谈 Android 10 更新了哪些内容?如何进行适配?
参考答案:
1、分区存储
Android Q可以在AndroidManifest文件中关闭分区存储,Android R只能借助MediaStore相关API进行适配 使用getExternalFileDir或getExternalCacheDir替换Environment.getExternalStorageDirectory 使用MediaStore共享媒体文件 2、限制从后台启动Activity 3、对不可重置的设备标识符实施了限制 使用mac、android_id或移动安全联盟SDK提供的id 4、对电话、Wifi和蓝牙一些API的访问需要定位权限 具体方案 Android 10适配攻略
15.请简述 Apk 的安装过程?
参考答案:
复制APK安装包到/data/app目录下,解压缩并扫描安装包,向资源管理器注入APK资源,解析AndroidManifest文件,并在/data/data目录下创建对应的应用数据目录,然后针对Dalvik/ART环境优化dex文件,保存到dalvik-cache目录,将AndroidManifest文件解析出的组件、权限注册到PackageManagerService并发送广播。
16.Java 与 JS 代码如何互调?有做过相关优化吗?
参考答案:
WebView?
java调js: 可以用loadUrl指定 javascript: 协议,然后带上js代码,如webView.loadUrl("javascript:alert('hello!')")
4.4以后还提供了一个execJavascript方法,更方便调用js调java: 可以先写好一个互调接口类,用addJavascriptInterface绑定好,然后js代码里调用 注意:api17以后希望被js调用的需要添加@JavascriptInterface注解,因为api17以前存在漏洞,通过互调接口的getClass()方法可以拿到Class对象,之后通过Class.forName()等一系列反射api可以调用任何方法 也可以在java层通过shouldOverrideUrlLoading拦截跳转请求,js代码里通过跳转页面传递给java
17.什么是 JNI?具体说说如何实现 Java 与 C++ 的互调?
参考答案:
在Java类中声明native方法使用javac命令将Java类生成class文件使用javah文件生成头文件编写cpp文件实现jni方法生成so库 到此为止,java就可以通过调用native方法调用c++函数了,对于在c++中调用java方法,通过完整类名获取jclass根据方法签名和名称获取构造方法id创建对象(如果要调用的是静态方法则不需要创建对象)获取对象某方法id通过JNIEnv根据返回值类型、是否是静态方法调用对应函数即可
18.请谈谈 App 的启动流程?
参考答案:
1.点击app图标,Launcher进程使用Binder IPC向systemserver进程发起startActivity请求; 2.systemserver进程收到1中的请求后,向zygote进程发送创建新进程的请求; 3.zygote进程fork出新的App进程 4.App进程通过Binder IPC向systemserver进程发起attachApplication请求; 5.systemserver进程收到4中的请求后,通过Binder IPC向App进程发送scheduleLauncherActivity请求; 6.App进程的ApplicationThread线程收到5的请求后,通过handler向主线程发送LAUNCHER_ACTIVITY消息; 7.主线程收到6中发送来的Message后,反射创建目标Activity,回调oncreate等方法,开始执行生命周期方法,我们就可以看到应用页面了。
第三方框架分析
1.谈一谈 LeakCanray 的工作原理?
参考答案:
LeakCanary 主要利用了弱引用的对象, 当 GC 回收了这个对象后, 会被放进 ReferenceQueue 中; 在页面消失, 也就是 activity.onDestroy 的时候, 判断利用 idleHandler 发送一条延时消息, 5秒之后, 分析 ReferenceQueue 中存在的引用, 如果当前 activity 仍在引用队列中, 则认为可能存在泄漏, 再利用系统类 VMDebug 提供的方法, 获取内存快照, 找出 GC roots 的最短强引用路径, 并确定是否是泄露, 如果泄漏, 建立导致泄露的引用链; System.runFinalization(); // 强制调用已经失去引用的对象的 finalize 方法
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime){// 1.. 从 retainedKeys 移除掉已经被会回收的弱引用 removeWeaklyReachableReferences();// 3.. 若当前引用不在 retainedKeys, 说明不存在内存泄漏if (gone(reference)) {return DONE;}// 4.. 触发一次gcgcTrigger.runGc();// 5.. 再次从 retainedKeys 移除掉已经被会回收的弱引用removeWeaklyReachableReferences();if (!gone(reference)) {// 存在内存泄漏 long startDumpHeap = System.nanoTime();long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);// 获得内存快照 File heapDumpFile = heapDumper.dumpHeap();if (heapDumpFile == RETRY_LATER) {// Could not dump the heap.return RETRY;}long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key).referenceName(reference.name).watchDurationMs(watchDurationMs).gcDurationMs(gcDurationMs).heapDumpDurationMs(heapDumpDurationMs).build();heapdumpListener.analyze(heapDump);}return DONE;}
2.说说 EventBus 的实现原理?
参考答案:
register注册一个订阅者 获取注册类 通过注册类获取所有的事件订阅内容 遍历所有注册类的订阅内容 执行注册订阅 将我们的订阅方法和订阅者封装到subscriptionsByEventType分发事件集合和typesBySubscriber中 如果是粘性事件的话,就立马投递、执行
事件发送逻辑获取到当前线程的事件队列 通过事件类型获取到所有订阅者集合 通过反射执行订阅者中的订阅方法
取消逻辑 1、首先通过unregister方法拿到要取消的订阅者 2、得到该订阅者的所有订阅事件类型 3、遍历事件类型,根据每个事件类型获取到所有的订阅者集合,并从集合中删除该订阅者 4、将订阅者从步骤2的集合中移除
3.谈谈网络请求中的拦截器 - Interceptor 的实现原理和使用场景?
参考答案:
系统自带的拦截器: 1,重试和重定向 2,请求头+响应头处理 3,缓存 4,dns + 三次握手 5,CallServer,读写数据流
常用的自定义拦截器: 1,日志拦截器 2,自定义缓存规则拦截器 3,重试机制等等
4.谈一谈 Glide 中的缓存机制?
参考答案:
Glide的缓存机制,主要分为2种缓存,一种是内存缓存,一种是磁盘缓存。 使用内存缓存的原因是:防止应用重复将图片读入到内存,造成内存资源浪费。 使用磁盘缓存的原因是:防止应用重复的从网络或者其他地方下载和读取数据。
具体来讲,缓存分为加载和存储: ①当加载一张图片的时候,获取顺序:Lru算法缓存-》弱引用缓存-》磁盘缓存(如果设置了的话)。
当想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,并将该图片放入WeakReference中,如果LruCache中没有,则去WeakReference中寻找,如果WeakReference中有,则从WeakReference中取出图片使用,如果WeakReference中也没有图片,则从磁盘缓存/网络中加载图片。
②将缓存图片的时候,写入顺序:弱引用缓存-》Lru算法缓存-》磁盘缓存中。
当图片不存在的时候,先从网络下载图片,然后将图片存入弱引用中,glide会采用一个acquired(int)变量用来记录图片被引用的次数, 当acquired变量大于0的时候,说明图片正在使用中,也就是将图片放到弱引用缓存当中;如果acquired变量等于0了,说明图片已经不再被使用了,那么此时会调用方法来释放资源,首先会将缓存图片从弱引用中移除,然后再将它put到LruResourceCache当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。
另:从Glide4.x开始,读取图片的顺序有所改变:弱引用缓存-》Lru算法缓存-》磁盘缓存
5.ViewModel 的出现是为了解决什么问题?并简要说说它的内部原理?
参考答案:
viewModel出现为了解决什么问题? 看下viewModel的优点就知道了: 1.对于activity/fragment的销毁重建,它们内部的数据也会销毁,通常可以用onSaveInstanceState()防法保存,通过onCreate的bundle中重新获取,但是大量的数据不合适,而vm会再页面销毁时自动保存并在页面加载时恢复。 2.对于异步获取数据,大多时候会在页面destroyed时回收资源,随着数据和资源的复杂,会造成页面中的回收操作越来越多,页面处理ui的同时还要处理资源和数据的管理。而引入vm后可以把资源和数据的处理统一放在vm里,页面回收时系统也会回收vm。加上databinding的支持后,会大幅度分担ui层的负担。 内部原理: vm内部很简单,只有一个onClean方法。 vm的创建一般是这样ViewModelProviders.of(getActivity()).get(UserModel.class); 1.ViewModelProviders.of(getActivity()) 在of方法中通过传入的activity获取构造一个HolderFragment,HolderFragment内有个ViewModelStore,而ViewModelStore内部的一个hashMap保存着系统构造的vm对象,HolderFragment可以感知到传入页面的生命周期(跟glide的做法差不多),HolderFragment构造方法中设置了setRetainInstance(true),所以页面销毁后vm可以正常保存。 2.get(UserModel.class); 获取ViewModelStore.hashMap中的vm,第一次为空会走创建逻辑,如果我们没有提供vm创建的Factory,使用我们传入的activity获取application创建AndroidViewModelFactory,内部使用反射创建我们需要的vm对象。
6.请说说依赖注入框架 ButterKnife 的实现原理?
参考答案:
1.通过注解器在编译期间生成一个XX_ViewBinding.java文件(XX可以是activity,fragment,adapter,dialog),这个文件这么生成的? 注解器里会添加需要类型的注解; 查找XX类中的特定类型注解,如果有,拼接成字符串,创建并写到XX_ViewBinding.java文件中 2.XX_ViewBinding.java会持有XX的引用, 如果是初始化控件,通过xx.findViewById实现 如果是设置监听,类似xx.setOnClickListener实现 3.XX类中初始化XX_ViewBinding对象,这样打通了整个流程
7.谈一谈 RxJava 背压原理?
参考答案:
当上游发送的事件过快或者过多的话就会形成阻塞 导致内存溢出 为了解决这一现象Rxjava2.0推出了Flowable 和 Subscriber 用来支持背压策略 MISSING:如果流的速度无法保持同步,可能会抛出 MissingBackpressureException 或 IllegalStateException。 BUFFER:上游不断的发出 onNext 请求,直到下游处理完,也就是和 Observable 一样了,缓存池无限大,最后直到程序崩溃。 ERROR:会在下游跟不上速度时抛出 MissingBackpressureException。 DROP:会在下游跟不上速度时把 onNext 的值丢弃。 LATEST:会一直保留最新的 onNext 的值,直到被下游消费掉。