面试精选-Android开发经典题
1.HashMap的内部实现原理?
HashMap是基于拉链法实现的一个散列表,内部由数组和链表实现。
-数组的初始容量为16,而容量是以2的次方扩充的,一是为了提高性能使用足够大的数组,二是为了能使用位运算代替取模预算。
-数组是否需要扩充是通过负载因子判断的,如果当前元素个数为数组容量的0.75时,就会扩充数组。这个0.75就是默认的负载因子,可由构造传入。我们也可以设置大于1的负载因子,这样数组就不会扩充,牺牲性能,节省内存。
-为了解决碰撞,数组中的元素是单向链表类型。当链表长度到达一个阈值时(7或8),会将链表转换成红黑树提高性能。而当链表长度缩小到另一个阈值时(6),又会将红黑树转换回单向链表提高性能,这里是一个平衡点。
-对于第三点补充说明,检查链表长度转换成红黑树之前,还会先检测当前数组数组是否到达一个阈值(64),如果没有到达这个容量,会放弃转换,先去扩充数组。所以上面也说了链表长度的阈值是7或8,因为会有一次放弃转换的操作。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
2.Handler 机制原理
Handler是Android中的消息发送器,handler是更新UI界面的机制,也是消息处理的机制,我们可以发送消息,也可以处理消息,Looper负责接收handler发送的消息,并把消息回传给handler自己。
handler主要中主要对象为MessageQueue,Looper,下面我们可以看到handler常用的两个方法,都是基于MessageQueue来实现的消息发送。
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
-handler封装消息的发送(主要包括消息发送给谁)
-Looper——消息封装的载体:
(1)内部包含一个MessageQueue,所有的Handler发送的消息都走向这个消息队列;
(2)Looper.Looper方法,就是一个死循环,不断地从MessageQueue取消息,如果有消息就处理消息,没有消息就阻塞。
-MessageQueue,一个消息队列,添加消息,处理消息
-handler内部与Looper关联,handler->Looper->MessageQueue,handler发送消息就是向MessageQueue队列发送消息。
3.Java设计模式
-单例模式:某个类只能有一个实例,提供一个全局的访问点。
-简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
-工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
-抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
-建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
-原型模式:通过复制现有的实例来创建新的实例。
-适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
-组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
-装饰模式:动态的给对象添加新的功能。
-代理模式:为其他对象提供一个代理以便控制这个对象的访问。
-亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。
-外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
-桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
-模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
-解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
-策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
-状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
-观察者模式:对象间的一对多的依赖关系。
-备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
-中介者模式:用一个中介对象来封装一系列的对象交互。
-命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
-访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
-责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
-迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
4.模式MVP,MVC,MVVM介绍
MVC
M(model)模型, 是应用程序中用于处理应用数据逻辑的部分,通常模型对象负责在数据库中进行存取
V(view)视图, 是应用程序中处理数据的显示部分,通常视图是一句模型数据来创建的
C(controller)控制器, 是应用程序中处理用户交互的部分,通常控制器负责从视图读取数据,控制用户的输入,并向模型发送数据。
MVP
M(model)负责数据的请求,解析,过滤等数据操作
V(View)负责图示部分展示,图示事件处理,Activity,Fragment,Dialog,ViewGroup等呈现视图的组件都可以承担该角色
P(presenter)是View和Model交互的桥梁。
MVVM
M(model)模型, 是应用程序中用于处理应用数据逻辑的部分,通常模型对象负责在数据库中进行存取
V(view)视图, 是应用程序中处理数据的显示部分,通常视图是一句模型数据来创建的
VM(viewmodel )核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View,即所谓的数据双向绑定。 Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象
5.四大引用与作用
强引用
强引用就是我们常见的使用new关键字直接创建一个对象就属于强应用。
如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它,当内存空间不足,Jawa虚机宁抛出0 ut Ofmemoryerror错误,使程序异常终止,也不会辞随意回收具有强引用的对象
来解决内存不足问题。
软引用( Softreference)
用来描述一些还有用但并非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。如果一个对象只具有软引用,就类似于可有可物的生活用品.如果内存空间不足时,就会回收这些对象。
弱引用( Weak Reference)
如果一个对象只具有弱引用,类似于可有可物的生活用品。弱引用与软引用的区别在于弱引用的对象拥有更短的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发現了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和个引用队列( Referencequeue)联合使用,如果弱引用所引用的对象被垃圾回收,ava虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用( Phantomreference)
虚引用顾名思义,就是形同虚设,与建他几种引用都不同,虚引用井不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任问时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别別在于:虚引用必须和引用队列( Referencequeue)联合使用,当垃圾回收器准回收一个对象时,如果发现它还有成引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中,程序可以通过判断引用队列中是否已经加入了虚解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经械加入到引用队列,那么就可以在所引用的对象的内存被回收之前来取必要的行动。
6.ANR 如何产生?
应用程序的响应是由ActivityManager和WindowManager服务系统服务监视的,当检测到下面三种情况的任何一种时,Android就会针对特定的应用程序显示ANR对话框。
Activity的UI在5秒内没有响应输入事件(例如,按键按下,屏幕触摸)–主要类型
BroadcastReceiver在10秒内没有执行完毕
Service在特定时间内(20秒内)无法处理完成–小概率类型
造成ANR的原因有很多,无论是在Activity或者BroadcastReceiver还是在Service,我们看到都是在主线程中操作引起的ANR,因此我们应该避免在主线程做太多耗时的操作,网络请求不用说了,Android4.0以后就禁止在主线程成执行请求了,除此之外就是要注意如下几个方面:
主线程频繁进行IO操作,比如读写文件或者数据库;
硬件操作如进行调用照相机或者录音等操作;
多线程操作的死锁,导致主线程等待超时;
主线程操作调用join()方法、sleep()方法或者wait()方法;
system server中发生WatchDog ANR;
service binder的数量达到上限。
7.JNI 的使用
JNI全称为Java Native Interface,即Java本地接口,JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。jni编程也是android ndk开发中的基础,我们开发过程中最常见的.so文件就是属于jni的最终实现结果。
jni 的实现公有6步:
1.Java中先声明一个native方法
2.编译Java源文件javac得到.class文件,命令:javah -jni 包名
3.通过javah -jni命令导出JNI的.h头文件
4.使用Java需要交互的本地代码,实现在Java中声明的Native方法(如果Java需要与C++交互,那么就用C++实现Java的Native方法。)
5.将本地代码编译成动态库,根据不同系统有不同文件格式.dll,.so,.jnilib
6.通过Java命令执行Java程序,最终实现Java调用本地代码。
8.Binder机制介绍
Binder是android中的进程间通信组件,它是由Client、Server、ServiceManager三个对象组成实现进程间通信。传统的IPC机制可能会增加进程的开销,以及出现进程过载和安全漏洞,Binder机制则有效避免和解决了这些问题。
1.客户端获取服务端的代理对象(proxy)。我们需要明确的是客户端进程并不能直接操作服务端中的方法,如果要操作服务端中的方法,那么有一个可行的解决方法就是在客户端建立一个服务端进程的代理对象,这个代理对象具备和服务端进程一样的功能,要访问服务端进程中的某个方法,只需要访问代理对象中对应的方法即可;
2.客户端通过调用代理对象向服务端发送请求。
3.代理对象将用户请求通过Binder驱动发送到服务器进程;
4.服务端进程处理客户端发过来的请求,处理完之后通过Binder驱动返回处理结果给客户端的服务端代理对象;
5.代理对象将请求结果进一步返回给客户端进程。
9.内存溢出&内存泄漏
内存泄露(OOM):
申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
1.java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
2.java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
3.java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小
内存溢出:
申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
10.进程、线程、协程
进程
进程是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程
单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
协程
协程与子例程一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程源自 Simula 和 Modula-2 语言,但也有其他语言支持。
协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
一个程序可以包含多个协程,可以对比与一个进程包含多个线程,
因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
协程和线程区别:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。
*以上都是通过网上总结而来,如果涉及侵权,联系立删。