Android性能优化大全

前言:

性能分析工具
在Android开发中,我们经常会遇到一些偶发问题,比如:无响应,应用退出,卡顿等。这些问题我们可以通过日志追踪,当然尽可能不让出现此类问题,这就需要在开发中及时发现和优化有风险的代码。除了利用一些工具检查以外,还要对代码质量有所提高,因此性能优化不是一朝一夕的事。本文主要对绘制,内存,稳定性以及安装包方面进行优化讲解,参考了一些博文和书籍,整理如下。

一,绘制优化

Android应用启动慢,使用经常卡顿,按场景可以分成如下图四类,这四类场景又可以分成两大类。

  • 页面绘制:主要原因是绘制的层级深,页面复杂,刷新不合理,由于这些原因导致卡顿的场景更多出现在UI和启动后的初始页面以及跳转到页面的绘制上。
  • 数据处理:导致这种卡顿场景的原因是数据处理量太大,一般分为三种情况,一是数据处理在UI线程,二是数据处理占用CPU高,导致主线程拿不到时间片,三是内存增加导致GC频繁,从而引起卡顿。

Android性能优化大全_第1张图片
1,绘制原理

Android的显示过程可以简单概括为:应用层把经过测量,布局,绘制后的surface缓存数据,通过SurfaceFlinger把数据显示到屏幕上。应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层。

应用层:

  • Measure:用深度优先原则递归得到所有视图的宽高。
  • Layout:用深度优先原则递归得到所有视图的位置。
  • Draw:支持两种绘制:软件绘制(CPU)和硬件加速(GPU),硬件加速Android3.0开始支持。硬件加速缺点:耗电,兼容问题,内存大。
    Android性能优化大全_第2张图片

系统层:

  • Android显示系统使用了匿名共享内存:SharedClient,每个应用和SurfaceFlinger之间都会创建一个SharedClient,最多可以创建31个SharedBufferStack集合,每个Surface都对应一个SharedBufferStack,也就是一个window。
  • 应用层绘制到缓存区,SurfaceFlinger把缓存区数据渲染到屏幕,由于两个进程不同使用了Android的匿名共享内存SharedClient。
    Android性能优化大全_第3张图片

绘制过程是CPU准备数据,通过Driver层把数据交给CPU渲染,其中CPU主要负责Measure,Layout,Record,Execute的数据计算工作,GPU负责Rasterization(栅格化)渲染。CPU和
GPU通信是通过图形驱动层(Graphics Driver)来连接两部分。图形驱动维护了一个队列CPU把display list添加到队列中,GPU从这个队列中取出数据进行绘制,最终显示出来。
Android性能优化大全_第4张图片

FPS(Frames Per Second):每秒传递的帧数,理想状态下,60FPS就感觉不到卡顿。Android系统每隔16ms发出VSYNC信号,这就意味着每个绘制时长应该在16ms以内。
Android性能优化大全_第5张图片

卡顿的根本原因:

  • 绘制任务太重,绘制一帧内容耗时太长。
  • 主线程太忙了,导致VSync信号来时还没有准备好数据导致丢帧。

主线程要做的工作:

  • UI生命周期控制
  • 系统事件处理
  • 消息处理
  • 界面布局
  • 界面绘制
  • 界面刷新

除了这些以外,尽量避免将其他处理放到主线程中,特别是复杂的数据计算和网络请求。

2,绘制分析工具

也可以看看这一篇:https://mp.weixin.qq.com/s/7cqwkEPlfyqcM1ZwByLGBA

绘制问题在某些情况下肉眼不容易发现,尽管能察觉到页面卡顿,也需要借助工具分析问题。

①,GPU呈现模式分析(Profile GPU rendering)

