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);
        }
    }
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页