一个一个问题来总结一下:答案由蚂蚁金服首席架构师提供
答:MVC是比较早的一种软件开发模式了。
M对应模型层(Model)针对业务模型,建立的数据结构和相关的类,它主要负责网络请求,数据库处理,I/O的操作。
V代表视图层:对应于xml布局文件和java代码动态view部分。
C代表Controller层对应Activity,用于绑定View及向Model层发送指令获取数据。
MVP中把MVC的C改P,View层和Model层不直接通信,通过实现一个View接口来进行通信,实现了View和Model的解耦,但是这样比较麻烦,每个V都需要对应一个Presenter及View接口,实现较为复杂。
MVVM 中View和Model通过ViewModel实现数据互通,与Databinding的配合使用实现了View和Model的双向绑定,主要也是通过观察者模式来实现相互的解耦。个人觉得是目前最为常用的Android软件开发的一种模式了。当然业务非常简单且后期不需要太多的维护的就还是用MVC,写法简单。较为复杂的还是推荐MVVM模式。
2、简要说一下四种启动模式及应用场景
1)、standard:Activity默认的启动方式,每次启动都会往Activity栈中不断添加新的Activity实例。最经常使用的一种模式
2)、singleTop:栈顶复用模式,当启动一个Activity时,如果Activity栈的栈顶不是这个Activity的实例,则创建新的实例加入栈中。主要应用于自己打开自己,例如:电商APP中,商品详情页面下面会有推荐商品,点击还是商品详情页面,此时使用singleTop
3)、singleTask:栈内复用模式,当启动一个新的Activity时,如果目标Activity栈中不存在此Activity实例,则创建新的实例加入栈中。如果存在此Activity实例的话,将此实例上面的实例出栈,将自己置于栈顶并获取焦点。主要用于应用程序的主页
4)、singleInstance:当启动一个新的Activity时,都会新建一个任务栈,自己独栈一个Activity栈,比较少用。实际应用中,我好像没用过这个,应用场景:在做支付时,我感觉调用支付宝微信支付的页面应该就属于这种启动模式,不知道是不是。
ActivityA启动ActivityB的完整生命周期、启动过程(AMS怎么知道要启动哪个Activity)
ActivityA启动ActivityB时的生命周期:
A-onPause
B-onCreate
B-onStart
B-onResume
A-onStop
ActivityB返回ActivityA的生命周期:
B-onPause
A-onRestart
A-onStart
A-onResume
B-onStop
B-onDestory
Activity的启动过程:(AMS怎么知道你要启动的是哪个页面)
重新总结一下:
答:ActivityRecord是存储一个Activity所有信息的类,可以代表一个Activity。AMS在创建时,初始化了一个ActivityStackSupervisor实例,执行startActivityLocked方法时初始化的ActivityRecord对象,用于存储要启动的Activity的所有信息,如:调用者所在的进程、包名信息、启动来源的Activity包名、配置信息、Intent的信息等。
3、线程A与线程B如何通信的,线程间通信原理
答:线程A要往线程B发送消息,则需要在线程B中初始化一个Handler对象,在线程B初始化Handler对象前需要确保线程B中有Looper对象(Looper.prepare(),一个线程只能有一个Looper对象,多次执行Looper.prepare()方法会抛出异常),否则在初始化Handler时会抛出异常。在线程A中,持有线程B的Handler对象实例,使用handler.sendMessage(Message msg)方法,最终还是调用sendMessageAtTime(),将Mesaage对象存入MessageQueue消息队列中,MessageQueue消息队列是Looper.prepare方法初始化的。线程B中Looper.loop方法就会开启一个无限循环,不断的从MessageQueue消息队列中取出Message对象,Message对象中有一个属性target,其实就是对应的handler,调用msg.target.dispatchMessage()方法,最终就回调到handler的handlerMessage方法了,也就是线程B的handler的handlerMessage方法去处理消息了。整个线程间通信的流程就是这样的。具体源码分析过程可以看看我的另外一篇博客:Android面试基础
4、View与ViewGroup的onMesure的方法有什么去区别
ViewGroup除了会测量自身,还会遍历所有子View进行测量。
这里再总结一下,争取下次遇到这个问题时能答的更好:
答:简单来讲就是:ViewGroup除了测量自身,还需要测量子View的大小,ViewGroup中提供了对子View的测量方法: measureChildren(int widthMeasureSpec, int heightMeasureSpec),在measureChildren中遍历所有子View,调用measureChild((int widthMeasureSpec, int heightMeasureSpec),在measureChild中调用了View的measure(int widthMeasureSpec, int heightMeasureSpec)方法,最终执行View的onMeasure()方法,让子View测量自身大小。
实际上ViewGroup是View的子类,也就说ViewGroup可以看成一个特殊的View,我们在这里可以不叫ViewGroup和View,可以说成父控件和子控件比较容易理解。
父控件在测量时,会测量自身,然后调用measureChild方法去循环测量子控件,测量子控件时,会带两个参数(widthMeasureSpec和heightMeasureSpec),这两个参数就是父控件告诉子控件可获得的空间以及关于这个空间的约束条件,子控件拿着这些条件就能正确的测量自身的宽高了。也就是子控件测量自身时,需要关注父控件的两个参数。至于这个限制条件就要了解一下MeasureSpec类,MeasureSpec是View的内部类,值保存在一个int值当中,一个int有32位,前两位是 mode(模式),后30位是 size(大小) 即:MeasureSpec = mode + size。ViewGroup中有一个getChildMeasureSpec方法,这个方法规定了子View的测量模式及大小。主要就是规则就是:
答:View和ViewGroup的事件分发主要区别在于ViewGroup比View多了一个拦截事件onInterceptTouchEvent方法,如果返回true,则交给自身的onTouchEvent方法,不再往下继续分发。看自身是否要消费事件,如果消费事件,则onTouchEvent返回true,不再向上传递。View就是一个View,不存在子View,所以不需要拦截,就是消费或者不消费的问题。
答:OKHttp采用建造者模式来初始化OkHttpClient以及Request对象,OkHttpClient中包含有调度器(Dispatcher)、连接池(ConnectionPool)、拦截器(Interceptor)等等,Request对象里面包含有method、url、header、body等等,通过OkHttpClient对象调用newCall方法获取Call对象(实际上是RealCall对象),之后通过call对象进行同步/异步请求。
同步请求将任务加入到调度器(Dispatcher)的runningSyncCalls双端队列中,然后直接调用了getResponseWithInterceptorChain。
异步请求将请求加入到调度器(Dispatcher)中,经历两个阶段:readyAsyncCalls、runningAsyncCalls,之后调用getResponseWithInterceptorChain。
调度器中定义了两个并发执行数的变量,分别对并发做限制,最大同时执行数为64,同主机最大执行数为5。
任务执行是通过线程池进行的,OkHttpClient的线程池是用的CacheThreadPool,其特点是核心线程为0的无界线程池,任务到达后检查是否有可复用的线程,没有则创建新的线程去执行任务,线程空闲超时时间为60秒。
getResponseWithInterceptorChain方法是通过责任链模式来执行的一串拦截器链,拦截器总共有七个,两个自定义拦截器,五个内置拦截器。其执行顺序是:
我想要打印请求的一些信息以及返回的一些信息怎么处理 ?
答:这个我们可以利用上面所说的拦截器来实现,通过OkHttp的建造者模式构建OkHttpClient时的一个addInterceptor方法添加一个自定义拦截器,实现Interceptor的intercept方法,利用Chain对象可以获取到Request信息以及Response信息。
7、你做过哪些性能优化相关的事情?
答:性能优化包括的内容比较多,例如:App的启动优化、内存优化、稳定性优化、APK瘦身优化、操作流畅度优化等等。
8、Bitmap的优化
答:Bitmap方面的优化就是压缩、bitmap复用等等。这里了解一点:Bitmap的内存不是由图片文件大小决定的,而是由图片长宽及单位像素所占字节数来确定的:长 * 宽 * 单位像素占用字节数。其他的就去了解一下Glide对Bitmap的优化。
9、HashMap底层原理了解过么?说说HashMap的底层原理
答:HashMap的底层是数组+链表实现的,数组长度默认为16,负载因子0.75,也就是说当数组长度超过12的时候,数组会自动扩容,扩容倍数为2。存储在数组位置是由key的hashCode值的hash散列值与数组长度的取模得来的,即hash(key.hashCode()) / 数组长度。当数组扩容后,数组存储位置要发生改变,所以数组扩容是一个比较耗费资源的一个操作。同时,数组上存储的是链表,1.7之前采用单链表,1.8之后采用单链表和红黑树,当链表长度超过8时,单链表改为红黑树,当长度变为6的时候,改回单链表,因为链表长度为6时,单链表和红黑树的查询时间复杂度一样,当链表超过6的时候,红黑树效率要比单链表高,为什么设置为8改变,应该是为了中间有一个过渡,使得不会在单链表和红黑树之间频繁的切换。
/**
* 冒泡排序
* 1、通过每一次遍历获取最大值/最小值
* 2、将最大值/最小值放在尾部/头部
* 3、除开最大值/最小值,剩下的数据进行遍历获取最大/最小值
* @param arr
*/
public static void bubbleSort(int[] arr){
for(int i=0; i < arr.length; i++){
for(int j=0; j < arr.length -i -1; j++){
//遍历获取最大值,放在尾部
if(arr[j] > arr[j+1]){
//交换
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}