Android开发性能优化总结

安卓开发应用首先要讲究良好的用户体验,如果一款软件卡顿现象严重,不流畅,经常崩溃,那么将给用户带来极不良好的体验,从而损失用户。 
在实际开发和学习中,我总结了一下关于安卓性能的优化,供大家参考交流。

应用程序的性能问题体现在很多方面, 比如第一次启动速度慢,或者进入某一界面速度慢;动画执行过程不流畅,或者动画执行卡顿时间长;ListView列表滑动过程中卡顿,不流畅;应用程序自定义的某特定界面执行速度慢;响应某一用户事件时长时间无响应(ANR);操作数据库时,执行大量数据的增删改查操作,执行速度慢;文件读写频繁,缓存文件过大导致卡顿;应用长时间运行后,随机出现卡顿现象。

以上的问题的原因可能不只一个,并且很多情况下并不是应用本身的问题,也有可能是系统其他层次有问题,只不过体现在应用层。所以开发人员在处理性能问题时,需要做的第一件事情就是判断是否是应用自身引起的性能问题,然后再对症下药;但有些时候应用本身逻辑正常,但由于系统的硬件配置不足引起了异常,此时就要根据产品或项目需求,采取一些更加精准的方式优化性能,以弥补硬件配置的不足。

以下从几个不同的角度总结一下应用程序性能优化的一些方法。

一、编程思想

应用层的性能优化通常可以从以下几个方面考虑: 
1. 了解编程语言的编译原理,使用高效编码方式从语法上提高程序性能; 
2. 采用合理的数据结构和算法提高程序性能,这往往是决定程序性能的关键; 
3. 重视界面布局优化; 
4. 采用多线程、缓存数据、延迟加载、提前加载等手段,解决严重的性能瓶颈; 
5. 合理配置虚拟机堆内存使用上限和使用率,减少垃圾回收频率; 
6. 合理使用native代码; 
7. 合理配置数据库缓存类型和优化SQL语句加快读取速度,使用事务加快写入速度; 
7. 使用工具分析性能问题,找出性能瓶颈; 
当然肯定还有很多其他的性能优化方法,此处仅列出一些经常会用到的方法。

二、编程技巧 
(一)Performance Tips (For Java) 
Google官网上有一些关于应用程序性能提升的技巧,之前公司内也有很多总结提到过,在此简单罗列一下,详细内容可以从官网获取。 
http://developer.android.com/training/articles/perf-tips.html 
需要说明的是,文章列出的优化技巧主要是一些微小的性能提升,决定程序整体性能的仍然取决于程序的业务逻辑设计、代码的数据结构和算法。研发人员需要将这些优化技巧应用到平时的编码过程中,积少成多,也会对性能有很大的影响。 
写出高效的代码需要遵循两条原则: 
不执行不必要的操作; 
不分配不必要的内存; 
两条原则分别针对CPU和内存,完成必要操作的前提下尽可能的节省CPU和内存资源,自然执行效率要高。单纯这样说听起来很虚,毕竟没有一个统一的标准判断什么是必要和不必要的,需要结合具体情况具体分析了。 
1. 避免创建不必要的对象 
创建太多的对象会造成性能低下,这谁都知道,可是为什么呢?首先分配内存本身需要时间,其次虚拟机运行时堆内存使用量是有上限的,当使用量到达一定程度时会触发垃圾回收,垃圾回收会使得线程甚至是整个进程暂停运行。可想而知,如果有对象频繁的创建和销毁,或者内存使用率很高,就会造成应用程序严重卡顿。 
2.合理使用static成员 
主要有三点需要掌握: 
如果一个方法不需要操作运行时的动态变量和方法,那么可以将方法设置为static的。 
常量字段要声明为“static final”,因为这样常量会被存放在dex文件的静态字段初始化器中被直接访问,否则在运行时需要通过编译时自动生成的一些函数来初始化。此规则只对基本类型和String类型有效。 
不要将视图控件声明为static,因为View对象会引用Activity对象,当Activity退出时其对象本身无法被销毁,会造成内存溢出。 
3. 避免内部的Getters/Setters 
面向对象设计中,字段访问使用Getters/Setters通常是一个好的原则,但是在Android开发中限于硬件条件,除非字段需要被公开访问,否则如果只是有限范围内的内部访问(例如包内访问)则不建议使用Getters/Setters。在开启JIT时,直接访问的速度比间接访问要快7倍。 
4. 使用增强for循环 
优先使用增强for循环通常情况下会获得更高的效率;除了一种情况,即对ArrayList进行遍历时,使用普通的for循环效率要更高。 
5. 使用package代替private以便私有内部类高效访问外部类成员 
   私有内部类的方法访问外部类的私有成员变量和方法,在语法上是正确的,但是虚拟机在运行时并不是直接访问的,而是在编译时会在外部类中自动生成一些包级别的静态方法,执行时内部类会调用这些静态方法来访问外部类的私有成员。这样的话就多了一层方法调用,性能有所损耗。 
