由于个人原因在一个公司连续呆了四年之久,然而这次面试也是人生中第一次面试,对于工作四年工作内容我分为三点:1.产品筹划、2.产品设计、3.安卓开发
一般面试时间短则30分钟,多则1个小时,这么点时间要全面考察一个人难度很大,需要一些技巧,这里我不局限于回答题主的问题,而是分享一下我个人关于如何做好Android技术面试的一些经验:
- 简历调查
简历到你手上的时候,你要做好充分的调查分析,不仅仅是对公司负责,也是对自己与候选人时间的尊重,明显不match的简历,就不要抱着“来试试看”的想法了,候选人也许很不错,但如果跟你的岗位不match, 也不要浪费大家时间,你要想清楚现在需要的人是有潜力可以培养的,还是亟需帮忙干活的。另外如果简历里附带了博客链接,GitHub地址,相关作品的,可以提前去看看,直接看人家多年积累的文章与代码,比这短短一小时的面试来得靠谱的多。
- 准备问题
了解清楚候选人背景后,要根据简历,有针对性的准备问题,可以是他作品或做过项目里的某个技术细节的实现方式,也可以是他声称精通的某些领域的相关问题。总之不要等到面试过程中现想问题,特别是刚开始面试别人的同学,往往经验不足稍带紧张导致大脑短路,其实也是很尴尬的,把要问的问题提前写下来,准备充分。
在面试之前看到很多朋友在博客,简书、csdn、掘金等发布自己的面试总结,其中有很多知识点都是模糊的,因为项目中很少用到,或者是用到了没有深入去研究。其实在过去,我常常有这种心理,觉得项目中不常用或者几乎不用普及的大可不必去研究。完全是为了开发而开发。久而久之我发现我所做的项目或多或少会出现一些bug,而这些bug有时候会困扰我几个小时,甚至大半天。后面当我去研究那些我认为项目中很少用,或者几乎用不到的知识点后,才发现自己所犯的错,而且你会很清楚的明白错误原由,以及解决办法。我之所以说这些就想说明要多去研究知识点,不要为了开发而开发。对一些不理解的api要学会看源码。学会读源码。好了,言归正算。下面我就汇总下我们平常面试中常见的问题(我认为)及自己的答题。
- activity生命周期?
onCreate() : 该方法是在Activity被创建时回调,它是生命周期第一个调用的方法,我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。
onStart : 此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示而我们无法看见摆了。
onResume() : 当此方法回调时,则说明Activity已在前台可见,可与用户交互了(处于前面所说的Active/Running形态),onResume方法与onStart的相同点是两者都表示Activity可见,只不过onStart回调时Activity还是后台无法与用户交互,而onResume则已显示在前台,可与用户交互。当然从流程图,我们也可以看出当Activity停止后(onPause方法和onStop方法被调用),重新回到前台时也会调用onResume方法,因此我们也可以在onResume方法中初始化一些资源,比如重新初始化在onPause或者onStop方法中释放的资源。
onPause() : 此方法被回调时则表示Activity正在停止(Paused形态),一般情况下onStop方法会紧接着被回调。但通过流程图我们还可以看到一种情况是onPause方法执行后直接执行了onResume方法,这属于比较极端的现象了,这可能是用户操作使当前Activity退居后台后又迅速地再回到到当前的Activity,此时onResume方法就会被回调。当然,在onPause方法中我们可以做一些数据存储或者动画停止或者资源回收的操作,但是不能太耗时,因为这可能会影响到新的Activity的显示——onPause方法执行完成后,新Activity的onResume方法才会被执行。
onStop() : 一般在onPause方法执行完成直接执行,表示Activity即将停止或者完全被覆盖(Stopped形态),此时Activity不可见,仅在后台运行。同样地,在onStop方法可以做一些资源释放的操作(不能太耗时)。
onRestart() :表示Activity正在重新启动,当Activity由不可见变为可见状态时,该方法被回调。这种情况一般是用户打开了一个新的Activity时,当前的Activity就会被暂停(onPause和onStop被执行了),接着又回到当前Activity页面时,onRestart方法就会被回调。
onDestroy() :此时Activity正在被销毁,也是生命周期最后一个执行的方法,一般我们可以在此方法中做一些回收工作和最终的资源释放。
2.两个activity分别是AB,在A中启动B,然后返回A,请问各自的生命周期?
当A中启动B时:A:onPause()进入暂停状态,当启动B时:onCreate()-onStart-onResume后B可见,A再执行onStop()停止
返回A时B的生命周期:先onPause()进入暂停状态,当返回A时:A生命周期:onReStart()-onStart()-onResume()后A重新可见。B再执行onStop()-onDestory()销毁界面
总结:当从一个activity到另一个acitvity的时候,第一个activity会先进入一个暂停状态,只有当另外一个activity可见(调用onResume())的时候第一个acitvity才会停止或者销毁
3.activity启动模式有几种并简单说明下各自的应用场景
1.Fragment的生命周期?
onAttach(): 这个时候 activity已经传进来了,获得activity的传递的值,就可以进行 与activity的通信里,当然也可以使用getActivity(),前提是这个fragment已经和宿主的activity关联,并且没有脱离他只调用一次。
onCreate(): 系统创建fragment的时候回调他,在他里面实例化一些变量 这些个变量主要是:当你 暂停 停止的时候 你想保持的数据,如果我们要为fragment启动一个后台线程,可以考虑将代码放于此处。参数是:Bundle savedInstance, 用于保存 Fragment 参数,Fragement也可以重写 onSaveInstanceState(BundleoutState)方法, 保存Fragement状态;可以用于 文件保护他也只调用一次。
onCreateView():第一次使用的时候 fragment会在这上面画一个layout出来,为了可以画控件 要返回一个 布局的view,也可以返回null当系统用到fragment的时候 fragment就要返回他的view,越快越好,所以尽量在这里不要做耗时操作,比如从数据库加载大量数据显示listview,当然线程还是可以的。给当前的fragment绘制ui布局,可以使用线程更新UI说白了就是加载fragment的布局的。这里一般都先判断是否为null
if(text==null){
Bundle args=getArguments();
text=args.getString("text");
}
if (view == null) {
view = inflater.inflate(R.layout.hello, null);
}
这样进行各判断省得每次都要加载,减少资源消耗
-onStart():和activity等同启动, Fragement 启动时回调, 此时Fragement可见;
onResume():和activity等同在activity中运行是可见的激活, Fragement 进入前台, 可获取焦点时激活;
onPause():和activity等同 其他的activity获得焦点,这个仍然可见第一次调用的时候,指的是用户离开这个fragment(并不是被销毁)通常用于 用户的提交(可能用户离开后不会回来了)
onStop():和activity等同fragment不可见的,可能情况:activity被stopped了 OR fragment被移除但被加入到回退栈中一个stopped的fragment仍然是活着的如果长时间不用也会被移除
onDestroyView():Fragment中的布局被移除时调用。表示fragemnt销毁相关联的UI布局清除所有跟视图相关的资源,以前以为这里没什么用处其实 大有文章可做,相信大家都用过ViewPager+Fragment,由于ViewPager的缓存机制,每次都会加载3页。
例如:有四个 fragment 当滑动到第四页的时候 第一页执行onDestroyView(),但没有执行onDestroy。
他依然和activity关联。当在滑动到第一页的时候又执行了 onCreateView()。 生命周期可以自己试一下。
那么问题来了。会出现重复加载view的局面,所以这么做(下面是先人的代码)
@Override
public void onDestroyView() {
if(view!=null){
((ViewGroup)view.getParent()).removeView(view);
}
super.onDestroyView();
}
onDestroy():销毁fragment对象跟activity类似了。
onDetach(): Fragment和Activity解除关联的时候调用。脱离activity
2.Fragemnt栈管理方式?
3.Fragment中出现内存泄露并导致闪退,请举例说出一种场景?
回答这个问题,我们首先需要知道什么是内存泄露,什么会导致内存泄露。
内存泄露:在android中不再使用的对象,Gc垃圾回收机制无法对其回收就会产生内存泄漏注:过多的内存泄漏会导致内存溢出OOM
4.activtiy,fragment数据传递通讯方式
1.service的启动方式且各自的生命周期?
提示:如果我们的service继承的时intentservice,一般这种情况就是我们要在service中做耗时操作,然而intentservice在
onHandleIntent()中创建一个工作线程。然后在onDestory()中会自动关闭线程
2.BroadCastReceiver有几种分类及注册方式?
分类:(1)有序广播(2)标准广播
注册方式:(1)动态注册:就是代码注册广播(2)静态注册:在注册文件中注册
ContentProvider:
四大组件的内容提供者,主要用于对外提供数据
实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了ContentProvider,你在自己的应用中可以读取和修改联系人的数据,不过需要获得相应的权限。其实它也只是一个中间人,真正的数据源是文件或者SQLite等
一个应用实现ContentProvider来提供内容给别的应用来操作,通过ContentResolver来操作别的应用数据,当然在自己的应用中也可以
ContentResolver:
内容解析者,用于获取内容提供者提供的数据
ContentResolver.notifyChange(uri)发出消息
ContentObserver:
内容监听器,可以监听数据的改变状态
目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地ContentObsever也分为表ContentObserver、行ContentObserver,当然这是与它所监听的Uri MIME Type有关的
ContentResolver.registerContentObserver()监听消息
什么情况会导致内存泄露?
Fragment中出现内存泄露并闪退说明内存泄露频繁导致oom.
内存优化
这个问题就不是三言两语了,内存优化是个比较广的知识面,他包括内存泄漏,布局优化,资源优化,内存溢出等知识点
我这里就总结内存优化的几点吧:
此处我说下MVP模式,既然是设计模式那么写法肯定不是固定的,因此每个人对其理解都不一样,我就在此说下我对mvp模式的理解吧。
MVP是MVC的升级版,其中M-Model数据模型,V-View就是我们的视图,P-Presenter控制层,他控制M,V逻辑,使M,V完全解耦。一般来说我们在M层定义一个接口和一个实现类,实现类中获取网络数据或者进行一些数据操作,P层中获取到,M,V层引用,把M层返回的结果交给V层,最后在V层做处理(更新ui一些操作)。
自定义view涉及到的知识点比较全面,也是个难点,一般来说自定义view就是去实现一些我们系统控件实现不了的效果,其具体流程就是:
其中onMeasue()就是测量,这个一般在组合控件中比较常见,onLayout()就是控制view的显示位置,onDraw()就是绘制view,绘制view涉及到canvas与paint一个画布一个画笔
说说跨进程通讯?
跨进程通讯即IPC,但是跨进行通讯也很常见
对于android开发者来说对于ANR这个词并不陌生,那么ANR是什么?什么情况会ANR?怎么避免?
所谓gc机制就是垃圾处理器一共需要两部:检测和回收垃圾
1.引用计数法:一个对象引用一次计数+1,引用失效-1
2.根搜索法:以根集对象为起始点进行搜索,如果有对象不可达的话,即为垃圾对象。这里的根集一般包括:java栈中的引用对象、本地方法栈中JNI的引用对象、方法区中运行常量池中的引用对象、方法区中静态属性引用的对象、运行中的线程、由引导类加载器加载的对象、GC控制的对象。总之,JVM在做垃圾回收的时候,会检查堆中的所有对象是否被这些根集中的对象所引用,不能够被引用的对象就会被垃圾回收器回收。
1.标记-清除:将所有需要回收的对象进行标记,然后统一清除
2.复制算法:将内存分两个区域,只使用其中一个区域存储对象,垃圾回收时遍历使用区域的所有对象并将使用对象复制到另外一个区域。
3.标记-整理:此算法为前两个算法的结合体,分两个阶段,第一部将所有被使用的对象标记,第二部遍历堆中所有对象将未被标记的对象进行回收并整理存活对象
4.分代收集算法:年轻代:复制算法,老年代:标记-整理
1.对于HaspMap原理我也是才通过一些资料研究了一下,我先根据我的理解说说HashMap原理及结构
(1)HaspMap结构在jdk1.8以前:数组+链表也就是散列表形式,在jdk1.8加了一个红黑树其结构:数组+链表+红黑树
至于其意义接下来请看HashMap原理
(2)HashMap原理:首先我们通过源码看HashMap他默认的数组大小16,每次扩容为其2倍,且数组大小为2的N次方。
数据由Entry实体类以键值对形式存储,而每个实体类对应一个HashCode.在java中每个对象都有一个hashcode()方法,它是一个由32位整数组成的,而在HashMap中我们只需要9位Int整数型,当我们put一个键值对的时候,会首先判断key是否位null,(为null默认返回第一个)然后根据K找到获取HashCode值然后对其进行hash运算获取到hash值,接着循环table根据其hash值及key判断是否相同如果相同就替换,如果不相同就添加一个Entry
另外特别注意:有可能hash冲突,也就是说我们随机参数的int整数型重复了,这时候我们就通过拉链表的方式进行处理,让每个Entry以单向链表形式存在,当链表长度达到8就会采用红黑树提高性能,当链表长度小于8就重新由转换成单向链表
总结:HashMap默认数组大小为16,每个键值对都是以Entry实体类存储的,然后每个K会对应一个HashCode也就是Hash值,当我们put一个数据的时候,首先会根据这个K获取到对应的hashcode通过hashcode获取到hash值,然后循环散列表也就是循环table判断k,hash值是否相等,如果相等就表示是更新数据,如果有一个不相等就表示添加数据(包括hash冲突,上面已经说到)。最后我们也可以通过不同构造函数设置数组默认大小,负载因子。
写在前面:说到android动画我们脑海里就会想起自定义,平移,旋转等一些特效。其实android动画也是个知识面比较广的。
1.android动画分几类,说说他们的区别与特点?
总结:帧动画与补间动画归为:视图动画。属性动画为另一类,此外我推荐另外两个动画,共享动画和过渡动画,有兴趣的朋友可以研究下
首先:字体用sp设置大小可以跟随系统字体大小改变而改变,控件用dp至少对不同分辨率适配了
接下来我们了解下sp,dp,dpi这几个单位:
其中height,width为长宽像素密度,size对角线长度
px=dp*(dpi/160)
特别注意:假设现在我有两部手机分辨率不一样,但是手机尺寸大小一样,假如第一部手机的像素密度及dpi=320,第二部手机的像素密度及dpi=160,我设置了一个2dp的宽的控件根据px=dp*(dpi/160)公式可得像素密度为160的px=dp及1px1dp,像素密度320的px=2*dp及1dp=2px.我们可以想象像素密度越大那么说明像素之间很紧密,反之就稀疏。那么当我在160dpi手机上设置宽为2dp的时候此时2dp=2px,在320dpi手机上设置宽的为2dp的时候此时2dp=4px.上面说到像素密度越大像素之间就紧密,那么此时在手机尺寸大小一样的情况下。此时2px与4px宽度相差不大的。你可以把一个像素想象成一个点,像素密度(dpi)越大每个点的距离就越小,反正就越大。这就是为什么设置宽高要用dp了,因为它适配了不同分辨率。
线程池的作用及原理?
首先我们看下使用线程池的原因:在我们开发中我们有时候需要频繁创建工作线程,然后这些工作线程及有可能导致线程阻塞,而且频繁创建线程也会降低性能
那么线程池的作用是什么呐?
根据上诉我们知道:线程池就是用来管理一个个线程的,它不会频繁创建线程,而且对其进行复用这样就不会频繁调用gc,提高了效率及性能。
那么线程池的原理是什么?
要知道线程池原理首先我们看下其构造函数:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory)
corePoolSize:核心线程最大数量
maximumPoolSize:最大线程数量
keepAliveTime:非核心线程的保活时间,就是说当非核心线程在空闲状态在保活时间,这样当工作任务时间短,可以避免频繁创建线程。提高效率及性能
unit:上面时间属性的单位
workQueue:任务队列
threadFactory:线程工厂,可用于设置线程名字等等
通过构造函数推出线程池的原理:
1.当有新的任务进入的时候(execute一个线程之后)会首先判断当前核心线程是否达到最大,如果没有就直接创建一个核心线程开执行任务
2.当有新任务进入的时候(execute一个线程之后)如果核心线程达到最大但是任务队列未满,那么次任务就会进入任务队列等待
3.当有新的任务进入的时候(execute一个线程之后)如果核心线程达到最大并且任务队列已经满了,但是线程数量未超过非核心线程最大数量,就创建一个新非核心线程执行任务
4.当有新任务进入的时候(execute一个线程之后)如果线程池中的线程数已经超过非核心线程数,则拒绝执行该任务,采取饱和策略,并抛出RejectedExecutionException异常