与面试官的终局之战:精选Android面试题,不再怕面试官血虐我了

原文做者:Focusing

废话很少说,直接开始正文就是对各位看官最大的尊重!html

 一、如何进行单元测试,如何保证App稳定 ?

参考回答:要测试Android应用程序,一般会建立如下类型自动单元测试:android

  • 本地测试:只在本地机器JVM上运行,以最小化执行时间,这种单元测试不依赖于Android框架,或者即便有依赖,也很方便使用模拟框架来模拟依赖,以达到隔离Android依赖的目的,模拟框架如Google推荐的Mockito;
  • Android官网-创建本地单元测试(https://developer.android.com...
  • 检测测试:真机或模拟器上运行的单元测试,因为须要跑到设备上,比较慢,这些测试能够访问仪器(Android系统)信息,好比被测应用程序的上下文,通常地,依赖不太方便经过模拟框架模拟时采用这种方式;
  • Android官网-创建仪表单元测试(https://developer.android.com...
注意:单元测试不适合测试复杂的UI交互事件

推荐文章:Android 单元测试只看这一篇就够了(https://juejin.im/post/5b57e3...git

App的稳定主要决定于总体的系统架构设计,同时也不可忽略代码编程的细节规范,正所谓“千里之堤,溃于蚁穴”,一旦考虑不周,看似可有可无的代码片断可能会带来总体软件系统的崩溃,因此上线以前除了本身本地化测试以外还须要进行Monkey压力测试。github

少部分面试官可能会延伸,如Gradle自动化测试、机型适配测试等面试

 二、Android中如何查看一个对象的回收状况 ?

参考回答:首先要了解Java四种引用类型的场景和使用(强引用、软引用、弱引用、虛引用)算法

举个场景例子:SoftReference对象是用来保存软引用的,但它同时也是一个Java对象,因此当软引用对象被回收以后,虽然这个SoftReference对象的get方法返回null,但SoftReference对象自己并非null,而此时这个SoftReference对象已经再也不具备存在的价值,须要一个适当的清除机制,避免大量SoftReference对象带来的内存泄露。编程

所以,Java提供ReferenceQueue来处理引用对象的回收状况。当SoftReference所引用的对象被GC后,JVM会先将softReference对象添加到ReferenceQueue这个队列中。当咱们调用ReferenceQueue的poll()方法,若是这个队列中不是空队列,那么将返回并移除前面添加的那个Reference对象。segmentfault

推荐文章:Java中的四种引用类型:强引用、软引用、弱引用和虚引用(https://segmentfault.com/a/11...后端

三、Apk的大小如何压缩 ?

参考回答:一个完整APK包含如下目录(将APK文件拖到Android Studio):api

  • META-INF/:包含CERT.SF和CERT.RSA签名文件以及MANIFEST.MF 清单文件。
  • assets/:包含应用可使用AssetManager对象检索的应用资源。
  • res/:包含未编译到的资源 resources.arsc。
  • lib/:包含特定于处理器软件层的编译代码。该目录包含了每种平台的子目录,像armeabi,armeabi-v7a, arm64-v8a,x86,x86_64,和mips。
  • resources.arsc:包含已编译的资源。该文件包含res/values/ 文件夹全部配置中的XML内容。打包工具提取此XML内容,将其编译为二进制格式,并将内容归档。此内容包括语言字符串和样式,以及直接包含在*resources.arsc8文件中的内容路径 ,例如布局文件和图像。
  • classes.dex:包含以Dalvik / ART虚拟机可理解的DEX文件格式编译的类。
  • AndroidManifest.xml:包含核心Android清单文件。该文件列出应用程序的名称,版本,访问权限和引用的库文件。该文件使用Android的二进制XML格式。

  • lib、class.dex和res占用了超过90%的空间,因此这三块是优化Apk大小的重点(实际状况不惟一)

减小res,压缩图文文件:图片文件压缩是针对jpg和png格式的图片。咱们一般会放置多套不一样分辨率的图片以适配不一样的屏幕,这里能够进行适当的删减。在实际使用中,只保留一到两套就足够了(保留一套的话建议保留xxhdpi,两套的话就加上hdpi),而后再对剩余的图片进行压缩(jpg采用优图压缩,png尝试采用pngquant压缩)

减小dex文件大小

  • 添加资源混淆

  • shrinkResources为true表示移除未引用资源,和代码压缩协同工做。
  • minifyEnabled为true表示经过ProGuard启用代码压缩,配合proguardFiles的配置对代码进行混淆并移除未使用的代码。
  • 代码混淆在压缩apk的同时,也提高了安全性。

推荐文章:Android混淆最佳实践(https://www.jianshu.com/p/cba...

减小lib文件大小:因为引用了不少第三方库,lib文件夹占用的空间一般都很大,特别是有so库的状况下。不少so库会同时引入armeabi、armeabi-v7a和x86这几种类型,这里能够只保留armeabi或armeabi-v7a的其中一个就能够了,实际上微信等主流app都是这么作的。

只需在build.gradle直接配置便可,NDK配置同理

推荐文章:APK瘦身(https://www.jianshu.com/p/592...

 四、如何经过Gradle配置多渠道包?

参考回答:首先要了解设置多渠道的缘由。在安装包中添加不一样的标识,配合自动化埋点,应用在请求网络的时候携带渠道信息,方便后台作运营统计,好比说统计咱们的应用在不一样应用市场的下载量等信息

这里以友盟统计为例:

  • 首先在manifest.xml文件中设置动态渠道变量:

  • 接着在app目录下的build.gradle中配置productFlavors,也就是配置打包的渠道:

最后在编辑器下方的Teminal输出命令行:

  • 执行./gradlew assembleRelease ,将会打出全部渠道的release包;
  • 执行./gradlew assembleVIVO,将会打出VIVO渠道的release和debug版的包;
  • 执行./gradlew assembleVIVORelease将生成VIVO的release包。

推荐文章:美团Android自动化之旅—Walle生成渠道包(https://github.com/Meituan-Di...

 五、插件化原理分析

参考回答:插件化是指将 APK 分为宿主和插件的部分。把须要实现的模块或功能当作一个独立的提取出来,在 APP 运行时,咱们能够动态的载入或者替换插件部分,减小宿主的规模

  • 宿主: 就是当前运行的APP。
  • 插件: 相对于插件化技术来讲,就是要加载运行的apk类文件。

而热修复则是从修复bug的角度出发,强调的是在不须要二次安装应用的前提下修复已知的bug。

类加载机制:Android中经常使用的两种类加载器,DexClassLoader和PathClassLoader,它们都继承于BaseDexClassLoader,二者区别在于PathClassLoader只能加载内部存储目录的dex/jar/apk文件。DexClassLoader支持加载指定目录(不限于内部)的dex/jar/apk文件

插件通讯:经过给插件apk生成相应的DexClassLoader即可以访问其中的类,可分为单DexClassLoader和多DexClassLoader两种结构。

  • 若使用多ClassLoader机制,主工程引用插件中类须要先经过插件的ClassLoader加载该类再经过反射调用其方法。插件化框架通常会经过统一的入口去管理对各个插件中类的访问,而且作必定的限制。
  • 若使用单ClassLoader机制,主工程则能够直接经过类名去访问插件中的类。该方式有个弊端,若两个不一样的插件工程引用了一个库的不一样版本,则程序可能会出错。

资源加载:原理在于经过反射将插件apk的路径加入AssetManager中并建立Resource对象加载资源,有两种处理方式:

  • 合并式:addAssetPath时加入全部插件和主工程的路径;因为AssetManager中加入了全部插件和主工程的路径,所以生成的Resource能够同时访问插件和主工程的资源。可是因为主工程和各个插件都是独立编译的,生成的资源id会存在相同的状况,在访问时会产生资源冲突。
  • 独立式:各个插件只添加本身apk路径,各个插件的资源是互相隔离的,不过若是想要实现资源的共享,必须拿到对应的Resource对象。

推荐文章:

Android动态加载技术 简单易懂的介绍方式(https://segmentfault.com/a/11...

深刻理解Android插件化技术(https://yq.aliyun.com/article...

为何要作热更新(https://www.cnblogs.com/baiqi...

 六、组件化原理

参考回答:引入组件化的缘由:项目随着需求的增长规模变得愈来愈大,规模的增大致使了各类业务错中复杂的交织在一块儿, 每一个业务模块之间,代码没有约束,带来了代码边界的模糊,代码冲突时有发生, 更改一个小问题可能引发一些新的问题, 牵一发而动全身,增长一个新需求,须要熟悉相关的代码逻辑,增长开发时间

  • 避免重复造轮子,能够节省开发和维护的成本。
  • 能够经过组件和模块为业务基准合理地安排人力,提升开发效率。
  • 不一样的项目能够共用一个组件或模块,确保总体技术方案的统一性。
  • 为将来插件化共用同一套底层模型作准备。

组件化开发流程就是把一个功能完整的App或模块拆分红多个子模块(Module),每一个子模块能够独立编译运行,也能够任意组合成另外一个新的 App或模块,每一个模块即不相互依赖但又能够相互交互,可是最终发布的时候是将这些组件合并统一成一个apk,遇到某些特殊状况甚至能够升级或者降级

举个简单的模型例子:

App是主application,ModuleA和ModuleB是两个业务模块(相对独立,互不影响),Library是基础模块,包含全部模块须要的依赖库,以及一些工具类:如网络访问、时间工具等。

注意:提供给各业务模块的基础组件,须要根据具体状况拆分红 aar 或者 library,像登陆,基础网络层这样较为稳定的组件,通常直接打包成 aar,减小编译耗时。而像自定义 View 组件,因为随着版本迭代会有较多变化,就直接以源码形式抽离成 Library

推荐文章:干货 | 从智行 Android 项目看组件化架构实践(https://mp.weixin.qq.com/s?__...

 七、跨组件通讯

跨组件通讯场景

  • 第一种是组件之间的页面跳转 (Activity 到 Activity, Fragment 到 Fragment, Activity 到 Fragment, Fragment 到 Activity) 以及跳转时的数据传递 (基础数据类型和可序列化的自定义类类型)。
  • 第二种是组件之间的自定义类和自定义方法的调用(组件向外提供服务)。

跨组件通讯方案分析:第一种组件之间的页面跳转实现简单,跳转时想传递不一样类型的数据提供有相应的 API便可。

第二种组件之间的自定义类和自定义方法的调用要稍微复杂点,须要 ARouter 配合架构中的 公共服务(CommonService) 实现:

  • 提供服务的业务模块:
  • 在公共服务(CommonService) 中声明 Service 接口 (含有须要被调用的自定义方法), 而后在本身的模块中实现这个 Service 接口, 再经过 ARouter API 暴露实现类。
  • 使用服务的业务模块:经过 ARouter 的 API 拿到这个 Service 接口(多态持有, 实际持有实现类), 便可调用 Service 接口中声明的自定义方法, 这样就能够达到模块之间的交互。
  • 此外,可使用 AndroidEventBus 其独有的 Tag, 能够在开发时更容易定位发送事件和接受事件的代码, 若是以组件名来做为 Tag 的前缀进行分组, 也能够更好的统一管理和查看每一个组件的事件, 固然也不建议你们过多使用 EventBus。

如何管理过多的路由表?

  • RouterHub 存在于基础库, 能够被看做是全部组件都须要遵照的通信协议, 里面不只能够放路由地址常量, 还能够放跨组件传递数据时命名的各类 Key 值, 再配以适当注释, 任何组件开发人员不须要事先沟通只要依赖了这个协议, 就知道了各自该怎样协同工做, 既提升了效率又下降了出错风险, 约定的东西天然要比口头上说强。
  • Tips: 若是您以为把每一个路由地址都写在基础库的 RouterHub 中, 太麻烦了, 也能够在每一个组件内部创建一个私有 RouterHub, 将不须要跨组件的路由地址放入私有 RouterHub 中管理, 只将须要跨组件的路由地址放入基础库的公有 RouterHub 中管理, 若是您不须要集中管理全部路由地址的话, 这也是比较推荐的一种方式。

ARouter路由原理:ARouter维护了一个路由表Warehouse,其中保存着所有的模块跳转关系,ARouter路由跳转实际上仍是调用了startActivity的跳转,使用了原生的Framework机制,只是经过apt注解的形式制造出跳转规则,并人为地拦截跳转和设置跳转条件。

常见的组件化方案以下:

 八、组件化中路由、埋点的实现

参考回答:由于在组件化中,各个业务模块之间是各自独立的, 并不会存在相互依赖的关系, 因此一个业务模块是访问不了其余业务模块的代码的, 若是想从 A 业务模块的 A 页面跳转到 B 业务模块的 B 页面, 光靠模块自身是不能实现的,这就须要一种跨组件通讯方案—— 路由(Router)

路由主要有如下两种场景:

  • 第一种是组件之间的页面跳转 (Activity 到 Activity, Fragment 到 Fragment, Activity 到 Fragment, Fragment 到 Activity) 以及跳转时的数据传递 (基础数据类型和可序列化的自定义类类型)
  • 第二种是组件之间的自定义类和自定义方法的调用(组件向外提供服务)

其原理在于将分布在不一样组件module中的某些类按照必定规则生成映射表(数据结构一般是Map,Key为一个字符串,Value为类或对象),而后在须要用到的时候从映射表中根据字符串从映射表中取出类或对象,本质上是类的查找。

埋点则是在应用中特定的流程收集一些信息,用来跟踪应用使用的情况:

  • 代码埋点:在某个事件发生时调用SDK里面相应的接口发送埋点数据,百度统计、友盟、TalkingData、Sensors Analytics等第三方数据统计服务商大都采用这种方案
  • 全埋点:全埋点指的是将Web页面/App内产生的全部的、知足某个条件的行为,所有上报到后台服务器
  • 可视化埋点:经过可视化工具(例如Mixpanel)配置采集节点,在Android端自动解析配置并上报埋点数据,从而实现所谓的自动埋点
  • 无埋点:它并非真正的不须要埋点,而是Android端自动采集所有事件并上报埋点数据,在后端数据计算时过滤出有用数据

推荐文章:安卓组件化开源方案实现(https://juejin.im/post/5a7ab8...

 九、Hook以及插桩技术

参考回答:Hook是一种用于改变API执行结果的技术,可以将系统的API函数执行重定向(应用的触发事件和后台逻辑处理是根据事件流程一步步地向下执行。而Hook的意思,就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件同样,而且可以在钩上事件时,处理一些本身特定的事件,例如逆向破解App)

Android 中的 Hook 机制,大体有两个方式:

  • 要 root 权限,直接 Hook 系统,能够干掉全部的 App。
  • 无 root 权限,可是只能 Hook 自身app,对系统其它 App 无能为力。

插桩是以静态的方式修改第三方的代码,也就是从编译阶段,对源代码(中间代码)进行编译,然后从新打包,是静态的篡改; 而Hook则不须要再编译阶段修改第三方的源码或中间代码,是在运行时经过反射的方式修改调用,是一种动态的篡改

推荐文章:

Android插件化原理解析——Hook机制之动态代理(http://weishu.me/2016/01/28/u...

android 插桩基本概念(https://blog.csdn.net/fei2012...

Android逆向之旅(http://www.520monkey.com/

 十、Android的签名机制?

参考回答:Android的签名机制包含有消息摘要、数字签名和数字证书

  • 消息摘要:在消息数据上,执行一个单向的 Hash 函数,生成一个固定长度的Hash值
  • 数字签名:一种以电子形式存储消息签名的方法,一个完整的数字签名方案应该由两部分组成:签名算法和验证算法
  • 数字证书:一个经证书受权(Certificate Authentication)中心数字签名的包含公钥拥有者信息以及公钥的文件

推荐文章:一篇文章看明白 Android v1 & v2 签名机制(https://blog.csdn.net/freekit...

 十一、v3签名key和v2还有v1有什么区别

参考回答:在v1版本的签名中,签名以文件的形式存在于apk包中,这个版本的apk包就是一个标准的zip包,V2和V1的差异是V2是对整个zip包进行签名,并且在zip包中增长了一个apk signature block,里面保存签名信息。

v2版本签名块(APK Signing Block)自己又主要分红三部分:

  • SignerData(签名者数据):主要包括签名者的证书,整个APK完整性校验hash,以及一些必要信息
  • Signature(签名):开发者对SignerData部分数据的签名数据
  • PublicKey(公钥):用于验签的公钥数据

v3版本签名块也分红一样的三部分,与v2不一样的是在SignerData部分,v3新增了attr块,其中是由更小的level块组成。每一个level块中能够存储一个证书信息。前一个level块证书验证下一个level证书,以此类推。最后一个level块的证书,要符合SignerData中自己的证书,即用来签名整个APK的公钥所属于的证书

推荐文章:

APK 签名方案 v3(https://source.android.google...

Android P v3签名新特性(https://xuanxuanblingbling.gi...

 十二、Android5.0~10.0之间大的变化

Android 5.0新特性:

  • MaterialDesign设计风格
  • 支持64位ART虚拟机(5.0推出的ART虚拟机,在5.0以前都是Dalvik。他们的区别是:Dalvik,每次运行,字节码都须要经过即时编译器转换成机器码(JIT)。ART,第一次安装应用的时候,字节码就会预先编译成机器码(AOT))
  • 通知详情能够用户本身设计

Android 6.0新特性

  • 动态权限管理
  • 支持快速充电的切换
  • 支持文件夹拖拽应用
  • 相机新增专业模式

Android 7.0新特性

  • 多窗口支持
  • V2签名
  • 加强的Java8语言模式
  • 夜间模式

Android 8.0(O)新特性

  • 优化通知:通知渠道 (Notification Channel) 通知标志 休眠 通知超时 通知设置 通知清除
  • 画中画模式:清单中Activity设置android:supportsPictureInPicture
  • 后台限制
  • 自动填充框架
  • 系统优化等等优化不少

Android 9.0(P)新特性

  • 室内WIFI定位
  • “刘海”屏幕支持
  • 安全加强等等优化不少

Android 10.0(Q)新特性

  • 夜间模式:包括手机上的全部应用均可觉得其设置暗黑模式。
  • 桌面模式:提供相似于PC的体验,可是远远不能代替PC。
  • 屏幕录制:经过长按“电源”菜单中的"屏幕快照"来开启。

推荐文章:Android Developers 官方文档(https://developer.android.com...

 1三、说下Measurepec这个类

参考回答:做用:经过宽测量值widthMeasureSpec和高测量值heightMeasureSpec决定View的大小

组成:一个32位int值,高2位表明SpecMode(测量模式),低30位表明SpecSize( 某种测量模式下的规格大小)。

三种模式:

  • UNSPECIFIED:父容器不对View有任何限制,要多大有多大。经常使用于系统内部。
  • EXACTLY(精确模式):父视图为子视图指定一个确切的尺寸SpecSize。对应LyaoutParams中的match_parent或具体数值。
  • AT_MOST(最大模式):父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。

决定因素:值由子View的布局参数LayoutParams和父容器的MeasureSpec值共同决定。具体规则见下图:

1四、请例举Android中经常使用布局类型,并简述其用法以及排版效率

参考回答:Android中经常使用布局分为传统布局和新型布局

传统布局(编写XML代码、代码生成):

  • 框架布局(FrameLayout):
  • 线性布局(LinearLayout):
  • 绝对布局(AbsoluteLayout):
  • 相对布局(RelativeLayout):
  • 表格布局(TableLayout):

新型布局(可视化拖拽控件、编写XML代码、代码生成):约束布局(ConstrainLayout):

注:图片出自Carson_Ho的Android:经常使用布局介绍&属性设置大全(https://blog.csdn.net/carson_...

对于嵌套多层View而言,其排版效率:LinearLayout = FrameLayout >> RelativeLayout

 1五、区别Animation和Animator的用法,概述其原理

动画的种类:前者只有透明度,旋转,平移,伸缩4种属性,而对于后者,只要是该控件的属性,且有setter该属性的方法就均可以对该属性执行一种动态变化的效果。

可操做的对象:前者只能对UI组件执行动画,但属性动画几乎能够对任何对象执行动画(无论它是否显示在屏幕上)。

动画播放顺序:在Animator中,AnimatorSet正是经过playTogether()、playSequentially()、animSet.play().with()、before()、after()这些方法来控制多个动画协同工做,从而作到对动画播放顺序的精确控制

 1六、使用过什么图片加载库?Glide的源码设计哪里很微妙?

参考回答:图片加载库:Fresco、Glide、Picasso等

Glide的设计微妙在于:

  • Glide的生命周期绑定:能够控制图片的加载状态与当前页面的生命周期同步,使整个加载过程随着页面的状态而启动/恢复,中止,销毁
  • Glide的缓存设计:经过(三级缓存,Lru算法,Bitmap复用)对Resource进行缓存设计
  • Glide的完整加载过程:采用Engine引擎类暴露了一系列方法供Request操做

推荐文章:Glide 源码分析(https://user-gold-cdn.xitu.io...

 1七、如何绕过9.0限制?

参考回答:

 1八、用过哪些网络加载库?OkHttp、Retrofit实现原理?

参考回答:网络加载库:OkHttp、Retrofit、xUtils、Volley等

推荐文章:

Android OkHttp源码解析入门教程(一)(https://juejin.im/post/5c4682...

Android OkHttp源码解析入门教程(二)(https://juejin.im/post/5c4682...

 1九、对于应用更新这块是如何作的?

(灰度,强制更新、分区域更新)

内部更新

  • 经过接口获取线上版本号,versionCode
  • 比较线上的versionCode 和本地的versionCode,弹出更新窗口
  • 下载APK文件(文件下载)
  • 安装APK

灰度更新

  • 找单一渠道投放特别版本。
  • 作升级平台的改造,容许针对部分用户推送升级通知甚至版本强制升级。
  • 开放单独的下载入口。
  • 是两个版本的代码都打到app包里,而后在app端植入测试框架,用来控制显示哪一个版本。测试框架负责与服务器端api通讯,由服务器端控制app上A/B版本的分布,能够实现指定的一组用户看到A版本,其它用户看到B版本。服务端会有相应的报表来显示A/B版本的数量和效果对比。最后能够由服务端的后台来控制,所有用户在线切换到A或者B版本~
  • 不管哪一种方法都须要作好版本管理工做,分配特别的版本号以示区别。 固然,既然是作灰度,数据监控(常规数据、新特性数据、主要业务数据)仍是要作到位,该打的数据桩要打。 还有,灰度版最好有收回的能力,通常就是强制升级下一个正式版。

强制更新:通常的处理就是进入应用就弹窗通知用户有版本更新,弹窗能够没有取消按钮并不能取消。这样用户就只能选择更新或者关闭应用了,固然也能够添加取消按钮,可是若是用户选择取消则直接退出应用。

增量更新:二进制差分工具bsdiff是相应的补丁合成工具,根据两个不一样版本的二进制文件,生成补丁文件.patch文件。经过bspatch使旧的apk文件与不定文件合成新的apk。 注意经过apk文件的md5值进行区分版本。

 20、会用Kotlin、Fultter吗? 谈谈你的理解

  • Kotlin是一种具备类型推断的跨平台,静态类型的通用编程语言。Kotlin旨在与Java彻底互操做,其标准库的JVM版本依赖于Java类库,但类型推断容许其语法更简洁。
  • Flutter是由Google建立的开源移动应用程序开发框架。它用于开发Android和iOS的应用程序,以及为Google Fuchsia建立应用程序的主要方法
  • 关于kotlin的重要性,相信你们在平常开发能够体会到,应用到实际开发中,须要避免语法糖(例如单列模式、空值判断、高阶函数等)
  • 至于Flutter,目前Google官方文档还不完善,市面上采用此语言编写的项目较少,如须要具体深刻,请参考闲鱼和官方文档

这里小编也分享一份资料,内容包含: Android学习PDF+架构视频+面试文档+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。分享给你们,很是适合近期有面试和想在技术道路上继续精进的朋友。但愿能够帮助到你们进入大厂、拿到高薪

若是你有须要的话,能够加Vx:15388039515(备注思否,须要进阶资料)

喜欢本文的话,不妨给我点个小赞、评论区留言或者转发支持一下呗~