Android性能优化<一>

2017-03-24  本文已影响36人  SmartisanBool

android 性能优化

<a name="简介"></a>

简介

性能优化是开发任何软件比较重要和困难的一点,特别是在android应用上,受硬件和系统的限制比较大,随着整个系统的不断发展,性能问题就越发突出,在移动端的直接体验就是app卡顿臃肿。

现代计算机科学鼻祖曾说过“过早的优化是万恶之源”。我觉得在移动端优化不是过早或者过晚的问题,因为android性能问题大多是由于内存泄漏和不能很好的利用和规避android系统一些特性造成的。工作中按照这些技巧来编写程序,就能尽可能的避免让app越发臃肿的问题。

接下来就从内存的管理、高性能的编码、合理使用android特性以及内存的分析及工具四个方面探讨android性能优化问题。

<a name="内存的管理和分析"></a>

内存的管理和分析

<a name="当界面不可见时释放内存"></a>

当界面不可见时释放内存

程序中怎么样知道界面已是或将是不可见了呢?Activity中有两个回调方法:onStop()和onTrimMemory()方法。

onStop()方法当用从A Activity跳转到B Activity时,就会回调A的onStop()方法,这时候可以在onStop方法中进行取消网络连接、注销广播接收器、关闭数据库、关闭计时器等操作。如下所示:

protected void onStop() {  
    super.onStop();
    //注销广播接收器
    unregisterReceiver(mBroadcastReceiver);
    //取消网络请求
    NetManager.cancleRequest(this);
}

onTrimMemory()方法当用户已经离开我们的程序时回调,此时可以进行UI相关资源的回收,比如本页面使用的大图片。如下所示:

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    switch (level) {
    case TRIM_MEMORY_UI_HIDDEN:
        ImageLoader.getInstance().clearMemory();****
        break;
    }
}

<a name="当内存紧张时释放内存"></a>

当内存紧张时释放内存

onTrimMemory()方法还有很多种其它类型的回调,可以在手机内存降低的时候及时通知我们。我们应该根据回调中传入的级别来去决定如何释放应用程序的资源。此外,当程序正常运行和在缓存中时,回调的等级是不同的,应该分开分析:

<a name="避免在Bitmap上浪费内存"></a>

避免在Bitmap上浪费内存

OOM问题大多由于加载图片造成,因为一些图片加载时如果不注意,很可能瞬间突破内存界限,造成OOM,所以如果应用中涉及到图片加载时,应多注意以下几点:

<a name="使用优化过的数据集合"></a>

使用优化过的数据集合

Android API当中提供了一些优化过后的数据集合工具类,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用这些API可以让我们的程序更加高效。传统Java API中提供的HashMap工具类会相对比较低效,因为它需要为每一个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间,此外更重要的是提高了内存效率。

<a name="知晓内存的开支情况"></a>

知晓内存的开支情况

我们还应当清楚我们所使用语言的内存开支和消耗情况,并且在整个软件的设计和开发当中都应该将这些信息考虑在内。可能有一些看起来无关痛痒的写法,结果却会导致很大一部分的内存开支,例如:

<a name="尽量避免使用依赖注入框架"></a>

尽量避免使用依赖注入框架

Android工程当中使用依赖注入框架,比如说像Guice或者RoboGuice等,因为它们可以简化一些复杂的编码操作,但是这些框架为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且还可能将一些你用不到的对象也一并加载到内存当中。这些用不到的对象会一直占用着内存空间,可能要过很久之后才会得到释放,相较之下,也许多敲几行看似繁琐的代码才是更好的选择。

<a name="使用ProGuard简化代码"></a>

使用ProGuard简化代码

ProGuard这个工具来混淆代码,但是除了混淆之外,它还具有压缩和优化代码的功能。ProGuard会对我们的代码进行检索,删除一些无用的代码,并且会对类、字段、方法等进行重命名,重命名之后的类、字段和方法名都会比原来简短很多,这样的话也就对内存的占用变得更少了。