Profile GPU rendering是Android4.1系统开始提供的一个开发辅助功能。

  • 它是一个图形监测工具,能实时反应当前绘制的耗时。
  • 横轴表示时间,纵轴表示每一帧的耗时(单位为ms)。
  • 随着时间的推移,从左到右的刷新呈现。
  • 提供了一个标准的耗时,如果高于标准耗时,表示当前这一帧丢失。
  • 每一条柱状图都由4种颜色组成:红,黄,蓝,紫,这些线对应每一帧在不同阶段的实际耗时。
  • 蓝色代表测量绘制的时间,它代表需要多长时间去创建和更新Display List。在 Android中,一个视图在进行渲染之前,它必须被转换成GPU熟悉的格式,简单来说就是几条绘制命令,蓝色就是记录了在屏幕上更新视图需要花费的时间,也可以理解为执行每一个view的onDraw方法。
  • 红色代表执行的时间,这部分是Android进行2D渲染Display List的时间,为了绘制到屏幕上,Android需要使用OpenGl ES的API接口来绘制Display List,这些API有效地将数据发送到GPU,最终在屏幕上显示出来。当红色的线非常高时可能是由重新提交了视图而导致的。
  • 橙色部分表示处理时间,或者是CPU告诉GPU渲染一帧的地方,这是一个阻塞调用,因为CPU会一直等待GPU发出接收命令的回复,如果柱状图很高,就意味着GPU太繁忙了。
  • 紫色段表示将资源转移到渲染线程的时间,只有Android4.0及以上版本才会提供。
    Android性能优化大全_第6张图片
    总结:任何时候超过绿线(警戒线,对应时长16ms),就有可能丢失一帧的内容,虽然对于大部分应用来说,丢失几帧确实感觉不到卡顿,但保持UI流畅的关键就在于让这些垂直的柱状条尽可能保持在绿线下面。

②,过度绘制监测

过度绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了多次。在多次重叠的UI结构(如带背景的TextView)中,如果不可见的UI也在做绘制的操作,就会导致某些像素区域被绘制了多次,从而浪费多余的CPU以及GPU资源。

开发者选项中打开“调试GPU过度绘制”,如右图会在UI上呈现不同的颜色。

  • 无色:没有过度绘制,每个像素绘制1次。
  • 蓝色:每个像素多绘制1次。大片的蓝色还是可以接受的。如果整个窗口是蓝色的,可以尝试优化 减少一次绘制。
  • 绿色:每个像素多绘制了2次。
  • 淡红:每个像素多绘制了3次。一般来说,这个区域不超过屏幕的1/4是可以接受的。
  • 深红:每个像素多绘制了4次或者更多。严重影响性能,需要优化。
    Android性能优化大全_第7张图片

③,如何避免过度绘制

①,布局上优化,可以使用Hierarchy View,在AS3.0以上版本在Tools中使用Layout Inspector工具查看层级情况,尽量减少层级数量。还有使用XML布局时,会设置很多背景,如果不必要,尽量移除。
布局优化总结几点如下:

  • 移除XML中非必须的背景,或者根据条件设置。
  • 移除Window默认的背景。
  • 按需显示占位背景图片。

②,自定义View优化,在自定义View中可以通过canvas.clipRect()来帮助系统识别哪些区域可见。这样只有这个区域内才会被绘制,起到节约CPU与GPU资源。

3,布局优化工具(Layout Inspector)

AS3.0以后版本工具,操作步骤:

  • 在连接的设备或模拟器上运行应用。
  • 点击 Tools > Android > Layout Inspector。
  • 在出现的 Choose Process 对话框中,选择您想要检查的应用进程,然后点击 OK。

布局检查器会捕获快照,将它保存为 .li 文件并打开。布局检查器将显示以下内容:
Android性能优化大全_第8张图片

  • View Tree:视图在布局中的层次结构。
  • 屏幕截图:设备上显示的应用布局的屏幕截图,其中显示了每个视图的布局边界。
  • Properties Table:选定视图的布局属性。

Layout Inspector主要用分析布局的层级结构,减少不必要的层级,避免overdraw,达到渲染优化的效果。虽然界面不如HierarchyView直观,但是提供的信息也足够详细,分析布局层级绝对够用了。

4,布局层级检查

Android Lint是Android SDK Tools 16之后引入的代码检查工具,通过代码静态检查,可以发现潜在的代码问题,并给出优化建议。

