zoukankan      html  css  js  c++  java
  • Android之——性能与内存优化

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46987951
    

    写出高效代码的两条主要的原则:(1)不要做不必要的事;(2)不要分配不必要的内存。

    1. 内存优化

          Android系统对每一个软件所能使用的RAM空间进行了限制(如:Nexus one 对每一个软件的内存限制是24M),同一时候Java语言本身比較消耗内存,dalvik虚拟机也要占用一定的内存空间。所以合理使用内存,彰显出一个程序猿的素养和技能。


    1) 了解JIT

      即时编译(Just-in-time Compilation。JIT),又称动态转译(Dynamic Translation),是一种通过在执行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。即时编译前期的两个执行时理论是字节码编译和动态编译。Android原来Dalvik虚拟机是作为一种解释器实现。新版(Android2.2+)将换成JIT编译器实现。性能測试显示,在多项測试中新版本号比旧版本号提升了大约6倍。

    2) 避免创建不必要的对象

      就像世界上没有免费的午餐,世界上也没有免费的对象。

    尽管gc为每一个线程都建立了暂时对象池。能够使创建对象的代价变得小一些。可是分配内存永远都比不分配内存的代价大。假设你在用户界面循环中分配对象内存。就会引发周期性的垃圾回收。用户就会认为界面像打嗝一样一顿一顿的。所以,除非必要。应尽量避免尽力对象的实例。以下的样例将帮助你理解这条原则:当你从用户输入的数据中截取一段字符串时。尽量使用substring函数取得原始数据的一个子串,而不是为子串另外建立一份拷贝。

    这样你就有一 个新的String对象,它与原始数据共享一个char数组。 假设你有一个函数返回一个String对象,而你确切的知道这个字符串会被附加到一个StringBuffer。那么。请改变这个函数的參数和实现方式。直接把结果附加到StringBuffer中,而不要再建立一个短命的暂时对象。
      一个更极端的样例是。把多维数组分成多个一维数组:
      int数组比Integer数组好,这也概括了一个基本事实。两个平行的int数组比 (int,int)对象数组性能要好非常多。

    同理,这试用于全部基本类型的组合。假设你想用一种容器存储(Foo,Bar)元组,尝试使用两个单独的 Foo[]数组和Bar[]数组。一定比(Foo,Bar)数组效率更高。(也有例外的情况。就是当你建立一个API。让别人调用它的时候。这时候你要注重对API接口的设计而牺牲一点儿速度。

    当然在API的内部,你仍要尽可能的提高代码的效率)
      整体来说,就是避免创建短命的暂时对象。降低对象的创建就能降低垃圾收集,进而降低对用户体验的影响。


    3)静态方法取代虚拟方法

      假设不须要訪问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法。由于你能够从方法声明中看出调用该方法不须要更新此对象的状态。


    4)避免内部Getters/Setters

      在源生语言像C++中,通常做法是用Getters(i=getCount())取代直接字段訪问(i=mCount)。这是C++中一个好的习惯,由于编译器会内联这些訪问,而且假设须要约束或者调试这些域的訪问,你能够在不论什么时间加入代码。而在Android中,这不是一个好的做法。虚方法调用的代价比直接字段訪问高昂很多。通常依据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的。但在一个字段常常被訪问的类中宜採用直接訪问。

    无JIT时,直接字段訪问大约比调用getter訪问快3倍。有JIT时(直接訪问字段开销等同于局部变量訪问),要快7倍。

    5)将成员缓存到本地

    訪问成员变量比訪问本地变量慢得多,以下一段代码:

    for(int i =0; i <this.mCount; i++)  {
    	dumpItem(this.mItems);
    }
    最好改成这样:

    int count = this.mCount;
    Item[] items = this.mItems;
    for(int i =0; i < count; i++)  {
         dumpItems(items);
    }
             还有一个相似的原则是:永远不要在for的第二个条件中调用不论什么方法。

    如以下方法所看到的,在每次循环的时候都会调用getCount()方法,这样做比你在一个int先把结果保存起来开销大非常多。还有一个相似的原则是:永远不要在for的第二个条件中调用不论什么方法。如以下方法所看到的,在每次循环的时候都会调用getCount()方法,这样做比你在一个int先把结果保存起来开销大非常多。

    for(int i =0; i < this.getCount(); i++) {
    	dumpItems(this.getItem(i));
    }
    相同假设你要多次訪问一个变量。也最好先为它建立一个本地变量,比如:

    protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
    	if(isHorizontalScrollBarEnabled()) {
    		intsize = mScrollBar.getSize(false);
    		if(size <=0) {
           		size = mScrollBarSize;
    		}
    		mScrollBar.setBounds(0, height - size, width, height);
    		mScrollBar.setParams(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(),false);
    		mScrollBar.draw(canvas);
    	}
    } 
    这里有4次訪问成员变量mScrollBar。假设将它缓存到本地。4次成员变量訪问就会变成4次效率更高的栈变量訪问。

    另外就是方法的參数与本地变量的效率同样。

    6)对常量使用static final修饰符

      让我们来看看这两段在类前面的声明:

    static int intVal = 42;
    static String strVal = "Hello, world!";
            必以其会生成一个叫做clinit的初始化类的方法,当类第一次被使用的时候这种方法会被运行。方法会将42赋给intVal,然后把一个指向类中常量表 的引用赋给strVal。当以后要用到这些值的时候,会在成员变量表中查找到他们。 以下我们做些改进,使用“final”keyword:
    static final int intVal = 42;
    static final String strVal = "Hello, world!";
    如今。类不再须要clinit方法,由于在成员变量初始化的时候。会将常量直接保存到类文件里。用到intVal的代码被直接替换成42,而使用strVal的会指向一个字符串常量。而不是使用成员变量。


      将一个方法或类声明为final不会带来性能的提升,可是会帮助编译器优化代码。举例说,假设编译器知道一个getter方法不会被重载,那么编译器会对其採用内联调用。


      你也能够将本地变量声明为final,相同。这也不会带来性能的提升。

    使用“final”仅仅能使本地变量看起来更清晰些(可是也有些时候这是必须的,比方在使用匿名内部类的时候)。

    7)使用改进的For循环语法

      改进for循环(有时被称为for-each循环)可以用于实现了iterable接口的集合类及数组中。

    在集合类中。迭代器让接口调用 hasNext()和next()方法。

    在ArrayList中。手写的计数循环迭代要快3倍(不管有没有JIT),但其它集合类中。改进的for循环语 法和迭代器具有同样的效率。以下展示集中訪问数组的方法:

    static class Foo {
            int mSplat;
        }
        Foo[] mArray = ...
        public void zero() {
            int sum = 0;
            for (int i = 0; i < mArray.length; ++i) {
                sum += mArray[i].mSplat;
            }
        }
        public void one() {
            int sum = 0;
            Foo[] localArray = mArray;
            int len = localArray.length;
      
            for (int i = 0; i < len; ++i) {
                sum += localArray[i].mSplat;
            }
        }
        public void two() {
            int sum = 0;
            for (Foo a : mArray) {
                sum += a.mSplat;
            }
    	}
    }
            在zero()中,每次循环都会訪问两次静态成员变量,取得一次数组的长度。


      在one()中,将全部成员变量存储到本地变量。
      two()使用了在java1.5中引入的foreach语法。

    编译器会将对数组的引用和数组的长度保存到本地变量中,这对訪问数组元素很好。 可是编译器还会在每次循环中产生一个额外的对本地变量的存储操作(对变量a的存取)这样会比one()多出4个字节,速度要略微慢一些。

    8)避免使用浮点数

      通常的经验是。在Android设备中,浮点数会比整型慢两倍。在缺少FPU和JIT的G1上对照有FPU和JIT的Nexus One中确实如此(两种设备间算术运算的绝对速度差大约是10倍)从速度方面说,在现代硬件上,float和double之间没有不论什么不同。更广泛的讲,double大2倍。

    在台式机上,因为不存在空间问题,double的优先级高于float。但即使是整型。有的芯片拥有硬件乘法,却缺少除法。这样的情况下。整型除法和求模运算是通过软件实现的,就像当你设计Hash表,或是做大量的算术那样,比如a/2能够换成a*0.5。

    9)了解并使用类库

      选择Library中的代码而非自己重写。除了通常的那些原因外,考虑到系统空暇时会用汇编代码调用来替代library方法,这可能比JIT中生成的等价的最好的Java代码还要好。



               i. 当你在处理字串的时候,不要吝惜使用String.indexOf(),String.lastIndexOf()等特殊实现的方法。这些方法都是使用C/C++实现的,比起Java循环快10到100倍。



               ii.System.arraycopy方法在有JIT的Nexus One上,自行编码的循环快9倍。

               iii.android.text.format包下的Formatter类,提供了IP地址转换、文件大小转换等方法;DateFormat类,提供了各种时间转换,都是很高效的方法。
          具体请參考 http://developer.android.com/reference/android/text/format/package-summary.html
               iv.TextUtils类
          对于字符串处理Android为我们提供了一个简单有用的TextUtils类,假设处理比較简单的内容不用去思考正則表達式最好还是试试这个在android.text.TextUtils的类,具体请參考http://developer.android.com/reference/android/text/TextUtils.html
                v.    高性能MemoryFile类。


           非常多人抱怨Android处理底层I/O性能不是非常理想。假设不想使用NDK则能够通过MemoryFile类实现高性能的文件读写操作。MemoryFile适用于哪些地方呢?对于I/O须要频繁操作的,主要是和外部存储相关的I/O操作,MemoryFile通过将 NAND或SD卡上的文件,分段映射到内存中进行改动处理,这样就用快速的RAM取代了ROM或SD卡。性能自然提高不少,对于Android手机而言同一时候还降低了电量消耗。

    该类实现的功能不是非常多,直接从Object上继承,通过JNI的方式直接在C底层运行。
    具体请參考 http://developer.android.com/reference/android/os/MemoryFile.html
    在此,仅仅简单列举几个经常使用的类和方法,很多其它的是要靠平时的积累和发现。

    多阅读Google给的帮助文档时非常故意的。

    10) 合理利用本地方法

      本地方法并非一定比Java高效。最起码,Java和native之间过渡的关联是有消耗的,而JIT并不能对此进行优化。当你分配本地资源时 (本地堆上的内存,文件说明符等),往往非常难实时的回收这些资源。

    同一时候你也须要在各种结构中编译你的代码(而非依赖JIT)。

    甚至可能须要针对同样的架构 来编译出不同的版本号:针对ARM处理器的GI编译的本地代码,并不能充分利用Nexus One上的ARM。而针对Nexus One上ARM编译的本地代码不能在G1的ARM上执行。当你想部署程序到存在本地代码库的Android平台上时。本地代码才显得尤为实用。而并非为了Java应用程序的提速。

    11)复杂算法尽量用C完毕

      复杂算法尽量用C或者C++完毕,然后用JNI调用。

    可是假设是算法比較单间。不必这么麻烦,毕竟JNI调用也会花一定的时间。请权衡。

    12) 降低不必要的全局变量

      尽量避免static成员变量引用资源耗费过多的实例,比方Context。Android提供了非常健全的消息传递机制(Intent)和任务模型(Handler),能够通过传递或事件的方式。防止一些不必要的全局变量。

    13) 不要过多指望gc

      Java的gc使用的是一个有向图,推断一个对象是否有效看的是其它的对象能到达这个对象的顶点,有向图的相对于链表、二叉树来说开销是可想而知。所以不要过多指望gc。

    将不用的对象能够把它指向NULL。并注意代码质量。同一时候,显示让系统gc回收,比如图片处理时,

    if(bitmap.isRecycled()==false) { //假设没有回收
         bitmap.recycle();
    }
    
    

    14)了解Java四种引用方式

      JDK 1.2版本号開始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
        i.强引用(StrongReference)
       强引用是使用最普遍的引用。假设一个对象具有强引用,那垃圾回收器绝不会回收它。

    当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止。也不会靠任意回收具有强引用的对象来解决内存不足的问题。
        ii.软引用(SoftReference)
       假设一个对象仅仅具有软引用,则内存空间足够,垃圾回收器就不会回收它;假设内存空间不足了,就会回收这些对象的内存。仅仅要垃圾回收器没有回收它,该对象就能够被程序使用。

    软引用可用来实现内存敏感的快速缓存。
        iii.弱引用(WeakReference)
     在垃圾回收器线程扫描它所管辖的内存区域的过程中。一旦发现了仅仅具有弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。

    只是,因为垃圾回收器是一个优先级非常低的线程,因此不一定会非常快发现那些仅仅具有弱引用的对象。


         iv.虚引用(PhantomReference)
        顾名思义,就是形同虚设。与其它几种引用都不同,虚引用并不会决定对象的生命周期。

    假设一个对象仅持有虚引用,那么它就和没有不论什么引用一样,在不论什么时候都可能被垃圾回收器回收。

    了解并熟练掌握这4中引用方式,选择合适的对象应用方式,对内存的回收是非常有帮助的

    15)使用实体类比接口好

    如果你有一个HashMap对象,你能够将它声明为HashMap或者Map:

    Map map1 = new HashMap();
    HashMap map2 = new HashMap();
    哪个更好呢?
    依照传统的观点Map会更好些,由于这样你能够改变他的详细实现类,仅仅要这个类继承自Map接口。传统的观点对于传统的程序是正确的,可是它并不适合嵌入式系统。

    调用一个接口的引用会比调用实体类的引用多花费一倍的时间。假设HashMap全然适合你的程序,那么使用Map就没有什么价值。假设有些地方你不能确定,先避免使用Map。剩下的交给IDE提供的重构功能好了。(当然公共API是一个例外:一个好的API经常会牺牲一些性能)

    16)避免使用枚举

    枚举变量很方便,但不幸的是它会牺牲运行的速度和并大幅添加文件体积。比如:

    public class Foo {
        public enum Shrubbery { GROUND, CRAWLING, HANGING }
    }
    会产生一个900字节的.class文件(FooShubbery.class)。在它被首次调用时。这个类会调用初始化方法来准备每一个枚举变量。每一个枚举项都会被声明成一个静态变量。并被赋值。

    然后将这些静态变量放在一个名为”VALUES”的静态数组变量中。而这么一大堆代码。不过为了使用三个整数。
    这样:Shrubbery shrub =Shrubbery.GROUND;会引起一个对静态变量的引用,假设这个静态变量是final int,那么编译器会直接内联这个常数。
             一方面说。使用枚举变量能够让你的API更出色,并能提供编译时的检查。所以在通常的时候你毫无疑问应该为公共API选择枚举变量。

    可是当性能方面有所限制的时候,你就应该避免这样的做法了。
            有些情况下。使用ordinal()方法获取枚举变量的整数值会更好一些,举例来说:

    for(int n =0; n < list.size(); n++) {
           if(list.items[n].e == MyEnum.VAL_X) {
                  // do something
           } else if(list.items[n].e == MyEnum.VAL_Y) {
                  // do something
           }
    }
    替换为:

    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++) {
           intvalItem = items[n].e.ordinal();
           if(valItem == valX) {
                  // do something
           } else if(valItem == valY) {
                  // do something
           }
    }
    会使性能得到一些改善。但这并非终于的解决之道。

    17)在私有内部内中,考虑用包訪问权限替代私有訪问权限

    public class Foo {
               public class Inner {
                    public void stuff() {
                           Foo.this.doStuff(Foo.this.mValue);
                    }
               }
               private int mValue;
               public void run() {
                    Inner in = new Inner();
                    mValue = 27;
                    in.stuff();
               }
               private void doStuff(int value) {
                     System.out.println("value:"+value);
               }
    }
             须要注意的关键是:我们定义的一个私有内部类(FooInner),直接訪问外部类中的一个私有方法和私有变量。这是合法的。代码也会打印出预期的“Valueis27”。但问题是,虚拟机觉得从FooInner中直接訪问Foo的私有成员是非法的,由于他们是两个不同的类。虽然Java语言同意内部类訪问外部类的私有成员,可是通过编译器生成几个综合方法来桥接这些间隙的

    /*package*/
    static int Foo.access$100(Foo foo) {
       return foo.mValue;
    }
    /*package*/
    static void Foo.access%200(Foo foo,int value) {
         foo.duStuff(value);
    }
    
            内部类会在外部类中不论什么须要訪问mValue字段或调用doStuff方法的地方调用这些静态方法。这意味着这些代码将直接存取成员变量表现为通过存取器方法訪问。

    之前提到过存取器訪问怎样比直接訪问慢。这样例说明。某些语言约会定导致不可见的性能问题。

    假设你在高性能的Hotspot中使用这些代码,能够通过声明被内部类訪问的字段和成员为包訪问权限,而非私有。

    但这也意味着这些字段会被其它处于同一个包中的类訪问,因此在公共API中不宜採用。

    18)将与内部类一同使用的变量声明在包范围内

    请看以下的类定义:

    public class Foo {
           private class Inner {
               void stuff() {
                   Foo.this.doStuff(Foo.this.mValue);
               }
           }
     
           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);
           }
    }
             这当中的关键是,我们定义了一个内部类(FooInner),它须要訪问外部类的私有域变量和函数。这是合法的,而且会打印出我们希望的结果Valueis27。问题是在技术上来讲(在幕后)FooInner是一个全然独立的类。它要直接訪问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);
    }
             内部类在每次訪问mValueg和gdoStuffg方法时,都会调用这些静态方法。就是说,上面的代码说明了一个问题,你是在通过接口方法訪问这些成员变量和函数而不是直接调用它们。

    在前面我们已经说过,使用接口方法(getter、setter)比直接訪问速度要慢。所以这个样例就是在特定语法以下产生的一个“隐性的”性能障碍。

    通过将内部类訪问的变量和函数声明由私有范围改为包范围,我们能够避免这个问题。这样做能够让代码执行更快。而且避免产生额外的静态方法。

    (遗憾的是,这些域和方法能够被同一个包内的其它类直接訪问,这与经典的OO原则相违背。

    因此当你设计公共API的时候应该慎重使用这条优化原则)。

    19)缓存

      适量使用缓存,不要过量使用。由于内存有限,能保存路径地址的就不要存放图片数据。不常常使用的尽量不要缓存,不用时就清空。


    20)关闭资源对象

           对SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O操作等都应该记得显示关闭。

    2.视图优化

    1)View优化

        i.    降低不必要的View以及View的嵌套层次。
      比方实现一个listview中经常使用的layout。能够使用RelativeLayout降低嵌套,要知道每一个View的对象会耗费1~2k内存。嵌套层次过多会引起频繁的gc,造成ANR。
        ii.    通过HierarchyViewer查看布局结构
    利用HierarchyViewer来查看View的结构:~/tools/hierarchyviewer。能非常清楚地看到RelativeLayout以下的扁平结构,这样能加快dom的渲染速度。


    具体请參考 http://developer.android.com/guide/developing/tools/hierarchy-viewer.html
        iii.    通过Layoutopt优化布局
      通过Android sdk中tools文件夹下的layoutopt 命令查看你的布局是否须要优化

    2)多线程解决复杂计算

      占用CPU较多的数据操作尽可能放在一个单独的线程中进行,通过handler等方式把运行的结果交于UI线程显示。

    特别是针对的网络訪问,数据库查询。和复杂的算法。

    眼下Android提供了AsyncTask,Hanlder、Message和Thread的组合。

    对于多线程的处理。假设并发的线程非常多,同一时候有频繁的创建和释放,能够通过concurrent类的线程池解决线程创建的效率瓶颈。另外值得注意的是。应用开发中自己定义View的时候,交互部分。千万不要写成线程不断刷新界面显示,而是依据TouchListener事件主动触发界面的更新。

    3)布局用Java完毕比XML快

      普通情况下对于Android程序布局往往使用XML文件来编写。这样能够提高开发效率,可是考虑到代码的安全性以及运行效率,能够通过Java代码运行创建,尽管Android编译过的XML是二进制的。可是载入XML解析器的效率对于资源占用还是比較大的,Java处理效率比XML快得多,可是对于一个复杂界面的编写,可能须要一些套嵌考虑,假设你思维灵活的话。使用Java代码来布局你的Android应用程序是一个更好的方法。


    4)对大型图片进行缩放

      图片读取是OOM(Out of Memory)的常客,当在Android手机上直接读取4M的图片时,死神一般都会降临,所以导致往往自己手机拍摄的照片都不能直接读取。对大型图片进行缩放处理图片时我们常常会用到BitmapFactory类。android系统中读取位图Bitmap时分给虚拟机中图片的堆栈大小仅仅有8M。用BitmapFactory解码一张图片时。有时也会遇到该错误。

    这往往是因为图片过大造成的。这时我们须要分配更少的内存空间来存储。BitmapFactory.Options.inSampleSize设置恰当的inSampleSize能够使BitmapFactory分配更少的空间以消除该错误。Android提供了一种动态计算的,例如以下:

    读取图片之前先查看其大小:

    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inJustDecodeBounds = true;
    Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
    使用得到的图片原始宽高计算适合自己的smaplesize

    BitmapFactory.Options opts = new BitmapFactory.Options();
     opts.inSampleSize = 4 ;// 4就代表容量变为曾经容量的1/4
     Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts); 
    对于过时的Bitmap对象一定要及时recycle。而且把此对象赋值为null。
    bitmap.recycle();
    bitmap = null; 

    5)合理使用ViewStub

      ViewStub 是一个隐藏的,不占用内存空间的视图对象,它能够在执行时延迟载入布局资源文件。

    当ViewStub可见,或者调用 inflate()函数时,才会载入这个布局资源文件。 该ViewStub在载入视图时在父容器中替换它本身。因此。ViewStub会一直存在于视图中,直到调用setVisibility(int) 或者inflate()为止。

    ViewStub的布局參数会随着载入的视图数一同被加入到ViewStub父容器。相同。你也能够通过使用 inflatedId属性来定义或重命名要载入的视图对象的Id值。

    所以我们能够使用ViewStub延迟载入某些比較复杂的layout,动态载入 View。採用ViewStub 避免一些不常常的视图长期握住引用。
    具体请參考http://developer.android.com/reference/android/view/ViewStub.html

    6)针对ListView的性能优化

         i. 复用convertView。
         ii.在getItemView中,推断convertView是否为空,假设不为空,可复用。

    假设couvertview中的view须要加入 listerner,代码一定要在if(convertView==null){}之外。
         iii.异步载入图片,item中假设包括有web image,那么最好异步载入。
         iv.高速滑动时不显示图片
       当高速滑动列表时(SCROLL_STATE_FLING),item中的图片或获取须要消耗资源的view,能够不显示出来。而处于其它两种状 态(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL)。则将那些view显示出来。
         v.    item尽可能的降低使用的控件和布局的层次。背景色与cacheColorHint设置同样颜色;ListView中item的布局至关重要。必须尽可 能的降低使用的控件。布局。

    RelativeLayout是绝对的利器,通过它能够降低布局的层次。同一时候要尽可能的复用控件,这样能够降低ListView的内存使用,降低滑动时gc次数。

    ListView的背景色与cacheColorHint设置同样颜色。能够提高滑动时的渲染性能。
         vi. getView优化
       ListView中getView是性能是关键,这里要尽可能的优化。getView方法中要重用view。getView方法中不能做复杂的逻辑计算,特别是数据库和网络訪问操作,否则会严重影响滑动时的性能。

    优化例如以下所看到的:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
           Log.d("MyAdapter", "Position:" + position + "---" + String.valueOf(System.currentTimeMillis()));
           final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
           View v = inflater.inflate(R.layout.list_item_icon_text, null);
           ((ImageView) v.findViewById(R.id.icon)).setImageResource(R.drawable.icon);
           ((TextView) v.findViewById(R.id.text)).setText(mData[position]);
           return v;
    }
    建议改为:
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
           Log.d("Adapter", "Position:" + position + " : " + String.valueOf(System.currentTimeMillis()));
           ViewHolder holder;
           if (convertView == null) {
                  final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                  convertView = inflater.inflate(R.layout.list_item_icon_text, null);
                  holder = new ViewHolder();
                 holder.icon = (ImageView) convertView.findViewById(R.id.icon);
                 holder.text = (TextView) convertView.findViewById(R.id.text);
                 convertView.setTag(holder);
           } else {
                 holder = (ViewHolder) convertView.getTag();
           }
                  holder.icon.setImageResource(R.drawable.icon);
                  holder.text.setText(mData[position]);
                  return convertView;
           }
     
           static class ViewHolder {
                   ImageView icon;
                   TextView text;
           }
    }
    以上是Google IO大会上给出的优化建议。经过尝试ListView确实流畅了很多。使用1000条记录。经測试第一种方式耗时:25211ms,另外一种方式耗时:16528ms。


    7)其它

       i.分辨率适配
       -ldpi,-mdpi, -hdpi配置不同精度资源。系统会依据设备自适应,包含drawable, layout,style等不同资源。
       ii.尽量使用dp(density independent pixel)开发,不用px(pixel)。


       iii.多用wrap_content, fill_parent
       iv.抛弃AbsoluteLayout
       v.使用9patch(通过~/tools/draw9patch.bat启动应用程序),png格式
       vi.採用<merge> 优化布局层数;採用<include >来共享布局。


       vii. 将Acitivity中的Window的背景图设置为空。

    getWindow().setBackgroundDrawable(null);android的默认背景是不是为空。
       viii.View中设置缓存属性.setDrawingCache为true。

    3.网络优化

    1)避免频繁网络请求

      訪问server端时,建立连接本身比传输须要跟多的时间。如非必要。不要将一交互能够做的事情分成多次交互(这须要与Server端协调好)。有效管理Service 后台服务就相当于一个持续执行的Acitivity,假设开发的程序后台都会一个service不停的去服务器上更新数据,在不更新数据的时候就让它sleep,这样的方式是很耗电的,通常情况下。能够使用AlarmManager来定时启动服务。例如以下所看到的,第30分钟执行一次。

    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, MyService.class);
    PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
    long interval = DateUtils.MINUTE_IN_MILLIS * 30;
    long firstWake = System.currentTimeMillis() + interval;
    am.setRepeating(AlarmManager.RTC,firstWake,  interval,  pendingIntent);

    2)数据压缩

    数据传输经过压缩 眼下大部门站点都支持GZIP压缩,所以在进行大数据量下载时,尽量使用GZIP方式下载。能够降低网络流量,通常是压缩前数据大小的30%左右。

    HttpGet request = new HttpGet("http://example.com/gzipcontent");
    HttpResponse resp = new DefaultHttpClient().execute(request);
    HttpEntity entity = response.getEntity();
    InputStream compressed = entity.getContent();
    InputStream rawData = new GZIPInputStream(compressed);

    3)使用线程池

      线程池,分为核心线程池和普通线程池。下载图片等耗时任务放置在普通线程池,避免耗时任务堵塞线程池后。导致全部异步任务都必须等待。

    4)选择合适的数据格式传输形式

    当中Tree Parse 是DOM解析 Event/Stream是SAX方式解析。


    非常明显。使用流的方式解析效率要高一些,由于DOM解析是在对整个文档读取完后,再依据节点层次等再组织起来。

    而流的方式是边读取数据边解析,数据读取完后,解析也就完成了。在数据格式方面,JSON和Protobuf效率明显比XML好非常多。XML和JSON大家都非常熟悉。从上面的图中能够得出结论就是尽量使用SAX等边读取边解析的方式来解析数据。针对移动设备,最好能使用JSON之类的轻量级数据格式为佳。

    5)其它

      设置连接超时时间和响应超时时间。

    Http请求依照业务需求。分为能否够缓存和不可缓存。那么在无网络的环境中。仍然通过缓存的HttpResponse浏览部分数据。实现离线阅读。

    4.数据库相关

    1)相对于封装过的ContentProvider而言,使用原始SQL语句运行效率高。比方用法rawQuery、execSQL的运行效率比較高。

    2)对于须要一次性改动多个数据时。能够考虑使用SQLite的事务方式批量处理。

    3)批量插入多行数据使用InsertHelper或者bulkInsert方法

    4)有些能用文件操作的,尽量採用文件操作,文件操作的速度比数据库的操作要快10倍左右。

    5.性能測试

      对于Android平台上软件的性能測试能够通过下面几种方法来分析效率瓶颈,眼下Google在Android软件开发过程中已经引入了多种測试工具包。比方Unit測试project,调试类,还有模拟器的Dev Tools都能够直接反应运行性能。

    1)在模拟器上的Dev Tools能够激活屏幕显示当前的FPS,CPU使用率,能够帮助我们測试一些3D图形界面的性能。

    2)一般涉及到网络应用的程序,在效率上和网速有非常多关系,这里须要多次的调试才干实际了解。

    3)对于逻辑算法的效率运行。我们使用Android上最普遍的。计算运行时间来查看:

    long start = System.currentTimeMillis();
    // do something
    long duration = System.currentTimeMillis() - start;
    终于duration保存着实际处理该方法须要的毫秒数。

    4)gc效率跟踪

          假设你运行的应用比較简单,能够在DDMS中查看下Logcat的VM释放内存情况。大概模拟下那些地方能够缓存数据或改进算法的。

    5)线程的使用和同步

          Android平台上给我们提供了丰富的多任务同步方法,但在深层上并没有过多的比方自旋锁等高级应用,不 过对于Service和 appWidget而言,他们实际的产品中都应该以多线程的方式处理,以释放CPU时间,对于线程和堆内存的查看这些都能够在DDMS中看到。

    6)利用traceview和monkey等工具測试应用。

    7)利用layoutopt和ninepatch等工具优化视图。


  • 相关阅读:
    scala 获取当前时间的两种方式
    log4j配置输出日志文件
    scala读取jar包外配置文件的方式
    scala 异常处理机制
    IDEA 安装scala插件
    第2部分 Elasticsearch查询-请求体查询、排序
    第1部分 Elasticsearch基础
    2.css
    1.html
    Linux搭建Nexus仓库+高可用方案
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7194908.html
Copyright © 2011-2022 走看看