我的Android移动端面试经验分享,大厂面试题总结:阿里腾讯美团
不得不说,前两年的移动开发确实很火,可以说随便能鼓捣出来点东西都很容易找到工作。而现在明显降温了,对人才的要求也越来越苛刻,所以跟前两年比需求确实少了很多。但是安卓不仅仅局限于手机,智能家居车载系统电视机顶盒智能机器人的触摸板设置大部分都是搭载的安卓系统,所以市场的需求还是很大的。
本文希望达到的目的是为职场新人和毕业生提供一个面试经验的分享,使读者在找工作时有一个参考少走弯路,通过本文的阅读将收获到:
- 面试前的准备,简历编写规范和重点;
- 面试经历,大厂和小厂的区别和考察点;
- 安卓面试常问的知识点解析;
- 目前主流框架的使用,如何快速上手项目;
- 附历年大厂面试题总结。
概述
对于我们程序员而言,面试就是将自己所学的技术与面试官表达出来。以前上学准备找工作的时候,发现最先找到工作的不是技术最好的而是善于沟通活跃度高的人,而我听的最多的抱怨是 “为什么我明明知道这个东西,面试的时候却不知从何说起”。
本文会从我的自身经历出发谈一谈面试前如何做好充分准备,怎么流畅的表达自己的技术,以及进入项目时如何从一开始的手足无措、盲目下手到后来的淡定从容。
面试前准备
简历编写
对于任何阶段的面试者来说拥有一份精致的简历是很重要的,所谓精致就是要突出重点,特别对于技术类的简历来说废话越少越好。好的简历要有辨识度,记得以前听 HR 聊天时说:“明明没啥东西写了两三页,看到后面才知道他要干嘛,简直浪费纸”大部分人写简历都会用网上的模板,当然用模板写会节省很大的人力而且格式也更好看。然而模板上面有很多套话,
这段自我评价估计很多人都会感到很熟悉,甚至一部分人的简历上面还有,因为每个人都可以这样写。想一想这种评价 HR 每天看了多少遍,再看下面这张简历案例,在评价栏罗列出自己的技术点,突出重点一目了然。
刚毕业出来找工作的同学来说,没有上图有那么多干货可以写,怎么才能突出重点呢,教一个小技巧,大家可以浏览几十家不同类型公司的任职要求,你会发现有两到三条几乎是相同的,那么这两到三条技术点就是你必须要掌握的了,而且技术评价里面一定要写出来。
简历编写要适度不要给自己挖坑,掌握不能写成精通了解不能写成掌握,精通在了解怎么用的同时还要了解它的底层实现,掌握的话就要知道它的用法以及出现问题怎么解决。了解的话就要知道这个技术点可以处理什么问题,了解一些 API 怎么调用。
技能储备
- 毕业一到两年:
Java 基础知识方面需要掌握的有:面向对象的理解、基本类型与引用类型、构造方法、常用类(内部类、匿名类、抽象类)、三大特性(封装、继承、多态)、重写与重载、接口与接口的实现等等。这些问题面试官会在掌握的层面上去问你,主要是考察你的基础知识是否扎实,毕竟安卓是用 Java 编写的。
Android 方面需要掌握的有:四大组件的简单使用、activity 的生命周期、fragment 的绑定、activity 和 fragment 之间的传值、 recyclerview 实现列表九宫格瀑布流式布局的实现、viewHolder 的复用问题、数据存储的几种方式的特点、常用框架 Glide、Retrofit、eventBus、butterknife 的使用。
- 毕业两到三年:
Java 基础知识需要掌握的有:对于两到三年的安卓程序员来说,Java 不仅仅是停留在一些基础知识的使用上了,而是在用的同时要有自己的理解。比如说封装,面试官不会问你什么是封装,而是会问你封装过公司的哪些代码/功能,你是如何封装的。这个时候考察的就是你是会写代码还是只会模仿代码,如果没有自己在项目中封装过代码的话可以去阅读下网上一些优秀的框架的源码学习一下别人是怎么封装的。
当然不仅仅是封装还有很多知识点都要按照这个要求去掌握,比如:Java 泛型、反射、集合框架、接口与抽象类、设计模式等等。掌握这些除了看视频学习还可以阅读一些优秀的源码。不懂的地方再查一查博客,理解透了后一定要在自己项目上运用,这样学习才能印象深刻面试官问到也能有列可举。
Android 方面需要掌握的有:APP 启动原理,想要详细了解的可以看我的另一篇 chat (APP 启动原理及启动优化详解 )、图片压缩与性能优化、自定义 view 、事件分发流程、屏幕适配、组件化和插件化、Glide 的缓存与复用、OkHttp 的责任链与连接池、序列化与反序列化、分析一个你最熟练框架的源码等等。可以看到,对于两到三年的程序员来说不仅掌握的知识点更多,而且还需要对原理有一定的了解。
面试经历分享,大厂和小厂的区别
大厂面试经历
本人刚出来找工作的时候面试了很多家,那时候是移动开发的爆发时期,其中有万能钥匙等中大型公司也有一些刚创业的小公司,当然大厂最终面试失败了。记得去万能钥匙面试的时候,去面试的人特别的多,需要在大厅等待,一批一批的去面试,先是群面,了解基本情况过后,就是单独面谈等待面试,总共三轮面试,面试官会通知面试结果出来的时间。
面试官提到的问题有:为什么从上一家离职、如何看待我们公司、自己以后的职业规划是什么、技术方面问了 Java 的内存回收机制,内存溢出和内存泄漏、如何避免 OOM 异常、什么是 AIDL,AIDL 怎么使用、 android 线程间通信有那几种方式、Glide 三级缓存怎么实现的。
大小厂考察点的区别
- 大厂:
流程比较多,一般会有三面以上,首先会有一套面试题等着你,当然不是所有公司都有面试题,只不过大厂有面试题的概率会高些,其次技术主管面试,然后技术经理面试,人事会跟你介绍公司的发展业务公司福利工作时间等等。大厂的开发一般是分模块开发,每个人单独负责某一个模块,所以要求你要有自己的优势点,比如你会写自定义组件,视频模块,对 NDK 深有研究等等。
- 小厂:
流程少,有可能一面过了就叫你来上班了。小公司面试一定要问清楚工资什么时候发,月初发说明公司资金充足,月中旬发是正常的,如果月末才发的话公司资金紧张很可能出现财务危机。还有五险一金有没有,有的话试用期有没有,目前还有一部分公司没有五险一金的,而且大部分公司试用期不交五险一金。关于五险一金的重要性大家可以去百度搜一搜,限购令出来后这个更加重要了。
关于技术方面的区别就是,小公司一般都是一个人开发,要求你知道整个项目的开发流程,但是对于技术深度要求不高(仅仅对于初级程序员来说,高级架构师就另说了)。面试之前多准备些项目去演示,有些人说懂技术的都不看作品的,但是对于小公司就不同了,有可能面试你的是产品经理、后台人员等等,对安卓了解的也不是很深,这时候有几个好的作品演示一定会给你的面试加不少分。
安卓面试常问的知识点解析
以下五个问题本人面试的时候都被问到过,也作为面试官考察过别人,算是比较有代表性的题目。
Activity 的生命周期
这个问题的关键是理解,这道面试题还有其他的问法,比如:问onStart()、与 onResume() 有什么区别?什么情况下 Activity 走了onCreat(),而不走 onStart()?
请介绍下 Android 的数据存储方式。
使用 SharedPreferences 存储数据;文件存储数据;SQLite 数据库存储数据;使用 ContentProvider 存储数据;网络存储数据。
Preference,File, DataBase 这三种方式分别对应的目录是 /data/data/Package Name/Shared_Pref, /data/data/Package Name/files, /data/data/Package Name/database 。
- 使用 SharedPreferences 存储数据
首先说明 SharedPreferences 存储方式,它是 Android 提供的用来存储一些简单配置信息的一种机制,例如:登录用户的用户名与密码。其采用了 Map 数据结构来存储数据,以键值的方式存储,可以简单的读取与写入,具体实例如下:
void ReadSharedPreferences(){ String strName,strPassword; SharedPreferences user = getSharedPreferences(“user_info”,0); strName = user.getString(“NAME”,””); strPassword = user getString(“PASSWORD”,””); } void WriteSharedPreferences(String strName,String strPassword){ SharedPreferences user = getSharedPreferences(“user_info”,0); uer.edit(); user.putString(“NAME”, strName); user.putString(“PASSWORD” ,strPassword); user.commit(); }
数据读取与写入的方法都非常简单,只是在写入的时候有些区别:先调用 edit() 使其处于编辑状态,然后才能修改数据,最后使用 commit() 提交修改的数据。实际上 SharedPreferences 是采用了 XML 格式将数据存储到设备中,在 DDMS 中的 File Explorer 中的 /data/data/<package name>/shares_prefs 下。使用 SharedPreferences 是有些限制的:只能在同一个包内使用,不能在不同的包之间使用。
- 文件存储数据
文件存储方式是一种较常用的方法,在 Android 中读取/写入文件的方法,与 Java 中实现 I/O 的程序是完全一样的,提供了 openFileInput() 和openFileOutput() 方法来读取设备上的文件。具体实例如下:
String fn = “moandroid.log”; FileInputStream fis = openFileInput(fn); FileOutputStream fos = openFileOutput(fn,Context.MODE_PRIVATE);
- 网络存储数据
网络存储方式,需要与 Android 网络数据包打交道,关于 Android 网络数据包的详细说明,请阅读 Android SDK 引用了 Java SDK 的哪些package?。
activity的启动模式有哪些?是什么含义?
在 Android 里,有 4 种 activity 的启动模式,分别为:
“standard” (默认)
“singleTop”
“singleTask”
“singleInstance”
它们主要有如下不同:
- 如何决定所属 task
“standard” 和 ”singleTop” 的 activity 的目标 task,和收到的 Intent 的发送者在同一个 task 内,除非 intent 包括参数 FLAG_ACTIVITY_NEW_TASK。
如果提供了 FLAG_ACTIVITY_NEW_TASK 参数,会启动到别的 task 里。
“singleTask” 和 ”singleInstance” 总是把 activity 作为一个 task 的根元素,他们不会被启动到一个其他 task 里。
- 是否允许多个实例
“standard” 和 ”singleTop” 可以被实例化多次,并且存在于不同的 task 中,且一个 task 可以包括一个 activity 的多个实例。
“singleTask” 和 ”singleInstance” 则限制只生成一个实例,并且是 task 的根元素。 singleTop 要求如果创建 intent 的时候栈顶已经有要创建的 Activity的实例,则将 intent 发送给该实例,而不发送给新的实例。
- 是否允许其它 activity 存在于本 task 内
“singleInstance” 独占一个 task,其它 activity 不能存在那个 task 里;如果它启动了一个新的 activity,不管新的 activity 的 launch mode 如何,新的activity 都将会到别的 task 里运行(如同加了 FLAG_ACTIVITY_NEW_TASK参数)。
而另外三种模式,则可以和其它 activity 共存。
- 是否每次都生成新实例
“standard” 对于没一个启动 Intent 都会生成一个 activity 的新实例。
“singleTop” 的 activity 如果在 task 的栈顶的话,则不生成新的该 activity 的实例,直接使用栈顶的实例,否则,生成该 activity 的实例。
比如现在 task 栈元素为 A-B-C-D(D在栈顶),这时候给 D 发一个启动 intent,如果 D 是 “standard” 的,则生成 D 的一个新实例,栈变为 A-B-C-D-D。
如果 D 是 singleTop 的话,则不会生产 D 的新实例,栈状态仍为 A-B-C-D。
如果这时候给 B 发 Intent 的话,不管 B 的 launchmode 是 ”standard” 还是 “singleTop” ,都会生成 B 的新实例,栈状态变为 A-B-C-D-B。
“singleInstance” 是其所在栈的唯一 activity,它会每次都被重用。
“singleTask” 如果在栈顶,则接受 intent,否则,该 intent 会被丢弃,但是该 task 仍会回到前台。
当已经存在的 activity 实例处理新的 intent 时候,会调用 onNewIntent() 方法 如果收到 intent 生成一个 activity 实例,那么用户可以通过 back 键回到上一个状态;如果是已经存在的一个 activity 来处理这个 intent 的话,用户不能通过按 back 键返回到这之前的状态。
什么是 ANR 如何避免它?
ANR:Application Not Responding。在 Android 中,活动管理器和窗口管理器这两个系统服务负责监视应用程序的响应,当用户操作的在 5s 内应用程序没能做出反应,BroadcastReceiver 在 10 秒内没有执行完毕,就会出现应用程序无响应对话框,这既是 ANR。
避免方法:Activity 应该在它的关键生命周期方法(如 onCreate() 和onResume())里尽可能少的去做创建操作。潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者异步方式)来完成。主线程应该为子线程提供一个 Handler,以便完成时能够提交给主线程。
注册广播有几种方式,这些方式有何优缺点?请谈谈 Android 引入广播机制的用意。
首先写一个类要继承 BroadcastReceiver
第一种:在清单文件中声明,添加
第二种使用代码进行注册如
两种注册类型的区别是:
- 第一种不是常驻型广播,也就是说广播跟随程序的生命周期;
- 第二种是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
目前主流框架的分享,如何快速上手项目
搭建项目的主流框架集
Dagger2+RxJava+Retrofit+MVP 是本人目前用的框架集,目前使用的很广泛, 四个相结合,组成项目的优美整体架构。 需要导入的包
> dependencies { // 网络请求 compile ‘com.squareup.retrofit2:retrofit:2.1.0‘ compile ‘com.squareup.retrofit2:adapter-rxJava:2.0.1‘ compile ‘com.squareup.retrofit2:converter-gson:2.0.0-beta4‘ compile ‘com.squareup.retrofit2:converter-scalars:2.0.0-beta4‘ compile ‘com.squareup.okhttp3:okhttp:3.2.0‘ // 注解 compile ‘com.google.dagger:dagger:2.0.2‘ apt ‘com.google.dagger:dagger-compiler:2.0.2‘ provided ‘org.glassfish:Javax.annotation:10.0-b28‘ compile ‘com.jakewharton:butterknife:7.0.1‘ // Rx compile ‘io.reactivex:rxandroid:1.1.0‘ compile ‘io.reactivex:rxJava:1.1.5‘ }
下面是 MVP 的架构图
如上图所示
View 与 Model 并不直接交互,而是使用 Presenter 作为 View 与 Model 之间的桥梁。
其中 Presenter 中同时持有 view 层以及 Model 层的 Interface 的引用,View 层持有 Presenter 层 Interface 的引用。当 View 层某个界面需要展示某些数据的时候,首先会调用 Presenter 层的某个接口,然后 Presenter 层会调用 Model 层请求数据。
当 Model 层数据加载成功之后会调用 Presenter 层的回调方法通知 Presenter 层数据加载完毕。
最后 Presenter 层再调用 View 层的接口将加载后的数据展示给用户。这就是 MVP 模式的整个核心过程,如果你是面试初级安卓开发,面试官应该只会让你阐述下整个调用过程,只要你能流畅的说完整个过程应该差不多了。
Dagger2 的流程图
什么是 Dagger2
Dagger2 是一个依赖注入框架,butterknife 也是一个依赖注入框架。不过 butterknife,最多叫奶油刀,Dagger2 被叫做利器啊,他的主要作用,就是对象的管理,其目的是为了降低程序耦合。
Dagger2 的优点
- 全局对象实例的简单访问方式
和 ButterKnife 库定义了view,事件处理以及资源的引用一样,Dagger2 提供全局对象引用的简易访问方式。声明了单例的实例都可以使用 @inject 进行访问。比如下面的 MyTwitterApiClient 和SharedPreferences 的实例:
> public class MainActivity extends Activity { @Inject MyTwitterApiClient mTwitterApiClient; @Inject SharedPreferences sharedPreferences; public void onCreate(Bundle savedInstance) { // assign singleton instances to fields InjectorClass.inject(this); }
- 复杂的依赖关系只需要简单的配置
Dagger2 会通过依赖关系并且生成易懂易分析的代码。以前通过手写的大量模板代码中的对象引用将会由它给你创建并传递到相应对象中。因此你可以更多的关注模块中构建的内容而不是模块中的对象实例的创建顺序。
- 让单元测试和集成测试更加方便
因为依赖关系已经为我们独立出来,所以我们可以轻松的抽取出不同的模块进行测试。依赖的注入和配置独立于组件之外。因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。
- 作用域实例(Scoped instances)
我们不仅可以轻松的管理全局实例对象,也可以使用 Dagger2 中的 scope 定义不同的作用域。(比如根据 user session、activity 的生命周期)
- 什么是 Retrofit
Retrofit 是 Square 开发的一个 Android 和 Java 的 REST 客户端库。这个库非常简单并且具有很多特性,相比其他的网络库,更容易让初学者快速掌握。
- 怎样创建 Retrofit 实例
创建 Retrofit 实例时需要通过 Retrofit.Builder,并调用 baseUrl 方法设置 URL。
接口定义
以获取时间列表为例
> public interface TimeService { > @GET("getTimes?") Call< ResponseBody > getTimes (@Query("month") String month); }
注意,这里是 interface 不是 class,所以我们是无法直接调用该方法,我们需要用 Retrofit 创建一个 TimeService 的代理对象。
TimeService timeService= createRetrofit().create(TimeService .class);
接口调用
> Call<ResponseBody> mService= timeService.getTimes ("1"); mService.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { try { Log(response.body().string()); } catch (IOException e) { e.printStackTrace(); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { t.printStackTrace(); } });
如何快速理解项目进行二次开发
当我们进入一个新公司工作,有可能接手的不是一个新项目而是维护别人开发的项目,面对庞大的项目不知从何下手。在这里我要告诉大家的是,拿到项目的时候不要盲目的进行开发而是阅读项目源码。阅读源码首先浏览项目结构,
通过这张结构图可以了解项目用的是 MVP 架构,有两个 lib 一个和 service 有关 一个和图片处理有关。然后再阅读 build.gradle 文件,里面有项目中用到的第三方库的引用地址,知道项目中用到了哪些技术,如果有不熟悉的第三方库就可以根据这个地址去查找资源熟悉调用方式项目中用到的模块,日后逐渐研究库的源码以及实现原理。等这些了解的差不多了,再看一看需求文档和设计图,对着需求走一遍流程,主要记录 activity 之间的跳转,可以画一张类之间跳转的结构图,这样整个跳转的逻辑会更清晰。
还有两个值得阅读的是:项目中的工具类和封装的组件。相信不少人遇到过,在网上找了很久的一个处理数据的方法,过了很多天发现项目的工具类中有直接就可以拿来用。封装的组件也和工具类似可以直接拿来用的,在后面的开发也提倡大家将项目组件化。
历年大厂面试题总结
腾讯
synchronize 用法
volatile 用法
动态权限适配方案,权限组的概念
网络请求缓存处理,okhttp 如何处理网络缓存的
图片加载库相关,bitmap 如何处理大图,如一张 30M 的大图,如何预防 OOM
进程保活
listview 图片加载错乱的原理和解决方案
https 相关,如何验证证书的合法性,https 中哪里用了对称加密,哪里用了非对称加密,对加密算法(如 RSA)等是否有了解
阿里巴巴
LRUCache 原理
图片加载原理
模块化实现(好处,原因)
视频加密传输
统计启动时长,标准
如何保持应用的稳定性
ThreadLocal 原理
谈谈 classloader
动态布局
热修复、插件化
HashMap 源码, SpareArray 原理
性能优化,怎么保证应用启动不卡顿
SP 是进程同步的吗?有什么方法做到同步
介绍下 SurfView
HashMap 实现原理,ConcurrentHashMap 的实现原理
BroadcastReceiver,LocalBroadcastReceiver 区别
Bundle 机制
Handler 机制
android 事件传递机制
线程间 操作 List
App启动流程,从点击桌面开始
动态加载
类加载器
OSGI
Https 请求慢的解决办法,DNS,携带数据,直接访问 IP
GC 回收策略
画出 Android 的大体架构图
描述清点击 Android Studio 的 build 按钮后发生了什么
大体说清一个应用程序安装到手机上时发生了什么;
对 Dalvik、ART 虚拟机有基本的了解;
Android 上的 Inter-Process-Communication 跨进程通信时如何工作的;
App 是如何沙箱化,为什么要这么做;
权限管理系统(底层的权限是如何进行 grant 的);
进程和 Application 的生命周期;
系统启动流程 Zygote 进程 –> SystemServer 进程 –> 各种系统服务 –> 应用进程
美团
线程挂起,休眠,释放资源相关,唤醒,线程同步,数据传递,问了很多线程的问题,问了 20 分钟大概
static synchronized 方法的多线程访问和作用,同一个类里面两个 synchronized 方法,两个线程同时访问的问题
内部类和静态内部类和匿名内部类,以及项目中的应用
泛型是什么以及在项目中的应用
handler 发消息给子线程,looper 怎么启动
down、move、up 事件的传递
activity 栈
封装 view 的时候怎么知道 view 的大小
intent-filter
arraylist 和 linkedlist 的区别,以及应用场景
怎么启动 service,service 和 activity 怎么进行数据交互
下拉状态栏是不是影响 activity 的生命周期,如果在 onStop 的时候做了网络请求,onResume 的时候怎么恢复
view 渲染
总结
对于基础知识考察的比较多,注重原理,要求面试者在学习技术的时候加深理解。不同的公司在业务侧重点方面有所不同,但总体需要掌握的技能有:高级 UI、性能优化、移动架构等方面。
最后
感谢大家能耐着性子,看完我啰哩啰嗦的文章。
我愿与各位坚守在Android开发岗位的同胞们互相交流学习,共同进步!
在这里我也分享一份自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习
如果你有需要的话,可以点赞,关注我,然后关注微信公众号【Android开发之家】免费领取