2016.7.19开更...........................................................................
(1):事件分发机制概述
首先应该搞清楚两个问题:事件分发机制分发的是什么?怎么进行分发?
分发的是MotionEvent事件了,因而我们讨论的问题就成了当MotionEvent事件生成之后,事件是怎么传递到某一个View控件上面并且得到处理的过程;
android事件产生后的传递过程是从Activity--->Window--->View的,即隧道式传递,而View又分为不包含子View的View以及包含子View的ViewGroup,事件产生之后首先传递到Activity上面,而Activity接着会传递到PhoneWindow上,PhoneWindow会传递给RootView,而RootView其实就是DecorView了,接下来便是从DecorView到View上的分发过程了,具体就可以分成ViewGroup和View的分发两种情况了;
对于ViewGroup而言,当事件分发到当前ViewGroup上面的时候,首先会调用他的dispatchTouchEvent方法,在dispatchTouchEvent方法里面会调用onInterceptTouchEvent来判断是否要拦截当前事件,如果要拦截的话,就会调用ViewGroup自己的onTouchEvent方法了,如果onInterceptTouchEvent返回false的话表示不拦截当前事件,那么事件将会继续往当前ViewGroup的子View上面传递了,如果他的子View是ViewGroup的话,则重复ViewGroup事件分发过程,如果子View就是View的话,则转到下面的View分发过程;
对于View而言,事件传递过来首先当然也是执行他的dispatchTouchEvent方法了,如果我们为当前View设置了onTouchListener监听器的话,首先就会执行他的回调方法onTouch了,这个方法的返回值将决定事件是否要继续传递下去了,如果返回false的话,表示事件没有被消费,还会继续传递下去,如果返回true的话,表示事件已经被消费了,不再需要向下传递了;如果返回false,那么将会执行当前View的onTouchEvent方法,如果我们为当前View设置了onLongClickListener监听器的话,则首先会执行他的回调方法onLongClick,和onTouch方法类似,如果该方法返回true表示事件被消费,不会继续向下传递,返回false的话,事件会继续向下传递,为了分析,我们假定返回false,如果我们设置了onClickListener监听器的话,则会执行他的回调方法onClick,该方法是没有返回值的,所以也是我们事件分发机制中最后执行的方法了;可以注意到的一点就是只要你的当前View是clickable或者longclickable的,View的onTouchEvent方法默认都会返回true,也就是说对于事件传递到View上来说,系统默认是由View来消费事件的,但是ViewGroup就不是这样了;
上面的事件分发过程只是正常情况下的,如果有这样一种情况,比如事件传递到最里层的View之后,调用该View的oonTouchEvent方法返回了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来处理,通俗点讲就是你自己没能力就别瞎BB,要不以后的事件就都不给你了;
(4):View的onTouchEvent方法是否执行是和他的onTouchListener回调方法onTouch的返回值息息相关的,onTouch返回false,onTouchEvent方法不执行;onTouch返回false,onTouchEvent方法执行,因为onTouchEvent里面会执行onClick,所以造成了onClick是否执行和onTouch的返回值有了关系;
(2):View视图绘制过程原理
View视图绘制需要搞清楚两个问题,一个是从哪里开始绘制,一个是怎么绘制?
先说从哪里开始绘制的问题:我们平常在使用Activity的时候,都会调用setContentView来设置布局文件,没错,视图绘制就是从这个方法开始的;
再来说说怎么绘制的:
在我们的Activity中调用了setContentView之后,会转而执行PhoneWindow的setContentView,在这个方法里面会判断我们存放内容的ViewGroup(这个ViewGroup可以是DecorView也可以是DecorView的子View)是否存在。不存在的话则会创建一个DecorView出来,并且会创建出相应的窗体风格,存在的话则会删除原先ViewGroup上面已有的View,接着会调用LayoutInflater的inflate方法以pull解析的方式将当前布局文件中存在的View通过addView的方式添加到ViewGroup上面来,接着在addView方法里面就会执行我们常见的invalidate方法了,这个方法不只是在View视图绘制的过程中经常用到,其实动画的实现原理也是不断的调用这个方法来实现视图不断重绘的,执行这个方法的时候会调用他的父View的invalidateChild方法,这个方法是属于ViewParent的,ViewGroup以及ViewRootImpl中都对他进行了实现,invalidateChild里面主要做的事就是通过do while循环一层一层计算出当前View的四个点所对应的矩阵在ViewRoot中所对应的位置,那么有了这个矩阵的位置之后最终都会执行到ViewRootImpl的invalidateChildInParent方法,执行这个方法的时候首先会检查当前线程是不是主线程,因为我们要开始准备更新UI了,不是主线程的话是不允许更新UI的,接着就会执行scheduleTraversals方法了,这个方法会通过handler来执行doTraversal方法,在这个方法里面就见到了我们平常所熟悉的View视图绘制的起点方法performTraversals了;
那么接下来就是真正的视图绘制流程了,大体上讲View的绘制经历了Measure测量、Layout布局以及Draw绘制三个过程,具体来讲是从ViewRootImpl的performTraversals方法开始,首先执行的将是performMeasure方法,这个方法里面会传入两个MeasureSpec类型的参数,他在很大程度上决定了View的尺寸规格,对于DecorView来说宽高的MeasureSpec值的获取与窗口尺寸以及自身的LayoutParams有关,对于普通View来说其宽高的MeasureSpec值的获取由父容器以及自身的LayoutParams属性共同决定,在performMeasure里面会执行measure方法,在measure方法里面会执行onMeasure方法,到这里Measure测量过程对View与ViewGroup来说是没有区别的,但是从onMeasure开始两者有差别了,因为View本身已经不存在子View了,所以他onMeasure方法将执行setMeasuredDimension方法,该方法会设置View的测量值,但是对于ViewGroup来说,因为它里面还存在着子View,那么我们就需要继续测量它里面的子View了,调用的方法是measureChild方法,该方法内部又会执行measure方法,而measure方法转而又会执行onMeasure方法,这样不断的递归进行下去,知道整个View树测量结束,这样performMeasure方法执行结束了;接着便是执行performLayout方法了,performMeasure只是测量出View树中View的大小了,但是还不知道View的位置,所以也就出现了performLayout方法了,performLayout方法首先会执行layout方法,以确定View自身的位置,如果当前View是ViewGroup的话,则会执行onLayout方法。在onLayout方法里面又会递归的执行layout方法,直到当前遍历到的View不再是ViewGroup为止,这样整个layout布局过程就结束了;在View树中View的大小以及位置都确定之后,接下来就是真正的绘制View显示在界面的过程了,该过程首先从performDraw方法开始,performDraw方法首先执行draw方法,在draw方法中首先绘制背景、接着调用onDraw方法绘制自己,如果当前View是ViewGroup的话,还要调用dispatchDraw方法绘制当前ViewGroup的子View,而dispatchDraw方法里面实际上是通过drawChild方法间接调用draw方法形成递归绘制整个View树,直到当前View不再是ViewGroup为止,这样整个View的绘制过程就结束了;
(3):解决滑动冲突的方式
在自定义View的过程经常会遇到滑动冲突问题,一般滑动冲突的类型有三种:(1)外部View滑动方向和内部View滑动方向不一致;(2)外部View滑动方向和内部View滑动方向一致;(3)上述两种情况的嵌套;
一般我们解决滑动冲突都是利用的事件分发机制,有两种方式外部拦截法和内部拦截法:
外部拦截法:实现思路是事件首先是通过父容器的拦截处理,如果父容器不需要该事件的话,则不拦截,将事件传递到子View上面,如果父容器决定拦截的话,则在父容器的onTouchEvent里面直接处理该事件,这种方法符合事件分发机制;具体实现措施是修改父容器的onInterceptTouchEvent方法,在达到某一条件的时候,让该方法直接返回true就可以把事件拦截下来进而调用自己的onTouchEvent方法来处理了,但是有一点需要注意的是如果想要让子View能够收到事件,我们需要在onInterceptTouchEvent方法里面判断如果是DOWN事件的话,返回false,这样后续的MOVE以及UP事件才有机会传递到子View上面,如果你直接在onInterceptTouchEvent方法里面DOWN情况下返回了true,那么后续的MOVE以及UP事件将由当前View的onTouchEvent处理了,这样你的拦截将根本没有意义的,拦截只是在满足一定条件才会拦截,并不是所有情况下都拦截;
内部拦截法:实现思路是事件从父容器传递到子View上面,父容器不做任何干预性的措施,所有的事件都会传递到子View上面,如果子元素需要改事件,那么就由子元素消耗掉了,该事件也就不会回传了,如果子元素不需要该事件,那么他就会回传给父容器来处理了;具体实现措施需要借助于requestDisallowInterceptTouchEvent方法,该方法用来告诉父容器要不要拦截当前事件,为了配合子View能够调用这个方法成功,父容器必须默认能够拦截除了DOWN事件以外的事件,为什么要除了DOWN事件以外呢?因为如果一旦父容器拦截了DOWN事件,那么后续事件将不再会传递到子元素了,内部拦截法也就失去作用了;
个人认为外部拦截法是符合正常逻辑的,按照事件隧道式分发过程,如果父容器需要就直接拦截,不需要则传递到子View;内部拦截法相当于人为干预分发这个过程,我会保证事件先都到子View上面,至于子View需不需要就要看我自己了,如果我不需要就回传给父容器了,需要的话自己就消耗掉了;感觉这两种方式只是父容器和子View处理事件的优先级不同而已;
(4):Android动画原理
Android动画可以分为View动画、帧动画、属性动画,其中View动画又可以分为平移(Translate)、缩放(Scale)、旋转(Rotate)、透明度(Alpha)四种,帧动画可以认为是View动画的一种,实现原理类似于放电影,通过一帧一帧的图片进行播放来达到动画的效果,正是因为这点需要注意他可能会出现OOM异常,属性动画是3.0之后出现的,他也可以实现View动画的效果;
在讲解动画原理之前需要明白两个概念,插值器和估值器:
插值器:根据时间流逝的百分比来计算出属性值改变的百分比,对应的接口是Interpolator;
估值器:根据属性改变的百分比计算出属性的改变值,对应的接口是TypeEvaluator;
先来说说View动画实现原理,其实如果你看View动画实现过程的源码的话就会发现,View动画其实就是在不断的调用View的invalidate方法来进行View的绘制以达到动画的效果的,所以理解View动画的核心其实应该是首先理解View的绘制过程;
我们使用View动画都是通过View的startAnimation方法开始的,那么分析View动画原理自然应该从这个方法开始了,这个方法里面会调用setAnimation设置当前View的动画,并且随后调用了我们经常见的invalidate方法,这个方法具体执行过程上面已经说过了,最后都会执行到ViewRootImpl的performTraversals方法,该方法就是我们进行视图绘制经常见到的开始方法了,经过一系列的measure测量以及layout布局过程执行到draw绘画阶段,这个阶段是我们动画比较关心的阶段,毕竟要在界面显示嘛,没有draw怎么做到,调用draw方法之后绘制流程是这样的:首选绘制背景,接着绘制自己,随后调用dispatchDraw绘制自己的孩子,在调用每个子View的draw方法之前,需要绘制的View的绘制位置是Canvas通过translate方法切换了,这点也看出来View动画实际上一直在动的是画布,而并不是View本身,最后还要绘制滚动条等修饰内容,这里调用了dispatchDraw方法,但是View没有实现这个方法,ViewGroup作为View的子类实现了这个方法,在ViewGroup的dispatchDraw方法中会执行drawChild方法来绘制当前ViewGroup的子View,drawChild方法实际上调用的就是View的draw方法了,这个draw方法是不同于前面ViewGroup绘制自己的draw方法,这个draw方法中有一个时间参数和画布参数Canvas,具体的绘制就是通过这个画布参数实现的,但是ChildView的画布是由其ParentView提供的,ParentView会根据ChildView在其内部的布局来调整Canvas,当子View调用,在该draw方法中会通过getAnimation获取到我们设置到View上的动画,接着便执行了drawAnimation方法来进行动画绘制了,在drawAnimation方法里面首先通过执行getChildTransformation方法获得子View的Transformation值,那么Transformation是什么呢?它主要进行的是矩阵运算的,其中有两个比较关键的属性其中之一是Matrix用于存储View的平移、缩放、旋转信息,还有一个alpha属性,主要存储的是View的透明度信息的,接着就会执行getTransformation方法,把刚刚获取的Transformation值以及当前时间作为参数传入,在getTransformation方法里面会通过当前时间计算出时间流逝的百分比,并且将该百分比作为参数调用插值器的getInterpolation方法,获得时间流逝百分比对应的属性改变的百分比,当然这里你可以使用自己定义的插值器,有了属性改变百分比之后我们就可以调用applyTransformation方法来进行具体的动画实现了,当然如果你自己想要实现自己定义的动画,可以重写applyTransformation方法,这样View动画的第一帧就绘制好了,那么后续的帧该怎么绘制呢?如果你细心的话会发现getTransformation有一个boolean类型的返回值,没错就是靠这个返回值来进行后续帧绘制的,查看getTransformation方法文档说明会发现返回真表示还有后续帧存在,具体判别方法当然就是通过比较当前时间是否超过动画要求最迟时间了,返回true则会继续执行invalidate方法,相当于又回到最开始处进行递归的绘制,返回false的话则动画结束,这就是View动画的执行过程了;
帧动画因为可以理解为电影的放映过程,所以他的一帧一帧过程是我们自己提供的,因为系统本身只需要切换我们提供的资源图片就可以了,没有多大原理需要解释;
属性动画实现原理:
既然名字上有属性两个字,那么肯定是通过改变View的属性来达到动画效果的,这点和View动画是有很大差别的,View动画只是ParentView不断的调整ChildView的画布来实现动画的,本质上View的属性是没有发生变化的,所以当你对移动到某个地方的View进行一些比如点击或者触摸操作的时候是根本不会执行当前移动过来的View的事件方法的,原因就在于你移动过去的只是原先View的影像而已,而属性动画就不一样了,他是实实在在的改变View属性真正的在移动的;属性动画要求动画作用的对象必须提供想要改变属性的set方法,如果你没有传递初始值的话还需要提供该属性的get方法,属性动画会根据你传入的该属性的初始值和最终值以动画的效果(也就是计算出某一时刻属性需要改变的值)多次通过反射调用set方法动态的改变作用对象的属性值,随着时间的推移,这个值将越来越接近设置的最终值,以达到动画的效果;
Android动画注意点:
在使用帧动画的时候,因为动画图片资源是我们自己提供的,所以一定要注意可能会出现的OOM异常;
View动画只是ParentView不断调整ChildView的画布实现的,只是对View的影响做动画,而不是真正的改变View的状态;
View动画在移动之后,移动的位置处的View不能触发事件,但是属性动画是可以的;
2016.7.20更新...........................................................................
(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生命周期的影响
正常情况下,如果不进行特殊设置的话,横竖屏切换会导致Activity重新创建,也就是会重新执行onCreate方法,在之前Activity是异常销毁的时候会执行他的onSaveInstanceState方法(正常销毁的话该方法是不会执行的),该方法会保存之前Activity已经有的一些信息,比如EditText的内容啊,ListView滚动的位置啊等等,那么这次调用onCreate的时候和普通的直接创建Activity调用onCreate方法是有区别的,直接创建的话onCreate方法的参数等于null,但是横竖屏切换之后再执行的onCreate方法参数里面是有值的,我们可以拿到这些值来恢复之前Activity的一些已有状态,当然如果没有在onCreate中恢复的话,系统会自动回调onRestoreInstanceState来进行恢复;
如果我们不想在横竖屏切换的时候进行Activity生命周期的重新加载,那么就需要配置Activity的android:configChanges了,orientation表示消除横竖屏的影响,keyboardHidden表示消除键盘的影响,screenSize表示消除屏幕大小的影响,适当的设置之后将不再会导致Activity生命周期的重新加载,每次横竖屏切换的时候将会回调onConfigurationChanged方法了;
当然可以通过设置Activity的android:screenOrientation属性,直接屏蔽掉横竖屏切换操作,这样横竖屏功能将不能使用,这和android:configChanges是有区别的,一个是可以用但是不会导致Activity生命周期重新加载,一个是干脆不能用;
(7):ThreadLocal工作原理
为了理解清楚Handler的消息处理机制,首先需要了解的知识就是ThreadLocal了,这个类并不是Android所特有的,它来自于java,ThreadLocal主要用来干什么呢?答案是用于如果某些数据是以线程作为作用域,但是每个线程又还想要该数据的副本的情况下,通俗点可以这样理解,有一块空菜地,你和你邻居都想在里面种菜,但是如果这块菜地分给你的话你邻居要想在这块菜地里面种菜那肯定会影响到你种菜,反之你会影响你邻居,那么怎么能解决这个问题呢?给你和你邻居都分一块菜地,自己种自己的,这样就不互相影响了,这就是ThreadLocal干的事了;java中的ThreadLocal实现原理是采用ThreadLocalMap的方式来存储当前线程用到的各ThreadLocal软引用及其对应值的,而android中ThreadLocal实现方式上区别于java,他的每个线程中都有一个Values类型的变量,而Values类型对象中有一个Object类型数组,数组大小只能是2的指数倍数,这个数组就是用于存储我们的ThreadLocal软引用及其对应值的,具体存储方式是ThreadLocal软引用的存储位置位于其值存储位置的前一个位置;
可能你会想使用ThreadLocal和使用synchronized有什么区别呢?个人认为区别挺大的,ThreadLocal的话,每个线程做自己的事,两者之间不互相影响,只是他们的ThreadLocal初始化值是相等的而已,而synchronized实际上是同一时间只有一个线程能够修改某一个共享变量的值而已,修改之后的值是会影响到另一个线程开始修改的该变量的值的;
(8):Handler工作机制
鉴于Android的UI线程不是线程安全的,这点也很好理解,如果有多个线程更改UI界面显示的元素的话,最终界面到底会显示出什么将是不确定的,这点会让人感觉莫名其妙,因而Android只规定主线程可以更新UI了,那么如果我的子线程想要更新UI该怎么办呢?难道就不能更新了吗?No,这就是Handler出现的原因了,虽然我们通常将Handler用在子线程需要更新UI的场景下,但是他的作用不止这点,他可以使用在不同线程之间的切换,而不仅仅是切换到主线程更新UI这么局限;
Handler工作原理:
先要弄清楚Handler消息处理中用到的一些概念,Message用于封装将要传送的数据内容,MessageQueue消息队列用于暂存那些需要处理的Message消息,Looper用于不断的从MessageQueue中取出消息进行处理,Handler消息的封装者和处理者,通过他进行Message消息的生成,通过他接收Looper传来的消息并且进行处理,有点类似于统领者的角色,那么Looper是什么鬼,好端端的冒出来他干什么呢?MessageQueue只是Message消息的存放者,Handler怎么知道什么时候需要处理消息呢?答案就是靠Looper了,他会不断的查看MessageQueue,有消息的话就交给Handler来处理了,如此看来Android消息处理中的角色分工真的好明确啊!!注意一点,一个Handler要想真正起作用的话,他所在的线程中必须存在一个Looper,而在创建Looper的过程中就会创建一个MessageQueue出来,也就是Looper和MessageQueue是一一对应的;
我们一般想要更新UI的话,都是在主线程中创建一个Handler对象,接着在子线程中使用它的sendMessage方法发送一条消息,然后该消息就会回调handler的handleMessage方法进行相应的更新操作了;
那我们分析Handler机制首先就该从主线程开始了,在Activity启动的时候会执行ActivityThread里面的main方法,在该方法里面会通过prepareMainLooper创建一个Looper对象出来,相应的也就创建了MessageQueue消息队列了,并且会将当前Looper对象存储到当前线程的ThreadLocal里面,也就是存储到主线程的ThreadLocal里面了,所以这也就是解释了你在主线程创建Handler的时候并没有自己创建Looper出来程序不会报错的原因了,因为主线程在Activity启动的时候就创建好了,接着我们便是在主线程创建Handler对象了,在创建Handler对象的构造方法里面会获取到在ActivityThread的main方法里面创建的Looper对象及其对应的MessageQueue对象,接着我们会在子线程中通过主线程的Handler对象调用他的sendMessage方法,该方法会传入封装有需要传递给主线程的数据的Message对象,sendMessage实际执行的操作是调用enqueueMessage方法将消息加入到MessageQueue消息队列中,除此之外在ActivityThread的main里面发现会调用Looper.loop(),也就是会让当前Looper运转起来,loop方法里面存在一个死循环会不断的去查看MessageQueue里面有没有消息存在,有的话则进行出队操作,获取到队头消息,并且获取到处理该消息所对应的Handler,具体来说其实就是Message的target属性值了,然后调用target也就是Handler对象的dispatchMessage方法将消息分发出去,dispatchMessage转而会执行handleMessage方法,这也就回到了我们主线程中了,所以我们可以在handleMessage里面获取到消息中封装的数据进而进行一些界面上元素的修改了,这就是在主线程中使用Handler的消息执行流程了;
那么如果想要使用Handler一个线程传递数据到另一个线程中,但是两个线程都不是主线程该怎么办呢?很明显这种使用情况将不同于上面了,我们就该自己创建Looper对象以及其对应的MessageQueue队列了,具体做法是:在接收数据的线程中通过Looper.prepare创建一个Looper对象及其对应的MrssageQueue队列,接着调用Looper.loop方法让该Looper运转起来,可以在MessageQeueu里面有消息的时候进行处理,创建一个Handler对象用来进行消息处理,并且在另一个线程中利用该消息进行消息发送即可,这里有一点需要注意,就是我们的loop方法是个死循环,他又是位于线程内部的,如果loop方法不结束的话,线程将一直处于运行状态,这会带来一个问题,就是我们已经明确知道消息队列里面的消息已经处理结束了,没有消息要处理了,Looper还是会不断的查看有没有消息存在,这会带来性能上的损失,解决这个问题的唯一方法就是想办法能让loop方法结束掉,查看loop方法的源码会发现,当Looper获取到的消息为null时就会执行return结束掉死循环,那么我们就该找到什么时候会向消息队列中插入一条null消息了,答案就是在Looper的quit方法里面了,所以我们如果在某一时刻已经明确知道MessageQueue队列没有消息的话调用Looper的quit方法结束掉loop方法进而结束掉当前线程,避免性能丢失;
(9):HandlerThread原理剖析
在(8)中我们分析了Handler消息处理机制,知道Handler要想真正起到作用的话需要借助于Looper,而Looper里面会创建一个MessageQueue对象出来,在主线程中使用Handler的时候我们完全不用考虑创建Looer以及其对应MessageQueue消息队列,以及Looper运行起来这些的事情,但是要想在子线程之间使用Handler,我们就必须通过Looper.prepare来创建Looper对象及其对应的MessageQueue对象,通过Looper.loop方法使得当前创建的Looper运转起来了,这点本来就已经能够满足我们在子线程之间使用Handler的要求了,但是google为了能减少开发人员在子线程中使用Handler的麻烦,提供了HanderThread,他的实现原理其实就是我刚刚说的那些,只不过做了封装而已,我们在创建Handler之前会先创建一个HandlerThread对象,并且调用它的start方法,这个start方法就比较重要了,他会调用HandlerThread的run方法,为什么呢?因为HandlerThread归根结底也是Thread嘛,调用start之后辗转都会执行到run方法,在run方法里面就会通过Looper.prepare创建Looper对象及其对应的MessageQueue消息队列了,同时会调用Looper.loop方法让当前Looper运转起来,所以这个run方法是最重要的了,之后创建Handler发送消息和接收消息的过程就和在主线程使用Handler一致了,当然和我们自己在子线程中创建Looper使用Looper出现的问题一样,通过HandlerThread方式使用Handler同样也会带来Looper对象的loop方法一直执行不会结束的情况,解决方法是调用HandlerThread的quit方法,该方法实际上还是调用的Looper的quit方法;
(10):IntentService原理分析
上面我们分析了Handler消息处理机制以及HandlerThread里面所涉及到的一些知识点,知道HandlerThread其实就是为了我们在子线程中减少自己创建Looper以及运转Looper而出现的,那么这次的IntentService其实封装的更巧妙,使用HandlerThread的时候我们还需要创建Handler对象出来,但是使用IntentService连Handler对象也不用我们创建了,可见google为了让程序员使用简便做了多少工作,先来说说IntentService是干什么的,他是一个抽象类,因而我们在使用的时候需要创建一个实现他的类出来,它里面仅有一个抽象方法就是onHandleIntent了,我们可以在这个方法里面做一些处理Intent的操作了,作为Service的一种,IntentService自然也是在后台执行的,也是通过startService启动的,他的优先级要高于一般的线程,那么IntentService有什么用处呢?适合于执行一些高优先级的后台耗时任务,高优先级的后台任务是Service的特点,但是由于Service是处于主线程的,他不适合处理耗时任务,但IntentService却可以,原因就在于IntentService在创建的时候就会开启一个线程出来,耗时任务是在该线程中进行的,具体点说这里的线程其实就是HandlerThread了,在耗时任务处理结束之后该Service会自动停止;
我们来看看IntentService具体是怎么做到封装了Handler来处理耗时任务的,在IntentService的构造方法里面你会看到创建了一个HandlerThread线程出来,并且调用了他的start方法启动了该线程,上面HandlerThread中已经讲过会在该线程的run方法里面创建Looper对象并且调用loop将Looper运转起来,接着会通过创建的Looper对象创建一个ServiceHandler出来,其实就是Handler对象而已,该对象里面有handleMessage方法,在我们通过startService方法启动IntentService的时候会回调onStartCommand方法,该方法会执行IntentService的onStart方法,而正是在onStart方法里面会将我们startService传入的intent对象封装成Message对象通过在构造函数中创建的ServiceHandler类型handler对象的sendMessage方法发送出去,那么紧接着就会回调ServiceHandler的handleMessage方法了,handleMessage方法实际上执行的是onHandleIntent方法,也就是我们在实现IntentService抽象类的时候需要实现的方法,具体实现对Intent的操作,操作结束之后handleMessage方法会执行stopSelf方法结束当前IntentService;
(11):使用new Message()和obtainMessage两种方式得到Message对象有什么区别?
我们在平常使用Handler sendMessage方法的时候都要传递Message参数进去,通常创建Message对象有两种方式,一种就是常用的通过构造函数的方式创建对象,一种就是通过Handler的obtainMessage了,既然都能new了说明Message的构造函数是public的,那么还来个obtainMessage干嘛呢?答案就是为了节省内存资源,如果你查看Message的定义的话,会发现它里面有一个next字段,这个字段的属性值是Message类型的,所以从这种角度看的话Message本身就可以作为链表存在,我们的Message消息池其实就是存储着第一个Message消息而已,之后的消息都是通过next字段链接到一起的,使用obtainMessage首先会去查看当前消息池中有没有消息存在,存在的话则直接取到该消息并且将该消息从消息池中删除同时将消息池大小减一即可,也就是将链表长度减一,如果消息池中不存在消息的话才会通过new Message的方式创建消息出来,我们每次使用完消息之后通过执行Message的recycle会将当前使用过的消息对象添加到消息池中,也就是加入链表中,当然在加入之前需要将原消息中的内容信息全部置位,这样有效减缓了你频繁通过new Message方式创建消息的内存开销,保证了只有在当前消息池不再存在可用消息的情况下才去创建消息出来,so perfect!!!
(12):AsyncTask工作原理浅析
要想理解清楚AsyncTask的工作原理首先就应该搞清楚Handler的工作机制,前面已经分析过啦,那我们就直接开始了,我们平常使用AsyncTask是创建AsyncTask对象之后执行execute,创建AsyncTask对象的时候会同时创建一个WorkerRunnable对象,并且以这个WorkerRunnable对象为参数会创建一个FutureTask对象,那么分析AsyncTask的原理就该从execute方法开始了,执行execute方法首先会执行executeOnExecutor方法,并且传入一个SerialExecutor类型的对象,SerialExecutor是一个串行线程池,一个线程里面的所有AsyncTask全部都在这个串行的线程池中排队执行,在executeOnExecutor里面首先会执行onPreExecute方法,该方法是在我们创建AsyncTask对象的时候自己实现的,运行在主线程中,我们可以在这个方法里面进行任务开始的提示性操作,接着线程池开始执行,也就是从这一步开始切换到了子线程中,传入的对象就是我们创建AsyncTask对象的时候生成的FutureTask对象,在SerialExecutor线程池的execute方法中首先会把当前FutureTask对象插入到任务队列中,如果当前任务队列中没有正在活动的AsyncTask任务的话,则会执行scheduleNext方法从队列中取得一个AsyncTask任务,同时当一个AsyncTask任务执行结束之后会在finally中调用scheduleNext方法执行任务队列中的下一个AsyncTask任务,从这里也看出来默认情况下AsyncTask是串行执行的,那么真正的执行操作就该在scheduleNext方法里面了,可以看到这个方法里面真正执行任务的线程池是THREAD_POOL_EXECUTOR,很多人都在想那刚刚的SerialExecutor线程池是用来干嘛的呢,它主要是用来任务排队的,保证默认情况下的串行执行而已,而THREAD_POOL_EXECUTOR才是真正的任务执行者,此外在AsyncTask里面还有一个InternalHandler对象,其实他就是一个Handler对象而已,他存在的作用就是为了从子线程切换到主线程中,为了便于在子线程执行的过程中进行一些与界面元素的交互过程,比如下载进度条的更新等等,那么也就必须要求该InternalHandler对象在主线程中创建了,查看源码你会发现InternalHandler对象是static的,也就是在AsyncTask对象创建的时候他就会创建,因此只要保证AsyncTask对象在主线程中创建就可以了,因此我们使用AsyncTask的时候一定要注意在主线程中创建他的对象,扯的有点远了,THREAD_POOL_EXECUTOR会执行他的execute方法,该方法实际上执行的是FutureTask的run方法,而FutureTask的run方法实际上执行的是创建FutureTask对象的时候传入的参数WorkerRunnable对象的call方法,查看call方法可以看到执行了doInBackground方法,该方法也是需要我们在创建AsyncTask对象的时候自己实现的,我们可以在这个方法里面执行一些比较耗时的操作,它运行在子线程中,在该方法中我们可以通过publishProgress来发送一些耗时任务已经处理的进度信息,该方法运行在子线程中,该方法中会通过InternalHandler将进度消息发送出去,接着在InternalHandler里面的handleMessage里面会发现是通过onProgressUpdate进行消息处理的,该方法运行在主线程中,可以进行更新进度条的一些操作,在doInBackground方法执行结束后会将返回结果作为参数传递给postResult方法,该方法同样会通过InternalHandler发送消息,最后在InternalHandler里面的handleMessage里面处理该消息,调用的是finish方法,也就是将线程切换到了主线程中了,在finish方法中会根据主线程有没有被暂停来执行onCancelled或者onPostExecute方法,这两个方法是运行在主线程的,到这里AsyncTask的执行结束了;
关于AsyncTask中需要注意的几点:
(1):默认情况下AsyncTask之间是串行执行的;
(2):AsyncTask里面存在两个线程池,一个是SerialExecutor类型的,它主要是用来进行AsyncTask任务排队的,一个是THREAD_POOL_EXECUTOR线程池,它才是任务的真正执行者;
(3):AsyncTask内部有一个InternalHandler类型的变量,主要用于在任务执行的过程中主线程和子线程之间的切换的,因此他必须在主线程中创建,因为他在AsyncTask中是static修饰的,因此在AsyncTask加载的时候他就被创建了,因此间接要求AsyncTask在主线程创建了;
(13):AsyncTask中各个方法哪些在主线程执行哪些在子线程执行?
onPreExecute在主线程执行;
doInBackground方法在子线程中执行;
publishProgress在子线程中执行;
onProgressUpdate在主线程中执行;
onCancelled在主线程中执行;
onPostExecute在主线程中执行;
(14):AsyncTask可以并行执行吗?
可以的,从Android3.0开始,我们可以通过直接调用AsyncTask方法的executeOnExecutor方法传入自己定义的线程池,没错,这个方法也是默认情况下调用AsyncTask的execute方法真正执行的方法,但是默认情况下传入的是SerialExecutor类型的线程池,他会对AsyncTask任务进行排队,虽然THREAD_POOL_EXECUTOR线程池本身是可以并行处理任务的,但是因为任务都是靠SerialExecutor线程池串行取出来的,所以也就造成了AsyncTask默认情况下串行执行的特点了;但是如果我们直接传入自己定义的线程池的话,默认线程池是可以并行处理的,你也可以传入AsyncTask内部已经定义的THREAD_POOL_EXECUTOR线程池,这样也行;
(15):IntentService和Service的对比
(1):首先从名字上看两者都是Service,所以呢都有Service的特点,都可以处理后台任务;
(2):IntentService是可以处理耗时任务的,原因在于在创建他的时候创建了一个HandlerThread类型的线程;而Service本身是不可以处理耗时任务的,因为它运行在主线程中,也就是说你在Servicve里面进行耗时操作会出现ANR异常,但是IntentService里面是不会的;
(3):IntentService在他的所有任务执行结束之后会自动调用stopSelf来结束该IntentService,但是Service却需要我们通过stopService方式来结束;
(4):IntentService是以串行的方式执行任务的,原因在于IntentService内部消息处理的实现原理是通过Handler加MessageQueue加Looper来实现的;2017.7.21更新...........................................................................
(16):Activity启动模式
Activity的启动模式分为:standard、singleTop、singleTask、singleInstance,可以在Activity的标签下通过android;launchMode来进行设置,为什么要有Activity的启动模式呢?默认情况下我们多次启动同一个Activity的时候默认会创建多个实例放入到任务栈中,这样重复创建实例的做法显然有点不太科学,我们有时候完全可以直接利用之前创建的实例就行了,Activity的启动模式就是做这个的;
standard:每次启动Activity都会创建新的Activity实例,即使在当前Activity栈中已经存在同样的Activity实例,这是默认的启动模式;
singleTop:通俗点就是栈顶复用模式,每次在启动Activity的时候首先会去查看当前Activity栈的栈顶位置的Activity实例是否是需要启动的Activity,如果是的话,则Activity将不再会创建,即不会执行onCreate、onStart方法,同时它的onNewIntent方法将会被调用;如果栈顶不是要激活的Activity的话,则会创建一个Activity出来,并且将它压入栈顶;
singleInTask:通俗点讲是栈内复用模式,什么意思呢?就是相当于栈内的单例模式吧,如果当前要启动的Activity在当前Activity栈存在的话,那么他会把栈内该Activity上面的所有Activity全部出栈,知道要用的Activity位于栈顶为止,然后调用它就可以了;如果当前Activity栈中不存在将要激活的Activity的话,则创建新的Activity出来,并且将该Activity压入到栈中;
singleInstance:单实例模式,可以认为是singleTask的加强模式,处于该模式的Activity只能单独位于一个任务栈中,也就是说该任务栈中将只有一个Activity实例;
上面多次提到了任务栈,判断一个Activity到底属于哪个任务栈这点会涉及到Activity的TaskAffinity属性,我们可以在Activity的标签下通过指定android:affinity来进行设置,默认情况下不进行设置的话Activity任务栈的名字就是当前Activity所在的包名;
(17):子线程中更新UI的方式
我们都知道只有主线程可以更新UI,那么如果子线程想要更新UI怎么办呢?只能是借助于Handler来实现了,那么具体的实现方式有哪些呢?
(1):最常见的方式就是通过Handler的sendMessage和handleMessage来进行处理了,这个比较简单,不再举例;
(2):通过Handler的post方法,这种执行方式需要在post方法中传入执行耗时任务的线程,接着在执行post方法的时候,会将该执行任务的线程封装到Message里面的callback属性,之后当Handler里面的Looper循环查看MessageQueue消息队列的时候会取到这条消息,取到消息中的执行耗时操作的线程,直接执行他的run方法就可以了,我们可以在该run方法中进行更新UI操作的;
(3):通过View的post方法,这种方法实质上是通过Handler的post方法完成的;
(4):通过Activity的runOnUiThread方法,该方法同样还是通过的Handler的post方法实现的;
详情可以看这篇博客
(18):Android中assets文件夹与raw文件夹的区别
相同点:两者目录下的文件在打包之后都会原封不动的打包在apk文件中,不会被编译成二进制文件;
不同点:(1):res/raw中的文件会在R.java中生成ID,但是assets文件夹中的内容不会在R.java中生成ID;
(2):因为res/raw中的文件在R.java中有ID,因此我们可以通过ID直接引用资源,但是对于assets中的文件只能通过AssetManager来处理;
(3):res/raw不可以有目录结构,而assets可以有目录结构,也就是可以在assets目录下建立文件夹;
(19):注册广播的方式有哪些,各自的应用场景?各有什么优缺点?
注册广播的方式有两种,一种是通过代码的方式,一种是通过在AndroifManifest.xml中注册的方式;
(1):通过代码的方式,通过registerReceiver方式进行注册,通过unregisterReceiver取消注册,当Broadcast需要更新UI的时候会选择这种方式进行注册,在Activity可见的时候调用registerReceiver进行广播接收,在Activity不可见的时候调用unregisterReceiver取消注册;
(2):通过在AndroidManifest.xml中进行注册,通过在<receiver></receiver>标签中设置<intent-filter></intent-filter>属性进行广播接收;
两种方式的区别:
方式1不是常驻型广播,该中方式注册的广播跟随程序的生命周期,确切点来说如果我们想通过广播更新UI的话一般会在Activity的onCreate里面进行广播的注册,在Activity的onDestroy中进行广播的取消;
方式2是常驻型广播,也就是在我们的应用程序关闭之后,如果有与<intent-filter>匹配的广播到来,应用程序还是会接收的,这种方式的弊端在于它始终处于活动状态,这多少会影响系统的性能;
(20):内存溢出和内存泄露的区别?
内存溢出指的是程序运行时内存超过可用的最大值,Android会为每个应用程序分配一个可用的内存最大值,这时候会报OOM异常;内存泄露指的是一些已经不再用到的引用或者对象仍然长期保存在内存中,造成内存资源的浪费;
(21):启动Activity的方式
启动Activity有两种方式,显式调用和隐式调用,显示调用就是在我们的代码中通过startActivity方式明确指定被启动对象的组件信息,比如包名和类名;隐式调用则不需要明确指定组件信息,我们可以设置一些过滤规则来启动那些符合规则的Activity即可;当显式调用和隐式调用同时出现在同一个Intent上的时候,执行的将是显式调用;
(22):IntentFilter的匹配规则
IntentFilter中的过滤信息有action、category、data,他们是在四大组件的<intent-filter></intent-filter>标签下进行配置的,一个过滤列表中可以有多个action、category、data,需要同时匹配列表中的action、category以及data才算匹配成功,否则失败;一个Activity可以有多个<intent-filter>匹配规则列表,一个Intent只要满足其中之一就可以成功启动该Activity了,也就是匹配成功了;
action匹配规则:
action的匹配规则要求Intent中的action必须存在,并且必须和过滤规则中的其中一个action相同,他的匹配是区分大小写的,在代码中通过setAction进行设置;
category匹配规则:
category匹配要求Intent中可以没有category,但是只要有category,不管你有几个,每一个都要和过滤规则中的其中之一category匹配,代码中通过addCategory进行设置;那么为什么Intent可以没有category而必须由action呢?原因在于系统在调用startActivity的时候默认会为Intent添加上"android.intent.category.DEFAULT"这个category,所以如果我们没有为Intent设置category,默认他会匹配包含有"android.intent.category.DEFAULT"的Activity的;
data匹配规则:
data是由mineType和URI组成,mineType表示媒体类型,URI和我们平常访问的网址类似,URI的默认值为content和file,也就是说如果你的Intent没有指定URI部分,那么默认情况下URI的schema部分是content或者file的,data的匹配规则和action类似,也就要求Intent中必须包含data数据,并且data数据可以完全匹配过滤规则中的某一个data;
(23):在Activity启动的时候获得View宽高的方式
因为View的measure过程和Activity的生命周期并不是同步的,因此在Activity调用了onCreate、onStart、onResume并不一定能够保证获取到View的宽高,很多情况下获取到的值都是0,那么我们该怎么在Activity启动的时候获得View的宽高呢?
方式1:通过设置点击按钮,在onClick事件里面获得View的宽高,为什么这种方式可行呢?原因在于在onClick执行的时候按钮已经显示出来了,说明View的绘制流程已经走完了,我们自然可以获得View的宽高了;
方式2:实现Activity的onWindowFocusChanged方法,在该方法中获取View的宽高,原因在于onWindowFocusChanged会在Activity布局绘制结束或者Activity暂停的时候调用,但是有个缺点就是该方法在Activity得到或者失去焦点都会回调,调用的次数比较频繁,当然你可以选择适当的时候屏蔽;
方式3:通过View树的观察者ViewTreeObserver实现,ViewTreeObserver实例的获得是通过getViewTreeObserver实现的,暗示了ViewTreeObserver的构造函数是不允许对外访问的,在View树的全局布局或者View树中的某一个视图状态发生变化的时候就会回调onGlobalLayoutListener里面的onGlobalLayout方法,我们可以在该方法里面获得View的宽高;
方式4:采用post方法将一个Runnable对象添加到消息队列的尾部,这样在我们执行Runnable的run方法之前View已经初始化好了,自然可以拿到他的宽高了;
详情可以看这篇博客;
(24):Fragment与Activity的关系
(1):Fragment是Android3.0出现的,我们可以将它认为是小Activity,碎片Activity,他是依托于Activity存在的,由Activity的FragmentManager来管理,也就是它的生命周期受Activity的影响,只有在Activity处于活动状态下才能个进行Fragment各个生命周期的变化,Activity被销毁的话,绑定在它上面的Fragment随之也会销毁;
(2):Fragment可以解决多Activity的问题,即可以将Activity的跳转转换为是Fragment的切换;
(3):Fragment可以被重用,即可以在不同的Activity*用同一个Fragment;
(4):Activity是间接继承了Context,但是Fragment不是,所以一个应用中Context的个数是不包括Fragment个数的;
(5):在Fragment里面可以通过getActivity获得当前Fragment所在的Activity对象,在Activity中可以通过FragmentManager查找它所包含的Fragment;
(25):Fragment生命周期
上面已经说了Fragment是依托于Activity存在的,也就是说Fragment不能单独存在,需要有Activity作为载体,只有在Activity处于活动状态的情况下才可以进行Fragment各生命走起状态间的转换,Activity一旦销毁它上面所附加的Fragment也将销毁;
Fragment生命周期所涉及到的方法有:onAttach、onCreate、onCreateView、onActivityCreated、onStart、onResume、onPause、onStop、onDestroyView、onDestroy、onDetach;
其中onAttach和onDetach是一对方法,分别表示Fragment被添加到Activity中和Fragment被从Activity移出;
onCreateView和onDestroyView是一对方法,分别在创建Fragment视图和移出Fragment视图的时候调用;
还有一个onActivityCreated,这个是在Activity的onCreate方法返回的时候调用的;
使用通常在定义Fragment的时候都要重写onCreateView方法,这个方法的返回值就是我们想要显示的View了,通常是通过LayoutInflater的inflate方法加载这个View的;
Fragment与Activity生命周期之间的关系可以通过官方的一张图来表示:
(26):Fragment与Fragment以及Fragment与Activity之间的通信
在Fragment里面可以通过getActivity获得当前Fragment所在的Activity,在Activity中可以通过FragmentManager来管理当前Activity所用到的Fragment,这样Fragment与Activity之间就可以进行通信了;那么Fragment之间该怎么进行通信呢?对于处于同一个Activity中的Fragment我们可以借助于他们都在该Activity上面这一点进行通信;当然上面所涉及到的Activity都是继承自FragmentActivity的Activity,不是平常的Activity;
(27):android存储数据的几种方式?
(1):使用sharedPreferences存储,底层实现原理是基于XML文件存储的key-value键值对;
(2):文件存储数据,Context提供了两个方法来打开数据文件里面的IO流FileInputStream openFileInput(String name);FileOutputStream openFileOutput(String name,int mode),可以用这两个方法将数据存储到文件中;
(3):使用SQLite存储数据,作为轻量级嵌入式数据库,当然可以进行数据的存储了,但是SQLite缺点在于只能在同一程序*享存储的数据;
(4):那么我们要是想在跨进程之间共享存储的数据该怎么办呢?也就是该我们ContentProvider出现的时候,因为ContentProvider的底层实现是Binder,所以他也是适合进程间数据共享的,ContentProvider内部是通过表格的方式来组织数据的,有点类似于SQLite数据库,但是ContentProvider对底层数据存储方式没有任何要求,每个应用程序对外都会提供一个公共的URI对象,如果某个应用程序有数据需要共享的时候,首先在该应用程序中会为这些数据定义一个URI,如果另外一个应用程序想要拿到这个应用程序的共享数据的话,就会通过ContentProvider传入这个URI来获取到数据,具体是怎么获取数据的就是通过ContentResolver对象来进行Insert、Update等等的操作了;
(5):网络数据存储,前面四种都是本地数据存储方式;
(28):HttpClient和HttpURLConnection他们各自的优缺点是什么?
(1):HttpClient是Apache提供的库,提供了高效的、最新的支持HTTP协议的工具包,封装了众多的http请求、响应等方法,但有个确定啊就是太重量级了,API太多了;HttpURLConnection是SUN公司的类库,他是轻量级的HTTP框架,它里面的方法都是一些我们进行http操作常用的,因而如果你想进行实现一些比较高级的功能比如代理、会话或者Cookie方面的,就需要自己写了;
(2):HttpURLConnection直接支持GZIP压缩,从2.3版本开始自动在每个发出的请求的请求头中加入Accept-Encoding:gzip;HttpClient也支持,但是需要你自己写;
(3):HttpURLConnection支持系统级连接池,即打开的连接不会直接关闭,在一段时间内所有应用程序可以共用;HttpClient也能做到,但至少不如直接系统级支持好;
(4):从4.0版本开始,HttpURLConnection在系统层面也开始支持缓存机制,加快了重复请求的次数;
(29):XML解析方式种类及其优缺点?
Android中对于XML的解析有SAX、DOM、PULL三种解析方式;
SAX解析器的优点是解析速度比较快,解析能立即开始,而不是等待所有的数据被处理,这点有别于DOM方式,因此不需要将数据存储到内存中,占用的内存比较少,非常适合于对较大文档的解析,他是基于事件模型的;
DOM方式在内存中是通过树状结构存放的,目前常用的是DOM4J方式了,这种方式对于查询或者检索的话效率相对高点,因为整个文档已经加载到内存中了,但是对于特别大的文档,解析和加载文档很耗费资源,需要占用较大内存资源,不适合移动端;
PULL解析方式和SAX一样,也是基于事件模式的,解析速度快,占用内存小,因此Android选择采用他来进行XML文件的解析,比如布局文件的加载等等;
(30):什么是ANR?造成ANR现象的原因?怎么避免和解决ANR现象?
ANR:Application Not Responding应用程序没有响应
ANR根据处理事件的不同分为三种类型:
(1):KeyDispatchTimeout----->5s,指的是按键或者触摸事件在5秒内未得到响应;
(2):BroadcastTimeout----->10s,指的是BroadcastReceiver在10s内无法处理完成;
(3):ServiceTimeout----->20s,指的是Service在20s内无法处理完成;
造成ANR现象的原因:
(1):当前的事件没有机会得到处理,比如主线程(UI线程)正在处理前一个事件,但是前一个事件比较耗时迟迟没有完成或者主线程中的looper因为某种原因阻塞了,因为我们知道在Activity启动的时候会创建一个Looper并且会通过Looper.loop()方法让该Looper运转起来;
(2):当前事件正在被处理,但是迟迟没有处理完成,导致用户界面一直收不到执行结果,一直在等待;
解决或者避免ANR的措施:
对于网络操作(当然现在网络操作是不可能在主线程中发起啦,要不直接会抛异常滴)、数据库操作或者IO等耗时的工作应该放到单独的线程中去执行处理,通过Handler来进行UI线程与子线程之间的交互切换,BroadcastReceiver的onReceive中尽量减少耗时代码的书写;
(31):Android系统进程等级类别?
Foreground Process(前台进程)
Visible Process(可见进程),一个透明的Activity下面覆盖的哪个Activity属于可见的,但是这个透明的Activity属于前台的;
Service Process(服务进程),也就是service;
Background Process(后台进程)
Empty Process(空进程)
系统回收进程是按照从低到高的优先级进行的;
(32):实现Service不被杀死常驻内存的方式有哪些?
(2):如果Service是被第三方杀毒软件或者清理软件杀死的话,我们可以提升Service的优先级来防止被杀除,具体方法就是使用前台Service,360就是这么做的,但使用前台Service有个坏处就是一直会有一个通知栏显示在界面上,这种方式比较流氓,但是效果相对来说比较好,具体实现方法是:通过调用Service的startForeground(int id, Notification notification)方法即可,第一个参数是Notification的id,第二个参数是Notification 对象了;
(3):据说某些杀毒应用软件以及手机厂商的系统中中是有白名单的,只是听说哈,这就是为什么你WX、QQ都不启动还会收到消息的原因了;
(4):还可以将Service单独放在一个进程中,为什么这么做呢?因为他占用内存资源比较少,不怎么会被盯上,同样是锁屏的状态下,系统肯定是先杀占用内存比较大的应用了;
(5):我们可以通过监听广播的方式实现,具体来说就是我们定义一个广播接收器,在这个接收器里面监听开机广播、网络切换广播、USB接口插入/拔出广播系统屏幕解锁广播等等,一旦收到这些广播之后,就去查看我们的服务Service有没有被启动,如果没有启动的话,则启动就可以了;
(6):我们的Service单独运行在一个进程中,而后在Service中调用JNI的代码fork出一个子进程,在子进程中监听Service的状态,当他被杀死的时候就重启他,这种方式在5.0以下是可以实现Service常驻内存的,但是5.0以上版本同样会杀死Service,原因就是5.0以上增加了killProcessGroup方法;
(7):使用QQ黑科技,在应用推到后台之后,启动一个只有一像素的页面停留在桌面上,让自己始终保持前台状态,保护自己不被后台清理工具杀死;