检查结果分为6类:

  • Accessibility(可达性)
  • Correctness(正确性)
  • Internationalization(国际化)
  • Performance(性能)
  • Security(安全性)
  • Usability(可用性)
    Android性能优化大全_第9张图片

在AS中启动Lint,可以从菜单栏选择Analyze->Inspect Code,进行全面扫描。问题的严重程度从高到低依次:

  • Fatal
  • Error
  • Warning
  • Information
  • Ignore

对于Fatal和Error级别的问题一定要进行优化处理。另外在推荐两个代码约束检查插件FindBug(Java维度的),阿里约束插件。

5,布局优化方法

布局的好坏会影响绘制的时间,减少Layout层级,减少测量,提高复用性。

  • 布局的层级越少,加载速度越快。
  • 减少同一层级控件的数量,加载速度会变快。
  • 一个控件的属性越少,解析越快。
  • 尽量多使用RelativeLayout,LinearLayout和ConstraintLayout。
  • 将可复用的组件抽取出来并通过标签使用。
  • 使用标签加载一些不常用的布局。
  • 使用标签减少布局的嵌套层次。
  • 尽可能少用wrap_content,wrap_content会增加布局measure时的计算成本,已知宽高为固定值时,不用wrap_content。

二,内存优化

内存问题也是优化中的重重一个环节,内存占用情况也直接影响APP的性能,做好内存优化刻不容缓。

1,对象生命周期

Android性能优化大全_第10张图片

①,创建阶段(Created)

  • 为对象分配存储空间。
  • 构造对象。
  • 从超类到子类对static成员进行初始化,类的static成员的初始化在ClassLoader加载该类时进行。超类成员变量按顺序初始化,递归调用超类的构造方法。
  • 子类成员变量按顺序初始化,一旦对象被创建,子类构造方法就调用该对象并为某些变量赋值,完成后这个对象的状态就切换到了应用阶段。

②,应用阶段(InUse)

对象至少被一个强引用(Strong Reference)持有,除非在系统中显式地使用了软引用(Soft
Reference),弱引用(Weak Reference)或虚引用(Phantom Reference)。

③,不可见阶段(Invisible)

处于不可见阶段的对象在虚拟机的对象引用根集合中再也找不到直接或间接地强引用,这些对象一般是所有线程栈中的临时变量。所有已经装载的静态变量或者是对本地代码接口的引用。

④,不可达阶段(Unreachable)

对象处于不可达阶段时指该对象不再被任何强引用持有,回收器发现该对象已经不可达。

⑤,收集阶段(Collected)

当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,对象进入“收集阶段”。如果该对象已经重写了finalize()方法,则执行该方法的操作。

⑥,终结阶段(Finalized)

当对象执行完finalized()方法后仍然处于不可达状态时,该对象进入终结阶段。在该阶段,等待垃圾回收器回收该对象空间。

⑦,对象空间重新分配阶段(Deallocated)

若垃圾回收器对该对象占用的内存空间进行回收或者再分配,则该对象彻底消失,这个阶段称为“对象空间重新分配阶段”。

注意: 创建对象后,在确定不需要使用该对象时,使对象置空,这样更符合垃圾回收标准。比如Object=null,可以提高内存使用效率,并且不要采用过深的集成层次。访问本地变量优于访问类中的变量。

2,内存泄漏

在我们了解了内存分配和虚拟机GC原理后,我们知道程序运行过程中,虚拟机会对内存进行回收。内存泄漏的出现就是有一部分内存不能进行回收,也就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。

①,WebView造成的泄漏

不同安卓版本的WebView会有差异,加上不同厂商的定制ROM的WebView的差异,这就导致WebView存在很大的兼容性问题。WebView都会存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主进程进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。

②,资源对象未关闭

资源对象比如Cursor、File等,往往都用了缓冲,不使用的时候应该关闭。把引用置为null,而不关闭它们,往往会造成内存泄漏。因此,在资源对象不使用时,一定要确保它已经关闭,通常在finally语句中关闭,防止出现异常时,资源未被释放的问题。

③,集合中的对象没清理

通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就会更加严重。

④,Bitmap对象

