android优化的一些经验总结

以下两个demo比较简单,包含了Activity和Service频繁数据通信框架搭建,希望对一些朋友有帮助。

语音输入在线听书demohttp://blog.csdn.net/ls0609/article/details/71519203

语音输入记账demohttp://blog.csdn.net/ls0609/article/details/72765789

本文总结的一些优化经验,是摘自零散的博文和自己的工作总结,由于来源比较零散,无法统一注明出处,姑且算做半个原创吧。

言归正传,进入正题。

一.android GC内存泄露问题

1. android内存泄露概念

 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露。其实如果我们一个程序中,已经不再使用某个

对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它, 当然该对象占用的内存就无法被使用,这就造成了内存泄露。如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了。当然java的,内存泄漏和C/C++是不一 样的。如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的。C/C++的内存泄露就 比较糟糕了,它的内存泄露是系统级,即使该C/C++程序退出,它的泄露的内存也无法被系统回收,永远不可用了,除非重启机器。

  Android的一个应用程序的内存泄露对别的应用程序影响不大。为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都会使用一个专有的Dalvik虚拟机实   例来运行,也就是说每个应用程序都是在属于自己的进程中运行的。Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进   程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被kill掉,这使得仅仅自己的进程被kill掉,而不会影响其他进程(如果是system_process等系统进程出问题的话    ,则会引起系统重启)。

内存泄露示例:

/*此时,所有的Object对象都没有被释放,因为变量v引用这些对象。实际上这些对象已经是无用的,但还被引用,GC就无能为力了(事实上GC认为它还有用),这一点是导致内存    泄漏最重要的原因。*/
Vector v = new Vector(10);      
for (int i = 1; i < 100; i++)      

{  
Object o = new Object();  
v.add(o);  
o = null;
}

循环申请Object对象,并将所申请的对象放入一个Vector中,如果仅仅释放对象本身,但因为Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加  入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。

  总的来说,内存管理中的内存泄漏产生的主要原因:保留下来却永远不再使用的对象引用。

2.引起内存泄露的情况

1)资源对象没关闭造成的内存泄露

   资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机 内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLiteCursor(在析构函数 finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使 用的时候,应该调用它close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

  程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作     的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

2)一些不良代码成内存压力

  有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大     影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,造成不必要的内存开支。
  a.Bitmap没调用recycle()   

Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才将它设置为null。虽然recycle()从源码上看,调用它应该能立即释放Bitmap的主要内存,但是测试结果显示 它并没能立即释放内存。但是我想应该还是能大大的加速Bitmap的主要内存的释放。

压缩图片
如果图片像素过大,使用BitmapFactory类的方法实例化Bitmap的过程中,需要大于8M的内存空间,就必定会发生OutOfMemory异常。这个时候该如何处理呢?如果有这种情况,   则可以将图片缩小,以减少载入图片过程中的内存的使用,避免异常发生。

使用BitmapFactory.Options设置inSampleSize就可以缩小图片。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一。即如果这个值为2,则取出的缩略图的宽和高都 是原始图片的1/2,图片的大小就为原始大小的1/4。

  如果知道图片的像素过大,就可以对其进行缩小。那么如何才知道图片过大呢?

使用BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,但是可计算出原始图片的宽 度和高度,即options.outWidth和options.outHeight。通过这两个值,就可以知道图片是否过大了。
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;// 设置inJustDecodeBounds为true
BitmapFactory.decodeFile(path, opts); // 使用decodeFile方法得到图片的宽和高
Log.d(“example”, opts.outWidth + “,” + opts.outHeight);// 打印出图片的宽和高
在实际项目中,可以利用上面的代码,先获取图片真实的宽度和高度,然后判断是否需要跑缩小。如果不需要缩小,设置inSampleSize的值为1。如果需要缩小,则动态计算并设 置inSampleSize的值,对图片进行缩小。需要注意的是,在下次使用BitmapFactory的decodeFile()等方法实例化Bitmap对象前,别忘记将opts.inJustDecodeBound设置回false。 否则获取的bitmap对象还是null。

  b.构造Adapter时,没有使用缓存的convertView

以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:
public View getView(int position, View convertView, ViewGroup parent)
来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来 。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的, getView()的第二个形参View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收 来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支。
ViewHolder的使用,保存layout文件中控件id。

3)ThreadLocal使用不当
如果我们粗暴的把ThreadLocal设置null,而不调用remove()方法或set(null),那么就可能造成ThreadLocal绑定的对象长期也能被回收,因而产出内存泄露。

