Android Performance Patterns: Rescue tips

英文地址:https://medium.com/@laanayabdrzak/android-performance-patterns-rescue-tips-8c1e4c7cb1f0#.tpvyr67vb

Apps nowadays are all about fancy animations, complex transitions andcustom views, and the user experience must be intuitive and similar as possible in any device. These patterns gonna help you build an app that’s smooth, responsive, and uses as little battery as possible, it covers micro-optimizations that can improve overall app performance.

Patterns to avoid bad performance

  • Avoid blocking main Thread
  • Avoid unnecessary invalidations that may trigger a cascade of invalidations
  • Avoid Nested weights in LinearLayouts ( Cz each child needs to be measured twice)
  • Avoid Customs views not properly made
  • Avoid Creating Unnecessary Objects
  • Use Static Final For Constants (static 15%-20% faster)
  • Primitives (Integer vs Float 2x slower)
  • Avoid Internal Getters/Setters (direct field access 3x faster)
  • Use Enhanced For Loop Syntax
  • Consider Package Instead of Private Access with Private Inner Classes
  • Use Native Methods Carefully

Patterns for Custom views

  • Keep It as Simple as Possible (KISS)
  • Use merge tag as root in your layout (avoid extra ViewGroups on inflation)
  • include tag (easy code reuse of common layouts)
  • Avoid unnecessary layouts
  • Don’t make allocations or heavy operations in OnDraw
  • Remove unnecessary calls to invalidate()
  • Consider creating your own ViewGroup
  • RecyclerView (replaces ListView and GridView)

Patterns to avoid memory churn

  • Don’t allocate a large amount of unnecessary objects:
  1. Immutable classes: String
  2. Autoboxing: Integer, Boolean..
  • Consider using Object Pools and caches to reduce churn
  • Be mindful of the overhead of enums (a single reference to an enum constant occupy 4 bytes)

Patterns to avoid memory leaks

  • Don’t leak contexts in inner classes
  • Don’t leak views inside activity
  • Favor static inner classes over non static
  • Don’t use WeakHashmap as cache. Only the keys areWeakReference

Patterns for CPU

  • Do not nest multi-pass layouts
  • Lazily compute complex data when needed
  • Cache heavy computational results for re-use
  • Consider RenderScript for performance
  • Keep work off of the main thread

Patterns to avoid Overdraw

  • Simplify your drawables
  • Use nine patch with transparent parts
  • Caution with setting alpha in your views
  • Remove unused background from your views

Patterns for Bitmaps

  • Decode bitmaps to the desire size: BitmapFactory.Options(inSampleSize, inDensity, inTargetDensity)
  • Load bitmaps in memory at the dimensions is going to be displayed
  • Don’t scale if you don’t need to (createScaledBitmap(btimap, int, int))
  • Use LRU cache

Patterns for Services

  • Do not keep running service unless it’s actively performing a job. Also be careful to stopservice it when its work is done
  • System prefers to always keep the service process in running. Then the RAM used by the service can’t be used by anything else or paged out
  • The best way to limit the lifespan of your service is to use anIntentService, which finishes itself as soon as it’s done handling the intent that started it
  • Leaving a service running when it’s not needed is one of the worst memory management mistakes an Android app can make

Patterns for Threads

  • In a Thread run() method use Process.setThreadPriority() withTHREAD_PRIORITY_BACKGROUND. This approach reduces resource competition between the Runnable object’s thread and the UI thread.
  • If you don’t set the thread to a lower priority this way, then the thread could still slow down your app because it operates at the same priorityas the UI thread by default.
  • Stores the current Thread reference in your app, so that you want to interrupt the Thread later on. e.g: On network failure you can cancel that thread operation.

Patterns to avoid ANRs

  • On UI thread do as little work as possible
  • If your app is doing work in the background in response to user input, show that progress is being made (such as with a ProgressBar in your UI).
  • Use performance tools such as Systrace and Traceview to determinebottlenecks in your app’s responsiveness.
  • If your application has a time-consuming initial setup phase, consider showing a launch screen or rendering the main view as quickly as possible, indicate that loading is in progress and fill the informationasynchronously.

Android Development : Some of the best practices

英文链接:https://medium.com/@laanayabdrzak/android-development-some-of-the-best-practices-27722c685b6a#.5balzou11

Hi! After a modest number of projects I’ve decided to share with you some of the things that experience made me learn, I mean the hard way.

Here is the selection:

  • Think twice before adding any third party library, it’s really a serious commitment
  • Don’t use a database unless you really need to
  • You can think about realm it’s really awesome!
  • Hitting the 65k method count mark is gonna happen fast, I mean really fast! And Multidexing can save you
  • RxJava is the best alternative to AsyncTasks and so much more
  • Retrofit is the best networking library there is
  • Don’t write your own HTTP client, use Volley or OkHttp libraries
  • Shorten your code with RetroLambda
  • Combine RxJava with Retrofit and RetroLambda for maximum awesomeness!
  • I use event Bus and it’s great, but I don’t use it too much because the codebase would get really messy
  • Package by Feature, not layers
  • Move everything off the application thread, that’s can save you fromANR
  • lint your views to help you optimize the layouts and layout hierarchies so you can identify redundant views that could perhaps be removed
  • Use Gradle and its recommended project structure
  • Put passwords and sensitive data in gradle.properties
  • Use styles to avoid duplicate attributes in layout XMLs
  • Do not make a deep hierarchy of ViewGroups
  • Monitor power source and battery more data updates while charging? Suspend updates when battery is low?
  • You can think about JobScheduler
  • onLowMemory() it will be called when the whole System runs low on memory, not your App, so you can’t exactly avoid OOMs with it.
  • Drain battery is 30% (image, animation, …), and 70% (Analytics, ads, maps, gps)
  • Monitor connectivity and type of connection (more data updates while on wifi?)
  • Use the Account Manager to suggest login usernames and email addresses
  • Give your methods a clear name for what they are going to do
  • The launch screen is a user’s first experience of your application
  • Do not show the launch screen if you don’t have to
  • Tests are great for performance: Write slow (but correct) implementation then verify optimizations don’t break anything with tests
  • Keep your colors.xml short and DRY, just define the palette
  • Also keep dimens.xml DRY, define generic constants
  • In reality perfExternal is rarely used as an application on theexternal storage is stopped once the device is connected to a computer and mounted as USB storage
  • Use StringBuffer or Stringbuilder classes when there is a lot of modifications to string of characters
  • To avoid Memory Leaks :

1. Don’t reference View inside AsyncCallback

2. Don’t reference View from static object

3. Avoid putting views inside collection that’s don’t have clear memory pattern, you can use WeakHasMap

  • FlatBuffers is an efficient cross platform serialization library, so use it
  • Serializable it’s simple to implement, but in term of performance it’s really bad 🙁

Crash率从2.2%降至0.2%,这个团队是怎么做到的?

转载自腾讯优测:

小优有话说:

App Crash就像地雷。

你怕它,想当它不存在。无异于让你的用户去探雷,一旦引爆,用户就没了。

你鼓起勇气去扫雷,它却神龙见首不见尾。