临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。 避免静态变量持有比较大的bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量。

⑤,监听器未关闭

很多系统服务需要register和unregister监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的Listener,要记得在合适的时候及时remove这个Listener。

⑥,非静态内部类的静态实例

非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接的长期维持着外部类的引用,阻止被系统回收。

⑦,Handler内存泄漏

Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler 是非静态的,则Handler也会导致引用它的Activity或者Service不能被回收。

3,内存泄漏监控

对于内存泄漏的问题,一般情况下不容易察觉,也不会有多大问题。但是如果内存泄漏场景多,那就需要考虑了。我们可以使用LeakCanary对内存泄漏现象进行监控。

①,LeakCanary使用:

Android性能优化大全_第11张图片
测试用例:

这是经典的单利模式,第一个方法会存在内存泄漏风险。使用LeakCanary我们可以模拟内存泄漏,在demo中有一个Activity调用第一个方法,当退出Activity时就会出现内存泄漏。
Android性能优化大全_第12张图片
这就是出现内存泄漏的日志,并且LeakCanary还会保存hprof文件助于我们分析问题。
Android性能优化大全_第13张图片
使用MAT分析内存泄漏,LeakCanary生成的hprof文件需要转换才能被MAT工具打开,这里可以使用AS工具转换。

②,MAT基本概览:

Android性能优化大全_第14张图片显示了堆快照文件的大小、类、实例和ClassLoader的总数饼图中显示了当前堆快照中最大的对象。将鼠标悬停在饼图中,可以在左侧的Inspector界面中,查看该对象的相应信息在饼图中单击某对象,可以对选中的对象进行更多的操作。

Actions一栏的下面列出了MAT提供的四种Action,其中分析内存泄漏最常用的就是Histogram和Dominator Tree。我们点击Actions中给出的链接或者在MAT工具栏中就可以打开Dorminator Tree和Histogram。

Histogram(直方图)

可以通过Histogram分析,Histogram列出了每个类的实例数量,点击Action下的Histogram,得到以下结果:
Android性能优化大全_第15张图片

  • Class Name : 类名称,java类名。
  • Objects : 类的对象的数量,这个对象被创建了多少个。
  • Shallow Heap :一个对象内存的消耗大小,不包含对其他对象的引用。
  • Retained Heap : Retained Heap是该对象GC之后所能回收到内存的总和。

在这里搜索了SecondActivity,程序中写了一个出现内存泄漏的程序。在某一项上右键打开菜单选择 list objects ->with incoming refs 将列出该类的实例。对于给定一个对象,通过MAT可以找到引用当前对象的对象,即入引用(Incomming References),以及当前对象引用的对象,即出引用(Outgoing References)。
Android性能优化大全_第16张图片Android性能优化大全_第17张图片快速找出某个实例没被释放的原因,可以右健 Path to GC Roots–>exclue all phantom/weak/soft etc. reference。这里可以看到TestManager这个类的引用没有被释放。
Android性能优化大全_第18张图片
Dominator Tree(支配树)

Dorminator Tree意味支配树,从名称就可以看出Dorminator Tree更善于去分析对象的引用关系。Dominator Tree提供了一个列表。Dominator Tree:对象之间dominator关系树。如果从GC Root到达Y的的所有path都经过X,那么我们称X dominates Y,或者X是Y的Dominator 。Dominator Tree由系统中复杂的对象图计算而来。从MAT的dominator tree中可以看到占用内存最大的对象以及每个对象的dominator,如下所示:
Android性能优化大全_第19张图片对于内存抖动问题可以看这个案例。

4,内存空间优化

没有内存泄漏,并不意味着内存就不需要优化了,Android系统对每个应用进程分配了有限的堆内存,因此使用最小内存的对象或者资源可以减小内存开销,同时让GC能更高效地回收不再需要使用的对象,让应用堆内存保持充足的可用内存,使应用更稳定高效地运行。

①,对象引用

