对于Android的UI性能优化,我一般从5个途径来分析:

1.Debug GPU overdraw;
2.Android CPU Profile;
3.dumpsys gfxinfo;
4.Profile GPU Rendering;
5.Systrace。
前一个途径会对它后面有帮助,我们从这5个途径一一来说明。

一.Debug GPU overdraw

这个是用于检测布局优化的工具,这个想必大家都非常熟悉了,在手机的开发者选项中打开”Debug GPU overdraw“,就能看到咱们的布局中有各种色块,色块的说明,一般避免出现粉色或红色:

  • 真彩色:没有过度绘制
  • 蓝色:过度绘制 1 次
  • 绿色:过度绘制 2 次
  • 粉色:过度绘制 3 次
  • 红色:过度绘制 4 次或更多次

优化布局的工作就是为了减少View的层级,因为在View的绘制流程里面,可能会包括各种 XML 的随机读的 I/O 时间、解析 XML 的时间、生成对象的时间(Framework 会大量使用到反射)

常见的优化布局的方法:
1.从View的创建方式:

传统的从xml解析View,会有xml解析的过程和反射生成View的过程,而这两个过程是很耗时的,那么可以将xml中的布局直接用代码来代替,就省去了xml解析和反射的过程,市面上有线程的框架,但是也不能做到所有的标签都能用代码替换。

2.从创建View的线程:

在View的渲染过程中,View的创建也是在UI线程,会占用UI线程的资源,那么可以将View的创建放到其他线程来处理,需要用主线程的MessageQueue代替当前这个线程的MessageQueue,然后创建View,创建完成之后需要再把当前这个线程的MessageQueue替换成原来的,具体的实现方法参考网上的例子。

3.从执行View绘制的流程:

一般View的绘制流程有measure、layout、draw三个过程,除了draw一定要在UI线程绘制以外,measure和layout的过程可以在子线程中执行。市场已有这类第三方框架:Litho。Litho是 Facebook 开源的声明式 Android UI 渲染框架。

4.从View的复用:

View的复用可以减少View的创建过程,不如ListView的ViewHolder和RecyclerView的复用机制。

5.从View布局的层级:

减少层级的方式可以使用 标签,使用ConstraintLayout布局。其中ViewStub按需加载,Merge减少一层。还有Litho也能够极致的减少View的层级。

二.Android CPU Profile

Android CPU Profile是手机开发者选项中的一个设置,打开手机的开发者选项,在”监控“一栏中会有一个”GPU呈现模式分析“,英文为”Profile GPU rendering“,根据这个工具我们能够大概看出来我们的视图是不是顿卡,如图:

其中不同的颜色区域代表不同执行阶段的执行的时间:

当绝大部分的柱状图都在图中2的那根绿线以下,表明这个视图的渲染速度在60帧/s以上,达到要求。当然柱状图越低越好。

三.dumpsys gfxinfo

dumpsys的命令:

adb shell dumpsys gfxinfo 包名
adb shell dumpsys gfxinfo 包名 framestats