你告诫自己一定开发过程中减少crash,少埋点地雷,但总是不得其法。

降低Crash率,需要的是技巧、工具、耐心与时间。

本文由腾讯天天P图测试团队现身说法,为你讲述他们将Crash率直降90%背后的故事,希望能为你“排雷”提供一些思路。

PS:以后每周四记得关注这里哦!小优将为你精选“干货”,让腾讯的开发&测试大牛们陪你一起为提升产品质量!

———–我是正文分割线———–

特约供稿人:腾讯公司天天P图项目 乔伟康、梁小龙

天天P图作为图像处理类APP,内部集成了很多功能,包括滤镜、人脸检测、美白、磨皮、美妆、拼图、相机等,而且这些功能多是用底层算法依靠GPU实现,如何保证这些功能在众厂商生产的Android手机上正常高效运行,对于测试来说是一项极具挑战的任务。本文主要针对Android天天P图业务介绍我们在降低Crash率方面所做的工作,当然这里也离不开开发同学们的大力支持。

一、Android天天P图业务介绍

1.Android天天P图功能模块数据及潜在测试挑战

Android天天P图内部共9个大模块,共包含近200个子功能,其中各模块内部子功能数目如下表所示:

其中:

1)美化照片的滤镜/马赛克,美容美妆的磨皮/美白/上妆等功能均需要用到Native层的算法库,具有爆发底层SIGSEGV、SEGABRT等算法类crash问题的风险,但此类问题很难在单一手机上通过测试数量有限的图片发现,需要考虑如何在有限的机型及时间的情况下,尽可能的暴露此类问题;

2)自拍相机模块由于需要适配各种定制的Android的平台设备,同样存在兼容性Crash的爆发风险,自拍相机的兼容性问题对测试人员来说是一项很具挑战的任务;

3)Android天天P图作为图像处理类软件,美容美妆/故事拼图等模块对内存的要求较为苛刻,需要预防OOM的风险,测试需要知道如何判断鉴别P图内存使用效率及释放时机是否高效合理;

2.Android天天P图各版本Crash率趋势图

面对如上风险,我们再来看一下Android各版本的Crash率情况

从上表可以看出,产品问世初期的两个版本Crash率较高,但在随后的版本中,在不断增加新功能的同时,Android天天P图的Crash率整体呈下降趋势,由最初版本发布的2.2%降低到目前的0.24%。

(小优注:因数据涉及产品隐私,小优打了马赛克,还请各位看官见谅)

二、问题分析与解决策略制定

1.问题分析

1)存量问题分析

Android天天P图接入了RDM(小优注:腾讯内部研发管理工具)异常上报和Bugly(小优注:腾讯bugly为产品质量监控平台)异常上报用于观察外网用户遇到的Crash问题,这里先看下版本发布初期上报的主要问题:

下图为V2.0版本上报的TOP10 Crash问题

从上图可以可到,Crash主要分如下几类:

lRuntimeException

lSIGSEGV/SIGBUS

lNullPointerException(NPE)

其中RuntimeException需要具体问题具体分析,SIGSEGV/SIGBUS类的问题属于底层算法类问题,NullPointer类的问题为代码保护不规范类问题。

2)新增问题预防

为了降低Crash率,不只是要解决存量遗留的问题,同时需要关注新增问题的预防,保证无突出严重的新增Crash问题。

2.策略制定

1)分层测试

天天P图内部的很多功能使用底层算法库,如人脸识别、五官定位、彩妆上妆等。鉴于图片和环境的多样性,以及在上层无法验证部分异常场景,测试团队将APP Java层和Native层代码分开测试的策略。

针对算法层的测试使得测试更具针对性,覆盖的场景更多,同时使用自动化测试工具也使得在图片多样性覆盖上的测试效率更高。下图为部分P图SDK测试图片集。

2)存量问题解决

天天P图测试团队持续关注每个版本外发后的异常上报,筛选出其中较严重的10-20个Crash问题,分析提单跟进,在下一版本中进行解决,截止当前版本,测试配合开发共修复超过100个Crash问题,有效保证了Crash率的降低。

以下数据为V3.3版本外发后Crash的分布分析,在V3.4版本针对性解决部分Crash问题后,Crash率降低了1个千分点。

从统计数据上看,主要的Crash问题集中在自拍相机模块,测试调整NewMonkey运行参数,针对自拍相机进行集中测试,

3)新增问题预防

由上面的分析,我们确定主要的Crash集中在底层算法、NullPointer和其他Java层异常导致,如何在版本测试初期尽快发现这类问题,我们主要采用Crash问题分析总结反馈补充测试用例、引入改造现有工具等方式完成,具体可参考第三部分;

4)Crash问题分析总结补充测试场景

每一个版本外发后,天天P图测试团队会总结分析前一版本系统测试中及bugly异常上报发现的Crash问题,归类发生场景,分析诱因,最后反补到测试用例中,截止目前,共补充测试用例34条,总结文档11篇。

三、解决方案实施

接入和改造的工具发现Crash问题数目一览表

1.NewMonkey接入改造:让严重Crash无处藏身

1)引入背景

终端产品需要迭代快,而外发前一个阻碍发布的问题是发现严重的Crash问题,在这个时间修改Bug一是可能会引入其他问题,同时也极有可能导致外发延期,测试团队需要考虑如何避免这种问题,保证产品按时高效发布。

2)NewMonkey接入改造

NewMonkey是社交网络质量部测试开发团队以Android Monkey为基础,订制的一个更高效的自动化稳定性测试工具,相较于Monkey,NewMonkey的测试更具有平均性,能够覆盖APP内更多的Activity。NewMonkey运行于测试开发团队维护的NewMonkey Wall上,接入后测试手机会每天定时拉取最新RDM安装包进行测试,这样能够保证今早发现版本中存在的严重Crash问题。

但鉴于天天P图APP内操作的特殊性,天天P图测试团队针对NewMonkey的操作封装了更多适用于天天P图的测试操作,如:涂抹屏幕、滑动滑竿等,同时结合天天P图业务模块使用的分布,通过配置文件修改测试Blacklist文件使得定制后的NewMonkey运行更高效,如下是改造前后NewMonkey发现Crash问题的相关数据:

截止目前,本地运行的NewMonkey共在天天P图项目中发现148个Crash问题,有效的保证了外发版本质量。

(小优注:NewMonkey系腾讯内部研发的测试工具,据小优所知暂不支持接入外部app,但需要相关工具介绍文档的童鞋们可以留言索取。

插播广告一则:有需求的童鞋可以试试优测的自动化测试,实现原理一致,效果更优哦 [手动骄傲脸])

2.Coverity/CodeDog为代码保驾护航

1)引入背景

正如第二部分问题分析中介绍的V2.0版本外发后的存量问题分析,版本中存在的Crash类问题有一部分为NullPointerException,根据统计数据,Android天天P图各个版本测试中发现的NullPointerException问题大部分都在10个以上,而NPE的问题可以通过代码静态扫描进行预防。

2)Coverity/CodeDog接入与自动提单实现