合理的利用对象引用,有助于优化内存空间占用。

  • 强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,垃圾回收器(GC)就绝不会回收它。当内存空间不足时,Java虚拟机会抛出OutOfMemoryError错误,不会回收具有强引用的对象来解决内存不足的问题。因此,如果是强引用的对象,在应用的声明周期中如果不在需要使用,一定要记得释放或转成弱引用,以便让系统回收。
  • 软引用( SoftReference ):软引用在保持引用对象的同时,保证在虚拟机报告内存不足的情况之前,清除所有的软引用。关键之处在于,垃圾收集器在运行时可能会(也可能不会)释放软引用对象。对象是否被释放取决于垃圾收集器的算法以及垃圾收集器运行时可用的内存数量。可用来实现内存敏感的高速缓存。
  • 弱引用(WeakReference):弱引用类的一个典型用途就是规范化映射(canonicalized
    mapping)。另外,对于那些生存周期相对较长,而且重新创建的开销也不高的对象来说,弱引用也比较有用。在垃圾回收器线程扫描过程中,一旦发现弱引用不管当前内存空间足够与否,都会回收它的内存。
  • 虚引用(PhantomReference):虚引用类只能用于跟踪即将对被引用对象进行的收集。虚引用必须与ReferenceQueue类联合使用。虚引用和没有任何引用一样,在任何时候都可能被垃圾收集器回收。

②,减小不必要的内存开销

  • 在Java中尽量使用基本数据类型。
  • 使用最优的数据类型(HashMap与ArrayMap)。
  • 不要大量使用枚举类型。

三,稳定性优化

稳定性优化其实是指编码本身的优化,不管任何语言,都有一些规范或者缺点。往往规范性的约定就是为了避免一些问题的出现,尤其是代码质量问题。比如同样是一个算法,可能有的效率高。这就需要我们不断的去发现和总结一些语言的特性和规范。

1,代码审查

代码审查在团队开发中也是重要的一环,有些公司会有代码评审,审核不通过不能提交。这些都是为了提升代码质量和程序的稳定性。

①,工具审查

使用Lint代码检查,另外推荐使用FindBugs工具检查Java代码。

②,人工审查

人工审查就需要制定一定的方向,如下:

  • 单一职责原则,一个模块有且只能有一个职责。
  • 开闭原则,对象对可扩展开放,对修改关闭。
  • 代码设计中实现代码复用。
  • 如果代码中包含算法,这个算法是否高效。
  • 代码风格,比如一些统一的代码规范。

③,Log审查

按时审查云端log,针对异常上报和Anr问题。

2,ANR问题

一般情况下应用无响应的时候产生一个日志文件,位于/data/anr/文件夹下面,trace文件是Android Davik虚拟机在收到异常终止信号时产生的,最常见的一个触发条件就是Android应用中产生了FC(force close)。由于该文件的产生是在DVM中的,所以只有运行DVM实例的进程才能产生该文件,也就是说只有Java代码才能产生该文件,App应用的Native层(如Android Library、用c/c++编译的库)即使异常也不会产生ANR日志文件。我们可以通过ANR产生的traces日志文件分析应用在哪里产生了ANR,以此来有效解决应用中的ANR。

为什么会产生ANR

在Android里,应用程序的响应是由ActivityManager和WindowManager服务系统服务监视的,当检测到下面三种情况的任何一种时,Android就会针对特定的应用程序显示ANR对话框。

  • Activity的UI在5秒内没有响应输入事件(例如,按键按下,屏幕触摸)–主要类型。
  • BroadcastReceiver在10秒内没有执行完毕。
  • Service在特定时间内(20秒内)无法处理完成–小概率类型。

造成ANR的原因有很多,无论是在Activity或者BroadcastReceiver还是在Service,我们看到都是在主线程中操作引起的ANR,因此我们应该避免在主线程做太多耗时的操作,网络请求不用说了,Android4.0以后就禁止在主线程成执行请求了,除此之外就是要注意如下几个方面:

  • 主线程频繁进行IO操作,比如读写文件或者数据库。
  • 硬件操作如进行调用照相机或者录音等操作。
  • 多线程操作的死锁,导致主线程等待超时。
  • 主线程操作调用join()方法、sleep()方法或者wait()方法。
  • system server中发生WatchDog ANR。
  • service binder的数量达到上限。

