Skip to content

Commit 2fe0e96

Browse files
committed
add tools.md
1 parent b81bc34 commit 2fe0e96

File tree

1 file changed

+98
-1
lines changed

1 file changed

+98
-1
lines changed

Android加强/性能优化相关工具.md

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ public void ProcessPeople() {
172172
3. 点击`Start Method Profiling`图标开始查看。
173173
4. 在查看完后点击`Stop Method Profiling`图标来显示`traceview`
174174

175+
大体的样子如下:
175176

177+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview_1.png?raw=true)
176178

177179
#####Traceview Layout
178180

@@ -181,20 +183,115 @@ public void ProcessPeople() {
181183
- `timeline panel`-展示了每个线程和方法的起始和结束
182184
- `profile panel`-提供了一个方法中的执行内容的简述
183185

186+
#####Timeline Panel
184187

188+
每个线程都会以时间从左往右递增的方式在单独的一行中显示它的执行情况,
185189

190+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview_timeline.png?raw=true)
186191

192+
#####Profile Panel
193+
194+
显示了一个方法所消耗的时间的概要情况。在表格中会同时显示`inclusive``exclusive`的时间。`Exclusive`的时间是该方法所消耗的时间。`InClusive`的时间是该方法消耗的时间加上任何调的方法所消耗的时间。我们简单的将调用的方法叫做`parents`,被调用的方法叫做`children`。如果一个方法被调用,它会显示对应的`parents``children``parents`会显示一个紫色的背景,`children`会显示一个黄色的背景。最后一列显示了该方法所调用的总数的调用数。在下面的图中我们能看到一共有14个地方调用了`LoadListener.nativeFinished()` .
195+
196+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview_profile.png?raw=true)
197+
198+
- Name 方法名
199+
- Inclusive CPU Time, CPU在处理该方法以及所有子方法(被它调用的所有方法)所消耗的时间。
200+
- Exlusive CPU Time, CPU在处理该方法的耗时。
201+
- Inclusive/Exclusive Real Time, 从方法开始执行到执行结束的耗时。
202+
- Cal+Rec, 这个方法被调用的次数,以及递归被调用的次数。
203+
- CPU/Real time per Call, 在处理这个方法时的`CPU`耗时的平均值。
204+
205+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview-getview.png?raw=true)
206+
207+
上图中`getView`方法被调用了12次,每次`CPU`消耗2.8秒,但是每次调用的总耗时是162秒,这里肯定有问题。
208+
而看看这个方法的children,我们可以看到这其中的每个方法在耗时方面是如何分布的。Thread.join()方法战局了98%的inclusive real time。这个方法在等待另一个线程结束的时候被调用。在Children中另外一个方法就是Tread.start()方法,而之所以整个方法耗时很长,我猜测是因为在getView()方法中启动了线程并且在等待它的结束。
209+
210+
但是这个线程在哪儿?
211+
212+
我们在getView()方法中并不能看到这个线程做了什么,因为这段逻辑不在getView()方法之中。于是我找到了Thread.run()方法,就是在线程被创建出来时候所运行的方法。而跟随这个方法一路向下,我找到了问题的元凶。
213+
214+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/traceview-thread.png?raw=true)
215+
216+
我发现了BgService.doWork()方法的每次调用花费了将近14毫秒,并且有四十个这东西!而且getView()中还有可能调用多次这个方法,这就解释了为什么getView()方法执行时间如此之长。这个方法让CPU长时间的保持在了繁忙状态。而看看Exclusive CPU time,我们可以看到他占据了80%的CPU时间!此外,根据Exclusive CPU time排序,可以帮我们更好的定位那些耗时很长的方法,而他们很有可能就是造成性能问题的罪魁祸首。
217+
218+
关注这些耗时方法,例如getView(),View#onDraw()等方法,可以很好的帮助我们寻找为什么应用运行缓慢的原因。但有些时候,还会有一些其他的东西来占用宝贵的CPU资源,而这些资源如果被运用在UI的绘制上,也许我们的应用会更加流畅。Garbage Collector垃圾回收机制会不时的运行,回收那些没用的对象,通常来讲这不会影响我们在前台运行的程序。但如果GC被运行的过于频繁,他同样可以影响我们应用的执行效率。而我们该如何知道回收的是否过于频繁了呢…
219+
220+
###Monitors
221+
222+
`Android Studio`中下方的`Android Monitor`中可以看到`Monitors`工具栏,它能不断的去检测内存、网络、CPU的消耗情况。
223+
224+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/monitor.png?raw=true)
225+
226+
我们可以直接点击`Dump Java Heap`或者`Call GC`等按钮,以便更好的去观察内存的使用情况。点击后会生成一个`.href`的文件。
227+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/dump_href.png?raw=true)
228+
在左边能看到所有堆内存中的实例。后面会显示他所占用的内存大小。
229+
对于内存泄漏的分析可以使用`MAT`或者`LeakCanary`来进行。这里就不仔细说了。
230+
231+
上面也显示了`GPU`的使用情况,这里要说一句,如果想要显示它,必须要在手机的开发者中心中开启`GPU显示配置文件`选项,将其设置为显示与`adb shell dumpsys gfxinfo`。然后再点击`Studio`中的按钮重新开始就可以看到了。 每一条线意味着一帧被绘制出来了。而线的颜色又代表不同的阶段:
232+
233+
- Draw(蓝色)代表着`View.onDraw()`方法。如果这个值很高就说明可能是该`View`比较复杂。 在这个环节会创建/刷新DisplayList中的对象,这些对象在后面会被转换成GPU可以明白的OpenGL命令。而这个值比较高可能是因为view比较复杂,需要更多的时间去创建他们的display list,或者是因为有太多的view在很短的时间内被创建。
234+
- Prepare(紫色),从`Android 6.0`开始,一个新的线程被引进来帮助`UI`线程进行绘制。 这个线程叫做`Render Thread`。它负责转换它负责转换display list到OpenGL命令并且送至GPU。在这过程中,UI线程可以继续开始处理后面的帧。而在UI线程将所有资源传递给RenderThread过程中所消耗的时间,就是紫色阶段所消耗的时间。如果在这过程中有很多的资源都要进行传递,display list会变得过多过于沉重,从而导致在这一阶段过长的耗时。
235+
236+
- Process(红色) 执行Display list中的内容并创建OpenGL命令。如果有过多或者过于复杂的display list需要执行的话,那么这阶段会消耗较长的时间,因为这样的话会有很多的view被重绘。而重绘往往发生在界面的刷新或是被移动出了被覆盖的区域。
237+
238+
- Execute (黄色) – 发送OpenGL命令到GPU。这个阶段是一个阻塞调用,因为CPU在这里只会发送一个含有一些OpenGL命令的缓冲区给GPU,并且等待GPU返回空的缓冲区以便再次传递下一帧的OpenGL命令。而这些缓冲区的总量是一定的,如果GPU太过于繁忙,那么CPU则会去等待下一个空缓冲区。所以,如果我们看到这一阶段耗时比较长,那可能是因为GPU过于繁忙的绘制UI,而造成这个的原因则可能是在短时间内绘制了过于复杂的view。
239+
240+
- Measure/Layout(绿色) 代表`Measure``Layout`的时间。
187241