代码扫描工具很多,天天P图测试团队采用CodeDog和Coverity进行空指针检查。CodeDog是社交网络质量部开发的一款集成工具,工具内部集成了findBugs,Coverity等工具,目前天天P图接入的CodeDog服务包含findBugs、安装包大小检查等,但鉴于资源限制,不包含Coverity,测试团队单独使用了MIG的Coverity进行扫描,由于MIG的Coverity不支持自动提单,测试团队在前人提供的基础提单脚本上进行了优化去重,配合findBugs发现空指针问题。

Coverity共发现96个NPE问题,各个版本发现的空指针数据如下:

CodeDog共发现163个NPE问题,各个版本发现的问题数据如下:

从数据上看,两个工具各有互补,但观察具体的提单可以发现,两个工具之间有一定交集,即提有重复Bug单,但也存在彼此都未发现的Bug。测试团队对自动提单工具进行了一定优化,减少了重复提单问题。

(小优注:同上,CodeDog系腾讯内部研发的测试工具,需要相关工具介绍文档的童鞋们可以留言索取哦)

3.LeakCanary 内存泄露检测利器

1)引入背景

天天P图作为图像类处理APP,对内存使用要求很苛刻,大量的图片资源加载导致即便微小的内存泄露也有引发OOM的风险,从异常上报看,版本发布初期OOM问题比较突出。

2)LeakCanary接入

LeakCanary是Square开源发布的一款内存泄露检测工具,接入简单,使用方便,在功能测试中便可以发现APP内隐藏的部分内存泄露问题,开发人员在V2.6版本接入了LeakCanary,目前Canary共发现了27个内存泄露问题。

接入LeakCanary后,Android天天P图的OOM问题呈逐步减少状态,相关数据如下:

LeakCanary问题报告详情:

4.腾讯优测云测试平台 发现机型兼容性问题

1)引入背景

Android平台测试最复杂的便是机型兼容性测试,Android天天P图外发后有时候会遇到个别机型频繁上报Crash,由于本地机型限制,及考虑到测试周期,测试团队需要一个自动化的机型兼容性测试平台/团队来解决这类Crash问题;

2)引入腾讯优测

腾讯优测是(utest.qq.com)一个自动化测试平台,MIG的产品,主要工作原理是维护大量测试机,进行自动化测试。利用优测的自动化测试Android天天P图共发现64个Crash异常问题,版本外发前进行一次测试,可基本保证外发无严重漏测Crash问题。

除了用线上的免费测试功能,去年开始Android天天P图开始使用优测的vip测试服务,每个版本做top50机型的核心遍历测试和一些专项测试。

3)优测云手机

优测线上有一个云手机功能,可以远程操控真机(不是模拟器),用户反馈哪个机型有bug,手里又没有对应机型的话,可以在优测直接在线复现,大部分热门机型都有,对测试同学来说挺实用。

5.Crash问题分析-测试场景补充

借助工具和功能测试发现的Crash问题只是第一步,我们需要从发现的Crash问题中寻找共性及复现场景,进而补充到功能测试用例中,避免此类问题再度发生。

Android天天P图经过近几个版本Crash分析积累,共补充测试用例34条,总结相关文档11篇。

1)举例1-Activity与Animation使用注意事项

问题描述:

天天P图分享界面有一个保存动画,如果在动画未完成时返回主界面,天天P图发生闪退;

问题分析:

为验证Activity的生命周期是否影响Animation的播放,在这里做了一个Demo,Demo里包含两个Activity: Main Activity与Animation Activity。其中Animation Activity由Main Activity呼起,在Animation Activity中点击Button播放动画,通过log形式输出各个阶段的时间戳。部分代码如下:

测试步骤如下:

1)在Main Activity中启动Animation Activity;

2)在Animation Activity中点击Button启动动画;

3)在动画播放期间点击返回按钮返回至Main Activity;

测试结果:

15:47:32.863: V/com.tencent.test(21258):Animation Start

15:47:40.822:V/com.tencent.test(21258):Animation Activity Destroyed.

15:48:02.858:V/com.tencent.test(21258):Animation End

程序代码中设置动画时间为30s,从log可看出实际Animation start与Animation End时间间隔为30s。

测试用例补充:

在动画未完成的时候,返回上一级或进入下一级

2)举例2-Activity数据状态恢复

问题描述:

在使用P图美化完自己的照片后进入分享页面,此时可供用户选择的有如下三种操作

1)一直点返回键直至退出程序;

2)点击右上角按钮回到主页面使用其他功能;

3)使用Home键将进程切换到后台;

问题发生在使用第三种操作,在切换到后台很长一段时间后,再次启动桌面APP时,Android会呈现APP上次退出的Activity界面,也即分享界面,此时,若点击左上角返回按钮,P图并没有返回上一个Activity,而是发生了crash。

问题分析:

首先对比操作1和操作3,两者的不同之处在于操作1是直接退出APP,而操作3是先将APP置于后台一段时间后再尝试退出。所以问题的关键在于APP在被置于后台时,Android对我们的APP做了什么。

Android为提升用户体验,系统通过ActivityManager为每一个APP都维护了一个Activity栈,APP第一次被启动时,Main Activity被压入栈(如P图的主界面),当用户点击使用魔法抠图功能时,图库预览Activity被压入栈,此时若使用返回键,栈顶部的Activity将被弹出并销毁,返回主界面Activity,如下图。在本案例中,我们是在点击返回按钮回到上一层Activity时Crash的,所以首先怀疑的是在构建上一层Activity时失败。

从Activity生命周期以及其基本状态图可知,当APP被切换到后台时,Activity变为Stopped状态,但当用户在后续操作中启动多个占内存较多的APP时,P图会被系统默默杀掉,流程图如下。

这里需要注意的是,Android在后台杀死进程和调用任务管理器杀死进程有很大不同:

l调用任务管理器杀死进程:系统中有关APP上次启动的全部状态被清除,包括Activity栈信息,再次启动APP时进入APP主Activity;

l系统在后台杀死进程:Android首先调用onSaveInstanceState()保存Activity的一些状态信息,再次启动APP时返回到上次切换APP前的Activity;

进程在后台被杀死的场景可以通过使用DDMS中的stop按钮模拟。当使用P图美化完照片进入分享界面后,使用Home切换APP至后台,启动DDMS,选中P图进程使用stop按钮,此时由ActivityManager维护的该APP的Activity栈信息将被全部清空。在此时,若使用返回键,Android将重新调用前一Activity的onCreate()创建该Activity,但在这里必须传递给该函数一个Bundle信息,用来恢复Activity显示内容。而开发在此处未使用Bundle信息来恢复前一Activity,这在大多数情况下都是可以正常工作的,因为多数情况下该Activity可以在栈中被找到,尤其是在内存较大的手机,但当Activity栈被清空时,问题便显现出来。所以问题的根源最终被定位为Activity的onCreate()函数实现。

测试用例补充:

在用户可能直接切换至后台的场景,使用DDMS或Android Studio里的模拟系统杀死进程工具杀掉进程,再次启动APP后使用返回键等操作。

1)举例3-IndexOutOfBounds类问题分析