一般traces.txt日志输出格式如下,本实例是在主线程中强行Sleep导致的ANR日志:
Android性能优化大全_第20张图片
第1行是固定头,指明下面都是当前运行的dvm thread:“DALVIK THREADS”。
第2行输出的是改进程中各线程互斥量的值,有些手机上面可能没有这一行日志信息。
第3行输出的是线程名字(“main”),线程优先级(“prio=5”),线程id(“tid=1”),线程状态(Sleeping),比较常见的状态还有Native、Waiting。
第4行分别是线程所处的线程组(“main”),线程被正常挂起的次(“sCount=1”),线程因调试而挂起次数(”dsCount=0“),当前线程所关联的java线程对象(”obj=0x73f11000“)以及该线程本身的地址(“0xf3c25800”)。
第5行 显示线程调度信息,分别是该线程在linux系统下得本地线程id (“sysTid=2957”),线程的调度有优先级(“nice=0”),调度策略(sched=0/0),优先组属(“cgrp=default”)以及处理函数地址(“handle=0xf7770ea0”)。
第6行 显示更多该线程当前上下文,分别是调度状态(从/proc/[pid]/task/[tid]/schedstat读出)(“schedstat=( 107710942 40533261131 )”),以及该线程运行信息 ,它们是线程用户态下使用的时间值(单位是jiffies)(“utm=4”),内核态下得调度时间值(“stm=6”),以及最后运行改线程的cup标识(“core=2”)。
第7行表示线程栈的地址(“stack=0xff49d000-0xff49f000”)以及栈大小(“stackSize=8MB”)。

3,代码优化

对于代码优化和规范约束不同,这需要我们在开发中去总结,针对一些案例去分析。

①,在代码中直接创建新的Thread

   new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
     }).start();

这种的做法是非常不可取的,缺点非常的多。浪费线程资源是第一,最重要的是我们无法控制该线程的执行,因此可能会造成不必要的内存泄漏。在Activity或者Fragment这种有生命周期的控件里面直接执行这段代码,相信大部分人都知道会可能有内存泄漏。

推荐做法:利用线程池,利用第三方库如RxJava

②,频繁使用HandlerThread

HandlerThread继承于Thread类,所以每次开启一个HandlerThread就和开启一个普通Thread一样,很浪费资源。

③,Handler规范使用

 private BaseHandler mBaseHandler;

    /**
     * 初始化一个Handler,如果需要使用Handler,先调用此方法,
     * 然后可以使用postRunnable(Runnable runnable),
     * sendMessage在handleMessage(Message msg)中接收msg
     */
    public void initHandler() {
        mBaseHandler = new BaseHandler(this);
    }

    /**
     * 返回Handler,在此之前确定已经调用initHandler()
     *
     * @return Handler
     */
    public Handler getHandler() {
        return mBaseHandler;
    }

    /**
     * 同Handler的postRunnable
     * 在此之前确定已经调用initHandler()
     */
    protected void postRunnable(Runnable runnable) {
        postRunnableDelayed(runnable, 0);
    }

    /**
     * 同Handler的postRunnableDelayed
     * 在此之前确定已经调用initHandler()
     */
    protected void postRunnableDelayed(Runnable runnable, long delayMillis) {
        if (mBaseHandler == null) initHandler();
        mBaseHandler.postDelayed(runnable, delayMillis);
    }

    protected static class BaseHandler extends Handler {
        private final WeakReference mObjects;

        public BaseHandler(BaseActivity mPresenter) {
            mObjects = new WeakReference(mPresenter);
        }

        @Override
        public void handleMessage(Message msg) {
            BaseActivity mPresenter = mObjects.get();
            if (mPresenter != null)
                mPresenter.handleMessage(msg);
        }
    }

    /**
     * 同Handler 的 handleMessage,
     * getHandler.sendMessage,发送的Message在此接收
     * 在此之前确定已经调用initHandler()
     *
     * @param msg
     */
    protected void handleMessage(Message msg) {
    }

