安卓面经
Posted 鹏达君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓面经相关的知识,希望对你有一定的参考价值。
1.事件分发机制概述和原理
答:第一,分发的事件是什么?是down,move,up。
第二,分发的顺序是什么?是activity→window→rootviiew(decorview)→view。
而最后的view是viewgroup和view的集合。
1)如果是传到了viewgroup,首先调用其dispatchTouchEvent方法,在该方法内部会有一个onInterceptTouchEvent()判断是否要拦截此事件,如果是,调用该组件的onTouchEvent()方法,如果不拦截,则事件将传递到该组件的子view中,如果子view还是viewgroup,则重复上述步骤,如果是view,则执行下述步骤:
2)首先执行子view的dispatchTouchEvent()方法,如果我们给该子view设定了OnTouchListener监听器的话,那么会调用其回调方法onTouch,其返回值决定是否继续往下传递事件,如果返回false,那么调用子view的onTouchEvent方法,如果返回值还是false,且给View设置了onLongClickListener监听器的话,那么之后会调用onLongClick()方法,如果返回的还是false,且我们设置了onClickListener监听器的话,则会执行他的回调方法onClick,该方法是没有返回值的,所以也是正常事件分发机制中最后执行的方法了。而一般情况下,子view的onTouchEvent()方法的返回值默认是true,因为系统默认是由 View来消费事件的,但是ViewGroup就不是这样了;
3)有一种特殊情况,比如事件传递到最里层的View之后,调用该View的onTouchEvent方法返回了 false,那么这时候事件将通过冒泡式的方式向他的父View传递,调用它父View的onTouchEvent方法,如果正好他的父View的 onTouchEvent方法也返回false的话,这个时候事件最终将会传递到Activity的onTouchEvent方法了,也就是最终就只能由 Activity自己来处理了;
事件分发机制需要注意的几点:
1)如果说除Activity之外的所有View都没有消费掉DOWN事件的话,那么事件将不再会传递到Activity里面的子View了,将直接由Activity自己调用自己的onTouchEvent方法来处理了;
2)一旦一个ViewGroup决定拦截事件,那么这个事件序列剩余的部分将不再会由该ViewGroup的子View去处理了,即事件将在此 ViewGroup层停止向下传递,同时随后的事件序列将不再会调用onInterceptTouchEvent方法了;
3)如果一个View开始处理事件但是没有消费掉DOWN事件,那么这个事件序列随后的事件将不再由该View来处理
2.解决滑动冲突的方式
答:滑动冲突的情况有三种:
1)外部View滑动方向和内部View滑动方向不一致;
2)外部View滑动方向和内部View滑动方向一致;
3)上述都有
解决方法有种:
1)外部拦截法:
实现思路就是事件首先通过父容器的拦截,也就是修改父容器的onInterceptTouchEvent方法,但是这个拦截是有条件的拦截,而不是全部拦截,即在上述所说的方法中设置某个判断条件,符合就调用该组件的ontouchevent()方法,不符合就传递给子view。特别注意的就是不要在DOWN事件的话,返回ture,因为这样会把全部的事件拦截下来,不传给子view,除非你要的需求就是全部都是父容器处理。
2)内部拦截法:
实现的思路就是父容器不做任何拦截,先让事件传递给子view,让子view去决定要不要消耗该事件,具体决定方式就是在子view中调用requestDisallowInterceptTouchEvent方法,该方法是通知父容器要不要拦截当前事件,而为了配合子View能够调用这个 方法成功,父容器必须默认能够拦截除了DOWN事件以外的事件,为什么要除了DOWN事件以外呢?因为如果一旦父容器拦截了DOWN事件,那么后续事件将 不再会传递到子元素了,内部拦截法也就失去作用了;
通过上述的阐述可以清楚的知道:外部和内部拦截法的本质区别只是父容器和子View处理事件的优先级不同而已;
3.view绘图机制原理
4.android动画原理
5.简单说activity生命周期
Activity常用到的生命周期方法包括:onCreate、onstart、onResume、onRestart、onPause、onStop、onDestroy七种;
另外还有两个Activity被异常销毁恢复的生命周期方法:onSaveInstanceState、onRestoreInstanceState
下面从不同环境条件下分析执行的生命周期方法:
(1):第一次启动某一Activity
onCreate----->onstart----->onResume
(2):从当前Activity跳转到某一Activity或者按下Home键
onPause----->onStop
(3):再次回到原来的Activity
onRestart----->onStart----->onResume
那么将(2)和(3)连起来理解就有一个问题出现了,再次返回原先Activity是先执行原先Activity的onResume方法呢,还是先执行当 前Activity的onPause方法呢?这个有点涉及到Activity栈的知识,你想想肯定是现在的Activity在栈顶了,那肯定是先执行当前 Activity的onPause方法了,这样他暂停之后才会执行栈内其他Activity的onResume方法了;
(4):在当前Activity界面按下Back键
onPause----->onStop----->onDestroy
(5):在当前Activity界面按下锁屏键进行锁屏操作
onPause----->onStop
(6):从锁屏状态返回到之前的Activity
onRestart----->onStart----->onResume
(7):在当前Activity窗体中以弹窗的形式显示另一个Activity只会执行当前Activity的onPause方法,并不会执行onStop方法,如果此时点击Back键退出弹窗Activity显示出原先的 Activity,则直接执行onResume方法,连onRestart与onStart方法都不执行;
(8):在当前Activty上通过按钮点击的形式弹出一个AlertDialog窗体,发现根本不会对Activity生命周期有任何影响,说明一点,AlertDialog其实是附在Activity上面的;
(9):接下来说说onSaveInstanceState与onRestoreInstanceState,这两个生命周期方法和onStop和onStart方法的执行顺序是:
但是onSaveInstanceState和onPause之间是没有先后关系的;
如果我们的Activity被异常关闭,比如你进行了横竖屏切换或者当前Activity因为优先级比较低被系统杀死,系统就会调用 onSaveInstanceState进行Activity状态的保存,比如说Edittext里面的值或者你ListView上面滑动到哪个Item 信息,当该Activity重新创建的时候就会调用onRestoreInstanceState方法恢复之前在onSaveInstanceState里面的数据了,注意的是onSaveInstanceState方法仅仅会出现在Activity被异常关闭的情况下,正常情况下是不会执行这个方法的,也就是正常情况下我们通过onCreate创建Activity的时候,他的Bundle参数是null的;
关于Activity生命周期的一点总结:
onCreate和onDestroy是一对相对的方法,分别标志Activity的创建和销毁;onStart和onStop是一对相对的方法,表示Activity是否可见;onPause和onResume是一对相对的方法,表示Activity是否处于前台;在正常的销毁Activity情况下是不会执行onSaveInstanceState的;
6.横竖屏切换对Activity的影响
答:一般对于横竖屏的切换,我们会进行处理,处理方式一般两种:
1)使用配置Activity的android:configChanges ,而orientation表示消除横竖屏的影响,keyboardHidden表示消除键盘的影响,screenSize表示消除屏幕大小的影响,适当 的设置之后将不再会导致Activity生命周期的重新加载,每次横竖屏切换的时候将会回调onConfigurationChanged方法了;
2)设置Activity的android:screenOrientation属性,直接屏蔽掉横竖屏切换操作,这样横竖屏功能将不能使用
这两者是有区别的,前者是切换不受影响,后者是不能使用切换。而如果不进行这两种方式的处理,那么将会使得activity重新创建,因为其是异常销毁,所以其会调用onSaveInstanceState方法,在我们重新登录onCreate()方法时,其Bundle已经不是null了,而是有值的,这个值来源于比如 EditText的内容等,正常的启动onCreate()方法是为空的,而当异常情况下启动aonCreate()的bundel为null时,系统会自动回调 onRestoreInstanceState来进行恢复。
(异步内容)
7.ThreadLocal工作原理
ThreadLocal是每一个线程的内部存储类,每一个线程内部都有ThreadLocal.ThreadLocalMap threadLocals = null;而ThreadLocalMap 是 ThreadLocal 的静态内部类,里面保存了一个 private Entry[] table
数组,这个数组就是用来保存 ThreadLocal 中的值。例如我们有一个子线程设置某个值,我们将在子线程run方法内部调用set方法,该set方法会判断该子线程的ThreadLocalMap是否存在该线程的值,有的话就直接赋值,为空时就创建一个新map对象并且赋值,然后利用get方法,来通过map对象来获取该线程的值,如果为空,就返回null。而正是通过这种方式,就能让我们在多个线程中互不干扰地存储和修改数据。
特别注意:可能你会想使用ThreadLocal和使用synchronized有什么区别呢?个人认为区别挺大的,ThreadLocal的话,每个线程做自己的 事,两者之间不互相影响,只是他们的ThreadLocal初始化值是相等的而已,而synchronized实际上是同一时间只有一个线程能够修改某一 个共享变量的值而已,修改之后的值是会影响到另一个线程开始修改的该变量的值的;
8.handler工作机制
9.使用new Message()和obtainMessage两种方式得到Message对象有什么区别?
答:obtainMessage()存在是为了节省内存资源,保证只有消息池里面的消息全都用完了才进行new创建一个新的对象,而一般情况下我们使用obtainMessage()方法我们会得到一个消息,并且将该消息在链表中删除,如果这个消息还是有用的,我们可以使用message的recycle方法来将该信息重新的加入到链表中,而减缓通过new Message的方法所需要话的内存开销。
10.AsyncTask工作原理浅析
答:
1)首先的创建一个类继承AsyncTask,该AsyncTask有一个泛型,有三个参数,从左到右分别是启动任务输入的参数,执行任务百分比,执行结果
2)在该类中需要写入你需要在前台展示的控件,通常会使用onPreExecute()方法进行初始化,这个类中还有doInBackground方法,其是执行耗时任务,如果我们需要动态的展示我们后台任务进展的程度,我们在该方法中会调用publishProgress()方法,注意刚才我们提的这两个方法是在子线程中执行的,其他都是在主线程中执行,通过调用publishProgress()方法会引起该类的onProgressUpdate()方法进行更新组件进度,当doInbackGround()方法执行完毕后,将会执行onPostExecute方法显示已经完成后天任务了,而在用户取消线程或者主线程调用onCancelled()时,AsyncTask的执行结束。
注意:
1)该task必须在主线程创建
2)该task的execute()方法必须在主线程中执行
3)该task一般只调用一次,不然容易发生异常
4)onpreexecute(),doinbackground(),publishprogress(),onprogressupdate(),onpostexecute()方法不要去调用,其会自动执行
5)asynctask可以并行处理?
答:可以,方式是
直接调用AsyncTask方法的executeOnExecutor方法传入自己定义的线程池,因为一般我们的asynctask传入到的线程池默认是SerialExecutor线程池,其实串行的,所以不可并行,但如果我们创建的线程池可以并行,那么就可以并行了.又或者是传入到asynctask内部定义的THREAD_POOL_EXECUTOR线程池也行,具体原因如上所叙述。
11.IntentService和Service的对比
1)都可以处理后台任务
2)IntentService服务是可以处理耗时后台任务的,因为创建他的时候创建了一个HandlerThread类型的线程;而Service本 身是不可以处理耗时任务的,因为它运行在主线程中,也就是说你在Servicve里面进行耗时操作会出现ANR异常,但是IntentService里面 是不会的;
3)intentservice在他的所有任务执行结束之后会自动调用stopSelf来结束该IntentService,但是Service却需要我们通过stopService方式来结束;
4)IntentService是以串行的方式执行任务的,原因在于IntentService内部消息处理的实现原理是通过Handler加MessageQueue加Looper来实现的;
12.activity启动模式
Activity的启动模式分为:standard、singleTop、singleTask、singleInstance,可以在Activity 的标签下通过android;launchMode来进行设置,具体的启动模式效果如下:
1)standard:每次启动Activity都会创建新的Activity实例,即使在当前Activity栈中已经存在同样的Activity实例,这是默认的启动模式;
2)singleTop:通 俗点就是栈顶复用模式,每次在启动Activity的时候首先会去查看当前Activity栈的栈顶位置的Activity实例是否是需要启动的 Activity,如果是的话,则Activity将不再会创建,即不会执行onCreate、onStart方法,同时它的onNewIntent方法 将会被调用;如果栈顶不是要激活的Activity的话,则会创建一个Activity出来,并且将它压入栈顶;
3)singleInTask:通 俗点讲是栈内复用模式,什么意思呢?就是相当于栈内的单例模式吧,如果当前要启动的Activity在当前Activity栈存在的话,那么他会把栈内该 Activity上面的所有Activity全部出栈,知道要用的Activity位于栈顶为止,然后调用它就可以了;如果当前Activity栈中不存 在将要激活的Activity的话,则创建新的Activity出来,并且将该Activity压入到栈中;
4)singleInstance:单实例模式,可以认为是singleTask的加强模式,处于该模式的Activity只能单独位于一个任务栈中,也就是说该任务栈中将只有一个Activity实例;
上面多次提到了任务栈,判断一个Activity到底属于哪个任务栈这点会涉及到Activity的TaskAffinity属性,我们可以在 Activity的标签下通过指定android:affinity来进行设置,默认情况下不进行设置的话Activity任务栈的名字就是当前 Activity所在的包名;
12.子线程中更新UI的方式
13.Android中assets文件夹与raw文件夹的区别
1)相同点:两者目录下的文件在打包之后都会原封不动的打包在apk文件中,不会被编译成二进制文件;
2)不同点:
(1)res/raw中的文件会在R.java中生成ID,但是assets文件夹中的内容不会在R.java中生成ID;
(2)因为res/raw中的文件在R.java中有ID,因此我们可以通过ID直接引用资源,但是对于assets中的文件只能通过AssetManager来处理;
(3)res/raw不可以有目录结构,而assets可以有目录结构,也就是可以在assets目录下建立文件夹;
14.注册广播的方式有哪些,各自的应用场景?各有什么优缺点?
1)静态注册:需要持久性的进行处理的时候使用,优:一开机就可以接受广播,退出应用之后还可以接受广播,缺:消耗性能,比如说耗电
2)动态注册:当Broadcast 需要更新UI的时候会选择这种方式进行注册,在Activity可见的时候调用registerReceiver进行广播接收,在Activity不可见 的时候调用unregisterReceiver取消注册。优:可节约性能。 缺:需要在ondestroy方法中调用取消注册方法,容易忘记,导致异常出现。
15.内存溢出和内存泄漏
1)内存溢出:是指安卓会给每个应用分配一个应用最大内存分配,超过该内存就会出现内存溢出oom错误
2)内存泄漏:指的是一些已经不再用到的引用或者对象仍然长期保存在内存中,造成内存资源的浪费;
16. 启动activity的方式
1)显示调用:明确活动名
2)隐式调用:合理利用action,category,date等属性进行启动
17.隐式调用的规则
1)IntentFilter中的过滤信息有action、category、data,他们是在四大组件的<intent- filter></intent-filter>标签下进行配置的;
2)一个过滤列表中可以有多个action、category、 data,需要同时匹配列表中的action、category以及data才算匹配成功,否则失败;
3) 一个Activity可以有多 个<intent-filter>匹配规则列表,一个Intent只要满足其中之一就可以成功启动该Activity了,也就是匹配成功了;
4)action:action匹配规则要求intent一定得有action,且字母大小敏感,通过setAction设定
5)category:其要求intent中可以没有category,因为其会默认调用default的category,而隐式调用的activity必须要声明这个default属性,如果有多个,那么只需和多个中的其中一个匹配,就可以了
6)date: data是由mineType和URI组成,mineType表示媒体类型,URI和我们平常访问的网址类似,URI的默认值为content和 file,data的匹配规 则和action类似,也就要求Intent中必须包含data数据,并且data数据可以完全匹配过滤规则中的某一个data;
18.在activity启动的时候获取View宽高的方式
原因:因为view的measure方法和activity周期并不是同步的,因此在Activity调用了onCreate、onStart、 onResume并不一定能够保证获取到View的宽高,那么如何获取view的宽高呢?
1)如果该控件可以设置onclicklistener的时候,在onclick方法里面获取宽高,因为要点击就一定绘画好了,所以定能获取宽高
2)实现Activity的onWindowFocusChanged方法,在该方法中获取view的宽高,原因在于onWindowFocusChanged 会在Activity布局绘制结束或者Activity暂停的时候调用,但是有个缺点就是该方法在Activity得到或者失去焦点都会回调,调用的次数 比较频繁,当然你可以选择适当的时候屏蔽;
3)采用post方法将一个Runnable(view .post(runnable))对象添加到消息队列的尾部,这样在我们执行Runnable的run方法之前View已经初始化好了,自然可以拿到他的宽高了;
19.Fragment和Activity的关系
1)fragment是小activity,它是依托于activity存在的,由activity的FragmentManager来管理,也就是说它的生命周期受Activity的影响,只有在activity处于活动状态下才能进行Fragment正常的各个生命周期的变化,activity被销毁时,绑定在它上面的fragment也随之销毁。
2)fragment可以解决一个activity中多个activity
3)fragment可以被多个activity重用
4)Activity是间接继承了Context,但是Fragment不是,所以一个应用中Context的个数是不包括Fragment个数的
5)在fragment中通过getActivity()方法获取fragment绑定的activity,在activity中通过FragmentManager查找它所包含的Fragment;
20.fragment的生命周期
1)fragment的生命周期有:onAttah(),onCreate(),onCreateView(),
以上是关于安卓面经的主要内容,如果未能解决你的问题,请参考以下文章
终于有人把安卓程序员必学知识点全整理出来了,送大厂面经一份!