过年回来到现在也一个月了,这段时间一直没写文章,这是因为我准备换工作了,一直在面试,也面试了四五家,但是效果都不是很好,虽然如此,但也算收获了一些经验,我就将我面试遇到的问题记录下来,与大家一起分享吧。(本人是做游戏sdk的,所以一些问题会偏向于sdk的,如果不找sdk方向的工作可以忽略其中的一些问题)
一、面试基础
1、自我介绍
这个大家自己可以好好看一下网上的一些攻略,自己组织一个好一点的自我介绍,主要是要把个人信息,之前做过什么介绍清楚,这个就看自我发挥了。
2、之前做过的项目或者工作经历,遇到了什么难点,解决了什么问题,技术上得到了哪些提高(这个必问,希望大家准备好)
这个问题是必问的,而且感觉还挺重要的,大家面试前先准备好几个技术难点,哪怕项目中没用过也没关系。
3、介绍一下之前项目中常用的第三方框架
二、java部分
一、ArrayList和HashMap实现原理,他们的特性是什么。
ArrayList:底层数据结构是Object数组,查询快,增删慢,查询是根据数组下标直接查询速度快,增删需要移动后边的元素和扩容,速度慢。线程不安全,元素单个,效率高。(如果要线程安全的List集合可以用Vector)
HashMap:jdk1.8之前的数据结构是数组+链表,1.8之后是数组+链表+红黑树,当链表长度小于8的时候使用的还是数组+链表,当链表长度超过8时转换为红黑树,HashMap元素成对,元素可为空,线程不安全,new HashMap的时候初始化默认大小为16。(如果要线程安全的Map集合可以用HashTable或ConcurrentHashMap)
参考资料
二、java四大引用
强引用(StrongReference):JVM 宁可抛出 OOM ,也不会让 GC 回收具有强引用的对象
软引用(SoftReference):只有在内存空间不足时,才会被回收的对象
弱引用(WeakReference):在 GC 时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
虚引用(PhantomReference):任何时候都可以被GC回收,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。可以用来作为GC回收Object的标志。
三、java抽象类和接口,有什么区别
抽象类:在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。抽象类的特点:
a、抽象类不能被实例化只能被继承;
b、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;
c、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;
d、一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;
e、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。
接口:Java中接口使用interface关键字修饰,特点为:
a、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);
b、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;
c、一个类可以实现多个接口;
d、JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。
相同点
(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点
(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
四、设计模式六大原则,能否给我说说Android中至少3个用到设计模式的例子
1、单一职责原则:有且只有一个原因会引起类的变化。
2、接口隔离原则。
3、里氏替换原则。
4、依赖倒置原则。
5、迪米特法则。
6、开闭原则。
23种设计模式:
创建型5个:单例模式、建造者模式、原型模式、工厂方法、抽象工厂
行为型11个:策略模式、状态模式、观察者模式、中介者模式、访问者模式、迭代器模式、模板方法、备忘录模式、命令模式、解释器模式、责任链模式
结构型模式7个:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
OKHttp内部采用责任链模式来完成每个interceptor拦截器的调用
定义:使得多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将 这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理该请求为止。
优点: 将请求者和处理者关系解耦,使代码更加灵活。
缺点: 在链中遍历寻找请求处理者中,如果处理者太多,会影响性能。
RxJava的观察者模式
定义:定义对象间一种一对多的依赖关系,使得每当一对象状态发生改变时,所有依赖它的对象 会得到通知并自动更新。
应用:listView的Adapter的notifyDataSetChanged方法,广播,事件总线机制。
观察者模式主要的作用是对象解耦,将观察者和被观察者分隔开,只依赖于observer(观察员) 和observable(可观察的)抽象。
优点: 1.观察者和被观察者是抽象耦合,以应对业务变化; 2.增强系统的灵活性和可扩展性。
缺点: 在Java中通知是顺序执行,如果观察者卡顿,会影响整体的执行效率。这种情况下,一般建议采 用异步的方式。
listview/gridview的适配器模式
1.使用场景: 1.接口不兼容 2.想建立一个可以重复使用的类 3.需要统一的输出接口,而输入端类型不可知。
优点: 1.更好的复用性:复用现有的功能; 2.更好的扩展性:扩展现有的功能。
缺点: 过多的使用适配器,会使系统凌乱不堪,不易于整体把握。如:明明调用的是A接口,内部适配 的却是B接口的实现,如果系统出现太多这种情况,无异于一场灾难。
Context外观模式
使用场景: 为复杂子系统提供一个简单接口。
优点: 1.隐藏子系统实现细节,减少客户和子系统的耦合,能够拥抱变化; 2.外观类对子系统接口封装,使子系统更加容易使用。
缺点: 1.外观类接口膨胀; 更多免费Android开发视频课程,2.外观类没有遵循开闭原则,当业务出现变更时,可能需要修改外观类。
AlertDialog、Notification 源码使用了建造者模式完成参数的初始化
优点:1.良好的封装性,隐藏内部实现细节; 2.建造者独立,容易扩展。
缺点: 会产生多余的Builder对象和Director对象,消耗内存。
安卓应用主题是抽象工厂模式的最好体现
安卓应用有两套主题:LightTheme亮色主题和DarkTheme暗色主题。主题之下有各种与之相关的 UI元素。创建主题也要随之创建各种UI元素,这就是抽象工厂模式的最好体现。
优点: 分离接口和实现,面向接口编程。在具体实现中解耦,同时基于接口和实现的分离,使得产品类 切换更加容易,灵活。
缺点: 1.类文件爆炸性增加 2.新的产品类不易扩展
参考文章
五、一个线程几个loop
三、Android部分
1、65536错误是怎么回事
该错误主要是由于我们打包后classes.dex文件里方法数量超出的65536个。
预防方法主要是少写方法,多用基类,能不用兼容包的就不要用兼容包的,引入jar或library工程时注意下它们的方法数,但是如果实在没办法,就用这个方法解决:
第一步:在项目的grade文件里面的defaultConfig闭包下添加: multiDexEnabled true
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
multiDexEnabled true
第二步:在dependencies下添加依赖 compile 'com.android.support:multidex:1.0.0'
dependencies {
compile 'com.android.support:multidex:1.0.0'
第三步:自定义继承于Application的类,并重写protected void attachBaseContext(Context base)方法,调用 MultiDex.install(this)初始化,最后记得在Manifest清单里注册自定义的application类,如图:
public class MyApplication extends Application{
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
该问题的参考文章一
该问题的参考文章二
2、什么是内存泄漏、什么是内存溢出,该怎么应对
内存溢出(OOM):指的是申请不到足够的内存;
原因:1、内存一次性开销过大(加载巨图)。2、内存持续性开销(循环创建对象)。3、内存回收不及时(内存开销过快,GC频率跟不上开销速度等)。4、内存无法回收(内存泄漏导致内存溢出)。
解决方案:1、调整图像大小。2、尽可能不在循环中申请内存。3、及时回收图像。
参考文章地址
内存泄露(Leak):无法释放已申请的内存;
在Java中,判断对象是否仍在使用的方法是:引用计数法,可达性分析。
原因:1、静态变量(单例模式)。2、监听器。3、内部类(内部类持有外部引用)。4、资源对象未关闭。5、容器中的对象没有清理。6、webview。
避免内存泄漏方法:
1、不要在匿名内部类中进行异步操作
2、将非静态内部类转为静态内部类 + WeakReference(弱引用)的方式
3、使用Context时,尽量使用Application 的 Context
4、尽量避免使用static 成员变量。另外可以考虑lazy初始化
5、为webView开启另外一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放
6、及时关闭资源。Bitmap 使用后调用recycle()方法
参考文章一
参考文章二
两者关系:内存泄露 → 剩余内存不足 → 后续申请不到足够内存 →内存溢出。
3、什么是Android的模块化、组件化、插件化
模块化:一个程序按照其功能做拆分,分成相互独立的模块(例如:登陆,注册)。模块化的具体实施方法分为插件化和组件化。
组件化:是将一个app分成多个模块,每个模块都是一个组件(module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件,但是最终发布的时候将这些组件合并成一个统一的apk,这就是组件化开发。
插件化:插件化开发和组件化不同,插件化开发就是将整个app拆分成很多模块,每个模块都是一个apk(组件化的每个模块是一个lib),最终打包的时候将宿主apk和插件apk分开打包,插件apk通过动态下发到宿主apk,这就是插件化。
参考文章地址
4、浅述APK的打包流程
第一步 aapt阶段: 资源打包工具,将res资源文件打包成R.java文件和res文件。
第二步 aidl阶段: 这个阶段处理.aidl文件,生成对应的Java接口文件。
第三步 Java Compiler阶段:通过Java Compiler编译R.java、Java接口文件、Java源文件,生成.class文件。
第四步 dex阶段: 通过dex2.jar将.class文件处理生成class.dex。
第五步 apkbuilder阶段:将classes.dex、res文件夹、AndroidManifest.xml打包成apk文件。
第六步 Jarsigner阶段:对apk文件加签,形成一个可以运行的apk。
参考文章地址一
参考文章地址二
5、打apk包时,V1、V2签名的区别是什么
v1签名是对jar进行签名,V2签名是对整个apk签名:官方介绍就是:v2签名是在整个APK文件的二进制内容上计算和验证的,v1是在归档文件中解压缩文件内容。
二者签名所产生的结果:
v1:在v1中只对未压缩的文件内容进行了验证,所以在APK签名之后可以进行很多修改——文件可以移动,甚至可以重新压缩。即可以对签名后的文件在进行处理
v2:v2签名验证了归档中的所有字节,而不是单独的ZIP条目,如果您在构建过程中有任何定制任务,包括篡改或处理APK文件,请确保禁用它们,否则您可能会使v2签名失效,从而使您的APKs与Android 7.0和以上版本不兼容。
根据实际开发的经验总结:
一定可行的方案: 只使用 v1 方案
不一定可行的方案:同时使用 v1 和 v2 方案
对 7.0 以下一定不行的方案:只使用 v2 方案
参考文章地址
6、Handler
什么是handler:
Handler是Android SDK来处理异步消息的核心类。
子线程与主线程通过Handler来进行通信。子线程可以通过Handler来通知主线程进行UI更新。
参考文章地址
7、ListView和RecylerView的区别,以及如何优化
1、 缓存不同:
ListView是2级缓存,RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
2、adapter不同
ListView有自带的Adapter,例如ArrayAdapter等,而RecylerView所有的adapter必须由自己实现。
3、布局不同
ListView布局较为单一,只有纵向布局,RecylerView横向、纵向、表格、瀑布流都可以实现。
4、刷新区别
ListView中通常使用全局刷新函数notifyDataSetChanged()来刷新ListView中的所有数据,这是一个非常耗费资源的行为,RecyclerView则可以实现数据的局部刷新,例如notifyItemChanged()函数等。
5、 动画区别:
在RecyclerView封装的类中已经自带了很多内置的动画API,而ListView则需要自己实现。
6、item点击事件:
ListView提供了setOnItemClickListener()这样的item点击事件,而RecylerView没有,需要自己实现。
ListView的优化:
优化方式一:
convertView的复用,进行布局的复用。
优化方式二:
ViewHolder的使用,避免每次都findviewById。
优化方式三:
使用分段加载。
优化方式四:
使用分页加载。
参考文章
RecylerView的优化:
参考文章
8、EventBus
EventBus是一种用于Android的事件发布-订阅总线,它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。
三个角色:
Event:事件,它可以是任意类型,EventBus会根据事件类型进行全局的通知。
Subscriber:事件订阅者,在EventBus 3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe,并且指定线程模型,默认是POSTING。
Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。
四个线程:
POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。
MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。
BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。
参考文章
9、ANR问题
ANR全称:Application Not Responding,也就是应用程序无响应。
以下四个条件都可以造成ANR发生:
InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
ContentProvider Timeout :ContentProvider的publish在10s内没进行完成。
造成ANR的原因及解决办法:
1、主线程阻塞或主线程数据读取。解决办法:使用子线程处理耗时任务或者阻塞任务
2、CPU满负荷,I/O阻塞。解决办法:文件读写或者数据库操作放在子线程。
3、内存不足。解决办法:优化内存,防止内存泄漏。
4、各大组件ANR。解决办法:各大组件的生命周期也应避免耗时操作。
参考文章一
参考文章二
10、android设备的唯一标识问题
没有IMEI授权就用android_id和MAC地址,但是android6.0之后MAC地址统一返回02:00:00:00:00:00。
有IMEI授权就用imei码。
android10不能用imei,用OAID吧。
以上的是主流的方法,还有MEID,ESN,IMSI等。
Android设备唯一标识
11、sdk打渠道包方法
12、APP从启动到看到第一个页面