<a name="高性能的编码"></a>

高性能的编码

编码的优化只能算是"微优化",不会有显著的性能提升,但平时写代码时注意一些,然后就会潜移默化在微观层次上提升程序的性能,而且代码也更加专业。

<a name="避免创建不必要的对象"></a>

避免创建不必要的对象

创建对象从来都不应该是一件随意的事情,因为创建一个对象就意味着垃圾回收器需要回收一个对象,而这两步操作都是需要消耗时间的。虽说创建一个对象的代价确实非常小,GC操作时的停顿时间也变得难以察觉,但是这些理由都不足以让我们可以肆意地创建对象,需要创建的对象我们自然要创建,但是不必要的对象我们就应该尽量避免创建。下面列举一些可以避免创建对象的场景:

<a name="静态优于抽象"></a>

静态优于抽象

如果并不需要访问一个对象中的某些字段,只是想调用它的某个方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,这会让调用的速度提升15%-20%,同时也不用为了调用这个方法而去专门创建对象了,这样还满足了上面的一条原则。另外这也是一种好的编程习惯,因为我们可以放心地调用静态方法,而不用担心调用这个方法后是否会改变对象的状态(静态方法内无法访问非静态字段)。

<a name="对常量使用static_final修饰符"></a>

对常量使用static final修饰符

static int intVal = 42;  
static String strVal = "Hello, world!";

编译器会对上述代码生成初始化方法<clinit>主要用来第一次创建时对字段进行赋值,之后访问字段时采取字段搜索的方式访问。如果使用final修饰如下:

static final int intVal = 42;  
static final String strVal = "Hello, world!";

经过这样修改之后,定义类就不再需要一个<clinit>方法了,因为所有的常量都会在dex文件的初始化器当中进行初始化。而调用时会用一种相对轻量级的字符串常量方式,而不是字段搜寻的方式。

<a name="多使用系统封装好的API"></a>

多使用系统封装好的API

Java语言当中其实给我们提供了非常丰富的API接口,我们在编写程序时如果可以使用系统提供的API就应该尽量使用,系统提供的API完成不了我们需要的功能时才应该自己去写,因为使用系统的API在很多时候比我们自己写的代码要快得多,它们的很多功能都是通过底层的汇编模式执行的。

比如说String类当中提供的好多API都是拥有极高的效率的,像indexOf()方法和一些其它相关的API,虽说我们通过自己编写算法也能够完成同样的功能,但是效率方面会和这些方法差的比较远。这里举个例子,如果我们要实现一个数组拷贝的功能,使用循环的方式来对数组中的每一个元素一一进行赋值当然是可行的,但是如果我们直接使用系统中提供的System.arraycopy()方法将会让执行效率快9倍以上

<a name="避免在内部调用Getters/Setters方法"></a>

避免在内部调用Getters/Setters方法

我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。不过我们肯定不能仅仅因为效率的原因就将封装的技巧给抛弃了,编写代码还是要按照面向对象思维的,但是我们可以在能优化的地方进行优化,比如说避免在内部调用getters/setters方法。如下:

public class Calculate {  
  
    private int one = 1;  
  
    private int two = 2;  

    public int getOne() {  
        return one;  
    }  

    public int getTwo() {  
     return two;  
    }  
  
    public int getSum() {  
        return getOne() + getTwo();  
    }  
}  

改成:

public class Calculate {  
  
    private int one = 1;  
  
    private int two = 2;  

 ......  
  
    public int getSum() {  
       return one + two;  
    }  
}  

<a name="合理使用android特性"></a>

合理使用android特性

合理使用android的特性,也会更加实际性的优化应用,例如布局优化、合理使用Activity生命周期等

<a name="重用布局文件"></a>

重用布局文件

使用<include>和<merge>这两个非常有用的标签,避免布局重写重用。

