本篇博文中,我们来说说Android面试中常见的那些问题。主要从 Java 和 Android 两个大块来例举。
Java
关于内存泄露
1.资源对象未关闭
如 Cursor, File 等资源。虽然他们会在 finalize 中关闭,当这样效率太低。容易造成内存泄露。
SQLiteCursor, 当数据量大时容易泄露。
2.使用 Adapter 时,没有使用系统缓存的 converView.
3.即时调用 recycle() 释放不在使用的 Bitmap.
4.在能使用 application 的 context 的地方就尽量不要用 activity 的 context,因为这样可能会导致 activity 被引用而无法释放。
5.注册没取消造成内存泄露,比如:广播
6.集合中的对象没清理造成内存泄露,我们通常把一些对象的引用加入到集合中,当我们不需要该对象时,并没有让 GC 及时清理它,导致这个集合越来越大,如果这个集合是 static 的话,可能 OOM.
7.Handler 因该声明为静态对象,并在其内部类中保存一个对外部类的弱引用,参见我的另一篇博客《你还在写导致context内存泄露的代码吗?》。
ArrayList 和 LinkedList 的区别
- ArrayList 初始大小为 10,大小不够会调用 grow 扩容: length = length + (length >> 1)
LikedList 中 Node first, last 分别指向头尾。
ArrayList 和 LinkedList 在性能上各有优缺点,需要根据业务场景来决定使用哪个:
1.在 ArrayList 的中间插入或删除一个元素意味着这个列表剩余的元素都会被移动;而在 LinkedList 中这个开销是固定的。
2.LinkedList 不支持高效的随机元素访问。
3.ArrayList 的空间浪费主要体现在list列表的结尾会预留一定的容量空间
hashmap 和 hashtable 的不同
1.父类不同:
1 | public class Hashtable extends Dictionary implements Map |
2.Hashtable 中的方法是同步的,而 HashMap 中的方法在缺省情况下是非同步的。在多线程并发的情况下,可以直接使用 Hashtable,但是要使用 HashMap 的话就要自己增加同步处理。
3.Hashtable 中,key 和 value 都不允许出现 null 值。而在 HashMap 中,null 可以作为 key,但这样的 key 只能存在一个,value 为 null可以存在多个;因为 HashMap 中存在 null 的 key,所以你不应该使用 get() 的方法来判断某个 key 是否存在,而应该使用 containsKey() 方法来判断。
4.两个遍历方式的内部实现不同。
Hashtable 和 HashMap 都使用了 Iterator。由于历史原因,Hashtable 还使用了 Enumeration 的方式。
5.哈希值的使用不同,HashTable直接使用对象的 hashCode.而 HashMap 重新计算 hash 值。
6.Hashtable 和 HashMap 他们两个内部实现方式的数组的初始大小和扩容方式不同。HashTable 中的 hash 数组默认大小是11,扩容方式是 old*2 + 1。HashMap 中的 hash 数组的默认大小是16,而且一定是2的指数。
Iterator 和 Enumeration 的不同
1.函数接口不同
Enumeration 只有2个函数接口。通过 Enumeration,我们只能读取集合的数据,而不能对数据进行修改。
Iterator 只有3个函数接口。 Iterator 除了能读取集合的数据之外,也能对数据进行删除操作。
2.Iterator 支持 fail-fast 机制,而 Enumeration 不支持。
Enumeration 是 JDK 1.0 添加的接口。使用到它的类包括 Vector, Hashtable 等类,这些类都是 JDK 1.0 中加入的,Enumeration 存在的目的就是为它们提供遍历接口。 Enumeration 本身并没有支持同步,而在 Vector, Hashtable 实现 Enumeration 时,添加了同步。
Iterator 是 JDK 1.2 添加的的接口,他也是为了 HashMap, ArrayList 等集合提供遍历的接口。 Iterator 是支持 fail-fast 机制的.
什么是 fail-fast 机制?
fail-fast 机制是 java 集合中的一种错误机制。当多个线程对同一个集合内的内容进行操作时,就可能会发生该事件。
例如:当某一个线程A通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了,那么线程A访问时,就会抛出 ConcurrentModificationException 异常,触发 fail-fast 事件。
接口
1.接口中的字段全部默认为 public static 类型。
2.接口中的方法全部默认为 public 类型。
3.接口中可以声明内部类,而默认为 public static,正因为是 static,只是命名空间属于接口,代码逻辑不属于接口。所以不违反接口定义。
4.接口本身可以声明为 public 或者缺省。
5.抽象类继承自某接口。如果在抽象类中实现了父类(接口)中的方法,在其子类可以不用实现,否则在子类必须实现。
final方法
将方法声明为 final 有两个原因:
第一就是说明你已经知道这个方法提供的功能已经满足你要求,不需要进行扩展,并且也不允许任何从此类继承的类来覆写这个方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。
第二就是允许编译器将所有对此方法的调用转化为 inline 调用的机制,它会使你在调用 final 方法时,直接将方法主体插入到调用处,而不是进行例行的方法调用,例如保存断点,压栈等,这样可能会使你的程序效率有所提高,然而当你的方法主体非常庞大时,或你在多处调用此方法,那么你的调用主体代码便会迅速膨胀,可能反而会影响效率,所以你要慎用 final 进行方法定义。
Android方面
Handler机制
1.Handler 对 Activity finish 影响。
在开发的过程中碰到一个棘手的问题,调用 Activity.finish 函数. Acitivity没有执行生命周期的 ondestory 函数,后面查找半天是因为有一个
handler 成员,因为它有一个 delay 消息没有处理,调用 Activity.finish,Activity 不会马上 destory,所以记得在 Ativity finish 前清
理一下 handle 中的未处理的消息,这样Activity才会顺利的destory
2.Looper
通过调用 Looper.prepare() 创建 Looper() 对象并绑定到ThreadLocal变量中。
Looper里面包含了messageQueue。
构造器如下:
1 | private Looper() { |
3.loop() 函数
从Looper中取出MessageQueue;
循环从MessageQueue中取出Message;
从Message中取出Target(Handler对象);
调用tartget的dispatchMessage分发消息。
4.Handler 对象
重要成员变量:
1 | final MessageQueue mQueue; |
Handler 对象在发送消息的时候,将 MSG 的 target 变量设为自己。这样在 Looper 对象循环取出 msg 的时候就可以调用对应 handler 的dispatchMessage()。此函数分发消息的优先级如下:
- Message在创建的时候调用 Obtain 设置了 Callback。
- Handler在创建的时候传入了 Callback。
- 交给 Handler 子类的 HandleMessage 处理(通常的做法)。
Android启动模式
standard 和 singleTop 模式
这两种比较简单。创建Activity放入当前的任务栈中,若当前是 singleInstace,则放入设置的任务栈中。其中如果 Activity 在栈顶,则调用onNewIntent。
singletask:栈内复用模式
不是在当前任务栈中查找是否存在,实际过程如下:
- 查找该 Activity 所需的任务栈是否存在(由 taskAffinity 控制,或者默认为包名)。
- 在任务栈当中查找该 Activity 是否存在。 这里面存在任务栈的切换,也就是当开启的 singtask 类型的 Activity 不属于当前任务栈时,则会切换到其任务栈。
singleInstance:单实例模式
包含了 singleTask 的所有特性,另外加上:设置为该模式的 Activity,只能单独存在于一个任务栈中。当有两个 singleInstace 的 Activity 设置成同样的任务栈时,会出现两个同名的任务栈,分别用来存放同名的 Activity。
注意:在任何跳转的时候,首先调用本 Activity 的 onPause,然后跳转。如果被跳转的 activity 由于启动方式而没创建新的实例,则会先调用onNewIntent,然后按照正常的生命周期调用。
如:
1:A→B,A:onPause;B:onCreate,onStart,onResume。
2:A(singleTop)→A,A:onPause;A:onSaveInstanceState;A:onResume。
View 的绘制
推荐郭霖的博客:
http://blog.csdn.net/guolin_blog/article/details/16330267
canvas 的使用
推荐以下博客:
http://blog.csdn.net/qinjuning/article/details/6936783
Activity 切换时生命周期交集
Activity 之间的协作,当一个 activity A 启动了另外一个 activity B,它们的生命周期是有交叉的:
首先 A 的 onPause() 被调用,之后 B 的 onCrate(), onStart() 及 onResume() 方法会被调用(此时B拥有用户焦点),最后,如果A在屏幕上不可见,onStop() 方法被调用.因此,我们在两个 activities 中传递数据,或者共享资源时(如数据库连接),需要在前一个activity的onPause() 方法而不是 onStop() 方法中进行.
Hybrid(重要加分项)
1.java 和 JS 的交互
http://droidyue.com/blog/2014/09/20/interaction-between-java-and-javascript-in-android/
http://rensanning.iteye.com/blog/2043049
2.WebView 开启 JavaScript 脚本执行
3.WebView 设置供 JavaScript 调用的交互接口。
网络编程
1.volley
- https://bxbxbai.github.io/2014/09/14/android-working-with-volley/
- http://blog.csdn.net/guolin_blog/article/details/17656437
2.如何控制 TCP 连接时的拥塞
http://blog.csdn.net/yechaodechuntian/article/details/25429143
3.三次握手
http://blog.csdn.net/whuslei/article/details/6667471
4.Android 客户端和服务端如何使用 Token 和 Session
http://wyong.blog.51cto.com/1115465/1553352
5.移动端获取网络数据优化的几个点连接复用
节省连接建立时间,如开启 keep-alive。
对于 Android 来说默认情况下 HttpURLConnection 和 HttpClient 都开启了 keep-alive。只是 2.2 之前 HttpURLConnection 存在影响连接池的 Bug,具体可见:Android HttpURLConnection 及 HttpClient 选择
6.请求合并
即将多个请求合并为一个进行请求,比较常见的就是网页中的 CSS Image Sprites。如果某个页面内请求过多,也可以考虑做一定的请求合并。
7.减少请求数据的大小
- 对于 post 请求,body 可以做 gzip 压缩的,header 也可以作数据压缩(不过只支持http 2.0)。
- 返回的数据的body也可以作 gzip 压缩,body 数据体积可以缩小到原来的30%左右。(也可以考虑压缩返回的 json 数据的 key 数据的体积,尤其是针对返回数据格式变化不大的情况,支付宝聊天返回的数据用到了)
- 根据用户的当前的网络质量来判断下载什么质量的图片(电商用的比较多)
android 开发中,可能会导致内存泄露的问题
- 不要让生命周期长于 Activity 的对象持有到 Activity 的引用
- 尽量使用 Application 的 Context 而不是 Activity 的 Context
- 尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用(具体可以查看细话Java:”失效”的private修饰符了解).如果使用静态内部类,将外部实例引用作为弱引用持有。
- 垃圾回收不能解决内存泄露,了解Android中垃圾回收机制
更多内容可以参考以下博客:
http://spencer-dev.lofter.com/post/d7b9e_6faf120