Android程序性能设计最佳实践

Android应用应该要很快,更精确的说应该是要有效率。那就是说移动设备环境中有限的计算能力和数据存储,很小的屏幕,有限的电池寿命中要更有效率。

这篇博客我就会向你展示为性能而设计的最佳实践。

1. 避免创建对象

对象的创建在android中开销要比在java中大的多。尽量去避免创建一个对象,越多的对象意味着越多的垃圾回收,越多的垃圾回收意味着用户会觉得有点“小卡”。

一般的说,尽可能避免创建短暂的临时变量,更少的对象创建意味着更少的垃圾回收,将会提升用户体验。如果能创建一个“池”来管理对象的话,那是最好的了。

2. 将阻塞操作从UI线程中剥离出来

使用AsyncTask, Thread, IntentService,或者简单的后台Service去做大开销的操作。使用Loaders去简化管理很长时间加载数据的状态就像一个指针。如果一个操作需要消耗时间和资源,那就放到另外的进程中异步进行,这样你的程序就能够继续响应并且用户也可以进行操作。

3. 使用Native方法

同样一个Java循环,C/C++代码可以快10到100倍。

4. 实现优于接口

假设你有一个HashMap对象,你可以使用通用的Map来声明这个HashMap:

Map myMap1 = new HashMap();

HashMap myMap2 = new HashMap();

哪一种更好?

传统的观点认为你应该使用Map,因为它允许你更改为只要实现了Map接口的底层实现。传统观点对于常规编程是正确的,但是对于嵌入式系统来说就不是那么好了。调用一个接口引用的方法相对于调用一个固定实现的引用要多花费2倍的时间。

如果你HashMap能处理你要做的事情,那么使用Map来申明就没有一点价值了。让IDE帮你重构你的代码,就算你对代码还没有头绪,使用Map也是没什么价值的。(当然,公共的API可能有不同:一个好的API肯定胜过小的性能问题)

5. 静态方法更好

如果你的方法不需要访问一个对象的变量,那么将你的方法改为静态的。调用起来会快很多,因为它将不需要经过virtual method table。这也是一个很好的实践,因为你可以告诉方法签名在调用方法的时候不会更改对象的状态。

6. 避免使用内部的Getters/Setters

在像C++这种语言中,经常会见到getters(比如 i=getCount())来代替直接访问变量(i=mCount)。这是一个非常好的习惯,因为编译器会使用内联访问,而且如果你相对字段做约束或者进行调试的时候,你可以随时的修改代码。

在Android中,这是一个不好的想法。虚拟函数的调用开销很大,远远超过了变量的访问。如果是一个类,你应该直接访问变量,如果是一个公共接口,还是尽量遵循面向对象编程的实践使用Getters/Setters。

7. 常量声明Final

考虑下面的变量声明:

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

编译器生成一个类的构造方法,叫<clinit>,它会在类第一次使用的时候触发。方法会将42保存到intVal,从String表里获取一个引用给strVal。当这些值被引用后,他们访问时就能够去查找了。

我们可以使用final关键字来提升性能:

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

类文件不再需要<clinit>方法了,因为常量转为了虚拟机进行处理。代码访问intVal的时候直接就能拿到42,访问strVal的时候开销也会相对更小。

声明类或者方法final并不能获得性能上的好处,不够能够有其他的效果。比如,如果不想让子类重写getter方法,那么就可以声明final。

你也可以本地变量final。然而这不会有任何的性能效益。对于本地变量,使用final仅仅能让代码语义更加清晰(或者你可以让匿名类访问到这个变量)。

8. 小心使用增强的for循环

增强行的for循环(也可以说是for-each循环)可以使用在任何实现了iterable接口的集合上。对于这些对象,iterator会调用hasNext()和next()接口来创建,对于ArrayList,你最好避开这种方式,不过对于其他种类的集合,增强型for循环和显式的使用迭代循环相差无几。

下面代码展示了增强型的for循环:

public class Foo {
    int mSplat;
    static Foo mArray[] = new Foo[27];

    public static void zero() {
        int sum = 0;
        for (int i = 0; i < mArray.length; i++) {
            sum += mArray[i].mSplat;
        }
    }

    public static void one() {
        int sum = 0;
        Foo[] localArray = mArray;
        int len = localArray.length;

        for (int i = 0; i < len; i++) {
            sum += localArray[i].mSplat;
        }
    }

    public static void two() {
        int sum = 0;
        for (Foo a: mArray) {
            sum += a.mSplat;
        }
    }
}

zero()检索static变量两次,而且每次循环都会获取数组的长度。

one()将所有东西就放到了临时变量中,避免了查找。

two()使用1.5版本java的增强型for循环,由编译器来生成代码,复制数组引用和数组长度到本地变量,产生一个好的访问数组方案,它会产生一个额外的本地加载存储(保存a这个对象),它会比one()要慢一点点。

总结就是:增强型for循环有更好的语义和代码结构,但是要谨慎使用它,因为有可能会有额外的创建对象开销。