<a name="仅在需要时才加载布局"></a>

仅在需要时才加载布局

有的时候我们会遇到这样的场景,就是某个布局当中的元素非常多,但并不是所有元素都一起显示出来的,而是普通情况下只显示部分常用的元素,而那些不常用的元素只有在用户进行特定操作的情况下才会显示出来.例如使用ViewStub标签。

<a name="合理利用Activity生命周期"></a>

合理利用Activity生命周期

<a name="内存的分析及工具使用"></a>

内存的分析及工具使用

虽然有很多方法避免内存泄漏,但难免没有漏网之鱼,所以合理的分析内存使用情况是在后续优化时非常重要的一个方法。接下来首先了解系统GC的过程,然后,了解几种查找内存泄漏的工具。

<a name="了解系统GC"></a>

了解系统GC

GC全称是Garbage Collection,也就是所谓的垃圾回收。Android系统会在适当的时机触发GC操作,一旦进行GC操作,就会将一些不再使用的对象进行回收。当一个对象不存在其他对象对他有引用时,就会被GC掉。

那么什么时候会触发GC操作呢?这个通常都是由系统去决定的,我们一般情况下都不需要主动通知系统应该去GC了(虽然我们确实可以这么做,下面会讲到),但是我们仍然可以去监听系统的GC过程,以此来分析我们应用程序当前的内存状态。那么怎样才能去监听系统的GC过程呢?其实非常简单,系统每进行一次GC操作时,都会在LogCat中打印一条日志,我们只要去分析这条日志就可以了,日志的基本格式如下所示:

D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>,  <Pause_time>  

首先第一部分GC_Reason,这个是触发这次GC操作的原因,一般情况下一共有以下几种触发GC操作的原因:

接下来第二部分Amount_freed,表示系统通过这次GC操作释放了多少内存。

然后Heap_stats中会显示当前内存的空闲比例以及使用情况(活动对象所占内存 / 当前程序总内存)。

最后Pause_time表示这次GC操作导致应用程序暂停的时间。

<a name="DDMS查看内存使用及检测内存泄漏"></a>

DDMS查看内存使用及检测内存泄漏

如果我们想要更加清楚地实时知晓当前应用程序的内存使用情况,只通过日志就有些力不从心了,我们需要通过DDMS中提供的工具来实现。

打开DDMS界面,在左侧面板中选择你要观察的应用程序进程,然后点击Update Heap按钮,接着在右侧面板中点击Heap标签,之后不停地点击Cause GC按钮来实时地观察应用程序内存的使用情况即可,如下图所示:

MacDown logo

接着继续操作我们的应用程序,然后继续点击Cause GC按钮,如果你发现反复操作某一功能会导致应用程序内存持续增高而不会下降的话,那么就说明这里很有可能发生内存泄漏了。

<a name="使用Eclipse_Memory_Analyzer(MAT)定位内存泄漏的位置"></a>

使用Eclipse Memory Analyzer(MAT)定位内存泄漏的位置

通过GC日志以及DDMS工具这两种方式,可以比较轻松地发现应用程序中是否存在内存泄露的现象。但如果真的出现了内存泄露,我们应该怎么定位到具体是哪里出的问题呢?这就需要借助一个内存分析工具了,叫做Eclipse Memory Analyzer(MAT)。下载地址: :http://eclipse.org/mat/downloads.php(适用于studio)

<a name="使用lint对代码进行优化"></a>

使用lint对代码进行优化

我们总希望发布的apk文件越小越好,不希望资源文件没有用到的图片资源也被打包进apk,不希望应用中使用了高于minSdk的api,也不希望AndroidManifest文件存在异常,lint就能解决我们的这些问题。Android lint是在ADT 16提供的新工具,它是一个代码扫描工具,能够帮助我们识别代码结构存在的问题,主要包括:

上一篇下一篇

猜你喜欢

热点阅读