`
砺雪凝霜
  • 浏览: 151344 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Android内存优化实践

阅读更多

                                               谈谈Android内存优化

     引起Android内存泄露的情况有很多,但是很多都是我们代码不规范引起的。只要我们平时代码规范点我们都能开发出性能比较高的APP应用。
    引起泄露的原因大都是,由于不适当的引用,导致内存较大的对象没有及时释放,导致内存居高不下,严重的时候可能会引起OOM。在Android开发中泄露的原因,总结下来,大都是由于Activity Context和bitmap对象使用不当引起的。下面就来与大家分享下我在开发一个TV项目中遇到的内存泄露的情况。

(一) adapter中保留对Activity的引用

     在创建adapter的时候传入Activity,并把Activity作为成员变量保存下来
    if (mAdapter == null) {
                        mAdapter = new ChannelDetailAdapter(this, mDataList,  mPageGridView);
                        mPageGridView.setAdapter(mAdapter);
                    } else {
                        mAdapter.setDataList(mDataList);
                        mPageGridView.notifyDataSetChanged(false);
                        mPageGridView.setSelectionPositionNotFocus(0);//重置 position
                    }
 
public class ChannelDetailAdapter extends BaseGridViewAdapter<AlbumModel> {
    private Context mContext;
    public ChannelDetailAdapter(Context mContext, List<AlbumModel> mDataList, PageGridView mPageGridView) {
        super(mContext, mDataList, mPageGridView);
        this.mContext = mContext;
    }
}
 
为什么会泄露呢?
      因为adapter对Activity进行了引用,而在我们finish Activity的时候,adapter并没有置为null,这样就会导致Activity内存无法及时释放。(补充:adapter的生命周期和Activity的生命周期是一致的,但是把Adapter置为null的话会让Activity的资源及时释放)
解决办法:
(1)adapter中保留非要保存一个Context对象的话,在我们new Adapter的时候传入Application   Context
(2)adapter中一定要保存一个Activity Context的话,比如在adapter中有点击跳转的逻辑。一定要在onDestroy方法中把adapter置为null

(二) listener回调引起的泄露

   (1)Activity中添加了回调监听,而没有把listener给干掉

    项目中为了方便处理网络请求的各种情况,我们自定义了一个数据异常的View,并给出相应的回调处理,例如重试操作,因为实现回调接口的是当前的Activity,所以listener就无形中对Activity进行了引用,所以我们必须在activityonDestroy前,把listener置为null,这样更容易让Activity的资源回收
this.mDataErrorView.setErrorListener(this);

 (2) 使用Volley的回调导致的内存泄露

     volley创建的时候会保留2个listener变量,一个错误的回调和一个正确的回调,如果我们在Activity结束的时候,没有把这2个回调给干掉,也有可能会导致内存泄露。但是Volley并没有提供相应的方法清理回调。下面我就来分析下问题是怎么产生的,并给出相应的解决方法。
先看看Request的构造方法,保留了mErrorListener

 

  public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mIdentifier = createIdentifier(method, url);
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());
        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

 

再看看Request的子类StringRequest的构造方法

 

 public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

 

 

     说明每个请求都保留了mErrorListenermListener对象,那我们就可以说,每个对象都有可能间接持有对Activity的引用
如何解决这个问题
    Volley提供了一个取消请求的方法,google官网是建议我们在Activity onStop的时候,cancel掉每个请求,这个时候我就想到在每个请求cancel的时候,把listener干掉。但是由于把请求取消的时候,不会触发回调,用的时候要注意。
由于请求队列一般都在application中取消的,所以在application中可以提供一个取消请求的方法
  /**
     * 网络请求优化,取消请求
     * @param tag
     */
    public  void  cancelRequest(String tag){
        try {
            mRequestQueue.cancelAll(tag);
        }catch (Exception e){
            Logger.e("LeSportsApplication","tag =="+ tag + "的请求取消失败");
        }
    }
真正取消请求的,实际上调用的方法是Request的cancel这个方法
 /**
     * Cancels all requests in this queue with the given tag. Tag must be non-null
     * and equality is by identity.
     */
    public void cancelAll(final Object tag) {
        if (tag == null) {
            throw new IllegalArgumentException("Cannot cancelAll with a null tag");
        }
        cancelAll(new RequestFilter() {
            @Override
            public boolean apply(Request<?> request) {
                return request.getTag() == tag;
            }
        });
    }

 最终调用的request的cancel方法,其实就是把mCaceled方法设为false,最终会触发请求将不会分发,  最后回调也就无效了

 public void cancelAll(RequestFilter filter) {
        synchronized (mCurrentRequests) {
            for (Request<?> request : mCurrentRequests) {
                if (filter.apply(request)) {
                    request.setTag(null);
                    request.cancel();
                }
            }
        }
    }
看看request中的finish方法,把mErrorListener置为空
 public void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
        }
        if (MarkerLog.ENABLED) {
            final long threadId = Thread.currentThread().getId();
            if (Looper.myLooper() != Looper.getMainLooper()) {
                // If we finish marking off of the main thread, we need to
                // actually do it on the main thread to ensure correct ordering.
                Handler mainThread = new Handler(Looper.getMainLooper());
                mainThread.post(new Runnable() {
                    @Override
                    public void run() {
                        mEventLog.add(tag, threadId);
                        mEventLog.finish(this.toString());
                    }
                });
                return;
            }
            mEventLog.add(tag, threadId);
            mEventLog.finish(this.toString());
        } else {
            long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
            if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
                VolleyLog.d("%d ms: %s", requestTime, this.toString());
            }
        }
        mErrorListener = null;
    }

   重新Request子类的finish方法,Volley是没有重写这个方法,你要改下源码就好

 

 public void finish(final String tag) {
        super.finish(tag);
        mListener = null;
    }

 

     总结:取消请求最终调用的方法是request的finish方法,只要在这个方法中把mErrorListener和mListener2个回调置为空,那么泄露问题就解决了。

 (三) 使用Handler导致的泄露

    如果handler保留了对Acitity的引用,那么同样也有可能导致内存泄露。我们都知道handler发送消息的时候,消息会放到消息队列里面,然后由Lopper来不断循环消息,直到把消息发送给消息的target(也就是发送消息的handler),而Lopper的生命周期跟Application是一样的,那么Lopper就会间接持有Activity的引用,所以会导致内存泄露。解决方法如下:

  (1) 用static 修饰handler,并用WeakRefrerence修饰Activity

 

 private static class MsgHandler extends Handler {  
     private WeakReference<Activity> mActivity;  
  
     MsgHandler(Activity activity) {  
         mActivity = new WeakReference<Activity>(activity);  
     }  
   
     @Override  
    public void handleMessage(Message msg) {  
        Activity activity = mActivity.get();  
        if (activity != null) {  
            activity.handleMessage(msg);  
        }  
    }  
 }  
   
 private Handler mHandler = new MsgHandler(this);

(2) 在onDestroy的方法中,把handler中运行的线程和消息全都

remove掉

 @Override
    public void onDestroy() {
        super.onDestroy();
        if(mHandler != null){
            mHandler.removeCallbacks(mPreloadingRunnable);
            mHandler.removeMessages(DERECTLY_SEARCH);
        }
        Logger.e(TAG, "onDestroy");
    }

 

(四) 在单例类中持有Activity的引用

      由于单例的生命周期是和Application保持一致的,当Activity被销毁的时候,而这个单例类并没有回收掉,也就是说Activity由于该单例类还持有对它的引用,导致Activity不能被回收掉。

 解决方法:在单例中使用ApplicationContext作为成员变量
public SingleInstance(Context contex){
   mContext = context.getApplicationContext();
}

(五) 养成良好的编码习惯

    虽然说Java内存回收线程会自动帮我们回收不需要的内存,但是这种回收的方式还是比较迟钝的。最好遵循谁用谁释放的原则,这样会更有利于内存的回收。我们new出一个对象的时候,一定要考虑在Activity或者fragment退出时把这些对象置为空,使这些对象的生命周期和Activity和Fragment的生命周期保持一致,这样就可以保证在Activity退出的时候,内存得到充分释放

      最近刚刚完成了一个TV项目,我们在开发中,程序经常会莫名的OOM,用MAT就发现,程序中有许多的内存泄露问题。解决了一部分的内存泄露问题,但是还是会经常OOM。网上找了一篇比较不错的内存优化博客,然后按照该文章实践,果然内存泄露导致的OOM问题,终于解决了。

文章地址分享给大家:http://bugly.qq.com/blog/?p=884
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 

分享到:
评论
2 楼 砺雪凝霜 2016-02-04  
可以加我QQ:1029457926  一起交流,一起进步,一起开发性能最优的APP
1 楼 zhu_jinlong 2016-01-31  
非常好的文章,做到这些并牢记处理原则,可以远离memory leak了

相关推荐

    Android内存优化实践与总结

    恰好最近做了内存优化相关的工作,这里也对Android内存优化相关的知识做下总结。在开始文章之前推荐下公司同事翻译整理版本《Android性能优化典范-第6季》,因为篇幅有限这里我对一些内容只做简单总结

    Android应用性能和内存优化实践.pptx

    Android应用性能和内存优化实践.pptx

    Android性能优化最佳实践(png图)

    布局优化,绘制优化,内存优化,启动优化,其他,稳定,省电优化,体积优化等

    总结Android App内存优化之图片优化

    网上有很多大拿分享的关于Android性能优化的文章,主要是通过各种工具分析,使用合理的技巧优化APP的体验,提升APP的流畅度,但关于内存优化的文章很少有看到。下面是我在实践过程中使用的一些方法,很多都是不太...

    Android高级开发实践课

    Android高级开发课,包含内存优化,APP启动优化,apk包体积优化,网络优化,编译插桩,移动技术架构演进等

    Android-WaveLineView一款性能内存友好的录音波浪动画

    A memory-friendly recording wave animation一款性能内存友好的录音波浪动画

    深入Android应用开发 核心技术解析与最佳实践

    第15章讲解了Android的系统管理原理,包含内存管理、应用管理、电源管理、系统管理,以及系统的还原、升级、配置和备份等多方面的内容,能帮助开发者从一个更高的视角去理解Android的运行机制。

    深入Android应用开发 核心技术解析与最佳实践.z01

    第15章讲解了Android的系统管理原理,包含内存管理、应用管理、电源管理、系统管理,以及系统的还原、升级、配置和备份等多方面的内容,能帮助开发者从一个更高的视角去理解Android的运行机制。

    移动应用课程开发实践-基于Android的体重管理APP实现.rar

    Dalvik是经过优化的Java编译器,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为一个独立的Linux进程执行,独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。项目的开发在Eclipse环境...

    Android开发:浅谈MVP模式应用与内存泄漏问题解决

    最近博主开始在项目中实践MVP模式,却意外发现内存泄漏比较严重,但却很少人谈到这个问题,促使了本文的发布,本文假设读者已了解MVP架构。 MVP简介 M-Modle,数据,逻辑操作层,数据获取,数据持久化保存。比如...

    Android开发艺术探索.任玉刚(带详细书签).pdf

    本书是一本Android进阶类书籍,采用理论、源码和实践相结合的方式来阐述高水准的Android应用开发要点。本书从三个方面来组织内容。第一,介绍Android开发者不容易掌握的一些知识点;第二,结合Android源代码和应用层...

    Android高级编程--源代码

    1.5.8 优化的内存和进程管理 8 1.6 开放手机联盟简介 8 1.7 运行Android的环境 8 1.8 从事Android开发的原因 9 1.8.1 推动Android普及的因素 9 1.8.2 Android的独到之处 10 1.8.3 改变移动开发格局 10 1.9 ...

    MDCC Android专场PPT资源

    05-胡凯 - Android内存优化之5R法则 06-董霖-Android远程问题诊断和监测 07-吴更新-开源项目选型之图片缓存 08-廖祜秋-React Native for Android 09 陈家林 - Android应用安全检测 20151014白皮书

    android开发艺术探索高清完整版PDF

    / 484 14.4 JNI调用Java方法的流程 / 486 第15章 Android性能优化 / 489 15.1 Android的性能优化方法 / 490 15.1.1 布局优化 / 490 15.1.2 绘制优化 / 493 15.1.3 内存泄露优化 / 493 15.1.4 响应速度优化和...

    Android-GC-Research:Android GC 机制调研

    网上也不乏众多的android 内存优化文章,成为开发者的编码守则。但不管怎么遵守,内存管理依然像一个黑盒子一样,反正我是写着不踏实。就比如下面这几种情况: System.gc(),真的是随叫随到? 软引用弱引用的错误使用...

    Android开发艺术探索

    《Android开发艺术探索》是一本Android进阶类书籍,采用理论、源码和实践相结合的方式来阐述高水准的Android应用开发要点。《Android开发艺术探索》从三个方面来组织内容。第一,介绍Android开发者不容易掌握的一些...

    Android中一张图片占用的内存大小

    最近面试过程中发现对Android中一些知识有些模棱两可,之前总是看别人的总结,自己没去实践过,这两天对个别问题进行专门研究 探讨:如何计算Android中一张图片占据内存的大小 解释:此处说的占据内存,是APP加载...

    Android程序设计基础

    Android针对低能耗、低内存的设备进行了优化,这种根本性的优化是之前的平台从未尝试过的。  高质量的图形和声音。将类似于Flash的光滑、无锯齿的2D矢量图形和动画与3D加速的OpenGL图形相结合,可实现各种新式的...

    Android代码-TinyPinyin

    适用于Java和Android的快速、低内存占用的汉字转拼音库。 特性 生成的拼音不包含声调,均为大写; 支持自定义词典,支持简体中文、繁体中文; 执行效率很高(Pinyin4J的4~16倍); 很低的内存占用(不添加词典时小于...

Global site tag (gtag.js) - Google Analytics