一种解决这个问题的方法就是将外部类的私有成员改为包级别的,这样内部类就可以直接访问,当然前提是设计上可接受。 
6.合理使用浮点类型 
   在Android设备中浮点型大概比整型数据处理速度慢两倍,所以如果整型可以解决的问题就不要用浮点型。 
另外,一些处理器有硬件乘法但是没有除法,这种情况下除法和取模运算是用软件实现的。为了提高效率,在写运算式时可以考虑将一些除法操作直接改写为乘法实现,例如将“x / 2”改写为“x * 0.5”。 
7.采用<merge>优化布局层数。 采用<include>来共享布局。 
8.延时加载View. 采用ViewStub 避免一些不经常的视图长期被引用,占用内存. 
9.移除Activity默认背景,提升activity加载速度。 
   如果确信在Activity中使用不透明的背景,那么可以移除Activity的默认背景。 
在代码中:getWindow().setBackgroundDrawable(null); 
也可以在styles样式文件中设置并在Manifest文件中配置

 <style name="MyStyle" parent="AppTheme">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@null</item>  
 </style>
  • 1
  • 2
  • 3
  • 4

10.cursor 的使用。 
要注意管理好cursor,不要每次打开关闭cursor.因为打开关闭Cursor非常耗时。 
不再使用的cursor要记得关闭(一般在finally语句块执行)。 
有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。