问题描述:

在使用动效拍最后一个滤镜后,点击切换模式选择,发生闪退。

问题分析:

回想Crash发生时的具体场景,Crash多发生在选择一个滤镜后,再点击模式切换按钮。再参考堆栈信息头部的相关信息,Crash的主要诱因在于索引position位置在39的元素时出错,从这里可以得到一个重要信息:一个包含至少三十个元素的ViewHolder在进行索引时出错,再查看自拍相机里所有的子功能,只有滤镜选择功能模块的元素有42个,因此,将定位范围缩小到滤镜选择子功能。

结合初步缩小的定位范围,查看本版本与之相关的新需求,与之相关的只有在模式切换时需要隐藏滤镜子模块中的两个子Icon,到这里很自然的会想到,由于隐藏两个子Icon导致Index索引发生变更,导致末尾的两个Index失效,这里剩下的就是验证了。

由第一部分得出的推断,是当切换到动效拍模式后,滤镜模块中的子Icon隐藏后会引发Crash,因此,首先我们会选择滤镜模块中位于第39位的滤镜素材,然后切换动效拍模式。为了触发滤镜的索引,需要再次点击模式切换按钮(使用滤镜),此时APP发生Crash,log日志与上报一致,问题得以重现。

测试用例补充:

当ViewHolder内部数据数目发生变化时,应该关注Index更新的问题,针对边界值素材及相关功能进行测试;

三、总结与规划

一切测试工作的开展源于对服务业务的理解,以及对当前业务痛点的把握,分析问题,制定方案,通过接入或开发改造工具帮我们提高效率,解决问题。问题的解决并不是最终的目的,我们需要从问题的解决归纳总结出测试的疏漏点,指导我们继续下一次的测试工作开展。(写得太好了,小优忍不住点个赞!)

Android天天P图业务之后的稳定性测试工作开展依然会立于当前业务痛点,通过解决留存问题,接入和改造成熟的测试工具来预防新问题,不断提高产品质量。

Android中的ANR用法详解

有过Android开发经历的人都不会对ANR陌生,它和崩溃一样是程序设计的问题。本文将以较为深入的视角来介绍什么是ANR,出现场景,如何避免以及如何定位分析ANR,希望可以帮助大家在编写程序时有所帮助。

什么是ANR

ANR全称Application Not Responding,意思就是程序未响应。如果一个应用无法响应用户的输入,系统就会弹出一个ANR对话框,如下图所示,用户可以自行选择继续等待亦或者是停止当前程序。

说说Android中的ANR

出现场景

  • 主线程被IO操作(从4.0之后网络IO不允许在主线程中)阻塞。
  • 主线程中存在耗时的计算
  • 主线程中错误的操作,比如Thread.wait或者Thread.sleep等

Android系统会监控程序的响应状况,一旦出现下面两种情况,则弹出ANR对话框

  • 应用在 5秒 内未响应用户的输入事件(如按键或者触摸)
  • BroadcastReceiver未在 10秒 内完成相关的处理

如何避免

基本的思路就是将IO操作在工作线程来处理,减少其他耗时操作和错误操作

  • 使用AsyncTask处理耗时IO操作。
  • 使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
  • 使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
  • Activity的onCreate和onResume回调中尽量避免耗时的代码
  • BroadcastReceiver中onReceive代码也要尽量减少耗时,建议使用IntentService处理。

画龙点睛

通常100到200毫秒就会让人察觉程序反应慢,为了更加提升响应,可以使用下面的几种方法

  • 如果程序正在后台处理用户的输入,建议使用让用户得知进度,比如使用ProgressBar控件。
  • 程序启动时可以选择加上欢迎界面,避免让用户察觉卡顿。
  • 使用Systrace和TraceView找出影响响应的问题。

如何定位

如果开发机器上出现问题,我们可以通过查看/data/anr/traces.txt即可,最新的ANR信息在最开始部分。我们从stacktrace中即可找到出问题的具体行数。本例中问题出现在MainActivity.java 27行,因为这里调用了Thread.sleep方法。

 root@htc_m8tl:/ # cat /data/anr/traces.txt | more

----- pid 30307 at 2015-05-30 14:51:14 -----
Cmd line: com.example.androidyue.bitmapdemo

JNI: CheckJNI is off; workarounds are off; pins=0; globals=272

DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)

"main" prio=5 tid=1 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 obj=0x416eaf18 self=0x416d8650
  | sysTid=30307 nice=0 sched=0/0 cgrp=apps handle=1074565528
  | state=S schedstat=( 0 0 0 ) utm=5 stm=4 core=3
  at java.lang.VMThread.sleep(Native Method)
  at java.lang.Thread.sleep(Thread.java:1044)
  at java.lang.Thread.sleep(Thread.java:1026)
  at com.example.androidyue.bitmapdemo.MainActivity$1.run(MainActivity.java:27)
  at android.app.Activity.runOnUiThread(Activity.java:4794)
  at com.example.androidyue.bitmapdemo.MainActivity.onResume(MainActivity.java:33)
  at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1282)
  at android.app.Activity.performResume(Activity.java:5405)

如果是线上版本引起的,Google Play后台有相关的数据可以帮助查看分析并解决问题。

细致分析

提问: BroadcastReceiver过了60秒居然没有ANR? 现场代码如下

 public class NetworkReceiver extends BroadcastReceiver{
    private static final String LOGTAG = "NetworkReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i(LOGTAG, "onReceive intent=" + intent);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.i(LOGTAG, "onReceive end");
    }
}

回答:实际上已经发生了ANR,只是没有进行对话框弹出而已。这种ANR就是background ANR,即后台程序的ANR,我们可以通过过滤日志验证

 adb logcat | grep "NetworkReceiver|ActivityManager|WindowManager"
I/NetworkReceiver( 4109): onReceive intent=Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x8000010 cmp=com.example.androidyue.bitmapdemo/.NetworkReceiver (has extras) }
I/ActivityManager(  462): No longer want com.android.exchange (pid 1054): empty #17
I/NetworkReceiver( 4109): onReceive end
W/BroadcastQueue(  462): Receiver during timeout: ResolveInfo{5342dde4 com.example.androidyue.bitmapdemo.NetworkReceiver p=0 o=0 m=0x108000}
E/ActivityManager(  462): ANR in com.example.androidyue.bitmapdemo
E/ActivityManager(  462): Reason: Broadcast of Intent { act=android.net.conn.CONNECTIVITY_CHANGE flg=0x8000010 cmp=com.example.androidyue.bitmapdemo/.NetworkReceiver (has extras) }
E/ActivityManager(  462): Load: 0.37 / 0.2 / 0.14
E/ActivityManager(  462): CPU usage from 26047ms to 0ms ago:
E/ActivityManager(  462):   0.4% 58/adbd: 0% user + 0.4% kernel / faults: 1501 minor
E/ActivityManager(  462):   0.3% 462/system_server: 0.1% user + 0.1% kernel
E/ActivityManager(  462):   0% 4109/com.example.androidyue.bitmapdemo: 0% user + 0% kernel / faults: 6 minor
E/ActivityManager(  462): 1.5% TOTAL: 0.5% user + 0.9% kernel + 0% softirq
E/ActivityManager(  462): CPU usage from 87ms to 589ms later:
E/ActivityManager(  462):   1.8% 58/adbd: 0% user + 1.8% kernel / faults: 30 minor
E/ActivityManager(  462):     1.8% 58/adbd: 0% user + 1.8% kernel
E/ActivityManager(  462): 4% TOTAL: 0% user + 4% kernel
W/ActivityManager(  462): Killing ProcessRecord{5326d418 4109:com.example.androidyue.bitmapdemo/u0a10063}: background ANR
I/ActivityManager(  462): Process com.example.androidyue.bitmapdemo (pid 4109) has died.

