谈谈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; }
说明每个请求都保留了mErrorListener和mListener对象,那我们就可以说,每个对象都有可能间接持有对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
相关推荐
恰好最近做了内存优化相关的工作,这里也对Android内存优化相关的知识做下总结。在开始文章之前推荐下公司同事翻译整理版本《Android性能优化典范-第6季》,因为篇幅有限这里我对一些内容只做简单总结
六、内存优化实践 在实际开发中,开发者应关注对象生命周期管理,避免内存泄漏,合理控制内存分配,以及选择合适的图片加载策略,确保应用在有限的内存资源下高效稳定运行。 总之,Android内存优化是一个涉及多...
【深入探索Android内存优化1】 Android内存优化是开发者必须掌握的关键技能之一,它涉及到应用程序的稳定性和用户体验。本文将深入探讨内存优化的相关概念、工具、管理机制以及常见问题,帮助开发者构建一个完整的...
本文将深入探讨Android内存优化的几个关键方面,并提供一些实用的策略和工具。 一、内存泄漏及其检测 内存泄漏是指应用占用的内存无法被系统回收,导致可用内存减少,甚至引发应用崩溃。Android中的内存泄漏常见于...
总结,Android内存优化是一个系统性的工作,涉及到多个层面,开发者需要深入理解内存管理机制,熟练掌握各种优化技巧,才能打造出运行流畅、占用资源合理的应用。通过持续学习和实践,开发者可以不断提升自己在内存...
在Android平台上,优化应用以适应最低内存环境是提高应用程序性能和用户体验的关键步骤。尤其是在资源有限的设备上,如入门级智能手机或平板电脑,确保应用高效运行尤为重要。以下是一些关于如何优化Android应用内存...
这个名为"Android学习资料之内存优化.zip"的压缩包包含了一些关于Android内存优化的重要文档,这些文档深入探讨了常见的内存泄露问题以及对应的优化策略。以下是根据这些知识点进行的详细解读: 1. **内存泄露**:...
本篇文章将深入探讨如何优化Android帧动画以降低内存使用。 首先,理解帧动画的工作原理至关重要。帧动画是通过`AnimationDrawable`类实现的,它是一个可绘制对象,可以包含一系列`<frame>`元素,每个元素代表一帧...
Android内存优化是提升应用性能和用户体验的关键因素,尤其是在防止Out Of Memory (OOM)错误方面。本文将深入探讨Android内存管理的基础、内存优化策略、Bitmap的使用及管理、内存泄漏的原因和解决方案,以及如何...
这份名为"Android应用性能和内存优化实践"的PPTX文件着重讲述了如何通过一系列技术和策略来改进应用的启动速度和整体性能,尤其是在中低端设备上。以下是针对该主题的详细说明: 1. **用户体验优化**:优化的核心是...
"Android应用性能优化最佳实践"这本书深入探讨了如何提升Android应用的性能,以下是一些核心知识点的总结: 1. **内存管理**:内存泄漏是Android应用性能问题的常见来源。开发者需要学会使用内存分析工具,如MAT ...
常见的Android内存泄露类型包括: 1. **静态变量引用**:当一个静态变量引用了一个Activity或Context,这个对象就无法被GC回收,因为静态变量在整个应用生命周期内都存在。解决方案是避免使用静态变量持有Activity...
总之,Android内存优化涉及多个层面,包括正确管理对象生命周期、减少不必要的内存分配、合理使用内存池、避免内存泄漏等。通过深入理解和实践这些原则,开发者可以创建出更高效、更稳定的Android应用。
五、内存优化策略 1. 使用轻量级数据结构:如使用ArrayList代替LinkedList,减少内存开销。 2. 及时释放资源:如关闭数据库连接、释放Bitmap等。 3. 使用WeakReference或SoftReference:避免内存泄漏。 4. 注意生命...
布局优化,绘制优化,内存优化,启动优化,其他,稳定,省电优化,体积优化等
这份名为"Google技术大会:如何将你的Android使用界面更快和更高效益"的PDF文档,很可能是对Android内存优化的一份详尽指南。以下是根据标题和描述推测的一些关键知识点: 1. **内存管理基础**: - Android系统的...
一、Android内存管理基础 Android系统采用Dalvik或ART虚拟机进行内存管理,它们都遵循Java的垃圾回收(Garbage Collection, GC)机制。GC会自动清理不再使用的对象,释放内存。然而,过度依赖GC可能导致不必要的内存...
在Android平台上,电池和内存优化是提升应用性能和用户体验的关键因素。本文将深入探讨一系列针对Android设备电池寿命和内存管理的优化策略、工具以及方法。 一、电池优化 1. **减少唤醒源(Wakelocks)**:...
本示例“工作中遇到的Android内存优化问题demo”旨在通过具体的代码实例,帮助开发者理解并解决常见的内存问题。下面,我们将深入探讨Android内存管理机制,以及如何进行有效的内存优化。 1. Android内存管理基础:...