Android 窗体泄露,内存泄露的优化法则

内存泄露:实质应用程序不能及时释放内存或者加载到内存上的数据太大而导致的OOM问题

窗体泄露:是指Activity或者Fragment在Destory的情况下启用了窗体API

StackOverflowError:应用程序调用中,导致栈空间无限延长,超过了虚拟机的承载能力


1>Handler的使方式和内存优化

消息队列遵循先进先出(First in First out)的原则来讲某些信息或者任务进行排队等待。android中有自己的消息队列,如Handler和Broadcastreceiver。

andriod提供了 Handler 和 Looper 来满足线程间的通信。Handler 先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(Message Exchange)。
1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。
2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到Message Queue里;或者接收Looper从Message Queue取出)所送来的消息。
3) Message Queue(消息队列):用来存放线程放入的消息。
4)线程:UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。

Handler的集中创建方式和用法

Handler mHandler = new Handler() {  
          public void handleMessage(Message msg) {   
               switch (msg.what) {   
                    case TestHandler.GUIUPDATEIDENTIFIER:   
                         myBounceView.invalidate();  
                         break;   
               }   
               super.handleMessage(msg);   
          }   
     };

在工作线程中创建(异步)Handler

class LooperThread extends Thread
{
public Handler mHandler;
public void run() 
{
    Looper.prepare();
    mHandler = new Handler() 
    {
            public void handleMessage(Message msg) 
            {
            
            }
        };
    Looper.loop();
}

使用HandlerThread创建(异步)Handler

HandlerThread handlerThread = new HandlerThread("my.handlerthread");
handlerThread.start(); 

Handler mHandler = new Handler(handlerThread.getLooper()){
   public void handleMessage(Message msg) 
   {
            
   }
};


Handler的用法很多,这里只贴出线程通信的用法,读者可以自行深入研究其他用法

class myThread implements Runnable {   
          public void run() {  
               while (!Thread.currentThread().isInterrupted()) {    
                    //这里可以使用 new,因为主线程中的消息队列只有一条
                    Message message = new Message(); 
                    message.what = 1024;   
                      
                    TmHandler.sendMessage(message);   
                    try {   
                         Thread.sleep(100);    
                    } catch (InterruptedException e) {   
                         Thread.currentThread().interrupt();   
                    }   
               }   
          }   
     }
1.1>Handler关于内存泄露
当我们这样写在一个Activity中时,Android Lint会提示我们这样一个 warning: In Android, Handler classes should be static or leaks might occur.。
意思说:在Android中,Handler 类应该是静态的否则可能发生泄漏

同样在Eclipse代码编辑区域的 Handler定义行也会出现类似的警告,一般都会加上supresslint的注解

先来看看,为什么会出现这种问题:

1.当Android程序第一次创建的时候,在主线程同时会创建一个Looper对象。Looper实现了一个简单的消息队列,一个接着一个处理Message对象。
程序框架所有主要的事件(例如:屏幕上的点击时间,Activity生命周期的方法等等)都包含在Message对象中,然后添加到Looper的消息队列中,
一个一个处理。主线程的Looper存在整个应用程序的生命周期内。
2.当一个Handler对象在主线程中创建的时候,它会关联到Looper的 message queue 。Message添加到消息队列中的时候Message
会持有当前Handler引用,当Looper处理到当前消息的时候,会调用Handler#handleMessage(Message).
3.在java中,no-static的内部类会 隐式的 持有当前类的一个引用。static的类则没有。

综上三点可知,这种泄露非常危险,Activity或者被持有者(宿主)不能及时释放内存,Looper也在不断循环,是导致内存泄露原因之一,

原因之二是 Activity未被回收,当Activity处于非活动状态时,如果handlerMessage的处理导致UI的改变,将会导致窗体泄露,比如弹框,UI改变等。

1.2定义静态类
  /** 
   * 使用静态的内部类,不会持有当前对象的引用 
   */ 
  private static class MyHandler extends Handler 
  { 
    private SoftReference<Activity> mActivityReference = null; 
 
    public MyHandler(SampleActivity activity) 
    { 
       mActivityReference = new SoftReference<Activity>(activity)
    } 
 
    @Override 
    public void handleMessage(Message msg) { 
      SampleActivity activity = (SampleActivity )mActivityReference.get(); 
      if (activity != null) { 
        // ... 
      } 
    } 
    