二.Java初级优化

  1. 对于明确不需要派生的类,添加final修饰符,此时该类的所有方法都是final的。Java编译器会寻找机会内联(inline)所有的final方法。(能使性能提升50%)
  2. 尽量重用对象,避免生成过多的对象。对于String的连接,用StringBuffer代替。
  3. 尽量少用全局变量如static等(heap中创建,慢),多用局部变量(Stack中创建,快)。
  4. 不要重复初始化变量。默认情况下,调用类的构造函数时, Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0, float和 double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中的所有构造函数都 被自动调用。即
    class test{
    private Object obj; //不需要写成 private Object obj=null;
    }
  5. 在java+Oracle的应用系统开发中,java中内嵌的SQL语言应尽量使用大写形式,以减少Oracle解析器的解析负担。
  6. java编程过程中,进行数据库连接,I/O流操作,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销。
  7. 过分的创建对象会消耗系统的大量内存,严重时,会导致内存泄漏,因此,保证过期的对象的及时回收具有重要意义。JVM的GC并非十分智能,因此建议在对象使用完毕后,手 动设置成null。
  8. 在使用同步机制时,尽量使用同步方法代替代码同步快。
  9. 尽量减少对变量的重复计算。
    for(int i=0; i
for (val = 0; val < 100000; val +=5) { 
           alterX = val * 8; myResult = val * 2; 
        }
   用移位操作替代乘法操作可以极大地提高性能。下面是修改后的代 码:
for (val = 0; val < 100000; val += 5) { 
          alterX = val << 3; myResult = val << 1; 
       }
   修改后的代码不再做乘以8的操作,而是改用等价的左移3位操作,每左移1位相当于乘以2。相应地,右移1位操作相当于除以2。值得一提的是,虽然移位操作速度快,但可能       使代码比较难于理解,所以最好加上一些注释。

15. 尽量使用HashMap和ArrayList;除非必要,否则不推荐使用HashTable和Vector,后者由于同步机制导致了性能的开销。

  1. 知道长度的情况下尽量使用定长数组array。

  2. 当复制大量数据时,使用System.arraycopy()命令。

    18.StringBuffer使用

    当你需要对一组String进行连接时,请不要使用:

 String str=  "Welcome"+ "to" + "our" + "site";

应当写成:

StringBuffer sb  =  new StringBuffer(50);sb.append("Welcome");sb.append("To");sb.append("our");sb.append("site");
  如果您知道StringBuffer的最大长度,请使用这个数字。例如:在上面的场景中,StringBuffer的最大长度设置为50,这使得StringBuffer在使用过程中,不需要考虑自增长      问题。这样就不需要再去为StringBuffer分配新的内存,而导致垃圾回收器回收旧的内存。当然,也不要分配过于大的、不必要的内存。建议三个以上用StringBuffer

三.Java高级优化技术

常用的:

1.优化循环。通过重新组织重复的子表达式来提高循环体的运行性能。

2.减少使用对象的数量来提高运行性能。

3.缩减网络传输数据来缩短等待时间。

其他:

1.采用对象池技术,提高对象的利用效率。

 性能的损耗主要源于创建和释放对象,因此要避免对象的创建和释放。采用对象池技术,预先定义一个对象池,预先创建一组待使用的对象:

 Enemy[5] enemy=new Enemy[5];
 for(int i=0;i<5;i++){
    enemy[i]=new Enemy();
 }    
 增加标志如used和reset标识Enemy的状态。需要创建对象时从对象池中获取 一个未被使用的对象并用reset方法初始化;需要释放时只需修改标志位以供下次使用即可。

2.尽可能使用基本数据类型代替对象
例如用二维数组代替一个写简单的对象。

3.优化算法
比如对于默写不要求很精细的场景和算法,用简单的算法模拟。

4.其他优化

a.如提取字符串时,试着返回子串而不是创建一个副本。

b.尽量的少创建短期的临时对象。

c.能用库函数的就不要自己创建(库函数是优化好的)

d.Map map=new HashMap();

     HashMap map=new HashMap();  //这个性能更高,重构代码

e.增强型for循环和Iterable使用时,多了一个对象的创建,慎用。

f.避免enum类型。

g.嵌入式开发时注意浮点的运用,尽量不用。(处理器是否支持浮点)

h.图片资源压缩、多张图片集中到一张图片上(比单独的和小很多,省去了每张的头文件、结束文件等数据块,合并了调色板)

5.软引用和弱引用

   Java从JDK1.2版本开始,就把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以      被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个      软引用加入到与之关联的引用队列中。
如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器      是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收      ,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
弱引用与软引用的根本区别在于:只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具有软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,      通常不被回收。   
在java.lang.ref包中提供了几个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。

   假设我们的应用会用到大量的默认图片,比如应用中有默认的头像,默认游戏图标等等,这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速    度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容      易发生OutOfMemory异常。这时,我们可以考虑使用软引用技术来避免这个问题发生。

  首先定义一个HashMap,保存软引用对象。
 private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
      //再来定义一个方法,保存Bitmap的软引用到HashMap。
    public void addBitmapToCache(String path) {        
        Bitmap bitmap = BitmapFactory.decodeFile(path);// 强引用的Bitmap对象
        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);// 软引用的Bitmap对象        
        imageCache.put(path, softBitmap);// 添加该对象到Map中使其缓存
    }
      //获取的时候,可以通过SoftReference的get()方法得到Bitmap对象。
    public Bitmap getBitmapByPath(String path) {       
        SoftReference<Bitmap> softBitmap = imageCache.get(path); // 从缓存中取软引用的Bitmap对象
        if (softBitmap == null) {// 判断是否存在软引用
            return null;
        }
        Bitmap bitmap = softBitmap.get();// 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
        return bitmap;
    }
