Android性能优化(4):UI渲染机制以及优化

从Android 6.0源码的角度剖析View的绘制原理一文中,我们了解到View的绘制流程有三个步骤,即measure(测量)、layout(布局)和draw(绘制),它们主要运行在系统应用框架层,而真正将数据渲染到屏幕上的则是系统Native层的SurfaceFlinger服务来完成的。本文将暂不对SurfaceFlinger服务的执行流程进行剖析,而是从更加底层的角度来了解APP的渲染机制,同时引出在渲染过程中遇到的性能问题,以及如何去发现、优化它们。

1. 渲染机制分析

1.1 渲染机制

 对于开发者来说,APP的界面主要是由XML布局文件来表现的,每个XML布局文件中又包括了很多视图组件,比如ImageView、Button、TextView等等,它们分别表示了图片、按钮、字符串等不同的信息,那么这些复杂的XML布局文件和标记语言,又是如何转化成为用户能够看得懂的图像的呢?答案是:格栅化(Rasterization)!所谓格栅化,就是将例如字符串按钮路径或者形状等的一些高级对象,拆分到不同的像素屏幕上进行显示。格栅化是一个非常费时的操作,CPU作为中央处理器本身任务就比较繁重,因此人们引入了GPU(图像处理器)这块特殊的硬件来加快格栅化操作(硬件加速)。格栅化操作大体如下:
在这里插入图片描述
 接下来,我们就来详细了解XML布局文件中的UI组件是如何被格栅化并渲染显示在屏幕上的?在APP绘制渲染过程中,与绘制渲染有关的硬件主要有CPUGPU(图形处理器),其中,CPU负责把UI组件计算成Polygons(多边形)Texture(纹理),而GPU负责格栅化和渲染工作。CPU和GPU通过图形驱动层进行连接,这个图形驱动层维护了一个Display List队列,它们分别充当生产者消费者,协作完成具体的绘制渲染过程。绘制渲染过程如下图所示:
在这里插入图片描述

  • 渲染流程如下:

首先CPU会对UI组件(View)进行MeasureLayoutRecordExecute的数据计算工作,以将其计算成的Polygons(多边形)Texture(纹理),它们是GPU能够识别的对象,而承载这些信息的是一个被称为Display List的结构体,它持有所有交给GPU绘制到屏幕上的数据信息,包含GPU要绘制的全部对象的信息列表和执行绘制操作的OpenGL命令列表。在某个View第一次需要被渲染的时候,Display List会因此被创建,当这个View需要显示到屏幕上时,GPU接收到绘制指令后会执行该Display List。每个View拥有自己的Display ListDisplay Lis本身也构成一个树状的结构,跟View Hierachy(视图树)保持一致。
在这里插入图片描述
其次,由于每次从CPU计算得到的PolygonsTexture提交(转移)GPU是一件比较麻烦且耗时的事情(注:实际提交的是Display List),因此Android系统又引入了OpenGL ES,该库API允许将那些需要渲染的PolygonsTexture存储(Hold)在GPU存储器(显存)中,当下一次需要渲染的时候只需要在GPU存储器里引用它,然后告诉OpenGL如何绘制就可以了,而不再需要经过CPU上传。需要注意的是,假如View的绘制内容发生变化,那么GPU Memory存储的Display List就无法再继续使用,这时就需要CPU重新计算创建Display List并重新执行指令更新到屏幕。
在这里插入图片描述
 最后,当GPU完成对纹理的格栅化、渲染后,它会将渲染的结果放入帧缓冲区,视频控制器会按照VSync(垂直同步)信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。

  • CPU、OpenGL与GPU之间的关系
    在这里插入图片描述

1.2 卡顿现象

 前面说到,当GPU渲染完成后会将渲染的结果存储到帧缓冲区,视频控制器会按照VSync(垂直同步)信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。那么问题来了,什么是VSYNC信号?在讲解VSYNC之前,我们需要了解两个相关的概念:Refresh RateFrame Rate

  • Refresh Rate(刷新频率)

 表示屏幕在一秒内被刷新的次数,取决于硬件的固定参数,目前大部分手机这个固定参数(屏幕刷新频率)为60Hz。也就是说,Android系统每隔约16ms(1000ms/60=16.66ms)就会重新刷新一次界面(Activity),因此我们有16ms的时间去完成每帧的绘制、渲染的逻辑操作。
