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不能触发事件,但是属性动画是可以的;
(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的;
可以通过设置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方法;