`
linfengsheng
  • 浏览: 129340 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

android,内存优化详解 (转)

阅读更多

一、 Android的内存机制

    Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似。程序员通过new为对象分配内存,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的。C/C++中的内存机制是“谁污染,谁治理”,java的就比较人性化了,给我们请了一个专门的清洁工(GC)。

    那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。

二、Android的内存溢出

    Android的内存溢出是如何发生的?

    Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。

为什么会出现内存不够用的情况呢?我想原因主要有两个:

  • 由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。
  • 保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。

三、万恶的static

    static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。

  1. public class ClassName {  
  2.      private static Context mContext;  
  3.      //省略  
  4. }  

以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。

    我们举Android文档中的一个例子。

  1. private static Drawable sBackground;  
  2.      
  3.  @Override  
  4.  protected void onCreate(Bundle state) {  
  5.    super.onCreate(state);  
  6.      
  7.    TextView label = new TextView(this);  
  8.    label.setText("Leaks are bad");  
  9.      
  10.    if (sBackground == null) {  
  11.      sBackground = getDrawable(R.drawable.large_bitmap);  
  12.    }  
  13.    label.setBackgroundDrawable(sBackground);  
  14.      
  15.    setContentView(label);  
  16.  }  

    sBackground, 是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:

    Drawable->TextView->Context

    所以,最终该Context也没有得到释放,发生了内存泄露。

    如何才能有效的避免这种引用的发生呢?

    第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。

    第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。

    第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;

    该部分的详细内容也可以参考Android文档中Article部分。

四、都是线程惹的祸

    线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

  1. public class MyActivity extends Activity {  
  2.     @Override  
  3.     public void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         setContentView(R.layout.main);  
  6.         new MyThread().start();  
  7.     }  
  8.   
  9.     private class MyThread extends Thread{  
  10.         @Override  
  11.         public void run() {  
  12.             super.run();  
  13.             //do somthing  
  14.         }  
  15.     }  
  16. }  

    这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。

    由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

 

    有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。

    这种线程导致的内存泄露问题应该如何解决呢?

    第一、将线程的内部类,改为静态内部类。

    第二、在线程内部采用弱引用保存Context引用。

    解决的模型如下:

  1. public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends  
  2.         AsyncTask<Params, Progress, Result> {  
  3.     protected WeakReference<WeakTarget> mTarget;  
  4.   
  5.     public WeakAsyncTask(WeakTarget target) {  
  6.         mTarget = new WeakReference<WeakTarget>(target);  
  7.     }  
  8.   
  9.     /** {@inheritDoc} */  
  10.     @Override  
  11.     protected final void onPreExecute() {  
  12.         final WeakTarget target = mTarget.get();  
  13.         if (target != null) {  
  14.             this.onPreExecute(target);  
  15.         }  
  16.     }  
  17.   
  18.     /** {@inheritDoc} */  
  19.     @Override  
  20.     protected final Result doInBackground(Params... params) {  
  21.         final WeakTarget target = mTarget.get();  
  22.         if (target != null) {  
  23.             return this.doInBackground(target, params);  
  24.         } else {  
  25.             return null;  
  26.         }  
  27.     }  
  28.   
  29.     /** {@inheritDoc} */  
  30.     @Override  
  31.     protected final void onPostExecute(Result result) {  
  32.         final WeakTarget target = mTarget.get();  
  33.         if (target != null) {  
  34.             this.onPostExecute(target, result);  
  35.         }  
  36.     }  
  37.   
  38.     protected void onPreExecute(WeakTarget target) {  
  39.         // No default action  
  40.     }  
  41.   
  42.     protected abstract Result doInBackground(WeakTarget target, Params... params);  
  43.   
  44.     protected void onPostExecute(WeakTarget target, Result result) {  
  45.         // No default action  
  46.     }  
  47. }  



    事实上,线程的问题并不仅仅在于内存泄露,还会带来一些灾难性的问题。由于本文讨论的是内存问题,所以在此不做讨论。

 

由于51cto不让我一次传完,说我的字数太多了,所以分开传了。 

五、超级大胖子Bitmap

    可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了。

    如何解决Bitmap带给我们的内存问题?

    第一、及时的销毁。

    虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。

    第二、设置一定的采样率。

    有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:

  1.  private ImageView preview;  
  2.  BitmapFactory.Options options = new BitmapFactory.Options();  
  3.  options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一  
  4.  Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);  
  5. preview.setImageBitmap(bitmap);  

    第三、巧妙的运用软引用(SoftRefrence)

    有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。如下例:

  1. /**本例子为博主随手一写,来说明用法,并未验证*/  
  2. private class MyAdapter extends BaseAdapter {  
  3.   
  4.     private ArrayList<SoftReference<Bitmap>> mBitmapRefs = new ArrayList<SoftReference<Bitmap>>();  
  5.     private ArrayList<Value> mValues;  
  6.     private Context mContext;  
  7.     private LayoutInflater mInflater;  
  8.   
  9.     MyAdapter(Context context, ArrayList<Value> values) {  
  10.         mContext = context;  
  11.         mValues = values;  
  12.         mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  13.     }  
  14.     public int getCount() {  
  15.         return mValues.size();  
  16.     }  
  17.   
  18.     public Object getItem(int i) {  
  19.         return mValues.get(i);  
  20.     }  
  21.   
  22.     public long getItemId(int i) {  
  23.         return i;  
  24.     }  
  25.   
  26.     public View getView(int i, View view, ViewGroup viewGroup) {  
  27.         View newView = null;  
  28.         if(view != null) {  
  29.             newView = view;  
  30.         } else {  
  31.             newView =(View)mInflater.inflate(R.layout.image_view, false);  
  32.         }  
  33.   
  34.         Bitmap bitmap = BitmapFactory.decodeFile(mValues.get(i).fileName);  
  35.         mBitmapRefs.add(new SoftReference<Bitmap>(bitmap));     //此处加入ArrayList  
  36.         ((ImageView)newView).setImageBitmap(bitmap);  
  37.   
  38.         return newView;  
  39.     }  
  40. }  

六、行踪诡异的Cursor

    Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。

    然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。

    所以我们使用Cursor的方式一般如下:

  1. Cursor cursor = null;  
  2. try {  
  3.     cursor = mContext.getContentResolver().query(uri,null, null,null,null);  
  4.     if(cursor != null) {  
  5.         cursor.moveToFirst();  
  6.         //do something  
  7.     }  
  8. } catch (Exception e) {  
  9.     e.printStackTrace();    
  10. } finally {  
  11.     if (cursor != null) {  
  12.        cursor.close();  
  13.     }  
  14. }  

    有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。

  1. @Override  
  2. protected void onDestroy() {        
  3.     if (mAdapter != null && mAdapter.getCurosr() != null) {  
  4.         mAdapter.getCursor().close();  
  5.     }  
  6.     super.onDestroy();   
  7. }  

  CursorAdapter中的changeCursor函数,会将原来的Cursor释放掉,并替换为新的Cursor,所以你不用担心原来的Cursor没有被关闭。

  你可能会想到使用Activity的managedQuery来生成Cursor,这样Cursor就会与Acitivity的生命周期一致了,多么完美的解决方法!然而事实上managedQuery也有很大的局限性。

    managedQuery生成的Cursor必须确保不会被替换,因为可能很多程序事实上查询条件都是不确定的,因此我们经常会用新查询的Cursor来替换掉原先的Cursor。因此这种方法适用范围也是很小。

七、其它要说的。

    其实,要减小内存的使用,其实还有很多方法和要求。比如不要使用整张整张的图,尽量使用9path图片。Adapter要使用convertView等等,好多细节都可以节省内存。这些都需要我们去挖掘,谁叫Android的内存不给力来着。

 

 

根据我个人的开发经验总结了如下几点优化内存的方法:

  1. 创建或其他方式获得的对象如不再使用,则主动将其置为null。
  2. 尽量在程序中少使用对图片的放大或缩小或翻转.在对图片进行操作时占用的内存可能比图片本身要大一些。
  3. 尽可能的将一些静态的对象(尤其是集合对象),放于SQLite数据库中。并且对这些数据的搜索匹配尽可能使用sql语句进行。
  4. 一些连接资源在不使用使应该释放,如数据库连接文件输入输出流等。应该避免在特殊的情况下不释放(如异常或其他情况)
  5. 一些长周期的对像引用了短周期的对象,但是这些短周期的对象可能只在很小的范围内使用。所以在查内存中也应该清除这一隐患。
  6. 一个对象被多个对象引用,但是只释放了一处,也可能会导致这个对像不会被释放。
分享到:
评论

相关推荐

    Android内存OOM优化详解.pdf

    Android 内存 OOM 优化和治理,Android 内存泄漏原因详解与应对措施,Android 中的 Bitmap 使用详解,Android 中的 Bitmap 内存治理和优化详解,Android 内存分析命令详解,Java内存模型。Android 内存 OOM 优化和...

    android内存优化详解

    android内存优化详解

    android优化技术详解

    android优化技术详解:包括:UI布局优化,内存优化,性能优化,代码优化

    Android开发内存优化详解文档

    详解Android的内存优化–LruCache.zip

    详解Android的内存优化–LruCache.zip

    Android Bitmap详解及Bitmap的内存优化

    Android Bitmap详解及Bitmap的内存优化 一、Bitmap: Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。 常用方法: ...

    解析Android开发优化之:对Bitmap的内存优化详解

    这里就有疑问了,Android系统有自己的垃圾回收机制,可以不定期的回收掉不使用的内存空间,当然也包括Bitmap的空间。那为什么还需要这个方法呢? Bitmap类的构造方法都是私有的,所以开发者不能直接new出一个Bitmap...

    Android性能优化之Bitmap图片优化详解

    在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError – 内存溢出),本篇博客,我们将一起探讨Bitmap的性能优化。 为什么Bitmap会导致OOM? 1...

    详解Android性能优化之内存泄漏

    那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况。在应用中内出现一次两次的内存泄漏...

    详解Android的内存优化–LruCache

    概念: LruCache 什么是LruCache? LruCache实现原理是什么? 这两个问题其实可以作为一个问题来回答,知道了什么是 LruCache,就只然而然的知道 LruCache 的实现原理;Lru的全称是Least Recently Used ,近期最少...

    Android典型技术模块开发详解

    2.2 HelloAndroid详解 2.2.1 程序结构 2.2.2 代码分析 2.3 权限permission 2.4 LogCat日志调试 2.5 示例练习 2.5.1 登录界面 2.5.2 事件处理 2.6 本章小结 第二篇 Android开发关键组件 第3章 Activity(活动) 3.1 ...

    Android性能优化之利用Rxlifecycle解决RxJava内存泄漏详解

    其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所有想着查找一下资料学习一下如何解决RxJava引起的内存泄漏...

    详解Android的内存优化--LruCache

    LruCache是基于Lru算法实现的一种缓存机制。本文对LruCache的概念和实现原理进行介绍,通过实例分析和使用介绍,让大家更好的了解LruCache,下面跟着小编一起来看下吧

    解析Android开发优化之:对界面UI的优化详解(一)

    这样做的好处很多,既可以减少内存的使用,又可以减少部分工作量,也可以缩减程序安装包的大小。 下面从几个方面来介绍如何利用系统资源。 1)利用系统定义的id 比如我们有一个定义ListView的xml文件,一般的,我们...

Global site tag (gtag.js) - Google Analytics