在这里插入图片描述

  • Frame Rate(帧率)

 表示GPU在一秒内能够渲染的帧数,通常为60fps。为什么是60fps?这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。简单的说,人眼看到的动画其实都是一帧帧连续播放的静态图片,当播放的速度达到1秒针24帧(24fps)时,对于人眼感知来说就是连续的线性运动。当然低于30fps在某些场景仍然无法顺畅表现绚丽的画面,此时就需要60fps来达到想要的效果。

 现在我们再来理解下VSYNC信号:**屏幕的刷新过程是每一行从左到右(水平刷新,Horizontal Scanning),从上到下(垂直刷新,Verical Scanning)。当整个屏幕刷新完毕,即一个垂直刷新周期完成,会有短暂的空白期,此时Android系统就会发出VSYNC信号,触发下一帧对UI的渲染、显示。VSYNC是一种定时中断,一旦收到VSYNC信号CPU就开始处理帧数据,通常Android系统每隔16ms发出VSYNC信号,这个周期时间由屏幕刷新频率决定。**通常来说,帧率超过刷新频率只是一种理想的状态,在超过60fps的情况下,GPU所产生的帧数据会因为等待VSYNC的刷新信息而被Hold住,这样能够保持每次屏幕刷新都有实际的新的数据可以显示。但是基本上我们遇到的是帧率<=屏幕刷新频率的情况,尤其是在帧率小于刷新频率时,会出现待VSYNC信号到来时,屏幕没有可以刷新的数据,即帧缓冲区还是之前的那帧图像,这就会导致屏幕显示的该帧画面内容仍然是上一帧的画面,而这种现象我们就称之为"掉帧",对于用户来说,就是界面出现了卡顿现象,即运行不流畅。
在这里插入图片描述
 由此我们可以得出,Android系统之所以会出现卡顿现象是因为的某些操作耗费了帧间隔时间(16ms),导致其他类似计算、渲染等操作的可用时间就会变少,在16ms无法完成正常的绘制、渲染工作。当下一个VSYNC信号到来时,Android系统尝试在屏幕上绘制新的一帧,但是新的一帧还没准备好,就会导致无法进行正常渲染,画面就不会刷新,用户看到的还是上一帧的画面,从而发生了丢帧。产生卡顿的原因有很多,主要有以下几点:

  • 布局Layout过于复杂,无法在16ms内完成渲染;
  • 同一时间动画执行的次数过多,导致CPU或GPU负载过重;
  • View过度绘制,导致某些像素在同一帧时间内被绘制多次
  • 在UI线程中做了很多耗时的操作;
  • GC回收时暂停时间过长或者频繁GC产生大量的暂停时间(内存抖动);

1.3 内存抖动

 在1.2小节我们谈到,大量不停的GC操作会显著占用帧间隔时间(16ms),如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了,就容易出现丢帧现象,而导致这种频繁GC的原因之一就是“内存抖动(Memory Churn)”。内存抖动是因为大量的对象被创建又在短时间内马上被释放,瞬间产生大量的对象会严重占用年轻代(Yong Generation)的内存区域,当达到阀值时剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。
在这里插入图片描述

2. 渲染优化方式

 从上一小节的分析可知,Android系统的渲染机制主要体现在CPU的绘制和GPU的格栅化、渲染两个阶段,其中,主要比较耗时的在CPU的绘制过程,即将UI对象转换为一系列的多边形和纹理,和从CPU上传Display List数据到GPU过程。如果CPU/GPU 负载过量或者上传数据次数过多,就会导致 16ms 内没有绘制、渲染完成出现渲染性能问题。因此,对于渲染性能方面的的优化,我们将从这两个方面进行剖析,即减轻CPU/GPU的负载量和减少占用额外的GPU资源。下面我们就介绍几个工具来帮助我们发现、解决一些渲染性能优化问题,主要有Profile GPU RenderingSysTrace以及TraceView等。

2.1 过度绘制优化

 所谓过度绘制,指的是在屏幕上某个像素在同一帧时间内被绘制多次,从而浪费了CPU和GPU的资源。举个例子来说,假如我们要粉刷房子的墙壁,一开始刷绿色,接着又开始刷黄色,这样黄色就将绿色盖住了,为此第一次的粉刷就白干了。产生过度绘制主要有两个原因:

  • 在XML布局中,控件有重叠且都有设置背景
  • View的onDraw在同一区域绘制多次;

2.1.1 Show GPU overdraw

