Android应用优化

简介: Android应用优化主要从两方面来考虑,其一是针对内存的优化,Android设备的内存相比较而言是比较珍贵,应及时回收不再使用的内存,防止内存泄露;其二是针对性能的优化,防止用户使用是出现卡顿,响应慢或ANR。

Android应用优化

Android应用优化主要从两方面来考虑,其一是针对内存的优化,Android设备的内存相比较而言是比较珍贵,应及时回收不再使用的内存,防止内存泄露;其二是针对性能的优化,防止用户使用是出现卡顿,响应慢或ANR。

性能调优Android官方有指导性的文档,以及相关的调试工具,可参考Android Developer

另外这里有一篇文章总结Android应用性能调优方案的专题,写得不错。

本文主要介绍如何使用MAT与Hierarchy View工具来进行内存优化以及如何使用Trace View进行性能调优。

内存优化

在进行内存优化前,需要明确的几个问题:

  • 如何确认内存占用量
  • 如何确认内存瓶颈或内存泄露
  • 如何优化内存占用

如果顺利解决了以上问题,则优化工作就取得比较理想的成果。

如何确认内存占用量

确认内存占用量有两种方式,

  • 通过dumpsys meminfo,可以查询系统内存使用情况,包括各个进程的内存占用量
    如:
37150 kB: Foreground
           37150 kB: com.yunos.tv.launcherdemo (pid 14884)
  • 通过Android Studio的Android面板中的Memory监控数据,可以查看所选进程的内存变化情况,如图:memory

如何确认内存泄露或内存瓶颈

内存是十分紧俏的资源,内存耗尽会导致应用体验差甚至出现OOM,因此需要以最优的方式来使用内存。

内存泄露指的是内存中残留一些对象,而这些对象在应用的后续流程中不再需要使用,但是由于被其它存活对象引用,所以其所占用的内存不能被回收,随着这种不可回收的无用对象的积累,内存会被慢慢吞噬,最终导致OutOfMemory。