除了日志,我们还可以根据前面提到的查看traces.txt文件。

提问:可以更容易了解background ANR么?

回答:当然可以,在Android开发者选项—>高级—>显示所有”应用程序无响应“勾选即可对后台ANR也进行弹窗显示,方便查看了解程序运行情况。

为什么 Android 中不建议使用 Enums

关于Android性能优化中一个常见的建议是不要在你的代码中使用Enums,就连 Android官网 上都强烈建议不要使用。

Why

Android中当你的App启动后系统会给App单独分配一块内存。App的DEX code、Heap以及运行时的内存分配都会在这块内存中。接下来看两种写法:

1.使用Int表示状态

public static final int VALUE1 =1;
public static final int VALUE1 =2;
public static final int VALUE1 =3;

2.使用Enums表示状态

public static enum Value{
  VALUE1,
  VALUE2,
  VALUE3
}

情形2中的DEX size增加是情形1中的13倍之多。这还只是DEX code的增加,同样,运行时的内存分配,一个enum值的声明会消耗至少20 bytes,这还不算其中的对象数组需要保持对enum值的引用。Why?使用javap反编译情形二中生成的class文件,去掉汇编代码后如下:

public final class VALUE extends java.lang.Enum{  
  public static final VALUE VALUE1;  
  public static final VALUE VALUE2;  
  public static final VALUE VALUE3;
  private static final VALUE[] values[];
  static{}
}

可以看到实际上enum类型继承java.lang.Enum,每个枚举项都会被声明成一个静态变量,并被赋值。VALUE value1 = VALUE.VALUE1则会引起对静态变量的引用。

因 此,当你的代码或包含的Lib中大量使用enums时,对于本身内存小的手机将是灾难性的。不可否认enums会使得代码更易读更安全,但是我们使用 Int也可以通过@IntDef 注解防止编译时Lint errors。当然如果你使用enums,proguard在一些情况下会优化你的代码使用Int代替。

[转载]Android应用启动优化:一种DelayLoad的实现和原理

0. 应用启动优化概述

在 Android 开发中,应用启动速度是一个非常重要的点,应用启动优化也是一个非常重要的过程.对于应用启动优化,其实核心思想就是在启动过程中少做事情,具体实践的时候无非就是下面几种:

  • 异步加载
  • 延时加载
  • 懒加载

不用一一去解释,做过启动优化的估计都使用过,本篇文章将详细讲解一下一种延时加载的实现以及其原理.其实这种加载的实现是非常简单的,但是其中的原理可能比较复杂,还涉及到Looper/Handler/MessageQueue/VSYNC等.以及其中碰到的一些问题,还会有一些我自己额外的思考.

1. 优化后的DelayLoad的实现

一提到DelayLoad,大家可能第一时间想到的就是在 onCreate 里面调用 Handler.postDelayed方法, 将需要 Delay 加载的东西放到这里面去初始化, 这个也是一个比较方便的方法. Delay一段时间再去执行,这时候应用已经加载完成,界面已经显示出来了, 不过这个方法有一个致命的问题: 延迟多久?大家都知道,在 Android 的高端机型上,应用的启动是非常快的 , 这时候只需要 Delay 很短的时间就可以了, 但是在低端机型上,应用的启动就没有那么快了,而且现在应用为了兼容旧的机型,往往需要 Delay 较长的时间,这样带来体验上的差异是很明显的.

这里先说优化方案:

  1. 首先 , 创建 Handler 和 Runnable 对象, 其中 Runnable 对象的 run方法里面去更新 UI 线程.
    private Handler myHandler = new Handler();
    private Runnable mLoadingRunnable = new Runnable() {
    @Override
    public void run() {
    updateText(); //更新UI线程
    }
    };
  2. 在主 Activity 的 onCreate 中加入下面的代码
    getWindow().getDecorView().post(new Runnable() {
    @Override
    public void run() {
    myHandler.post(mLoadingRunnable);
    }
    });

其实实现的话非常简单,我们来对比一下三种方案的效果.

2. 三种写法的差异对比

为了验证我们优化的 DelayLoad的效果,我们写了一个简单的app , 这个 App 中包含三张不同大小的图片,每张图片下面都会有一个 TextView , 来标记图片的显示高度和宽度. MainActivity的代码如下:

public class MainActivity extends AppCompatActivity {
private static final int DEALY_TIME = 300 ;

private ImageView imageView1;
private ImageView imageView2;
private ImageView imageView3;
private TextView textView1;
private TextView textView2;
private TextView textView3;

private Handler myHandler = new Handler();
private Runnable mLoadingRunnable = new Runnable() {
@Override
public void run() {
updateText();
}
};

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

imageView1 = (ImageView) findViewById(R.id.image1);
imageView2 = (ImageView) findViewById(R.id.image2);
imageView3 = (ImageView) findViewById(R.id.image3);

textView1 = (TextView) findViewById(R.id.text1);
textView2 = (TextView) findViewById(R.id.text2);
textView3 = (TextView) findViewById(R.id.text3);

//      第一种写法:直接Post
myHandler.post(mLoadingRunnable);

//      第二种写法:直接PostDelay 300ms.
//        myHandler.postDelayed(mLoadingRunnable, DEALY_TIME);

//      第三种写法:优化的DelayLoad
//      getWindow().getDecorView().post(new Runnable() {
//            @Override
//            public void run() {
//                myHandler.post(mLoadingRunnable);
//            }
//        });

// Dump当前的MessageQueue信息.
getMainLooper().dump(new Printer() {
@Override
public void println(String x) {
Log.i("Gracker",x);
}
},"onCreate");
}

private void updateText() {
TraceCompat.beginSection("updateText");
textView1.setText("image1 : w=" + imageView1.getWidth() +
" h =" + imageView1.getHeight());
textView2.setText("image2 : w=" + imageView2.getWidth() +
" h =" + imageView2.getHeight());
textView3.setText("image3 : w=" + imageView3.getWidth() +
" h =" + imageView3.getHeight());
TraceCompat.endSection();
}

我们需要关注两个点:

  • updateText 这个函数是什么时候被执行的?
  • App 启动后,三个图片的长宽是否可以被正确地显示出来?
  • 是否有 Delay Load 的效果?