protected void onDestroy() { 
      if (mAdapter != null && mAdapter.getCurosr() != null) { 
          mAdapter.getCursor().close(); 
      } 
      super.onDestroy(); 
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

11.广播BroadCast动态注册时,记得要在调用者生命周期结束时unregisterReceiver,防止内存泄漏。 
12.针对ListView的性能优化 
item尽可能的减少使用的控件和布局的层次;背景色与cacheColorHint设置相同颜色;ListView中item的布局至关重要,必须尽可能的减少使用的控件,布局。RelativeLayout是绝对的利器,通过它可以减少布局的层次。同时要尽可能的复用控件,这样可以减少ListView的内存使用,减少滑动时GC次数。ListView的背景色与cacheColorHint设置相同颜色,可以提高滑动时的渲染性能。ListView中getView是性能是关键,这里要尽可能的优化。getView方法中要重用view;getView方法中不能做复杂的逻辑计算,特别是数据库操作,否则会严重影响滑动时的性能;ListView数据项较多时考虑分页加载。 
13.注意使用线程的同步机制(synchronized),防止多个线程同时访问一个对象时发生异常。 
14.合理使用StringBuffer,StringBuilder,String 
在简单的字符串拼接中,String的效率是最高的,例如String s = “hello” + “world”; 
但大家这里要注意的是,如果你的字符串是来自另外的String对象的话,速度就没那么快了,例如: 
    String str2 = “This is”; 
    String str3 = “ a ”; 
    String str4 = “ test”; 
    String str1 = str2 +str3 + str4; 
这里就要求使用StringBuilder了 
在单线程中,StringBuilder的性能要比StringBuffer高。多线程为了线程安全需要采用StringBuffer,因为它是同步的。常规下一般用StringBuilder。 
15. 尽量使用局部变量 
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化。 
16.I/O流操作记得及时关闭流对象。 
17.使用IntentService代替Service 
IntentService和Service都是一个服务,区别在于IntentService使用队列的方式将请求的Intent加入队列,然后开启一个worker thread(线程)来处理队列中的Intent(在onHandleIntent方法中),对于异步的startService请求,IntentService会处理完成一个之后再处理第二个,每一个请求都会在一个单独的worker thread中处理,不会阻塞应用程序的主线程,如果有耗时的操作与其在Service里面开启新线程还不如使用IntentService来处理耗时操作。 
18.使用Application Context代替Activity中的Context 
不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的 
对于生命周期长的对象,可以使用Application Context 
不要把Context对象设置为静态。 
19.集合中的对象要及时清理 
 我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。 
20.Bitmap的使用 
较大的Bitmap注意压缩后再使用,加载高清大图可以考虑BitmapRegionDecoder的使用, 
不再使用的Bitmap注意及时recycle().

21.巧妙的运用软引用(SoftRefrence) 
 有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。有关Java引用机制的介绍可以看我的另一篇博客:http://blog.csdn.net/gs12software/article/details/51051813 
22.尽量不要使用整张的大图作为资源文件,尽量使用9path图片 
应用图标优先放在mipmap目录下(AndroidStudio环境),其他资源图,.9图应该放在drawable-xxxx下,需要复制到手机sd卡上使用的应放在asset目录 
23.了解并使用库函数 
Java标准库和Android Framework中包含了大量高效且健壮的库函数,很多函数还采用了native实现,通常情况下比我们用Java实现同样功能的代码的效率要高很多。所以善于使用系统库函数可以节省开发时间,并且也不容易出错。 
24.关于WebView 
在Activity或者Fragment销毁时记得把WebView也销毁

@Override
    protected void onDestroy() {
        if (webView!= null) {
            webView.destroy();
            webView= null;
        }
        super.onDestroy();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8


三、安卓UI性能检测与优化

UI是安卓应用程序与用户打交道的最直接途径,UI设计的好不好,直接影响到用户的体验,如果没有达到他们心目中的自然流畅细节,用户要是能够感觉出来,少则影响心情,多则卸载应用;所以一个应用的UI显示性能问题就不得不被开发人员重视。

1.UI卡顿常见原因:

  1. 在UI线程中做了耗时操作,导致UI线程卡顿;

  2. 布局Layout过于复杂,无法在16ms内完成渲染;

  3. 同一时间动画执行的次数过多,导致CPU或GPU负载过重;

  4. View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;

  5. View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;

  6. 内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;

  7. 冗余资源及逻辑等导致加载和执行缓慢;

2.UI性能分析

2.1使用HierarchyViewer分析UI性能

Android SDK提供了一个工具HierarchyViewer,可以用来分析UI布局复杂程度及冗余等

在\sdk\tools\目录下有个hierarchyviewer.bat,双击可启动(如下图),注意,启动前你需要启动你的模拟器或者接入你的手机进入调试模式

(1)选中其中一个item,点击Load View Hierarchy,会出现如下图:


上图显示了我的应用程序中MainActivity的View树,通过这个树可以分析出View嵌套的冗余层级,左下角可以输入View的id直接自动跳转到中间显示;Save as PNG用来把左侧树保存为一张图片;Capture Layers用来保存为psd的PhotoShop素材;右上角为View的总体框架图;右侧居中显示的是选中View的当前属性和状态;右下角显示当前View在Activity中的位置以及模块分布等;左下角三个进行切换;Load View Hierarchy用来手动刷新变化(在页面变更时要手动刷新)。


上图可以很方便的查看到当前View的许多信息;上图最底那三个彩色圆点代表了当前View的性能指标,从左到右依次代表测量、布局、绘制的渲染时间,红色和黄色的点代表速度渲染较慢的View(当然了,有些时候较慢不代表有问题,譬如ViewGroup子节点越多、结构越复杂,性能就越差)。

在自定义View的性能调试时,可以使用HierarchyViewer上面的invalidate Layout和requestLayout按钮,它可以帮助我们调试自定义View执行invalidate()和requestLayout()过程,我们只需要在代码的相关地方打上断点就行了,接下来通过它观察绘制即可。

2.2使用Android Lint进行资源和布局的优化

Android Lint是SDK Tools 16 (ADT 16)之后才引入的工具,通过它对Android工程源代码进行扫描和检查,可发现潜在的问题,以便程序员及早修正这个问题。也就是说,它可以用来检查你的代码是否合法,当然,它也可以用来检测UI布局和资源文件的冗余性。

(1)在Android Studio中使用Lint:

将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->OK  出现如下图:


如上,它提示我们,控件的显示的字符串最好使用@String的形式,当前布局中有个RelativeLayout冗余了,应该去掉。

(2)在Eclipse中,则提供了一个全局的lint检查,可以在Window->Show View->other->Lint Warnings调出lint检查视图(一般都默认出现在控制台区域),Lint Warnings如下图


它也给了我们相应的提示和问题出现的位置,大家可以自行尝试。另外,Eclipse在编译导出APK时,也会执行lint检查,这时的lint检查将更加详细,你能看到更多的信息。

2.3 使用Traceview进行分析优化

关于UI卡顿问题我们还可以通过运行Traceview工具进行分析,它是一个分析器,记录了应用程序中每个函数的执行时间;我们可以打开DDMS然后选择一个进程,接着点击上面的“Start Method Profiling”按钮(红色小点变为黑色即开始运行),然后操作我们的卡顿UI(小范围测试,所以操作最好不要超过5s),完事再点一下刚才按的那个按钮,稍等片刻即可出现下图,如下:


整个界面上面是你测试的进程中每个线程运行的时间线,下面是每个方法执行的各个指标的值。通过上图的时间面板可以直观发现,整个trace时间段main线程做的事情特别多,其他的做的相对较少。当我们选择上面的一个线程后可以发现下面列出了该方法的Parents和Children,它主要展示了线程中各个方法的调用信息(CPU使用时间、调用次数等),这些信息就是我们分析UI性能卡顿的核心关注点,所以我们先看几个重要的属性说明,如下:

属性名含义
name线程中调运的方法名;
Incl CPU Time当前方法(包含内部调运的子方法)执行占用的CPU时间;
Excl CPU Time当前方法(不包含内部调运的子方法)执行占用的CPU时间;
Incl Real Time当前方法(包含内部调运的子方法)执行的真实时间,ms单位;
Excl Real Time当前方法(不包含内部调运的子方法)执行的真实时间,ms单位;
Calls+Recur Calls/Total当前方法被调运的次数及递归调运占总调运次数百分比;
CPU Time/Call当前方法调运CPU时间与调运次数比,即当前方法平均执行CPU耗时时间;
Real Time/Call当前方法调运真实时间与调运次数比,即当前方法平均执行真实耗时时间;(重点关注)


有了对上面Traceview图表的一个认识之后我们就来看看具体导致UI性能后该如何切入分析,一般Traceview可以定位两类性能问题:

  • 方法调运一次需要耗费很长时间导致卡顿;

  • 方法调运一次耗时不长,但被频繁调运导致累计时长卡顿。

譬如有时候我们写完App在使用时不觉得有啥大的影响,但是当我们启动完App后静止在那却十分费电或者导致设备发热,这种情况我们就可以打开Traceview然后按照Cpu Time/Call或者Real Time/Call进行降序排列,然后打开可疑的方法及其child进行分析查看,然后再回到代码定位检查逻辑优化即可。

2.4  使用Systrace进行分析优化

Systrace它是对整个系统进行分析(同一时间轴包含应用及SurfaceFlinger、WindowManagerService等模块、服务运行信息),不过这个工具需要你的设备内核支持trace(命令行检查:/sys/kernel/debug/tracing)且设备是eng或userdebug版本才可以,所以使用前麻烦自己确认一下。

打开DDMS->点击上面工具栏的Capture system wide trace using Android systrace->设置时间与选项点击OK就开始了抓取,接着操作APP,完事生成一个trace.html文件,用Chrome打开即可如下图:

 
在浏览器中浏览分析该文件我们可以通过键盘的W-A-S-D键来放大缩小平移视图,由于上面我们在进行trace时选择了一些选项,所以上图生成了左上方相关的CPU频率、负载、状态等信息,其中的CPU N代表了CPU核数,每个CPU行的柱状图表代表了当前时间段当前核上的运行信息;下面我们再来看看SurfaceFlinger的解释,如下:

这里写图片描述

可以看见上面左边栏的SurfaceFlinger其实就是负责绘制Android程序UI的服务,所以SurfaceFlinger能反应出整体绘制情况,可以关注上图VSYNC-app一行可以发现前5s多基本都能够达到16ms刷新间隔,5s多开始到7s多大于了15ms,说明此时存在绘制丢帧卡顿;同时可以发现surfaceflinger一行明显存在类似不规律间隔,这是因为有的地方是不需要重新渲染UI,所以有大范围不规律,有的是因为阻塞导致不规律,明显可以发现0到4s间大多是不需要渲染,而5s以后大多是阻塞导致;对应这个时间点我们放大可以看到每个部分所使用的时间和正在执行的任务,具体如下: 
这里写图片描述

可以发现具体的执行明显存在超时性能卡顿(原点不是绿色的基本都代表存在一定问题,下面和右侧都会提示你选择的帧相关详细信息或者alert信息),但是遗憾的是通过Systrace只能大体上发现是否存在性能问题,具体问题还需要通过Traceview或者代码中嵌入Trace工具类等去继续详细分析。

2.5 使用traces.txt文件分析ANR

ANR(Application Not Responding)是Android中应用响应超时的表现;ANR是直接卡死UI不动且必须要解掉的Bug,我们必须尽量在开发时避免它的出现。

我们应用开发中常见的ANR主要有如下几类:

  • 按键触摸事件派发超时ANR,一般阈值为5s(设置中开启ANR弹窗,默认有事件派发才会触发弹框ANR);

  • 广播阻塞ANR,一般阈值为10s(设置中开启ANR弹窗,默认不弹框,只有log提示);

  • 服务超时ANR,一般阈值为20s(设置中开启ANR弹窗,默认不弹框,只有log提示);

当ANR发生时除了logcat可以看见的log以外我们还可以在系统指定目录下找到traces文件或dropbox文件进行分析,

然后我们用txt编辑器打开可以发现如下结构分析:

//显示进程id、ANR发生时间点、ANR发生进程包名

----- pid 19073 at 2015-10-08 17:24:38 -----

Cmd line: com.example.yanbo.myapplication

//一些GC等object信息,通常可以忽略

......

//ANR方法堆栈打印信息!重点!

DALVIK THREADS (18):

"main" prio=5 tid=1 Sleeping

  | group="main" sCount=1 dsCount=0 obj=0x7497dfb8 self=0x7f9d09a000

  | sysTid=19073 nice=0 cgrp=default sched=0/0 handle=0x7fa106c0a8

  | state=S schedstat=( 125271779 68162762 280 ) utm=11 stm=1 core=0 HZ=100

  | stack=0x7fe90d3000-0x7fe90d5000 stackSize=8MB

  | held mutexes=

  at java.lang.Thread.sleep!(Native method)

  - sleeping on <0x0a2ae345> (a java.lang.Object)

  at java.lang.Thread.sleep(Thread.java:1031)

  - locked <0x0a2ae345> (a java.lang.Object)

//真正导致ANR的问题点,可以发现是onClick中有sleep导致。我们平时可以类比分析即可,这里不详细说明。

  at java.lang.Thread.sleep(Thread.java:985)

  at com.example.yanbo.myapplication.MainActivity$1.onClick(MainActivity.java:21)

  at android.view.View.performClick(View.java:4908)

  at android.view.View$PerformClick.run(View.java:20389)

  at android.os.Handler.handleCallback(Handler.java:815)

  at android.os.Handler.dispatchMessage(Handler.java:104)

  at android.os.Looper.loop(Looper.java:194)

  at android.app.ActivityThread.main(ActivityThread.java:5743)

  at java.lang.reflect.Method.invoke!(Native method)

  at java.lang.reflect.Method.invoke(Method.java:372)

  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988)

  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)