确定是否存在内存泄露的方法有两种:

  • Memory监控——粗略判断

    应用运行时,通过Android Studio的Android-Memory面板可以监控内存的变化情况,如果反复在应用中执行操作会导致内存持续上涨,则基本可以断定存在内存泄露。

    内存正常的应用在使用过程中,内存不会持续高涨,在进入某个页面时,内存上涨,但是退出这个页面时,内存会回落,因此会出现锯齿状的内存监控图。

  • MAT内存分析——精确分析内存泄露点

    MAT可以很方便的分析内存情况,用于定位内存泄露点经常事半功倍。

    • MAT工具的使用(MAT插件安装请自行google)

      MAT是Memory Analysis Tools的简称,该工具用于分析hprof文件,该文件可以通过SDK工具生成,其生成方式如下:
      
      a) 在Android Studio的Android-Memory面板中,点击“Dump Java Heap”按钮,可能需要dump多次,dump成功后,会再Capture视图中生成相应的Heap Snapshot文件
      
      b) 这个文件不能直接被MAT工具识别,需要使用SDK提供的工具将其转换成标准的hprof文件,转换方式:
      
          . ${SDK_DIR}/tools/hprof-conv xxx/captures/Snapshot_2015.09.16_11.58.03.hprof yyy/targetfile.hprof
      
      c) 使用MAT工具导入转换后的hprof文件
      
    • Memory Leak Suspects

      a) 在导入Heap Dump文件时,可以选择Memory Leak Report选项
      ![LeakSuspects](http://img1.tbcdn.cn/L1/461/1/d499f073a42eae73f2288f58a521d29d93dc1f18)
      
      b) 加载完后,会进入可疑泄露点汇总,其展现形式如图:
                ![leakoverview](http://img2.tbcdn.cn/L1/461/1/9bd24d7f69026763b4c08b9074cdd5d5a4fb51b0)
      

该图会列出几个嫌疑最大的类,包括其实例的数量以及所占用内存大小,根据这些信息,可以分析这些对象包含其他那些对象以及各自的大小是多少、从该对象出发,到GC Root的路径是什么、该对象被那些对象引用或者引用了哪些对象、该对象所属类引用了其他哪些类或别其他哪些类引用,具体含义见下表:

选项 含义
List objects-with outgoing references 以该对象为起点,向外的引用,指的是该对象引用的其他对象列表
List objects-with incoming references 以该对象为起点,向内的引用,指的是该对象被哪些对象引用
Show objects by class-with outgoing references 以该对象所属类为起点,向外的引用,指的是该对象所属类引用了哪些类
Show objects by class-with incoming references 以该对象所属类为起点,向内的引用,指的是该对象所属类类被哪些类引用
Path to GC Roots-with all references 从GC Roots节点到该对象的引用路径,包含所有引用类型
Path to GC Roots-exclude weak references 从GC Roots节点到该对象的引用路径,去除所有弱引用,其他类似
Merge Shortest Paths to GC Roots-with all references 从GC Roots节点到该对象的最短引用路径,包含所有引用类型
Merge Shortest Paths to GC Roots-exclude weak references 从GC Roots节点到该对象的最短引用路径,去除所有弱引用,其他类似
Show Retained Set 列出该对象直接或间接占用的空间具体包含那些对象
  • OQL(Object Query Language)

    
    通过上述方案,怀疑某个对象存在泄露后,可以通过OQL来进一步证明。OQL可以通过查询语法,来查询具体的对象信息,主要使用的是查询某个类的实例信息。
    
    语法为:
    

    select * from instanceof Class
    例如,查询类com.yunos.tv.launchersdk.view.component.ScreenContainer的所有实例:
    select * from instanceof com.yunos.tv.launchersdk.view.component.ScreenContainer

    得到的结果如图:
    ![OQL](http://img4.tbcdn.cn/L1/461/1/5813a3e473cb3a8ade09d17eeb1b3510bd0e1f89)
    
    | Class Name | Shallow Heap | Retained Heap |
    | ------------ | ------------- | ------------- |
    | com.yunos.tv.launchersdk.view.component.ScreenContainer @ 0x443108e8| 680 | 2,712 |
    | com.yunos.tv.launchersdk.view.component.ScreenContainer @ 0x42817648| 680 | 16,289,472 |
    
    图中说明com.yunos.tv.launchersdk.view.component.ScreenContainer这个类有两个实例,两个实例占用的内存大小分别为2,712和16,289,472。
    
    如过实例的数量超出预期,说明存在内存泄露,如果实例所占内存的大小超出预期,说明这个实例内部的引用存在问题,可以进一步分析这个实例的具体信息。
    

如何优化内存占用

通过上述步骤确定哪个类的实例存在泄露或者哪个类占用的内存过多,则基本可以通过代码来排查内存泄露或内存瓶颈的位置。

内存泄露的最常见的方式是:在View中定义了非static的内部类,并将该内部类注册到了第三方接口中或注册到单例中,当View Destory时,由于外部持有该View的引用,导致这个View不能被GC回收。

常见的泄露场景以及解决方案

  • Context泄露

    Android开发中,经常需要使用Context对象,当第三方接口请求Context实例时,如果不假思索的传入当前Activity或Service,则有可能导致内存泄露
    
    解决方案:统一使用ApplicationContext
    
    1、方式一,所有使用Context的地方,都调用context.getApplicationContext()
    
    2、方式二,自定义Application,在Application中提供getContext静态方法,并将Application注册到Manifest中替换默认的Application,这样有个好处是:内部定义的接口可以不需要Context参数,直接从Application获取,另外,可以保证使用的context都是与Application的生命周期相同,不会出现泄露。
    
  • 非static内部类泄露

    Java在定义内部类时,如果该内部类不是static,则会默认携带外部类的引用。
    
    如:
    

    public class MainActivity extends Activity implements ActionBar.TabListener {

    static Leaky leak = null;
    class Leaky {
        void doSomething() {
            System.out.println("Wheee!!!");
        }
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (leak == null) {
            leak = new Leaky();
        }
    ...

    }

    
    其中Leak是MainActivity的内部类,由于是非static的,默认会持有MainActivity的引用,如图:

    Reference

    static Leaky leak = null;

    
    leak是MainActivity的静态变量,只要MainActivity一执行OnCreate就会被初始化,初始化时,该Leaky对象持有第一次执行onCreate方法时的MainActivity对象的引用,导致横竖屏切换,第二次进入onCreate时,MainActivity的第一个对象仍然不会被GC回收掉。
    
    这种类型的泄露修改方式为:将Leaky定义为static。
    

    static class Leaky {

    void doSomething() {
        System.out.println("Wheee!!!");
    }

    }

    但是在大多数情况下,内部类会调用外部类的方法,因此需要使用外部内的引用,为了避免泄露,可以使用弱引用的方式,修复方案如下:
    

    public class MainActivity extends Activity implements ActionBar.TabListener {

    static Leaky leak = null;
    static class Leaky {
        private WeakReference<MainActivity> mainActivityRef;
    
        public Leaky(MainActivity mainActivity) {
            mainActivityRef = new WeakReference<MainActivity>(mainActivity);
        }
        void doSomething() {
            System.out.println("Wheee!!!");
            MainActivity mainActivity = mainActivityRef.get();
            if(mainActivity != null) {
                mainActivity.doSomethingElse();
            }
        }
    
        public boolean statusOK() {
            return mainActivityRef.get() != null;
        }
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (leak == null || !leak.statusOK()) {
            leak = new Leaky();
        }
        ...
    }
    
    public void doSomethingElse() {
        ...
    }
    ...

    }

    非static内部类(包括匿名内部类)泄露常见于Handler,Listener,Runnable,Timer,Thread,AsyncTask,TimerTask,Callback等等
    
  • 内存瓶颈

    
    主要是占用内存的大对象,主要的优化手段为压缩算法,比如图片的压缩。
    

性能优化

这里主要指CPU的性能优化,也就是Application占用CPU时间的优化,APP的CPU性能差主要体现在用户操作时卡顿,比如进入应用时时间长,加载数据时导致主线程阻塞,刷新图片时阻塞主线程等。

主要从以下三个方面来介绍:

  • 优化工具TraceView介绍
  • 如何确定性能瓶颈
  • 如何优化

优化工具TraceView介绍

工欲善其事,必先利其器。选择一个好的工具往往能做到事半功倍。TraceView工具很强大,能快速定位出某个操作过程中,好时的操作在哪里。

在Android Studio中,使用TraceView的步骤如下:

  • 打开Android Device Monitor

    Tools -> Android -> Android Device Monitor
  • 连接ADB
  • 连接成功后,devices面板会列出系统中所有的进程列表
    devices
  • 选中需要监测的进程
  • 点击Start Method Profiling
  • 在应用中执行需要监测的操作
  • 操作完成后,点击Stop Method Profiling
  • 会自动生成并打开.trace文件,如图所示:traceview

详细使用方法可以参考[正确使用Android性能分析工具——TraceView
](http://bxbxbai.github.io/2014/10/25/use-trace-view/)Android性能调优工具TraceView介绍

如何确定性能瓶颈

Android Monitor Device打开TraceView后,就可以分析应用在执行这个操作时,各个函数所花费的时间。如图
trace_view

TraceView分为上下两个主要部分,上面部分列出了应用中运行的所有线程在这段时间内的图形化运行情况,下面部分通过函数的树状调用,显示了函数的执行时间。

下半部分的表格中,标题栏含义如下表:

序号 名称 含义
1 Incl Cpu Time % 当前函数及其调用的子函数执行时所占CPU时间百分比
2 Incl Cpu Time 当前函数及其调用的子函数一共执行时所占CPU时间
3 Excl Cpu Time % 当前函数执行时所占CPU时间百分比(不包含调用子函数的执行时间)
4 Excl Cpu Time 当前函数执行时所占CPU时间(不包含调用子函数的执行时间)
5 Incl/Excl Real Time %/NA 对比1~4,Real Time指的是实际执行时间,不包括CPU上下文切换等
6 Calls + Recur Calls / Total Call表示这个方法调用的次数,Recur Call表示递归调用次数
7 Cpu Time / Call 该函数平均执行时间
8 Real Time / Call 该函数平均执行时间,不包括CPU上下文切换等

在了解上述指标后,可以比较直观的看出各个函数执行时所占用的时间,而且能快速定位到时间最长的函数——也就是性能瓶颈。

一般可以抓住从三个方面切入:

  • 从top_level来看,其children所占用的时间最长的TOP5
  • 一类是调用次数不多,但每次调用却需要花费很长时间的函数。可以通过CPU Time / Call的排序来排查,关注调用时间最长的前几个方法的调用情况。
  • 一类是那些自身占用时间不长,但调用却非常频繁的函数。可以通过Calls + Recur Time / Total排序,可以根据其调用链来追踪调用量异常的原因。

如何优化

找到性能瓶颈后,需要分析程序的执行流程,包括正常流程和异常流程。

可能的优化措施:

  • 如果应用出现ANR,将耗时的操作放到异步线程中去完成
  • 如果数据处理过慢,考虑使用线程池
  • 如果出现CPU占用持续高涨,且某些函数调用量很高,排查是否陷入死循环
目录
相关文章
|
20天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
23小时前
|
存储 缓存 安全
Android系统 应用存储路径与权限
Android系统 应用存储路径与权限
4 0
Android系统 应用存储路径与权限
|
6天前
|
缓存 移动开发 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第18天】 在移动开发的世界中,打造一个既快速又流畅的Android应用并非易事。本文深入探讨了如何通过一系列创新的技术策略来提升应用性能和用户体验。我们将从用户界面(UI)设计的简约性原则出发,探索响应式布局和Material Design的实践,再深入剖析后台任务处理、内存管理和电池寿命优化的技巧。此外,文中还将讨论最新的Android Jetpack组件如何帮助开发者更高效地构建高质量的应用。此内容不仅适合经验丰富的开发者深化理解,也适合初学者构建起对Android高效开发的基础认识。
3 0
|
6天前
|
移动开发 Android开发 开发者
构建高效Android应用:采用Kotlin进行内存优化的策略
【4月更文挑战第18天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,由于设备和版本的多样性,确保应用流畅运行且占用资源少是一大挑战。本文将探讨使用Kotlin语言开发Android应用时,如何通过内存优化来提升应用性能。我们将从减少不必要的对象创建、合理使用数据结构、避免内存泄漏等方面入手,提供实用的代码示例和最佳实践,帮助开发者构建更加高效的Android应用。
5 0
|
7天前
|
缓存 移动开发 Java
构建高效的Android应用:内存优化策略
【4月更文挑战第16天】 在移动开发领域,尤其是针对资源有限的Android设备,内存优化是提升应用性能和用户体验的关键因素。本文将深入探讨Android应用的内存管理机制,分析常见的内存泄漏问题,并提出一系列实用的内存优化技巧。通过这些策略的实施,开发者可以显著减少应用的内存占用,避免不必要的后台服务,以及提高垃圾回收效率,从而延长设备的电池寿命并确保应用的流畅运行。
|
9天前
|
搜索推荐 开发工具 Android开发
安卓即时应用(Instant Apps)开发指南
【4月更文挑战第14天】Android Instant Apps让用户体验部分应用功能而无需完整下载。开发者需将应用拆分成模块,基于已上线的基础应用构建。使用Android Studio的Instant Apps Feature Library定义模块特性,优化代码与资源以减小模块大小,同步管理即时应用和基础应用的版本。经过测试,可发布至Google Play Console,提升用户便利性,创造新获客机会。
|
10天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
10天前
|
编解码 人工智能 测试技术
安卓适配性策略:确保应用在不同设备上的兼容性
【4月更文挑战第13天】本文探讨了提升安卓应用兼容性的策略,包括理解平台碎片化、设计响应式UI(使用dp单位,考虑横竖屏)、利用Android SDK的兼容工具(支持库、资源限定符)、编写兼容性代码(运行时权限、设备特性检查)以及优化性能以适应低端设备。适配性是安卓开发的关键,通过这些方法可确保应用在多样化设备上提供一致体验。未来,自动化测试和AI将助力应对设备碎片化挑战。
|
12天前
|
存储 数据库 Android开发
构建高效安卓应用:采用Jetpack架构组件优化用户体验
【4月更文挑战第12天】 在当今快速发展的数字时代,Android 应用程序的流畅性与响应速度对用户满意度至关重要。为提高应用性能并降低维护成本,开发者需寻求先进的技术解决方案。本文将探讨如何利用 Android Jetpack 中的架构组件 — 如 LiveData、ViewModel 和 Room — 来构建高质量的安卓应用。通过具体实施案例分析,我们将展示这些组件如何协同工作以实现数据持久化、界面与逻辑分离,以及确保数据的即时更新,从而优化用户体验并提升应用的可维护性和可测试性。
|
16天前
|
移动开发 API Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【4月更文挑战第7天】 在移动开发领域,性能优化和应用响应性的提升一直是开发者追求的目标。近年来,Kotlin语言因其简洁性和功能性在Android社区中受到青睐,特别是其对协程(Coroutines)的支持,为编写异步代码和处理并发任务提供了一种更加优雅的解决方案。本文将探讨Kotlin协程在Android开发中的应用,揭示其在提高应用性能和简化代码结构方面的潜在优势,并展示如何在实际项目中实现和优化协程。

推荐镜像

更多