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占用持续高涨,且某些函数调用量很高,排查是否陷入死循环
目录
相关文章
|
2月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
271 4
|
1月前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
74 14
|
1月前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
|
1月前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
1月前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
51 0
|
2月前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
2月前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
2月前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。
|
2月前
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
95 2
|
3月前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
125 5

热门文章

最新文章