Android 内存管理与优化

作者: KaelQ | 来源:发表于2016-09-06 17:15 被阅读1089次

    1.Android 内存基础

    • 所有的内存都是基于物理内存的,即移动设备上的RAM。当启动一个Android程序时,会启动一个Dalvik vm 进程,系统会给它分配固定的内存空间,这块内存会映射到RAM上某个区域,然后Android程序就运行在这块空间上。JAVA里会将这块空间分成Stack栈内存Heap堆内存。stack里存放对象的引用,heap里存放实际对象数据。

    2.Android 问题与调节

    • Memory Leak(内存泄露):在程序中创建对象,不及时回收无效空间。
    • OutOfMemory(内存溢出):当应用程序申请的java heap空间超过Dalvik vm HeapGrowthLimit时,溢出。(内存溢出并不是内存不够,而是超过了Dalvik vm的限制)
    • Memory Churn(内存抖动):GC操作频繁,导致内存抖动。
    • 调节:如果RAM申请的空间不足,那么Android的Memory Killer会杀死优先级低的进程。

    3.Android默认内存回收机制——垃圾回收GC(Garbage Collection)

    • 进程优先级:
      1. Foreground:处于焦点的进程。
      2. Visible:可以看见的进程。
      3. Service:服务进程。
      4. Background:后台进程。
      5. Empty:空进程。
    • 进行GC时刻:
      1. app空闲的时候。
      2. 内存紧张的时候 。
      3. 分配大的内存块不够用的时候。
    • GC回收模型:
      Generational Heap Memory
      采用分代技术,分为年轻代、老年代、持久代。

      最先清理的是年轻代,只要清理出足够的空间就不去清理老年代了。依次持久带也是这样。
    • GC回收步骤:
      1. 从GC Root开始,开始对对象引用遍历,找出GC Root直接引用的所有对象集1,再找出直接引用对象集1的对象集2……直到遍历完所有对象。这些对象都是有效对象
      2. 删除其他无效对象。
    • 注:GC执行的时候,当前所有线程的任何操作都会需要暂停。

    4.优化——减少额外的内存使用

    4.1 Bitmap

    基本思路:缩略图,像素,图片回收,捕获OOM异常

    • 缩略图:使用BitmapFactory.Options方法里的inSampleSize设置缩小比。
    • 像素:
      Android中图片有四种属性,分别是:
      ALPHA_8:每个像素占用1byte内存
      ARGB_4444:每个像素占用2byte内存
      ARGB_8888:每个像素占用4byte内存 (默认)
      RGB_565:每个像素占用2byte内存
      默认是显示效果最好的ARGB_8888,可以使用BitmapFactory.OptionsinPreferredConfig方法设置。
    • 图片回收:
    // 先判断是否已经回收  
    if(bitmap != null && !bitmap.isRecycled()){  
        // 回收并且置为null  
        bitmap.recycle();  
        bitmap = null;  
    }  
    System.gc();  
    
    • 捕获OOM异常:
    Bitmap bitmap = null;  
    try {  
        // 实例化Bitmap  
        bitmap = BitmapFactory.decodeFile(path);  
    } catch (OutOfMemoryError e) {  
        // 捕获OutOfMemoryError,避免直接崩溃  
    }  
    if (bitmap == null) {  
        // 如果实例化失败 返回默认的Bitmap对象  
        return defaultBitmapMap;  
    }  
    

    4.2 软引用和弱引用

    软件用和弱引用的概念解释

    • 想避免OutOfMemory异常的发生,则可以使用软引用。
    • 想要提高应用性能,尽快回收占用内存更大的对象,可以使用弱引用。

    4.3 小Tips

    • Context的引用,具体请看这篇Android Context详解
    • 避免创建不必要的对象:你要频繁操作一个字符串时,使用StringBuffer代替String。
    • 避免使用浮点数:在Android设备中,浮点数会比整型慢两倍。
    • 循环:尽量避免在for的条件参数(即())中访问成员变量,调用方法,例如:
    for(int i=0;i<a.length;i++)//访问成员变量,会慢很多。
    for(int i=0;i<a.length();i++)//调用方法,也会慢很多。
    

    正确的做法如下:

    int b = a.length/int b=a.length();
    for(int i=0;i<b;i++)//这样循环体最快。
    

    java还有一种访问数组的方法,这种方法比普通循环多出4个字节,因为产生了一个额外的变量,如下列的a。

    for(Foo a:Array){
        m+=a.value;
    }
    
    • 了解并使用类库
      当你在处理字串的时候,不要吝惜使用String.indexOf()String.lastIndexOf()等特殊实现的方法。这些方法都是使用C/C++实现的,比起Java循环快10到100倍。
      android.text.format包下的Formatter类,提供了IP地址转换、文件大小转换等方法;DateFormat类,提供了各种时间转换,都是非常高效的方法。
      TextUtils类,对于字符串处理Android为我们提供了一个简单实用的TextUtils类,如果处理比较简单的内容不用去思考正则表达式不妨试试这个在android.text.TextUtils的类。

    5.优化—— 重复使用内存资源

    • 将已经存在的内存资源重新使用而避免去创建新的。最典型的使用就是**缓存(Cache)和池(Pool)。
    • 缓存:
      一种是内存缓存,一种是硬盘缓存。
      • 内存缓存(LruCache):存在宝贵的内存中。
      • 硬盘缓存(DiskLruCache):存在硬盘中。
    • 池:
      一种是对象池,一种是线程池。
      • 对象池:将用过的对象保存起来,等下一次使用时,再拿出来使用, 从而在一定情况下减少创造对象所产生的开销。但是不是任何对象都适合使用对象池,因为维护对象池也需要一定的开支。一定要“维护对象池的开支”小于“创建对象的开支”
      • 线程池:基本思想任然是对象池的思想,在开辟的一片空间里,存着很多线程,线程池的调度由池管理器来处理。当需要一个线程时,从池中取一个,用完后重新归池,减少了创建线程的开支。
        java提供了ExecutorServiceExecutors类,我们可以用它们去创建线程池。

    6.优化——回收不需要的内存

    • 回收主要是Dalvik的GC机制。下面来讲一些具体的:

    • 线程回收:
      线程中涉及的任何东西GC都不能回收,所以线程很容易造成内存泄露。

    Thread t = new Thread() {  
        public void run() {  
            while (true) {  
                try {  
                    Thread.sleep(1000);  
                    System.out.println("thread is running...");  
                } catch (InterruptedException e) {             
                }  
            }  
        }  
    };  
    t.start();  
    t = null;  
    System.gc();  
    

    这个时候GC并不会回收这个线程,因为正在运行的线程属于GCRoot的一种,并不会被GC进行回收。

    • 游标回收:
      Cursor是Android查询数据后得到的一个管理数据集合的类,应该在使用完之后立即手动进行关闭。一般使用如下:
    Cursor cursor = null;  
    try {  
        cursor = mContext.getContentResolver().query(uri,null, null,null,null);  
        if(cursor != null) {  
            cursor.moveToFirst();  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    } finally {  
        if (cursor != null) {  
            cursor.close();  
        }  
    }  
    

    :在CursorAdapter中应用时,我们不能直接关掉Cursor,而且CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,需要在onDestroy函数中,手动关闭。

    @Override    
    protected void onDestroy() {          
        if (mAdapter != null && mAdapter.getCurosr() != null) {    
            mAdapter.getCursor().close();    
        }    
        super.onDestroy();     
    }    
    
    • Receiver(接收器)回收
      当在Activity进行了Receiver注册时,需要注意最后要注销。
      即调用registerReceiver(),使用完毕后调用unregisterReceiver()。
      一般在onDestroy()进行回收。
    @Override    
    protected void onDestroy() {    
          this.unregisterReceiver(receiver);    
          super.onDestroy();    
    }    
    
    • Stream/File(流/文件)回收:
      各种流/文件如:
      InputStream/OutputStream,SQListeOpenHelper,SQLiteDatabase,Cursor,File,I/O,Bitmap都需要关闭。

    7.优化——视图布局优化

    • 使用HierarchyViewer查看并减少OverDraw。
    • ViewStub标签:可以使布局在可见与不可见之间切换,默认不可见的情况下不占用内存。
    • include标签:复用UI资源。
    • merge标签:解决include标签的问题,减少一个层级,并且方便使用。
    • 重用系统资源:使用系统自带的资源(sdk\platforms\android-x\data\res),例如:
      • id
        android:id="@android:id/list"
      • 字符串
        @android:string/yes
      • Style
        android:textAppearance="?android:attr/textAppearanceMedium"
      • 颜色
        android:background ="@android:color/transparent"

    相关文章

      网友评论

      本文标题:Android 内存管理与优化

      本文链接:https://www.haomeiwen.com/subject/taxlettx.html