2.1 第一种写法

  1. updateText执行的时机?

    下面是第一种写法的Trace图:

    Android应用启动优化:一种DelayLoad的实现和原理

    可以看到 updateText 是在 Activity 的 onCreate/onStart/onResume三个回调执行完成后才去执行的.

  2. 图片的宽高是否正确显示?

    Android应用启动优化:一种DelayLoad的实现和原理

    从图片看一看到,宽高并没有显示. 这是为什么呢? 这个问题就要从Activity 的 onCreate/onStart/onResume三个回调说起了. 其实Activity 的 onCreate/onStart/onResume三个回调中,并没有执行Measure和Layout操作, 这个是在后面的performTraversals中才执行的. 所以在这之前宽高都是0.

  3. 是否有 Delay Load 的效果?并没有. 因为我们知道, 应用启动的时候,要等两次 performTraversals 都执行完成之后才会显示第一帧, 而 updateText 这个方法在第一个 performTraversals 执行之前就执行了. 所以 updateText 方法的执行时间是算在应用启动的时间里面的.

2.2 第二种写法

第二种写法我们Delay了300ms .我们来看一下表现.

  1. updateText执行的时机?

    Android应用启动优化:一种DelayLoad的实现和原理

    可以看到,这种写法的话,updateText是在两个performTraversals 执行完成之后(这时候 APP 的第一帧才显示出来)才去执行的, 执行完成之后又调用了一次 performTraversals 将 TextView 的内容进行更新.

  2. 图片的宽高是否正确显示?

    Android应用启动优化:一种DelayLoad的实现和原理

    从上图可以看到,图片的宽高是正确显示了出来. 原因上面已经说了,measure/layout执行完成后,宽高的数据就可以获取了.

  3. 是否有 Delay Load 的效果?

    不一定,取决于 Delay的时长.

    从前面的 Trace 图上我们可以看到 , updateText 方法由于 Delay 了300ms, 所以在应用第一帧显示出来170ms之后, 图片的文字信息才进行了更新. 这个是有 Delay Load 的效果的.

    但是这里只是一个简单的TextView的更新, 如果是较大模块的加载 , 用户视觉上会有很明显的 “ 空白->内容填充” 这个过程, 或者会附加”闪一下”特效…这显然是我们不想看到的.

    有人会说:可以把Delay的时间减小一点嘛,这样就不会闪了. 话是这么说,但是由于 Android 机器的多元性(其实就是有很多高端机器,也有很多低端机器) , 在这个机子上300ms的延迟算是快,在另外一个机子上300ms算是很慢.

    我们将Delay时间调整为50ms, 其Trace图如下:

    Android应用启动优化:一种DelayLoad的实现和原理

    可以看到,updateText 方法在第一个 performTraversals 之后就执行了,所以也没有 Delay Load 的效果(虽然宽高是正确显示了,因为在第一个 performTraversals 方法中就执行了layout和measure).

2.3 第三种写法

经过前两个方法 , 我们就会想, 如果能不使用Delay方法, updateText 方法能在 第二个performTraversals 方法执行完成后(即APP第一帧在屏幕上显示),马上就去执行,那么即起到了 Delay Load的作用,又可以正确显示图片的宽高.第三种写法就是这个效果:

  1. updateText执行的时机?Android应用启动优化:一种DelayLoad的实现和原理

    可以看到这种写法. updateText 在第二个 performTraversals 方法执行完成后马上就执行了, 然后下一个 VSYNC 信号来了之后, TextView就更新了.

  2. 图片的宽高是否正确显示?

    当然是正确显示的.如图:

    Android应用启动优化:一种DelayLoad的实现和原理

  3. 是否有 Delay Load 的效果?
    从 Trace 图上看, 是有 Delay Load的效果的, 而且可以在应用第一帧显示后马上进行数据 Load , 不用考虑 Delay时间的长短.

3. 一些思考

关于优化的 Delay Load 的实现,从代码层面来看其实是非常简单的.其带来的效果也是很赞的.但是实现之后我们还需要思考一下,为何这么做就可以实现这种功能呢?很显然要回答这个问题,我们需要知道更底层的一些东西.这个还涉及到 Handler/Message/MessageQueue/Looper/VSYNC/ViewRootImpl等知识. 往大里说应该还涉及到AMS/WMS等.由于涉及到的东西比较多,我就不在这一篇里面阐述了, 下一篇文章将会从从原理上讲解一下为何优化的 Delay Load 会起作用.

转载:http://www.androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load.html

[转载]Android性能优化之常见的内存泄漏

前言

对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary、MAT等工具来检测应用程序是否存在内存泄漏,MAT是一款强大的内存分析工具,功能繁多而复杂,而 LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查。

最近腾讯bugly也推出了三篇关于Android内存泄漏调优的文章,有兴趣的可以看看:

1、内存泄露从入门到精通三部曲之基础知识篇

2、内存泄露从入门到精通三部曲之排查方法篇

3、内存泄露从入门到精通三部曲之常见原因与用户实践

内存泄漏

为什么会产生内存泄漏?

当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。

内存泄漏对程序的影响?

内存泄漏是造成应用程序OOM的主要原因之一!我们知道Android系统为每个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。

Android中常见的内存泄漏汇总

单例造成的内存泄漏

单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏,由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。如下这个典例:

public class AppManager {
  private static AppManager instance;
  private Context context;
  private AppManager(Context context) {
    this.context = context;
  }
  public static AppManager getInstance(Context context) {
    if (instance != null) {
      instance = new AppManager(context);
    }
    return instance;
  }
}

 

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长

2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。

所以正确的单例应该修改为下面这种方式:

public class AppManager {
  private static AppManager instance;
  private Context context;
  private AppManager(Context context) {
    this.context = context.getApplicationContext();
  }
  public static AppManager getInstance(Context context) {
    if (instance != null) {
      instance = new AppManager(context);
    }
    return instance;
  }
}

这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏

非静态内部类创建静态实例造成的内存泄漏

有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:

public class MainActivity extends AppCompatActivity {
  private static TestResource mResource = null;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if(mManager == null){
      mManager = new TestResource();
    }
    //...
  }
  class TestResource {
    //...
  }
}

这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext

Handler造成的内存泄漏

Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:

public class MainActivity extends AppCompatActivity {
  private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      //...
    }
  };
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    loadData();
  }
  private void loadData(){
    //...request
    Message message = Message.obtain();
    mHandler.sendMessage(message);
  }
}

这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:

public class MainActivity extends AppCompatActivity {
  private MyHandler mHandler = new MyHandler(this);
  private TextView mTextView ;
  private static class MyHandler extends Handler {
    private WeakReference<Context> reference;
    public MyHandler(Context context) {
      reference = new WeakReference<>(context);
    }
    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = (MainActivity) reference.get();
      if(activity != null){
        activity.mTextView.setText("");
      }
    }
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTextView = (TextView)findViewById(R.id.textview);
    loadData();
  }
  private void loadData() {
    //...request
    Message message = Message.obtain();
    mHandler.sendMessage(message);
  }
}

创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了 Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,更准确的做法如下:

public class MainActivity extends AppCompatActivity {
  private MyHandler mHandler = new MyHandler(this);
  private TextView mTextView ;
  private static class MyHandler extends Handler {
    private WeakReference<Context> reference;
    public MyHandler(Context context) {
      reference = new WeakReference<>(context);
    }
    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = (MainActivity) reference.get();
      if(activity != null){
        activity.mTextView.setText("");
      }
    }
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTextView = (TextView)findViewById(R.id.textview);
    loadData();
  }
  private void loadData() {
    //...request
    Message message = Message.obtain();
    mHandler.sendMessage(message);
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
  }
}

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的 Runnable和Message。

线程造成的内存泄漏

对于线程造成的内存泄漏,也是平时比较常见的,如下这两个示例可能每个人都这样写过:

//——————test1
    new AsyncTask<Void, Void, Void>() {
      @Override
      protected Void doInBackground(Void... params) {
        SystemClock.sleep(10000);
        return null;
      }
    }.execute();
//——————test2
    new Thread(new Runnable() {
      @Override
      public void run() {
        SystemClock.sleep(10000);
      }
    }).start();

上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成,那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:

 static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    private WeakReference<Context> weakReference;
    public MyAsyncTask(Context context) {
      weakReference = new WeakReference<>(context);
    }
    @Override
    protected Void doInBackground(Void... params) {
      SystemClock.sleep(10000);
      return null;
    }
    @Override
    protected void onPostExecute(Void aVoid) {
      super.onPostExecute(aVoid);
      MainActivity activity = (MainActivity) weakReference.get();
      if (activity != null) {
        //...
      }
    }
  }
  static class MyRunnable implements Runnable{
    @Override
    public void run() {
      SystemClock.sleep(10000);
    }
  }
//——————
  new Thread(new MyRunnable()).start();
  new MyAsyncTask(this).execute();

这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。

资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

一些建议

1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext

2、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:

Android性能优化之常见的内存泄漏

**其中:**NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建

3、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏

4、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:

  1. 将内部类改为静态内部类
  2. 静态内部类中使用弱引用来引用外部类的成员变量

5、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null6、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期

 

转载:http://blog.csdn.net/u010687392/article/details/49909477

Android性能优化之加快应用启动速度

应用的启动

启动方式

通常来说,在安卓中应用的启动方式分为两种:冷启动和热启动。

1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。

2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。

特点

1、冷启动:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。

2、热启动:热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。

上面说的启动是点击app的启动图标来启动的,而另外一种方式是进入最近使用的列表界面来启动应用,这种不应该叫启动,应该叫恢复。

应用启动的流程

在安卓系统上,应用在没有进程的情况下,应用的启动都是这样一个流程:当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上,所以直到这里,应用的第一次启动才算完成,这时候我们看到的界面也就是所说的第一帧。

所以,总结一下,应用的启动流程如下:

Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。

测量应用启动的时间

在上面这个启动流程中,任何一个地方有耗时操作都会拖慢我们应用的启动速度,而应用启动时间是用毫秒度量的,对于毫秒级别的快慢度量我们还是需要去精确的测量到到底应用启动花了多少时间,而根据这个时间来做衡量。

什么才是应用的启动时间

从点击应用的启动图标开始创建出一个新的进程直到我们看到了界面的第一帧,这段时间就是应用的启动时间。

我们要测量的也就是这段时间,测量这段时间可以通过adb shell命令的方式进行测量,这种方法测量的最为精确,命令为:

adb shell am start -W [packageName]/[packageName.MainActivity]

执行成功后将返回三个测量到的时间:

1、ThisTime:一般和TotalTime时间一样,除非在应用启动时开了一个透明的Activity预先处理一些事再显示出主Activity,这样将比TotalTime小。

2、TotalTime:应用的启动时间,包括创建进程+Application初始化+Activity初始化到界面显示。

3、WaitTime:一般比TotalTime大点,包括系统影响的耗时。

下面是测量一个应用冷启动和热启动的时间:

冷启动:

Android性能优化之加快应用启动速度

热启动:

Android性能优化之加快应用启动速度

可以看到在进程已经存在的情况下,只需要重新初始化MainActivity,这样的启动比较快,不过大多数情况下应用的启动都是冷启动,因为用户都会在任务列表中手动关闭遗留的应用进程。

减少应用启动时的耗时

针对冷启动时候的一些耗时,如上测得这个应用算是中型的app,在冷启动的时候耗时已经快700ms了,如果项目再大点在Application中配置了更多的初始化操作,这样将可能达到1s,这样每次启动都明显感觉延迟,所以在进行应用初始化的时候采取以下策略:

1、在Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化,一些数据预取放在异步线程中,可以采取Callable实现。

2、对于sp的初始化,因为sp的特性在初始化时候会对数据全部读出来存在内存中,所以这个初始化放在主线程中不合适,反而会延迟应用的启动速度,对于这个还是需要放在异步线程中处理。

3、对于MainActivity,由于在获取到第一帧前,需要对contentView进行测量布局绘制操作,尽量减少布局的层次,考虑StubView的延迟加载策略,当然在onCreate、onStart、onResume方法中避免做耗时操作。

遵循上面三种策略可明显提高app启动速度。

优化应用启动时的体验

对于应用的启动时间,只能是尽量的避免一些耗时的、非必要的操作在主线程中,这样相对可以缩减一部分启动的耗时,另外一方面在等待第一帧显示的时间里,可以加入一些配置以增加体验,比如加入Activity的background,这个背景会在显示第一帧前提前显示在界面上。1、先为主界面单独写一个主题style,设置一张待显示的图片,这里我设置了一个颜色,然后在manifest中设置给MainActivity:

<style name="AppTheme.Launcher"> <item name="android:windowBackground">@drawable/bule</item> </style>
//...
        <activity  android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.Launcher">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

2、然后在MainActivity中加载布局前把AppTheme重新设置给MainActivity:

@Override
    protected void onCreate(Bundle savedInstanceState) {

        setTheme(R.style.AppTheme);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
}

这样在启动时会先显示background,然后待界面绘制完成再显示主界面

Android性能优化之加快应用启动速度

Android内存优化之static使用篇

在Android开发中,我们经常会使用到static来修饰我们的成员变量,其本意是为了让多个对象共用一份空间,节省内存,或者是使用单例模式,让该类只生产一个实例而在整个app中使用。然而在某些时候不恰当的使用或者是编程的不规范却会造成了内存泄露现象(java上的内存泄漏指内存得不到gc的及时回收,从而造成内存占用过多的现象)

本文中我们主要分析的是static变量对activtiy的不恰当引用而造成的内存泄漏,因为对于同一个Activity页面一般每次打开时系统都会重新生成一个该activity的对象(standard模式下),而每个activity对象一般都含有大量的视图对象和bitmap对象,如果之前的activity对象不能得到及时的回收,从而就造成了内存的泄漏现象。

下面一边看代码一边讲解。

单例模式不正确的获取context

public class LoginManager {

    private Context context;
    private static LoginManager manager;

    public static LoginManager getInstance(Context context) {
        if (manager == null)
            manager = new LoginManager(context);
        return manager;
    }

    private LoginManager(Context context) {
        this.context = context;
    }

在LoginActivity中

public class LoginActivity extends Activity  {