一般通过这个命令可以知道大概的渲染情况,比如丢帧率,引起丢帧的原因和该原因导致的丢帧数量。
这个工具不仅能够统计关于渲染方面的信息,还能够统计其他模块的信息,从源码看:

 public void setSystemProcess() {try {ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);ServiceManager.addService("meminfo", new MemBinder(this));ServiceManager.addService("gfxinfo", new GraphicsBinder(this));ServiceManager.addService("dbinfo", new DbBinder(this));if (MONITOR_CPU_USAGE) {ServiceManager.addService("cpuinfo", new CpuBinder(this));}ServiceManager.addService("permission", new PermissionController(this));ServiceManager.addService("processinfo", new ProcessInfoService(this));ApplicationInfo info = mContext.getPackageManager().getApplicationInfo("android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());synchronized (this) {ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);app.persistent = true;app.pid = MY_PID;app.maxAdj = ProcessList.SYSTEM_ADJ;app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);synchronized (mPidsSelfLocked) {mPidsSelfLocked.put(app.pid, app);}updateLruProcessLocked(app, false, null);updateOomAdjLocked();}} catch (PackageManager.NameNotFoundException e) {throw new RuntimeException("Unable to find android system package", e);}}

dumpsys可以查看很多信息,详情可以查看Gityuan大佬的博客:dumpsys
如果需要看dumpsys命令的代码执行流程,可以参考我的另一篇:命令dumpsys之gfxinfo调用流程
在终端输入命令 adb shell dumpsys gfxinfo,得到输出结果中:
dumpsys gfxinfo的信息详解:

Graphics info for pid 31148 [com.android.settings]: 表明当前dump的为设置界面的帧信息,pid为31148
Total frames rendered: 105 本次dump搜集了105帧的信息
Janky frames: 2 (1.90%) 105帧中有2帧的耗时超过了16ms,卡顿概率为1.9%
Number Missed Vsync: 0 垂直同步失败的帧
Number High input latency: 0 处理input时间超时的帧数
Number Slow UI thread: 2 因UI线程上的工作导致超时的帧数
Number Slow bitmap uploads: 0 因bitmap的加载耗时的帧数
Number Slow issue draw commands: 1 因绘制导致耗时的帧数
HISTOGRAM: 5ms=78 6ms=16 7ms=4 … 直方图数据,表面耗时为0-5ms的帧数为78,耗时为5-6ms的帧数为16,同理类推。

图形化

对于dumpsys gfxinfo的两条指令
adb shell dumpsys gfxinfo 包名
adb shell dumpsys gfxinfo 包名 framestats
会在终端输出一些数据,一般是最近2秒内的数据(120帧),这些数据可以用来制作可视化的图标,下面来详细讲解一下:

(1)adb shell dumpsys gfxinfo 包名:

在终端会出现这样一串数据:
Draw Prepare Process Execute
4.27 0.18 1.58 1.14
3.21 0.16 1.37 1.24
2.28 0.17 1.59 1.13
1.70 0.18 1.51 1.03
2.17 0.16 1.33 1.28
2.68 0.18 1.33 2.02
1.95 0.17 1.37 1.02
6.68 0.17 1.37 0.98
3.39 8.13 1.59 1.20
2.09 0.15 1.88 1.02
… …
2.10 0.19 1.62 1.15
2.72 1.21 1.81 1.09
3.52 0.19 1.37 1.11
3.73 0.21 1.46 1.14
3.07 6.05 1.56 1.06
2.76 0.17 1.55 1.04
2.69 0.18 1.48 1.09
3.70 1.25 2.38 1.75
2.94 0.25 2.23 1.57
3.26 7.65 1.83 1.34
1.96 0.19 1.80 1.11
3.08 0.18 1.71 1.02
1.92 0.18 1.65 1.10
2.54 0.89 1.71 1.09
2.90 0.17 1.55 1.21
2.04 0.17 1.65 1.10
2.42 6.11 1.85 1.11
2.55 0.18 1.63 1.02
4.53 0.23 2.02 1.06
3.23 0.98 1.97 1.10
2.29 0.19 1.72 1.02
2.12 0.18 1.77 1.22
3.38 6.65 1.81 1.43

每一列给出了每一帧花在渲染上的时间估计:
Draw:是指Java层用在创建“display lists”(显示列表)上的时间。它表明运行例如View.onDraw(Canvas)需要多少时间。
Prepare:Bitmap上传到RenderThread的时间,如果是大图片,可以选择提前上传,Bitmap.prepareDraw()。由于draw方法后面,需要将未上传的Bitmap上传到GPU,因此调用这个方法提前上传到GPU,会减少一帧渲染所花的时间,这个后面也会讲到。
Process:是指Android 2D渲染引擎用在执行“display lists”上的时间。你的UI层级(hierarchy)中的View数量越多,需要执行的绘画命令就越多。
Execute:是指将一帧图像交给合成器(compositor)的时间。这部分占用的时间通常比较少。

关于“Execute”:
如果Excute花费很多时间,这就意味着你跑在了系统绘图流水线的前面。Android在运行状态时最多可以用3块缓存,如果此时你的应用还需要一块缓存,那应用就会被阻塞直到三块中的一块缓存被释放。这种情况的发生一般有两个原因。第一个原因是你的应用在Dalvik(java虚拟机)端画的太快,而在它的Display list在GPU端执行太慢。第二个原因是你的应用花费太多时间在前几帧的渲染上,一旦流水线满了,它就跟不上,直到动画的完成。这些是我们想在下一个版本的Android改进的地方。

图形化步骤和效果
  • 将数据复制到excel表中,点击“数据”-》“分列”-》“分隔符号”-》选择按空格分,就可以看到数据放到了不同的单元格中:
  • 选中所有数据包括文本,插入表格,选择“堆积柱状图”:

    这样数据的图形化就ok了,在UI绘制的时候,哪些步骤比较好使就一目了然了。
    注意:上面这个柱状图中,Prepare耗时比较多,是因为我的app中Bitmap比较大,那我们将app中的Bitmap在获取到数据后马上上载到GPU,看看是什么效果:

    可以到看Prepare阶段所耗费的时间就很少了,可见Bitmap.prepareDraw()的好处了。
(2)adb shell dumpsys gfxinfo 包名 framestats:

执行这条命令,除了上面的Draw、Prepare、Process、Execute的数据就以外,还多了一些更加详细的数据(PROFILEDATA):

—PROFILEDATA—
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
0,97486439714446,97486439714446,9223372036854775807,0,97486440576664,97486440612029,97486440615206,97486445094476,97486446370935,97486446594268,97486446848643,97486448986039,97486450130831,255000,436000,
… …
0,97488412103234,97488412103234,9223372036854775807,0,97488412742132,97488412779111,97488412782392,97488413639215,97488414567965,97488414635153,97488414814267,97488416990569,97488417966715,220000,324000,
0,97488428814928,97488428814928,9223372036854775807,0,97488429356507,97488429397601,97488429400882,97488434722288,97488435767497,97488435954372,97488436163747,97488438490622,97488439421507,203000,292000,
—PROFILEDATA—

不过这些数据与之前的(Draw、Prepare、Process、Execute)数据不同,这里的数据是对应的是时间戳,而不是对应的时间段长度(一定要注意,否则接下来绘制的柱状图就千差万别了)。

这里对这些数据进行解释:

Framestats数据格式

由于数据块以CSV格式输出,因此将其粘贴到我们选择的电子表格工具,或者使用脚本进行收集和解析非常简单。下面说明了输出数据列的格式。所有时间戳都以纳秒为单位(1纳秒=1e-6毫秒)。

1.FLAGS

如果flags为0,则此帧的总耗时时间 = FRAME_COMPLETED(第14列,帧的结束时间) -
INTENDED_VSYNC(第2列,帧的预期开始时间)。
如果flags不为0,则忽略该行,因为该帧的布局和绘制时间超过16ms,为异常帧。以下是可能发生的一些原因:
(1)窗口布局发生变化(例如app的第一帧或旋转后);
(2)帧被跳过也是有可能的,在这种情况下,某些值将具有垃圾时间戳。例如,如果帧超出60fps,或者屏幕上没有任何内容变脏,则可能跳过一个帧,这不一定是app中出现问题的迹象。

2.INTENDED_VSYNC

帧的预期开始时间。如果此值与VSYNC不同,则UI线程上发生了阻止其及时响应vsync信号的工作。

3.VSYNC

花费在vsync监听器和帧绘制的时间(Choreographer frame回调,动画,View.getDrawingTime()等)。
要了解VSYNC的更多信息及其对app的影响,请查看Understanding VSYNC 视频。

4.OLDEST_INPUT_EVENT

输入队列中最早输入事件的时间戳。如果此帧没有输入事件,则为Long.MAX_VALUE。 此值主要用于平台工作,对app开发人员的用处不大。

5.NEWEST_INPUT_EVENT

输入队列中最后输入事件的时间戳,如果此帧没有输入事件,则为0。 此值主要用于平台工作,对app开发人员的用处不大。
但是,通过计算FRAME_COMPLETED - NEWEST_INPUT_EVENT的值,可以大致了解app添加的延迟时间。

6.HANDLE_INPUT_START

将输入事件分派给app的时间戳。 通过计算ANIMATION_START -
HANDLE_INPUT_START的值,可以测量app处理输入事件所花费的时间。 如果它们的时间差很高(>
2ms),则表示app花费了非常长的时间处理输入事件,例如View.onTouchEvent(),这可能表示此工作需要优化,或者分发到其他线程。但是请注意,在某些情况下,例如发起新活动或类似活动的点击事件时,预计可接受的时间差是很大的。

7.ANIMATION_START

运行Choreographer注册动画的时间戳。 通过计算PERFORM_TRANVERSALS_START -
ANIMATION_START的值,可以得到评估正在运行的所有动画器(ObjectAnimator,ViewPropertyAnimator和常用转换器)所花费的时间。
如果它们的时间差很高(>
2ms),请检查您的app是否已编写了自定义动画或者设置了ObjectAnimators动画的字段,并确保它们适用于动画。
要了解Choreographer的更多信息,请查看 For Butter or Worse 视频。

8.PERFORM_TRAVERSALS_START

计算DRAW_START -
PERFORM_TRAVERSALS_START的值,可以得到完成布局和度量阶段所需的时间。(注意,在滚动或动画期间,你会希望它应该接近于零)
要了解有关渲染管道的度量和布局阶段的更多信息,请查看 Invalidations, Layouts and Performance 视频。

9.DRAW_START

performTraversals的绘制阶段开始的时间戳。这是录制任何无效视图的显示列表的起点。
此时间与SYNC_START之间的时间是在树中所有无效视图上调用View.draw()所需的时间。 有关绘图模型的更多信息,请查看
Hardware Acceleration 或者 Invalidations, Layouts and Performance 视频。

10.SYNC_QUEUED

将同步请求发送到RenderThread的时间。
这标志着开始同步阶段的消息被发送到RenderThread的时刻。如果此时间与SYNC_START之间的时间差很长(>
0.1ms左右),则表示RenderThread正忙于处理不同的帧。在内部,这用于区分执行太多工作以至于超过16ms预算的帧和由于前一帧超过16ms预算而导致被停止的帧。

11.SYNC_START

绘图同步阶段开始的时间。 如果此时间与ISSUE_DRAW_COMMANDS_START之间的时间很长(>
0.4ms左右),则通常表示已绘制了许多必须上传到GPU的新位图。 要了解有关同步阶段的更多信息,请查看 Profile GPU Rendering 视频。

12.ISSUE_DRAW_COMMANDS_START

硬件渲染器开始向GPU发出绘图命令的时间。 计算FRAME_COMPLETED -
ISSUE_DRAW_COMMANDS_START的值,可以大致了解app生成多少GPU工作。这里会出现很多过度绘制或低效的渲染效果等问题。

13.SWAP_BUFFERS

调用eglSwapBuffers的时间,在平台工作之外相对无用。

14.FRAME_COMPLETED

帧的结束时间戳。可以通过执行FRAME_COMPLETED - INTENDED_VSYNC来计算在此帧上工作的总时间。

15.DequeueBufferDuration和QueueBufferDuration

数据的分列跟与上面一样,只是这里分隔符是“,”。
这里我们一般只需要将下面一部分数据计算出来绘制到柱状图:

细心的同学会发现,下面这些计算出来的时间跟(第二种途径不同颜色对应不同阶段的执行时间的图)对应起来了,这样为了避免在手机上颜色分辨不出来,可以通过这种方式在Excel中绘制出柱状图来,看得更加清晰,且清楚的直到哪些时段耗时。

  • 其他时间&Vsync延迟时间(Other&Delay) = OLDEST_INPUT_EVENT - INTENDED_VSYNC
    如果这个时间比较长,表示此工作需要优化,或者分发到其他线程。
  • app处理输入事件所花费的时间(Input Event) = ANIMATION_START - HANDLE_INPUT_START
  • 正在运行的所有动画器所花费的时间(Animation) = PERFORM_TRANVERSALS_START - ANIMATION_START
  • 布局和度量阶段所花的时间(MeasureAndLayout) = DRAW_START - PERFORM_TRAVERSALS_START
  • View树中所有无效视图(调用了Invalidate())上调用View.draw()所花的时间(Draw) = SYNC_START - DRAW_START
  • 新位图上传到GPU所花的时间(Prepare) = ISSUE_DRAW_COMMANDS_START - SYNC_START
  • 硬件渲染器向GPU发出绘图命令的时间(Commands) = SWAP_BUFFERS - ISSUE_DRAW_COMMANDS_START
    这里会出现很多过度绘制或低效的渲染效果等问题。
  • 交换缓冲区的时间(Swap buffer) = FRAME_COMPLETED - SWAP_BUFFERS

我们将这些公式在Excel表格中实现,并绘制出柱状图:

看这个柱状图就能清晰的看出哪个阶段比较耗时,需要去优化。

四.Profile GPU Rendering

Profile GPU Rendering是Android studio中的一个工具,这个工具比较强大,可以分析CPU、Memory、Network、Energy。这里只讲解分析CPU来分析UI绘制的顿卡问题。通过这个工具可以分析出在某一个时间段中,函数调用占用的时间比例,如图:

如图有1,2,3,4四种方式来分析,我一般比较喜欢使用第二种,也就是大名鼎鼎的火焰图,图中的色块显示,函数调用流程是从下往上,色块区域一次减小,在图中箭头指向的地方就是我们app中的方法调用,其他的是系统函数调用或者是第三方函数调用。因此通过通过这个工具能够判断大概哪些函数处理比较耗时。

五.Systrace

对比:Profile GPU Rendering这个工具虽然非常强大,但是比较重量级,我们会发现使用它时,我们的app界面就会变得顿卡,会影响方法调用时间。而相比Systrace而言,Systrace非常轻量级,从而获取的时间比较准确,因此推荐使用Systrace。

Systrace这个工具真的是非常好用,通常使用 systrace 跟踪系统的 I/O 操作、CPU 负载、Surface 渲染、GC 等事件。它能够统计分析各个模块各个Trace.beginxxx与Trace.endxxx的时间。
我们在这里只讲解使用Systrace来分析UI渲染。

关于Systrace的使用:
python systrace.py [options] [category1] [category2] … [categoryN]

Systrace API的使用:

  • framework层:
    import android.os.Trace;
    Trace.traceBegin(long traceTag, String methodName)
    Trace.traceEnd(long traceTag)
  • app层:
    import android.os.Trace;
    Trace.beginSection(String sectionName)
    Trace.EndSection()
  • native层:
    #include<utils/Trace.h>
    ATRACE_CALL();

其中systrace.py的路径:/android-sdk/platform-tools/systrace/systrace.py
python需要是python2的版本

options 解释
option 解释
-o <FILE> 输出的目标文件
-t N, –time=N 执行时间,默认5s
-b N, –buf-size=N buffer大小(单位kB),用于限制trace总大小,默认无上限
-k <KFUNCS>,–ktrace=<KFUNCS> 追踪kernel函数,用逗号分隔
-a <APP_NAME>,–app=<APP_NAME> 追踪应用包名,用逗号分隔
–from-file=<FROM_FILE> 从文件中创建互动的systrace
-e <DEVICE_SERIAL>,–serial=<DEVICE_SERIAL> 指定设备
-l, –list-categories 列举可用的tags
category 解释
category 解释
gfx Graphics
input Input
view View System
webview WebView
wm Window Manager
am Activity Manager
sm Sync Manager
audio Audio
video Video
camera Camera
hal Hardware Modules
app Application
res Resource Loading
dalvik Dalvik VM
rs RenderScript
bionic Bionic C Library
power Power Management
sched CPU Scheduling
irq IRQ Events
freq CPU Frequency
idle CPU Idle
disk Disk I/O
mmc eMMC commands
load CPU Load
sync Synchronization
workq Kernel Workqueues
memreclaim Kernel Memory Reclaim
regulators Voltage and Current Regulators
图形化

将trace图形化,需要使用Google浏览器。
方法:

  • 直接通过右键,点击通过Google浏览器打开,这个方式有时候会出错;
  • 在Google浏览器中输入”chrome://tracing/“,会出现一个界面,如图:


点击load加载生成的trace.html文件,推荐使用这种方式,不会出错。
通过Google浏览器打开后的样子:

图中具体的菜单和标题你们自行百度或者查询Google官方,篇幅问题我这里就不详细讲了。

放大一帧:


可以看到UI Thread和ReaderThread中各个方法的执行顺序和时间,色块从上往下为函数调用流程,图中的有一个圆圈里面有个F,表示一帧,如果这个F是绿色说明当前帧的时间小于16ms,如果F是桔色,说明当前帧的时间大于16ms。

到这里,虽然能够看到如measure、layout、draw的时间片段,但是还是无法知道具体是哪个函数或者哪些函数执行耗时,这里就需要我们自己在代码里面加Trace.beginSection()和Trace.endSection(),注意这两句代码一定要成对出现,同一对一定要出现在同一个线程,一般将Trace.endSection()放在finally中。注意:这里如果需要在图形化中显示自己添加的标签,需要在命令行中添加 -a ,否则是看不到自己的标签的,其实通过Trace.beginSection()方法的源码也可以分析得来(以后添加这部分的源码分析)

命令示例:

python systrace.py -a xxx -b 16384 -o ./trace2020.html view input gfx一般-a要放在最前面,并且在AndroidManifest.xml中设置可调试android:debuggable="true",不然在浏览器中看不到自定义的label

那么到底需要在哪里加上这两句代码呢?其实上面的第四步就可以分析得出大概出现耗时的地方,然后根据自己的猜测去添加这两行代码,然后通过命令输出trace文件分析到底是不是这里耗时。

其中Trace.beginSection()的函数参数,作为一个字符串标志,会在图形化界面展示出来,如:Trace.beginSection(“addAndMeasureChild”);
如图:

可以看出Trace.beginSection()与Trace.endSection()之间执行的代码比较耗时,占用layout大部分时间,因此被包裹起来的代码就是我们需要优化的地方。

这里有必要给出一张图来展示UI Thread、ReaderThread、Graphics的执行顺序:

UIThread -> RenderThread -> Graphics
其实从Trace图形化中也可以看出这个顺序。

那么,我们经常需要去绘制Bitmap,绘制Bitmap比较耗时,有时候在RenderThread中占用大部分的时间,如图:

这会导致整个帧的耗时超过16ms,出现顿卡。
那么有什么办法可以解决这个问题呢?我们可以看到这个在UI Thread工作的时候,RenderThread是闲置的,那么是否可以利用起来呢?答案是肯定的,Bitmap在数据获取完成之后,可以通过Bitmap.prepareToDraw()将Bitmap上传到GPU中,不需要等待UIThread的draw步骤完成时再去上传,效果如图:

可以看到bitmap的上传确实提前了,那么通过上面的步骤二,你也会发现柱状图也降低了不少,蓝色区块减少了不少,这样就优化了帧时间。

Android UI性能优化相关推荐

  1. Android UI性能优化 检测应用中的UI卡顿

    本文已在我的公众号hongyangAndroid首发. 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/58626355 本文出自 ...

  2. Android UI性能优化详解

    此文来自于MrPeak杂货铺,由于没法转载,只能贴这了,妄作者见谅:http://mrpeak.cn/android/2016/01/11/android-performance-ui 设计师,开发人 ...

  3. Android 系统性能优化(34)---Android UI 性能优化

    Android官网 Slow rendering:个人觉得非常有价值,比如指出 对象分配.垃圾回收(GC).线程调度以及Binder调用 是Android系统中常见的卡顿原因,更重要的是给出了定位和解 ...

  4. Android UI性能优化——ViewStub和Merge的使用

    ViewStub的使用 简介 ViewStub 是一种没有任何维度的轻量型视图,它不会绘制任何内容或参与布局. ViewStub是一种没有大小,不占用布局的View. 直到当调用 inflate() ...

  5. android的UI性能优化

    设计师,开发人员,需求研究和测试都会影响到一个app最后的UI展示,所有人都很乐于去建议app应该怎么去展示UI.UI也是app和用户打交道的部分,直接对用户形成品牌意识,需要仔细的设计.无论你的ap ...

  6. android app性能优化大汇总(UI渲染性能优化)

    UI性能测试 性能优化都需要有一个目标,UI的性能优化也是一样.你可能会觉得"我的app加载很快"很重要,但我们还需要了解终端用户的期望,是否可以去量化这些期望呢?我们可以从人机交 ...

  7. [Android]ListView性能优化之视图缓存

    前言 ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题.本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流 ...

  8. Android APP性能优化

    转载自:https://www.cnblogs.com/qwangxiao/p/8727229.html Android APP性能优化(最新总结) 导语 安卓大军浩浩荡荡,发展已近十个年头,技术优化 ...

  9. Android APP性能优化(一)

    Android APP性能优化(最新总结) 安卓大军浩浩荡荡,发展已近十个年头,技术优化日异月新,如今Android 8.0 Oreo 都发布了,Android系统性能已经非常流畅了.但是,到了各大厂 ...

最新文章

  1. AI安防崛起迅速 开疆拓土少不了专利作“盔甲”
  2. postgresql 使用指南
  3. protobuf repeated类型的使用
  4. android 使用电脑画图软件输入文字
  5. ASP.NET MVC Pager Helper Extensions
  6. STM32之定时器中断控制LED闪烁
  7. Windows CE如何根据文件名获取其对应文件图标icon
  8. 梅特勒托利多xk3124电子秤说明书_托利多电子秤设置说明书1
  9. 商品期货日内 Dual Thrust 交易策略
  10. 机器学习和模式识别怎么区分?
  11. 聊聊Web 3为什么可以解决数据使用中存在的难题
  12. 部署Gbase 8c的系统要求
  13. 基于移动位置服务器,基于移动位置的服务系统及方法
  14. 星空云协同开发入门(一)
  15. vim autoformat php,Vim 之 vim-autoformat 自动切换使用本地或全局eslint
  16. 1187:统计字符数
  17. 【飞桨PaddleSpeech语音技术课程】— 语音合成
  18. 房产门户企业织梦模板/DedeCMS房地产楼盘网站模板下载
  19. C#-iBatis.NET使用小结
  20. 各大公司大数据面试题

热门文章

  1. 【Redis】Redis基础知识点
  2. ThreeJS第一人称视角处理
  3. OpenCV的CvMat与cvSolve函数
  4. NVME协议解读(三)
  5. 深入理解MySQL学习记录
  6. mate20por3d人脸识别_体验华为Mate20 Pro 3D结构光 生物识别新高度
  7. 山东大学面向对象编程导论复习提纲(包含各种名词解释)
  8. 数据库连接池设置多大才合适?
  9. 西米的最佳搭配 西米搭配什么最好吃
  10. 【AI文本工具站】日活近4万