Show GPU overdraw是Android系统提供的一个查看界面过度绘制的工具,我们可以在手机设置里的开发者选项中开启它,即开发者选项->调试GPU过度绘制。Show GPU overdraw效果:
在这里插入图片描述
 开启调试GPU过度绘制后,我们的界面就会出现各种颜色,它们的具体含义如下:

  • 白色:没有过度绘制,即每个像素点在屏幕上只绘制了一次;
  • 蓝色:一次过度绘制,即每个像素点在屏幕上绘制了两次;
  • 绿色:二次过度绘制,即每个像素点在屏幕上绘制了三次;
  • 粉红色:三次过度绘制,即每个像素点在屏幕上绘制了四次;
  • 红色:四次以上过度绘制;

 为此我们可以得出,一个合格的界面应该以白色和蓝色为主,绿色以上的区域不要超过整体的三分之一,总之颜色越浅越好。

2.1.2 Profile GPU Rendering

 Profile GPU Rendering是Android4.1系统引入用于展示GPU渲染每帧时各个阶段所消耗的时间,以便于我们可以直接观察到渲染该帧时各个阶段的耗时情况,即有没有超过16ms。我们可以在手机设置里开发者选项中打开它(开发者选项->GPU显示配置文件/GPU呈现模式分析->在屏幕上显示为条形图),接着手机屏幕的底部就会出现彩色的柱状。效果如下图所示:
在这里插入图片描述
 图中绿色横轴代表帧时间,彩色的柱状(纵轴)表示某一帧的耗时。绿色的横线为警戒线,超过这条线则意味着该帧绘制渲染的时间超过了16ms,我们应尽量保证垂直的彩色柱状图保持在绿线下面,任何时候一帧超过绿线,我们的app将会丢掉一帧。每一个彩色柱状图代表一帧的渲染,越长的垂直柱状图表示这一帧需要渲染的时间越长。当APP在运行时,我们会看到手机底部的柱状图会从左到右动态地显示,随着需要渲染的帧数越来越多,他们回堆积在一起,这样就可以观察到这段时间帧率的变化。下图为柱状图中不同颜色代表的意义:
在这里插入图片描述
颜色意义解释

  • 橘色:代表处理的时间。代表CPU在等待GPU完成工作的时间,如果过高表示GPU很繁忙;

  • 红色:代表执行的时间。Android 的2D渲染器向OpenGL发出命令绘制或重绘display lists 花费的时间,柱子的高度等于所有Display list绘制时间的总和。如果红色柱状图很高,可能由于重新提交视图而导致,还有复杂的自定义View也会导致红的柱状图变高;

  • 浅蓝色:向CPU传输Bitmap花费的时间,过高代表了加载了大量图形;

  • 深蓝色:代表绘制的时间。也就是需要多长时间去创建和更新Dispaly List。过高代表在onDraw中花费过多时间,可能是自定义画图操作太多或执行了其它操作;

  • 淡绿色:代表了onLayoutonMeasure方法消耗的总时间,这段很高代表遍历整个view树结构花费了太多时间;

  • 绿色:代表为该帧内所有animator求值(属性动画中代表通过估值器计算属性的具体值)所花费的时间.如果这部分过高,代表自定义animator性能不佳或者更新view属性引发了某些意外操作;

  • 深绿色:代表app在用户输入事件回调中花费的时间,这部分过高可能意味着app处理用户输入事件时间过长,建议将操作分流到工作线程;

  • 墨绿色:代表在连续两帧间的时间间隔,可能是因为子线程执行时间过长抢占了UI线程被cpu执行的机会。

 为此,我们可以得出在Profile GPU Rendering中,假如红色/橘色占比较大,说明可能出现重复布局的情况,可以从否减少视图层级、减少无用的背景图、减轻自定义控件复杂度等方面去优化;假如蓝色/浅蓝/各种绿色占比较大,应该从耗时操作这方面去优化。当然,Profile GPU Rendering可以找到渲染有问题的界面,但是想要修复的话,只依赖Profile GPU Rendering是不够的,我们可以通过如下两款工具来定位问题,即TraceViewHierarchy Viewer,前者能够详细分析问题原因;后者能够查看布局的层次和每个View所花费的时间。Systrace