    private LoginManager loginManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        loginManager = LoginManager.getInstance(this);
    }

这种方式大家应该一看就明白问题在哪里了,在LoginManager的单例中context持有了LoginActivity的this对象,即使登录成功后我们跳转到了其他Activity页面,LoginActivity的对象仍然得不到回收因为他被单例所持有,而单例的生命周期是同Application保持一致的。

正确的获取context的方式

public class LoginManager {

    private Context context;
    private static LoginManager manager;

    public static LoginManager getInstance(Context context) {
        if (manager == null)
            manager = new LoginManager(context);
        return manager;
    }

    private LoginManager(Context context) {
        this.context = context.getApplicationContext();
    }

修改方式也非常简单我们单例中context不再持有Activity的context而是持有Application的context即可,因为Application本来就是单例,所以这样就不会存在内存泄漏的的现象了。

单例模式中通过内部类持有activity对象

第一种方式内存泄漏太过与明显,相信大家都不会犯这种错误,接下来要介绍的这种泄漏方式会比较不那么容易发现,内部类的使用造成activity对象被单例持有。

还是看代码再分析,下面是一个单例的类:

public class TestManager {
    public static final TestManager INSTANCE = new TestManager();
    private List<MyListener> mListenerList;

    private TestManager() {
        mListenerList = new ArrayList<MyListener>();
    }

    public static TestManager getInstance() {
        return INSTANCE;
    }

    public void registerListener(MyListener listener) {
        if (!mListenerList.contains(listener)) {
            mListenerList.add(listener);
        }
    }
    public void unregisterListener(MyListener listener) {
        mListenerList.remove(listener);
    }
}

interface MyListener {
    public void onSomeThingHappen();
}

然后是activity:

public class TestActivity extends AppCompatActivity {

    private MyListener mMyListener=new MyListener() {
        @Override
        public void onSomeThingHappen() {
        }
    };
    private TestManager testManager=TestManager.getInstance();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        testManager.registerListener(mMyListener);
    }
}

我们知道在java中,非静态的内部类的对象都是会持有指向外部类对象的引用的,因此我们将内部类对象mMyListener让单例所持有时,由于mMyListener引用了我们的activity对象,因此造成activity对象也不能被回收了,从而出现内存泄漏现象。

修改以上代码,避免内存泄漏,在activity中添加以下代码:`

@Override
protected void onDestroy() {
    testManager.unregisterListener(mMyListener);
    super.onDestroy();
}

AsyncTask不正确使用造成的内存泄漏

介绍完以上两种情况的内存泄漏后,我们在来看一种更加容易被忽略的内存泄漏现象,对于AsyncTask不正确使用造成内存泄漏的问题。

mTask=new AsyncTask<String,Void,Void>()
        {
@Override
protected Void doInBackground(String... params) {
        //doSamething..
        return null;
        }
        }.execute("a task");

一般我们在主线程中开启一个异步任务都是通过实现一个内部类其继承自AsyncTask类然后实现其相应的方法来完成的,那么自然的mTask就会持有对activity实例对象的引用了。查看AsyncTask的实现,我们会通过一个SerialExecutor串行线程池来对我们的任务进行排队,而这个SerialExecutor对象就是一个static final的常量。

具体的引用关系是:

1.我们的任务被封装在一个FutureTask的对象中(它充当一个runable的作用),FutureTask的实现也是通过内部类来实现的,因此它也为持有AsyncTask对象,而AsyncTask对象引用了activity对象,因此activity对象间接的被FutureTask对象给引用了。

2.futuretask对象会被添加到一个ArrayDeque类型的任务队列的mTasks实例中

3.mTasks任务队列又被SerialExecutor对象所持有,刚也说了这个SerialExecutor对象是一个static final的常量。

具体AsyncTask的实现大家可以去参照下其源代码,我这里就通过文字描述一下其添加任务的实现过程就可以了,总之分析了这么多通过层层引用后我们的activity会被一个static变量所引用到。所以我们在使用AsyncTask的时候不宜在其中执行太耗时的操作,假设activity已经退出了,然而AsyncTask里任务还没有执行完成或者是还在排队等待执行,就会造成我们的activity对象被回收的时间延后,一段时间内内存占有率变大。

解决方法在activity退出的时候应该调用cancel()函数

@Override
protected void onDestroy() {
    //mTask.cancel(false);
    mTask.cancel(true);
    super.onDestroy();
}

具体cancel()里传递true or false依实际情况而定:

1.当我们的任务还在排队没有被执行,调用cancel()无论true or false,任务会从排队队列中移除,即任务都不会被执行到了。

2.当我们的任务已经开始执行了(doInBackground被调用),传入参数为false时并不会打断doInBackground的执行,传入参数为true时,如果我们的线程处于休眠或阻塞(如:sleep,wait)状况是会打断其执行。

这里具体解释下cancle(true)的意义:

mTask=new AsyncTask<String,Void,Void>()
        {
@Override
protected Void doInBackground(String... params) {
        try {
        Thread.sleep(10000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        Log.d("test", "task is running");
        return null;
        }
        }.execute("a task");
        try {
        //保证task得以执行
        Thread.sleep(2000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        mTask.cancel(true);

在这样的情况下我们的线程处于休眠状态调用cancel(true)方法会打断doInBackground的执行――即不会看到log语句的输出。

但在下面的这种情况的时候却打断不了

mTask=new AsyncTask<String,Void,Void>()
        {
@Override
protected Void doInBackground(String... params) {

        Boolean loop=true;
        while (loop) {
        Log.d("test", "task is running");
        }
        return null;
        }
        }.execute("a task");
        try {
        Thread.sleep(2000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        mTask.cancel(true);

由于我们的线程不处于等待或休眠的状况及时调用cancel(true)也不能打断doInBackground的执行――现象:log函数一直在打印输出。

解决方法:

mTask=new AsyncTask<String,Void,Void>()
        {
@Override
protected Void doInBackground(String... params) {
        //doSomething..
        Boolean loop=true;
        while (loop) {
        if(isCancelled())
        return null;
        Log.d("test", "task is running");
        }
        return null;
        }
        }.execute("a task");
        try {
        Thread.sleep(2000);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        mTask.cancel(true);

这里我们通过在每次循环是检查任务是否已经被cancle掉,如果是则退出。因此对于AsyncTask我们也得注意按照正确的方式进行使用,不然也会造成程序内存泄漏的现象。

以上内容就是在使用static时,我们需要怎么做才能优化内存的使用,当然对于以上3种情况是我们编程中使用static经常遇到的内存泄漏的情况,但仍然还有很多情况我们不易察觉到。比如:如果不做介绍,上面的第三种情况就很难察觉到,这时我们最终的内存泄漏优化方法就是:使用内存泄漏分析工具,在下一篇文章里我会参照第三种情况(AsyncTask)造成的内存泄漏,通过使用MAT工具进行分析,讲解MAT排除内存泄漏的使用方法。

文章来源:http://blog.csdn.net/ys408973279/article/details/50389200

对应的另一篇:http://blog.csdn.net/ys408973279/article/details/50403756