9. 避免Enums

枚举非常的方便,但是不幸的是他的速度的大小都是让人痛苦的。比如说:

public class Foo {
   public enum Shrubbery { GROUND, CRAWLING, HANGING }
}

会编译成900byte的.class文件 (Foo$Shrubbery.class)。当第一次使用,类会调用初始化函数<init>来对每一个枚举值创建对象。每一个对象都有自己的静态变量,而且全部都保存在一个数组中(一个叫"$VALUES"的静态变量)。这么多的代码,仅仅只是为了三个整数。

下面这句代码:

Shrubbery shrub = Shrubbery.GROUND;

一个静态变量的查找。如果"GROUND"是一个静态的int常量,编译器会把它当做一个已知常量并且使用内联。

从另一个方面说,你当然会从枚举类型中获得很多好用的API和编译时的检查。所以,通常需要这样来权衡:你应该在所有公共API的地方尽量使用枚举类型,不过在性能重要的时候,尽量避免使用。

在一些环境中,通过ordinal()方法来获取enum的数值很有用,比如:

for (int n = 0; n < list.size(); n++) {
    if (list.items[n].e == MyEnum.VAL_X)
       // do stuff 1
    else if (list.items[n].e == MyEnum.VAL_Y)
       // do stuff 2
}

然后:

int valX = MyEnum.VAL_X.ordinal();
int valY = MyEnum.VAL_Y.ordinal();
int count = list.size();
MyItem items = list.items();

for (int  n = 0; n < count; n++)
{
     int  valItem = items[n].e.ordinal();

     if (valItem == valX)
       // do stuff 1
     else if (valItem == valY)
       // do stuff 2
}

在一些案例中,这将会更快,尽管这没有保证。

10. 对内部类使用Package访问权限

考虑一下下面的类定义:

public class Foo {
    private int mValue;

    public void run() {
        Inner in = new Inner();
        mValue = 27;
        in.stuff();
    }

    private void doStuff(int value) {
        System.out.println("Value is " + value);
    }

    private class Inner {
        void stuff() {
            Foo.this.doStuff(Foo.this.mValue);
        }
    }
}

关键的事事:我们定义了一个内部类(Foo$Inner)直接访问了外部类的private方法和private实例。这是合法的,而且代码打印出了我们期望的"Value is 27"。

问题是Foo$Inner在技术上来说,是一个完全独立的类。它来直接访问Foo的私有成员是违法的。为了弥补这个问题,编译器会生成下面的方法:

/*package*/ static int Foo.access$100(Foo foo) {
    return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
    foo.doStuff(value);
}

内部类代码会调用方法来访问外部的mValue或者调用外部的doStuff方法。这意味着上面的代码真正的情况是,你是通过的访问器访问而不是通过直接访问的代码。之前我们就讨论过了使用访问器要比直接访问变量更慢,所以这就是一个特定的语言习惯形成的一种"隐形"的性能影响。

我们可以通过声明package访问空间而不是private访问空间来避免这个问题。这将会运行的更快,而且避免了生成方法产生的开销。(不幸的是,这可能让在同一个package下的其他包能够直接访问这个变量,这违反了面向对象的设计思想。再次说明一下,如果你要设计一个公共的API,你就要仔细考虑一下了。)

11. 避免Float

在奔腾CPU发布之前,几乎所有的游戏开发者都会尽量使用整数运算。奔腾CPU将浮点数协处理器设置成了内置功能。通过交叉整数和浮点操作使游戏比以前纯整数运算的时候要快。桌面程序现在常见的做法是随意使用float。

不幸的是,嵌入式处理器通常没有浮点支持,所以所有的float和double操作开销都很大。一些基本的浮点操作可以在一毫秒的顺序完成。

同样,即使是整数,一些芯片支持乘法,但缺乏除法。在这种情况下,在软件执行整数除法和模量操作时,想想如果你设计一个哈希表或做大量的数学。

12. 一些简单的性能数字

为了说明我们的一些想法,我们对一些基本的行为列出了近似运行时间。请注意,这些值不应被视为绝对数字:他们是CPU和时钟时间的组合,并且对于系统的改进将变化。值得注意的是这些值是相对于彼此——例如,添加一个成员变量目前需要的时间大约是添加一个本地变量的四倍。


Action

Time

Add a local variable

1

Add a member variable

4

Call String.length()

5

Call empty static native method

5

Call empty static method

12

Call empty virtual method

12.5

Call empty interface method

15

Call Iterator:next() on a HashMap

165

Call put() on a HashMap

600

Inflate 1 View from XML

22,000

Inflate 1 LinearLayout containing 1 TextView

25,000

Inflate 1 LinearLayout containing 6 View objects

100,000

Inflate 1 LinearLayout containing 6 TextView objects

135,000

Launch an empty activity

3,000,000

13. 总结