......

//省略一些不常关注堆栈打印

......

2.6 UI性能优化总结

  • 布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局,尽量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自定义View来取代,减少measure与layout次数等。

  • 列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。

  • 背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。

  • 自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。

  • 避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次操作数据库操作等。

四、内存性能检测优化

每个Android应用程序都执行在自己的虚拟机中,那了解Java的一定明白,每个虚拟机必定会有堆内存阈值限制(值得一提的是这个阈值一般都由厂商依据硬件配置及设备特性自己设定,没有统一标准,可以为64M,也可以为128M等;它的配置是在Android的属性系统的/system/build.prop中配置dalvik.vm.heapsize=128m即可,若存在dalvik.vm.heapstartsize则表示初始申请大小),也即一个应用进程同时存在的对象必须小于阈值规定的内存大小才可以正常运行。

接着我们运行的App在自己的虚拟机中内存管理基本就是遵循Java的内存管理机制了,系统在特定的情况下主动进行垃圾回收。但是要注意的一点就是在Android系统中执行垃圾回收(GC)操作时所有线程(包含UI线程)都必须暂停,等垃圾回收操作完成之后其他线程才能继续运行。这些GC垃圾回收一般都会有明显的log打印出回收类型,常见的如下:

  • GC_MALLOC——内存分配失败时触发;

  • GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发;

  • GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ;

  • GC_EXTERNAL_ALLOC——外部内存分配失败时触发;

1. Android内存泄露检测

众所周知,在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用,那就会导致内存泄漏;这样的后果就是随着我们的应用被长时间使用,他所占用的内存越来越大。

(1)AndroidStudio中有个Memory窗口如下(在Debug模式下查看内存波动),这里就不详细说明了:


(2)DDMS-Heap内存监测工具,窗口如下


选择工具栏中的update heap既可以在Heap视图中看到相关内存分配和GC信息

(3)使用MAT内存检测工具

一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具

这个工具是非常专业级的工具,很多大神使用,但它过于庞大和复杂,这里不再赘述,有兴趣的可以自行搜索

(4)使用LeakCanary内存泄漏检测工具

关于LeakCanary的介绍可以查看我的另一篇博客:LeakCanary检测安卓内存泄漏


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值