④,正确的使用Context

Context应该是每个入门Android的程序员第一个接触的概念,他代表当前上下文的环境,可以用来实现很多功能的调用,语句如下:

//获取资源管理器对象,进而可以访问到例如String,color等资源
Resources resources = context.getResources();
 
//启动指定的Activity
context.startActivity(new Intent(this, MainActivity.class));
 
//获取各种系统服务
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
 
//获取系统文件目录
File internalDir = context.getCacheDir();
File externalDir = context.getExternalCacheDir();
 
//更多...

可见,正确的理解Context的概念是很重要的,虽然在应用开发中随处可见Context的使用,但并不是所有的Context实力都具有相同的功能,在使用上需要区别对待,否则会很容易引入问题。

Context的种类

根据Context依托的组件以及用途的不同,我们可以将Context分为如下几种。

  • Application:Android应用中的默认单例类,在Activity或者Service中通过getApplication()可以获取到这个单例,通过context.getApplicationContext()可以获取到应用全局唯一的Context实例。
  • Activity/Service:这两个类都是ContextWrapper的子类,在这两个类中可以通过getBaseContext()获取到他们的Context实例,不同的Activity或者Service实例,他们的Context都是独立的,不会复用。
  • BroadcastReceiver:和Activity以及Service不同,BroadcastReceiver本身并不是Context的子类,而是在回调函数onReceive()中由Android框架传入一个Context实例。系统传入的这个Context实例都是经过功能裁剪的,他不能调用registerReceiver()以及bindService()这两个函数。
  • ContentProvider:同样的,ContentProvider也不是context的子类,但是在创建时系统会传入一个Context实例,这样在ContentProvider中可以通过调用getContext()函数获取。如果ContentProvider和调用者处于相同的应用进程中,那么getContext()将返回应用全局唯一的Context实例。如果是其他进程调用的ContentProvider,那么ContentProvider将持有自身所在进程的Context实例。

不同的Context对比
Android性能优化大全_第21张图片

  • NO[1]表示对应的组建并不是真的启动Activity,而是建议不要这么做,因为这些组件会在新的Task中创建Activity,而不是在原来的Task中。
  • NO[2]标记也是表示也不建议这么做,因为在非Activity中进行LayoutInflation,会使用系统默认的主题,而不是应用中设置的主题。
  • NO[3]标记表示在Android4.2及以上的系统上,如果注册的BroadcastReceiver是null时是可以的,用来获取sticky广播的当前值。

四,安装包大小优化

APK大小优化可以看成是非必须且重要的环节,APK包太大不仅会导致客户的流失,而且也会影响加载。

1, res资源优化

  • 只使用一套图片,使用高分辨率的图片。
  • 使用TinyPNG,对图片进行无损压缩。
  • 使用svg图片,推荐网站阿里矢量图。
  • 图片使用WebP(https://developers.google.com/speed/webp/)的格式(Facebook、腾讯、淘宝在用。)缺点:加载相比于PNG要慢很多。但是配置比较高。工具:http://isparta.github.io/
  • 使用tintcolor(android - Change drawable color programmatically)实现按钮反选效果。

2,代码优化

  • 实现功能模块的逻辑简化。
  • 移除无用的依赖库,保持代码的整洁。
  • Lint工具检查无用文件将无用的资源列在“UnusedResources: Unused resources”,删除。

3,代码混淆

使用proGuard 代码混淆器工具,它包括压缩、优化、混淆等功能。

4,assets资源优化

  • 音频文件使用有损压缩格式,比如采用opus、mp3等格式,但是最好不要使用无损压缩的音乐格式。
  • 对ttf字体文件压缩,可以采用FontCreator工具只提取出你需要的文字。比如在做日期显示时,其实只需要数字字体,但是使用原有的字体库可能需要10MB大小,如果只是把你需要的字体提取出来生成的字体文件只有10KB。

5,lib资源优化

  • 动态下载的资源。
  • 一些模块的插件化动态添加。
  • so文件的过滤和适配。

你可能感兴趣的:(android知识点,内存优化,布局优化,android,性能优化)