写出好的、有效的代码的最好的方式,就是去理解你的代码到底作了什么。如果你真的想要在List上通过迭代器使用增强的for循环来访问;让它成为一个深思熟虑的选择,而不是成为副作用。

俗话说有备无患!知道你引入了什么,插入一些你最喜欢的东西混合在一起也可以,不过必须慎重考虑你的代码在做什么,然后找机会去提升它的速度。

时间: 2024-10-12 22:36:16

Android程序性能设计最佳实践的相关文章

【读书笔记】《Android应用性能优化最佳实践》

<第一行代码>读书笔记 一.引言 二.读书内容 书名:<Android应用性能优化最佳实践> 作者:罗彧成 (腾讯音乐Android开发总监) 出版社:机械工业出版社 封面: 三.书籍评价 四.个人心得 五.参考文档

atitit.基于http json api 接口设计 最佳实践 总结o7

atitit.基于http  json  api 接口设计 最佳实践 总结o7 1. 需求:::serverand android 端接口通讯 2 2. 接口开发的要点 2 2.1. 普通參数 meth,param, 2 2.2. 全部的參数定义 2 2.3. key,dynami key)韩式 static key? 2 2.4. 防篡改 sign 2 2.5. Encry加密 3 2.6. zip压缩:: 3 2.7. 首先压缩韩式加密??? 3 3. 选型大全:rim ,ws, http 

前端性能优化最佳实践

最佳实践1:使用DocumentFragments或innerHTML取代复杂的元素注入 DOM操作在浏览器上是要付税的.尽管性能提升是在浏览器,DOM很慢,如果你没有注意到,你可能会察觉浏览器运行非常的慢.这就是为什么减少创建集中的DOM节点以及快速注入是那么的重要了. 现在假设我们页面中有一个<ul>元素,调用AJAX获取JSON列表,然后使用JavaScript更新元素内容.通常,程序员会这么写: Javascript代码 var list = document.querySelecto

提高 Web 站点性能的最佳实践

本文内容 提高 Web 站点性能的最佳实践 最大限度减少 HTTP 请求 使用内容分发网络(CDN) 添加 Expires 或 Cache – Control 头 Gzip 组件 CSS 放在页面顶部 JavaScript 放在页面底部 避免 CSS 表达式 使用外部 JavaScript 和 CSS 减少 DNS 查询 精简 JavaScript 和 CSS 避免重定向 删除重复的脚本 配置 ETags 使得 Ajax 可缓存 尽早强制地发送缓冲给客户端 用 GET 发送 Ajax 请求 延迟

转 Web程序优化的最佳实践:Cookie、图片及移动应用篇

[编者按]来自Yahoo!的Exceptional Performance团队为我们带来了改善Web性能的最佳实践方案.为此,他们为此进行了 一系列的实验.开发了各种工具.写了大量的文章和博客并在各种会议上参与探讨.最佳实践的核心就是提高网站性能.通过各种努力,xcetional Performance团队总结出了一系列可以提高网站速度的方法.可以分为 7 大类 34 条.包括内容.服务器.cookie.CSS.JavaScript.图片.移动应用等七部分. 延伸阅读: Web程序优化的最佳实践

转 Web程序优化的最佳实践:JavaScript和CSS篇

Yahoo!的Exceptional Performance团队为改善Web性能带来最佳实践.他们为此进行了 一系列的实验.开发了各种工具.写了大量的文章和博客并在各种会议上参与探讨.最佳实践的核心就是旨在提高网站性能. Excetional Performance 团队总结出了一系列可以提高网站速度的方法.可以分为 7 大类 34 条.包括内容.服务器.cookie.CSS.JavaScript.图片.移动应用等七部分. 本文为CSS和Javascript部分: 除此之外,JavaScript

Google Developing for Android 二 - Memory 最佳实践 // lightSky‘Blog

Google Developing for Android 二 - Memory 最佳实践 |   分类于 Android最佳实践 原文:Developing for Android, II The Rules: Memory 在决定应用的行为,是否有好的用户体验以及整体的设备体验来说,内存的使用可能是独立因素中最重要的.内存因素包括应用的内存占用,以及内存搅动(导致的垃圾回收会对运行期间的性能有影响). 避免在循环中分配内存 内存分配虽然不可避免,但是应尽可能的避免,特别是在平凡的调用的代码块

RESTful API 设计最佳实践(转)

摘要:目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API格式如何?你的API是否应该加入版本信息? 背景 目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API格式如何?你的API是否应该加入版本信息?当你开始写一个app的时候,特别是后端模型部分已经写完

RESTful API 设计最佳实践

1. 背景 REST(英文:Representational State Transfer,表述性状态转移)描述了一个架构样式的网络系统,比如 web 应用程序. 目前互联网上充斥着大量的关于RESTful API(为方便,下文中"RESTful API "简写为"API")如何设计的文章,然而却没有一个"万能"的设计标准:如何鉴权?API 格式如何?你的API是否应该加入版本信息?当你开始写一个app的时候,特别是后端模型部分已经写完的时候,你