到底什么时候使用软引用,什么时候使用弱引用呢?

个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。
还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。
另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。    WeakHashMap使用ReferenceQueue实现的这种机制。 

四.android UI优化:

 在Android中,最常用LinearLayout表示UI的布局。比起LinearLayout,在资源利用上,RelativeLayout会占用更少的资源而达到相同的效果。针对RelativeLayout有一点需要   注意,因为它内部是通过多个View之间的关系而确定的布局,那么当其中某一个View因为某些需要调用GONE来完全隐藏掉后,会影响与其关联的Views,属性      alignWithParentIfMissing用于解决类似问题。

 简单或复杂的问题都需要时常考虑如何优化资源的分配。当我们面对Android UI优化时,有必要继续考虑资源复用。定义Android布局文件时,有4个比较特别的标签是非常重要    :<viewStub/>, <requestFocus/>, <merge/>和<include/>,其中3个与资源复用有关。

1. <viewStub/> 直观效果类似于View的不可见性,但其所包裹的View在默认状态下不会占用任何内存空间。viewStub通过include从外部导入View元素。用法:通过属性              android:layout来设定内容。

2. <include/> 用于直接加载xml,是复用UI资源的常用标签。用法:通过属性layout来设定加载的xml。<include layout="@layout/navigator_bar" />

3. <requestFocus/> 用于设定焦点。用法:将<requestFocus/>放于View标签中。

4. <merge/> 目的:删减多余或额外的层级。

查看当前UI结构:运行android sdk/tools/hierarchyviewer.bat
如果你创建的Layout不是把FrameLayout当作根节点,就不能使用<merge/>来优化。  
当使用include或viewStub从外部导入xml结构时,可以将被导入的xml用merge作为根节点,这样当被嵌入后可以很好地融合,不会出现冗余的节点。
<merge/>只可以作为xml-layout的根节点。扩充的xml-layout是merge作为根节点时,需要将被导入的xml-layout置于viewGroup中,同时需要设置attachToRoot为true。

一般情况下,在项目的初期就能够大致确定整体UI的风格。所以早期的时候就可以做一些规划,将通用的模块先写出来。
下面是可能可以抽出的共用的布局:
1)背景。有的应用在不同的界面里会用到统一的背景。后期可能会经常修改默认背景,所以可以将背景做成一个通用模块。
2)头部的标题栏。如果应用有统一的头部标题栏,就可以抽取出来。
3)底部的导航栏。如果应用有导航栏,而且大部分的Activity的底部导航栏是相同的,就可以将导航栏写成一个通用模块。
4)ListView。大部分应用都会用到ListView展示多条数据。项目后期可能会经常调整ListView的风格,所以将ListView作为一个通用的模块比较好。

ViewStub使用:

有时候,我们的页面中可能会包含一些布局,这些布局默认是隐藏的,当用户触发了一定的操作之后,隐藏的布局才会显示出来。比如,我们有一个Activity用来显示好友的列      表,当用户点击Menu中的“导入”以后,在当前的Activity中才会显示出一个导入好友的布局界面。从需求的角度来说,这个导入功能,一般情况下用户是不使用的。即大部分      时候,导入好友的布局都不会显示出来。这个时候,就可以使用延迟加载的功能。 

ViewStub是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。当ViewStub被设置为可见,或者调用inflate()函数时,才会真的去加载这个布局     资源文件。该ViewStub在加载视图时会在父容器中替换它本身。因此,ViewStub会一直存在于视图中,直到调用setVisibility(int)或者inflate()为止。ViewStub的布局参数       随着加载的视图数一同被添加到ViewStub父容器。同样,也可以通过使用inflated Id属性来定义或重命名要加载的视图对象的Id值。

<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />

通过“stub_import”这个id可以找到被定义的ViewStub对象。加载布局资源文件“progress_overlay”后,ViewStub对象从其父容器中移除。可以通过“panel_import”这个id     找到由布局资源“progress_overlay”创建的View。

执行加载布局资源文件的推荐方式如下:   
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
 // 或者
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

当inflate()被调用, 这个ViewStub被加载的视图所替代,并且返回这个视图对象。这使得应用程序不需要额外执行findViewById()来获取加载视图的引用。

经验分享:
利用ViewStub可以与xml文件里面指定的布局资源文件关联起来,让布局资源文件在需要使用的时候再加载上去。什么时候用什么时候才加载,不用在开始启动的时候一次加载。     这样做既可以加快应用的启动速度,又可以节省内存资源。 

以上摘自网文和自己的工作总结,如有错误之处,欢迎指正。

以下两个demo比较简单,包含了Activity和Service频繁数据通信框架搭建,希望对一些朋友有帮助。

语音输入在线听书demohttp://blog.csdn.net/ls0609/article/details/71519203

语音输入记账demohttp://blog.csdn.net/ls0609/article/details/72765789

猜你喜欢

转载自blog.csdn.net/ls0609/article/details/72778884