总之,我们应尽量从以下几个方面避免过度绘制。

  • 移除无用的背景图;
  • 减少视图层级,尽量使用扁平化布局,比如Relativeayout;
  • 减轻自定义控件复杂度,重叠区域可以使用canvas.clipRect方法指定绘制区域;`

2.2 卡顿优化

 前面我们谈到,Android系统之所以会出现卡顿现象是因为的某些操作耗费了帧间隔时间(16ms),导致其他类似计算、渲染等操作的可用时间就会变少,在16ms无法完成正常的绘制、渲染工作。当下一个VSYNC信号到来时,Android系统尝试在屏幕上绘制新的一帧,但是新的一帧还没准备好,就会导致无法进行正常渲染,画面就不会刷新,用户看到的还是上一帧的画面,从而发生了丢帧,这就是"卡顿现象"。下面我们介绍两款分析界面卡顿的利器,即SysTraceTraceView,其中,SysTrace为卡顿原因指明大体方向,TraceView则是找到是什么让CPU繁忙、某些方法的调用次数等具体信息。

2.2.1 SysTrace

SysTrace是Android 4.1中新增的性能数据采样和分析工具,它可以帮助我们收集Android关键子系统的运行信息,如SurfaceFlinger、WMS等Framework部分关键模块、服务、View体系系统等。Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状态。**对于UI显示性能,比如动画播放不流畅、渲染卡顿等问题提供了分析数据。**我们可以在AS的Android Device Monitor(DDMS)开启Systrace,需要注意的是,AS 3.0以后谷歌已将DDMS从AS面板上移除,但我们仍然可以打开sdk目录/tools/monitor.bat使用它。SysTrace使用方法如下:
在这里插入图片描述

  • 具体步骤:
  1. 双击sdk目录/tools/monitor.bat打开DDMS,单击DDMS面板上的Systrace按钮;

  2. 进入抓取设置界面后设置跟踪的时长,以及trace.html文件输出路径等内容;

  3. 操作APP我们怀疑卡顿的地方,对该过程进行跟踪;

  4. 待跟踪结束后就会在指定路径生产trace.html文件,接下来用Chrome浏览器打开它进行分析。

  • 用Chrome分析trace.html
    在这里插入图片描述
     总体来说,trace.html主要记录了Android系统中所有进程和线程运行信息,且同一个进程内按线程进行纵向拆分,每个线程均记录了自己的工作。从上图可知,在trace.html文件中我们应着重关注三个方面:AlertsFramesKernel CPU,下面我们就详细分析如何使用它们来定位问题。
  1. Alerts部分

 Alerts部分是Systrace自动分析trace中的事件,并能自动高亮某个性能问题作为一个Alerts(警告),建议调试人员下一步怎么做。可以通过点击顶部浅蓝色圆圈查看某一个警告,或者点击右侧的Alerts选项列出所有警告信息,这些信息将会按类别列出,比如上图中列出了Scheduling delay。选中一条Alert详情如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g2UlHVKr-1574213695824)(F:\360MoveData\Users\Jiangdg\Desktop\性能优化\插图\APK6.png)]

Scheduling delay(调度延迟)的意思就是一个线程在处理一块运算的时候,在很长一段时间都没有被分配到CPU上面做运算,从而导致这个线程在很长一段时间都没有完成工作。从上图可以看出,选中的这帧只运行了2.343ms,而有101.459ms是在休眠,这就意味着这一帧的渲染过程非常慢。当然仅仅通过Alerts的提示仍然无法找到渲染慢的原因,接下来就需要借助Frames部分进一步定位。

  1. Frames部分

 Frames部分给出的是应该的帧数,每一帧就是一个F圆圈F圆圈有三种颜色:绿色、黄色和红色,其中,绿色表示该帧渲染流畅(即没有超过16.66ms,满足每秒60帧稳定所需的帧),黄色和红色表示该帧的渲染时间超过了16.66ms,这就意味着黄色和红色代表的帧存在渲染性能问题(红色比黄色更严重)。我们点击红色的F圆圈,在面板的底部会给出具体的Alert信息(同Alert部分),然后我们再按下电脑键盘上的M键可以看到被高亮的部分就是这帧精确的时间耗时。应用区域的Frames展示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yIq8gjzg-1574213695824)(F:\360MoveData\Users\Jiangdg\Desktop\性能优化\插图\APK7.png)]

 由于Android 5.0以上系统UI渲染的工作是在UI ThreadRenderThread完成的,我们使用键盘的W键对这两个区域放大,然后选择两个线程中最长的一块区域(表示某个函数方法)观察那些View在被填充过程中耗时比较严重。我们点击DrawFrame这个方法,在面板的底部可以得到以下信息:
在这里插入图片描述
 从上图可以看出,Wall Duration代表着这一块区域的开始到结束的耗时,为204.877ms;CPU Duration代表实际CPU在处理这一块区域的耗时,即分配CPU的时长0.899ms。很显然,这两个时间差距非常大,CPU只消耗了不到1ms的时间来运算这块区域。这就意味着可能其他线程/进程长期占用CPU,导致RenderThread无法进行正常运算出现渲染异常。接下来,我们就通过分析Kernel CPU部分进一步确定是哪个线程/进程长期占用CPU。

  1. Kernel CPU部分

 在Kernel CPU区域,每一行代表一个CPU核心和它执行任务的时间片,放大后会看到每个色块代表一个执行的进程,色块的长度代表其执行时间。如下图所示:
在这里插入图片描述
 在之前选中的帧区域,我们很容易看到一个很长的绿色块,位于CPU2上,这个绿色块CPU执行的进程为HeapTaskDaemon,从进程名来看很容易猜出这个进程就是我们的GC守护进程。这就意味着,在这一帧内执行了长时间的垃圾回收操作,由于系统执行GC操作时会将正在执行的其他进行挂起,GC进程会独占CPU,长时间的GC操作或频繁GC就会导致其他进程长时间分配不到CPU时间片,这就是导致渲染变慢的根本原因。我们点击HeapTaskDaemon颜色块,可以看到具体的GC时间,如下图所示:
在这里插入图片描述

操作快捷键

  • W、S:放大、缩小
  • A、D:向左、向右移动

 需要注意的是,由于Systrace是以系统的角度返回一些信息,只能提供一个大概且深度有限,我们可以用它来进行粗略的检查,以便获得具体分析的方向。如果需要进一步得到是哪块代码引起的CPU繁忙、某些方法的调用次数等,就需要借助TraceView这个工具进行。

2.2.2 TraceView

TraceView是Android SDK中自带的数据采集和分析工具,与Systrace不同的是,它是从代码层面来分析性能问题,且针对的是每个方法来分析,比如当我们发现应用出现卡顿的时候,就可以通过TraceVIew来分析出现卡顿时在方法的调用上有没有很耗时的操作。开启TraceView方法与Systrace一样,都是在DDMS中启动。在使用TraceView时,我们需要着重关注以下两个方面:

  • 调用次数不多,但是每一个执行都很耗时;
  • 方法耗时不大,但是调用次数太多;

TraceView使用方法:

  1. 双击sdk目录/tools/monitor.bat打开DDMS,单击DDMS面板上左上角带小红点按钮,文字提示为start Method Profiling,然后按钮左上方会出现一个灰色的正方形,文字提示为stop Method Profiling,此时按钮变为停止采集功能;
  2. 进入Profiling Options配置采样频率,默认为1000微秒;
  3. 操作APP我们怀疑卡顿的地方,对该过程进行跟踪;
  4. 操作结束后再次点击之前的按钮(文字提示stop Method Profiling)结束采集。
    在这里插入图片描述
     从上图可以看出,TraceView的面板分上下两个部分:
  • 时间线面板以每个线程为一行,右边是该线程在整个过程中方法执行的情况;
  • 数据分析面板是以表格的形式展示所有线程的方法的各项数据指标;

时间线面板:

 时间线面板以每个线程为一行,右边是该线程在整个过程中方法执行的情况,比如上图中显示的main线程就是Android应用的主线程,当然也会存在其他线程,可能会因操作不同而发生改变。每个线程的右边对应的是该线程中每个方法的执行信息,左边为第一个方法执行开始,最右边为最后一个方法执行结束,其中的每一个小立柱就代表一次方法的调用,你可以把鼠标放到立柱上,就会显示该方法调用的详细信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-57KrViMV-1574213695825)(F:\360MoveData\Users\Jiangdg\Desktop\性能优化\插图\APK12.png)]

 如上图所示,墨绿色的立柱表示FibonacciActivity.computeFubonacci()方法的执行情况,可以看出这个方法执行的时间很长,尤其是对于处于主线程中,这是非常不正常的现象。如果你想在分析面板中详细查看该方法,可以双击该立柱,数据分析面板自动跳转到该方法。

数据分析面板:

 数据分析面板右侧为时间线面板中每个立柱表示的方法,当我点击墨绿色立柱后,FibonacciActivity.computeFubonacci()方法被高亮,展开该方法我们可以看到ParentsChildren,其中,Parents表示谁调用了computeFubonacci()方法,Children表示computeFubonacci()方法调用了哪些方法。数据分析面板的左侧展示的是每个方法(一行)执行的耗时,我们着重看下computeFubonacci()方法Incl Real Time值为1595.360ms,这显然是不正常的。另外,再看下Calls+Recur Calls/Total的值显示computeFubonacci()方法数为1,但是被递归调用了1491次。因此,我们就可以得出之前的APP操作之所以会出现卡顿,是因为在主线程的FibonacciActivity中递归调用了computeFubonacci()方法,导致CPU被长期占用,从而导致渲染线程无法获得CPU资源出现无法正常渲染的性能问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RKq6Y0q2-1574213695825)(F:\360MoveData\Users\Jiangdg\Desktop\性能优化\插图\APK13.png)]

 每一列数据代表的含义如下表所示:

名称意义
Name方法的详细信息,包括包名和参数信息
Incl Cpu TimeCpu执行该方法该方法及其子方法所花费的时间
Incl Cpu Time %Cpu执行该方法该方法及其子方法所花费占Cpu总执行时间的百分比
Excl Cpu TimeCpu执行该方法所话费的时间
Excl Cpu Time %Cpu执行该方法所话费的时间占Cpu总时间的百分比
Incl Real Time该方法及其子方法执行所话费的实际时间
Incl Real Time %上述时间占总的运行时间的百分比
Excl Real Time %该方法自身的实际允许时间
Excl Real Time上述时间占总的允许时间的百分比
Calls+Recur Calls/Total调用次数+递归次数
Calls/Total调用次数和总次数的占比
Cpu Time/CallCpu执行时间和调用次数的百分比,代表该函数消耗cpu的平均时间
Real Time/Call实际时间于调用次数的百分比,该表该函数平均执行时间
  • FibonacciActivity源码如下:
/** UI卡顿现象
 * @Auther: Jiangdg
 * @Date: 2019/11/18 15:06
 * @Description: 使用斐波那契数列人为制造卡顿现象
 */
public class FibonacciActivity extends AppCompatActivity {
    private static final String LOG_TAG = "FibonacciActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fibonacci);

        Button mbtn = (Button) findViewById(R.id.caching_do_fib_stuff);
        mbtn.setText("计算斐波那契数列");

        mbtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(LOG_TAG, String.valueOf(computeFibonacci(40)));
            }
        });
        WebView webView = (WebView) findViewById(R.id.webview);
        webView.getSettings().setUseWideViewPort(true);
        webView.getSettings().setLoadWithOverviewMode(true);
        webView.loadUrl("file:///android_asset/shiver_me_timbers.gif");
    }

    public int computeFibonacci(int positionInFibSequence) {
        //0 1 1 2 3 5 8
        if (positionInFibSequence <= 2) {
            return 1;
        } else {
            return computeFibonacci(positionInFibSequence - 1)
                    + computeFibonacci(positionInFibSequence - 2);
        }
    }
}
  • 12
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
第1章 android,后起之秀  1.1 android简介  1.2 版本分裂  1.3 谷歌的角色  1.3.1 android开源项目  1.3.2 android market  1.3.3 挑战赛、设备播种计划和谷歌i/o  1.4 android的功能和体系结构  1.4.1 内核  1.4.2 运行库和dalvik虚拟机  1.4.3 系统库  1.4.4 应用程序框架  1.5 软件开发工具包  1.6 开发人员社区  1.7 设备,设备,设备  1.7.1 硬件  1.7.2 设备的范围  1.8 所有设备之间的兼容性  1.9 不同的手机游戏  1.9.1 人手一台游戏机  1.9.2 随时上网  1.9.3 普通用户与游戏迷  1.9.4 市场很大,开发人员很少  1.10 小结 第2章 从android sdk开始  2.1 搭建开发环境  2.1.1 安装jdk  2.1.2 安装android sdk  2.1.3 安装eclipse  2.1.4 安装adt eclipse插件  2.1.5 eclipse快速浏览  2.1.6 一些实用的eclipse快捷键  2.2 android环境下的hello world  2.2.1 创建项目  2.2.2 进一步分析项目  2.2.3 编写应用程序代码  2.3 运行和调试android应用程序  2.3.1 连接设备  2.3.2 创建一个android虚拟设备  2.3.3 运行应用程序  2.3.4 调试应用程序  2.3.5 logcat和ddms  2.3.6 使用adb  2.4 小结 第3章 游戏开发基础  3.1 游戏类型  3.1.1 休闲游戏  3.1.2 益智游戏  3.1.3 动作和街机游戏  3.1.4 塔防游戏  3.1.5 创新  3.2 游戏设计:笔比代码更强大  3.2.1 游戏的核心机制  3.2.2 一个故事和一种艺术风格  3.2.3 画面和切换  3.3 代码:具体细节  3.3.1 应用程序和窗口管理  3.3.2 输入  3.3.3 文件i/o  3.3.4 音频  3.3.5 图形  3.3.6 游戏框架  3.4 小结 第4章 面向游戏开发人员的android  4.1 定义一个android应用程序:清单文件  4.1.1 [manifest]元素  4.1.2 [application]元素  4.1.3 [activity]元素  4.1.4 [uses-permission]元素  4.1.5 [uses-feature]元素  4.1.6 [uses-sdk]元素  4.1.7 10个简单步骤建立android游戏项目  4.1.8 市场过滤器  4.1.9 定义游戏图标  4.2 android api基础  4.2.1 创建测试项目  4.2.2 活动的生命周期  4.2.3 处理输入设备  4.2.4 文件处理  4.2.5 音频编程  4.2.6 播放音效  4.2.7 音乐流  4.2.8 基本图形编程  4.3 最佳实践  4.4 小结 第5章 android游戏开发框架  5.1 制定计划  5.2 androidfileio类  5.3 androidaudio、androidsound和androidmusic  5.4 androidinput和accelerometer-handler  5.4.1 accelerometerhandler:手机哪一面朝上  5.4.2 compasshandler  5.4.3 pool类:重用相当有用  5.4.4 keyboardhandler  5.4.5 触摸处理程序  5.4.6 androidinput:优秀的协调者  5.5 androidgraphics和androidpixmap  5.5.1 处理不同屏幕大小和分辨率的问题  5.5.2 androidpixmap:人物的像素  5.5.3 androidgraphics:满足绘图需求  5.5.4 androidfastrenderview  5.6 androidgame:合并所有内容  5.7 小结 第6章 mr. nom入侵android  6.1 创建资源  6.2 建立项目  6.3 mrnomgame:主要活动  6.3.1 资源:便捷的资源存储  6.3.2 设置:跟踪用户的选项设置和高分榜  6.3.3 loadingscreen:从磁盘获取资源  6.4 主菜单画面  6.5 helpscreen类  6.6 高分榜画面显示  6.6.1 渲染数字  6.6.2 画面的实现  6.7 抽象  6.7.1 抽象mr. nom的世界:模型、视图、控制器  6.7.2 gamescreen类  6.8 小结 第7章 opengl es介绍  7.1 opengl es概述以及关注它的原因  7.1.1 编程模型:一个比喻  7.1.2 投影  7.1.3 规范化设备空间和视口  7.1.4 矩阵  7.1.5 渲染管道  7.2 开始之前  7.3 glsurfaceview:从2008年开始,事情变得简单了  7.4 glgame:实现游戏接口  7.5 绘制一个红色的三角形  7.5.1 定义视口  7.5.2 定义投影矩阵  7.5.3 指定三角形  7.5.4 综合示例  7.6 指定每个顶点的颜色  7.7 纹理映射:轻松地创建壁纸  7.7.1 纹理坐标  7.7.2 上传位图  7.7.3 纹理过滤  7.7.4 释放纹理  7.7.5 有用的代码片段  7.7.6 启用纹理  7.7.7 综合示例  7.7.8 texture类  7.8 索引顶点:重用是有好处的  7.8.1 代码整合  7.8.2 vertices类  7.9 半透明混合处理  7.10 更多图元:点、线、条和扇  7.11 2d变换:操作模型视图矩阵  7.11.1 世界空间和模型空间  7.11.2 再次讨论矩阵  7.11.3 第一个使用平移的示例  7.11.4 更多的变换  7.12 性能优化  7.12.1 测量帧率  7.12.2 android 1.5平台下hero的奇特案例  7.12.3 使opengl es渲染如此慢的原因  7.12.4 移除不必要的状态改变  7.12.5 减小纹理大小意味着需要获取更少的像素  7.12.6 减少opengl es/jni方法的调用  7.12.7 绑定顶点的概念  7.12.8 写在结束之前  7.13 小结 第8章 2d游戏编程技巧  8.1 写在开始  8.2 向量  8.2.1 使用向量  8.2.2 一点三角学的知识  8.2.3 实现一个向量类  8.2.4 一个简单的用法示例  8.3 2d物理定律浅析  8.3.1 牛顿和欧拉,永远的好朋友  8.3.2 力和质量  8.3.3 理论上的运动  8.3.4 运动的实现  8.4 2d碰撞检测和对象表示  8.4.1 边界形状  8.4.2 构造边界形状  8.4.3 游戏对象的属性  8.4.4 宽阶段和窄阶段碰撞检测  8.4.5 一个详细的示例  8.5 2d照相机  8.5.1 camera2d类  8.5.2 示例  8.6 纹理图集  8.7 纹理区域、精灵和批处理:隐藏opengl es  8.7.1 textureregion类  8.7.2 spritebatcher类  8.8 精灵动画  8.8.1 animation类  8.8.2 示例  8.9 小结 第9章 super jumper:一个2dopengl es游戏  9.1 核心游戏机制  9.2 背景故事和艺术风格  9.3 画面和切换  9.4 定义游戏世界  9.5 创建资源  9.5.1 ui元素  9.5.2 使用点阵字体处理文本  9.5.3 游戏元素  9.5.4 用于救援的纹理图集  9.5.5 音乐与音效  9.6 实现super jumper  9.6.1 assets类  9.6.2 settings类  9.6.3 主活动  9.6.4 font类  9.6.5 glscreen  9.6.6 主菜单画面  9.6.7 帮助画面  9.6.8 高分画面  9.6.9 模拟类  9.6.10 游戏画面  9.6.11 worldrenderer类  9.7 是否需要优化  9.8 小结 第10章 opengl es:进入3d世界  10.1 准备工作  10.2 3d中的顶点  10.2.1 vertices3:存储3d空间位置  10.2.2 示例  10.3 透视投影:越近则越大  10.4 z-buffer:化混乱为有序  10.4.1 完善上一个例子  10.4.2 混合:身后空无一物  10.4.3 z-buffer精度与z-fighting  10.5 定义3d网格  10.5.1 立方体:3d中的“helloworld”  10.5.2 一个示例  10.6 矩阵和变换  10.6.1 矩阵堆栈  10.6.2 用矩阵堆栈实现分层系统  10.6.3 木箱太阳系的简单实例  10.7 小结 第11章 3d编程技巧  11.1 准备工作  11.2 3d中的向量  11.3 opengl es中的光照  11.3.1 光照的工作机制  11.3.2 光源  11.3.3 材质  11.3.4 opengl es中如何对光照过程进行运算:顶点法线  11.3.5 实践  11.3.6 关于opengl es中光照应用的一些建议  11.4 材质变换(mipmapping)  11.5 简单的照相机  11.5.1 第一人称照相机或欧拉照相机  11.5.2 一个欧拉照相机的示例  11.5.3 跟随照相机  11.6 加载模块  11.6.1 wavefront obj格式  11.6.2 obj加载器的实现  11.6.3 使用obj加载器  11.6.4 关于加载模型的一些建议  11.7 3d中的一些物理知识  11.8 碰撞检测与3d中的对象表达法  11.8.1 3d中的边界形状  11.8.2 边界球重叠测试  11.8.3 gameobject3d与dynamic-gameobject3d  11.9 小结 第12章 droid invaders游戏  12.1 游戏的核心机制  12.2 游戏的故事背景与艺术风格  12.3 屏幕与场景切换  12.4 定义游戏世界  12.5 创建资源  12.5.1 用户界面的资源  12.5.2 游戏资源  12.5.3 音效与音乐  12.6 开始编写代码  12.7 assets类  12.8 settings类  12.9 主活动  12.10 主菜单  12.11 游戏设置画面  12.12 模拟类  12.12.1 shield类  12.12.2 shot类  12.12.3 ship类  12.12.4 invader类  12.12.5 world类  12.13 gamescreen类  12.14 worldrender类  12.15 游戏优化  12.16 小结 第13章 发布游戏  13.1 关于测试  13.2 成为注册开发人员  13.3 给游戏的apk包签名  13.4 将游戏发布至market  13.4.1 上传资源  13.4.2 产品详情  13.4.3 发布选项  13.4.4 发布  13.4.5 市场推广  13.5 开发人员控制台  13.6 小结 第14章 进阶内容  14.1 社交网络  14.2 位置识别  14.3 多玩家功能  14.4 opengl es 2.0以及更多内容  14.5 框架及引擎  14.6 网络资源  14.7 结束语

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值