188242
###Hierarchy Viewer
189243

244+
布局分析工具,非常常用。
245+
246+
`Android Device Monitor`中打开`Hierarchy Viewer`即可。
247+
248+
- 连接你的手机或者模拟器。
249+
出于安全性考虑,`Hierarchy Viewer`只能连接开发者版的系统的手机。
250+
- 运行程序,并且让界面显示出来。
251+
- 启动`hierarchy view`工具,接着就能看到左边栏显示出了对应的设备,展开后可以看到一些组件的名称。这个页面包含了应用的界面以及系统的界面。选择你的应用中想要查看的界面即可。
252+
253+
但是我并打不开。
254+
如果你的手机是`Android 4.1`及以上版本你必须要在你的电脑上设置一个`ANDROID_HVPROTO`DE 环境变量才可以。
255+
256+
- Windows
257+
增加一个名为`ANDROID_HVPROTO`值为`ddm`的环境变量就可以了。
258+
- Mac
259+
- 打开`.bash_profile`
260+
- `touch .bash_profile`创建
261+
- `open -e .bash_profile`打开
262+
- 添加
263+
```
264+
#Hierarchy Viewer Variable
265+
export ANDROID_HVPROTO=ddm
266+
267+
```
268+
- `source .bash_profile`
269+
270+
271+
###过度绘制
272+
273+
在开发者选项中将调试GPU过度绘制设置为显示过度绘制区域,就能看到程序的绘制情况。
274+
过度绘制往往发生在我们需要在一个东西上面绘制另外一个东西,例如在一个红色的背景上画一个黄色的按钮。那么GPU就需要先画出红色背景,再在他上面绘制黄色按钮,此时过度绘制就是不可避免的了。如果我们有太多层需要绘制,那么则会过度的占用GPU导致我们每帧消耗的时间超过16毫秒。
275+
这些过度绘制可能发生在我们给Activity或Fragment设置了全屏的背景,同时又给ListView以及ListView的条目设置了背景色。而通过只设置一次背景色即可解决这样的问题。
276+
277+
注意:默认的主题会为你指定一个默认的全屏背景色,如果你的activity又一个不透平的背景盖住了默认的背景色,那么你可以移除主题默认的背景色,这样也会移除一层的过度绘制。这可以通过配置主题配置或是通过代码的方法,在onCreate()方法中调用getWindow().setBackgroundDrawable(null)方法来实现。
278+
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/overdraw-gif.gif?raw=true)
279+
280+
越红说明绘制
190281
191-
###GPU Profiling
192282
193283
194284
###Hardware Acceleration
195285
286+
在Honeycomb版本中引入了硬件加速(Hardware Accleration)后,我们的应用在绘制的时候就有了全新的绘制模型。它引入了DisplayList结构,用来记录View的绘制命令,以便更快的进行渲染。但还有一些很好的功能开发者们往往会忽略或者使用不当——View layers。
287+
288+
使用View layers(硬件层),我们可以将view渲染入一个非屏幕区域缓冲区(off-screen buffer,前面透明度部分提到过),并且根据我们的需求来操控它。这个功能主要是针对动画,因为它能让复杂的动画效果更加的流畅。而不使用硬件层的话,View会在动画属性(例如coordinate, scale, alpha值等)改变之后进行一次刷新。而对于相对复杂的view,这一次刷新又会连带它所有的子view进行刷新,并各自重新绘制,相当的耗费性能。使用View layers,通过调用硬件层,GPU直接为我们的view创建一个结构,并且不会造成view的刷新。而我们可以在避免刷新的情况下对这个结构进行进行很多种的操作,例如x/y位置变换,旋转,透明度等等。总之,这意味着我们可以对一个让一个复杂view执行动画的同时,又不会刷新!这会让动画看起来更加的流畅。下面这段代码我们该如何操作:
289+
290+
在阅读了ViewPager的源码后,我发现了在滑动的时候会自动为左右两页启动一个硬件层,并且在滑动结束后移除掉。
196291
292+
在两页间滑动的时候创建硬件层也是可以理解的,但对我来说小有不幸。通常来讲加入硬件层是为了让ViewPager的滑动更加流畅,毕竟它们相对复杂。
197293
294+
有关如何使用`Hardware Layer`请参考之前写的文章:[通过Hardware Layer提高动画性能](https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%8A%A0%E5%BC%BA/%E9%80%9A%E8%BF%87Hardware%20Layer%E6%8F%90%E9%AB%98%E5%8A%A8%E7%94%BB%E6%80%A7%E8%83%BD.md)
198295
199296
200297

0 commit comments

Comments
 (0)