    public void release()
    {
       removeCallbacksAndMessages(null);
       mActivityReference.clear();

    }
  } 
 
  private final MyHandler mHandler = new MyHandler(this);

在Activity的OnDestroy中调用

  public void onDestroy()
  {
       mHandler.release();
  }
1.3 Handler替代方案

有些时候,Handler无需自己定义,我们可以使用View自身提供的Handler进行操作

myView.post()

myView.postDelay()

myView.getHandler()

runOnUiThread()

.............................

LocalBroadcastReceiver

BroadcastReceiver

或者使用开源方案EventBus


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

2>避免(static)全局化或被全局对象持有,严格按照Android注册/解除机制

2.1 避免全局static化

对于广播,或者一些Runnable,CallBacks甚至普通内部类,我们要避免全局化,导致变量不能被释放,从而对象也不能释放

这里所说的全局化是被静态对象持有,比如单例,全局静态List,Map等

2.2全局化往往是容易持有对象,因此我们必须学会

add——remove

addAll——clearAll

register——unregister

bind——unbind


这里相关的主要有View,ViewTreeObserver,ContentObserver,static Map,static List, static SparseArray,singleObject如

View.addOnAttachStateChangeListener

View.addOnLayoutChangeListener

ViewTreeObserver.add

registerBroadcast

bindService

registerContentObserver

........

2.3如果全局化对象非要持有Context对象

建议使其持有ApplicationContext对象,而不是Service,BroadCastReceiver或者Activity

举个栗子

android.support.v4.content.LocalBroadcastManager的getInstance

   private static LocalBroadcastManager mInstance;
   
  public static LocalBroadcastManager getInstance(Context context)
  {
        if(mInstance == null)
            mInstance = new LocalBroadcastManager(context.getApplicationContext());
        return mInstance;
       
    }

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

3>Bitmap内存优化

Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存
Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。 所以在对图片效果不是特别高的情况下使用RGB_565(565没有透明度属性)

publicstaticBitmapreadBitMap(Contextcontext, intresId) {
    BitmapFactory.Optionsopt = newBitmapFactory.Options();
    opt.inPreferredConfig = Bitmap.Config.RGB_565;
    opt.inPurgeable = true;
    opt.inInputShareable = true;
    //获取资源图片 
    InputStreamis = context.getResources().openRawResource(resId);
    returnBitmapFactory.decodeStream(is, null, opt);
}
// 先判断是否已经回收
if(bitmap != null && !bitmap.isRecycled()){
    // 回收并且置为null
    bitmap.recycle();
    bitmap = null;
}
3.1Bitmap调用的时候有个方法是recycle,用来释放内存
3.2使用SoftReference<Bitmap>,WeakReference<Bitmap>进行小内存引用管理
3.3建立磁盘缓存LruDisk,LruCache,具体需要Http 304文件校验,Http 206断点续传等方面知识
3.4进行图片压缩,具体参考http://my.oschina.net/ososchina/blog/495861


4>使用Android中高效的数据结构

具体用法请参考http://my.oschina.net/ososchina/blog/355721


5>关闭IO流,关闭Cursor,关闭Formatter,按时结束线程,避免使用AsyncTask


6>.普通内部类

内部类隐式持有主类的当前对象,对于内部类需不需要和Handler一样进行静态化,完全取决于内部类会不会被全局对象引用,如果被全局对象引用,那么static,否则no-static


7>判断Activity的状态机制,在Activity被压入栈中时阻止某些设计UI的API操作,防止窗体泄露

参考:Android postDelay+Dialog引起的窗体泄露

8>对Adapter中的View进行缓存

findViewById本身不需要缓存,因为每次都是树形遍历,而LayoutInflater每次都是创建一个新的View,所以必要时使用ViewHolder或者ViewModel进行缓存


9>不要过于使用枚举,枚举在内存中所占字节是2倍的静态变量所占的字节


10.提前释放某些不再用或者急需要减少内存的变量

this.field = null;
---------------------------------------------------------------------------------------------------
ViewGroup  container= getWindow().getDecorView().findViewById(android.R.id.content);
container.removeAllViews();
---------------------------------------------------------------------------------------------------
Iterator it = mImageList.iterator();
while(it.hasNext())
{
   Bitmap bmp = it.next();
    bmp.recycle();
     it.remove();
}
---------------------------------------------------------------------------------------------------
mList.clear();



你可能感兴趣的:(Handler内存泄露,Handler内存溢出,Handler遇到OOM)