android中使用Cursor时防止内存泄露的几个方面

2024-05-12 07:38

本文主要是介绍android中使用Cursor时防止内存泄露的几个方面,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 最近在工作中处理了一些内存泄露的问题,在这个过程中我尤其发现了一些基本的问题反而忽略导致内存泄露,比如静态变量,cursor关闭,线程,定时器,反注册,bitmap等等,我稍微统计并总结了一下,当然了,这些问题这么说起来比较笼统,接下来我会根据问题,把一些实例代码贴出来,一步一步分析,在具体的场景下,用行之有效的方法,找出泄露的根本原因,并给出解决方案。
   现在,就从cursor关闭的问题开始把,谁都知道cursor要关闭,但是往往相反,人们却常常忘记关闭,因为真正的应用场景可能并非理想化的简单。
1. 理想化的cursor关闭

// Sample Code
Cursor cursor = db.query();
List<String> list = convertToList(cursor);
cursor.close();
 这是最简单的cursor使用场景,如果这里的cursor没有关闭,我想可能会引起万千口水,一片骂声。

   但是实际场景可能并非如此,这里的cursor可能不会关闭,至少有以下两种可能。

2. Cursor未关闭的可能
    (1). cursor.close()之前发生异常。
    (2). cursor需要继续使用,不能马上关闭,后面忘记关闭了。

3. Cursor.close()之前发生异常
    这个很容易理解,应该也是初学者最开始碰到的常见问题,举例如下:

try{ Cursor c = queryCursor(); int a = c.getInt(1); ......// 如果出错,后面的cursor.close()将不会执行......c.close();}catch(Exception e) { 
}

  正确写法应该是:

Cursor c;
try{ c = queryCursor(); int a = c.getInt(1); ......// 如果出错,后面的cursor.close()将不会执行//c.close();
}catch(Exception e) { 
} finally{if(c != null) {c.close();}
} 

   很简单,但是需要时刻谨记。

4. Cursor需要继续使用,不能马上关闭
   有没有这种情况?怎么办?
   答案是有,CursorAdapter就是一个典型的例子。
   CursorAdapter示例如下:

mCursor = getContentResolver().query(CONTENT_URI, PROJECTION,
null,null,null);
mAdapter = newMyCursorAdapter(this, R.layout.list_item, mCursor);
setListAdapter(mAdapter);
// 这里就不能关闭执行mCursor.close(),
// 否则list中将会无数据

5. 这样的Cursor应该什么时候关闭呢?

   这是个可以说好回答也可以说不好回答的问题,那就是在Cursor不再使用的时候关闭掉。
   比如说,
   上面的查询,如果每次进入或者resume的时候会重新查询执行。
   一般来说,也只是这种需求,很少需要看不到界面的时候还在不停地显示查询结果,如果真的有,不予讨论,记得最终关掉就OK了。
   这个时候,我们一般可以在onStop()方法里面把cursor关掉(同时意味着你可能需要在onResume()或者onStart()重新查询一下)。

@Override
protected void onStop() {super.onStop();// mCursorAdapter会释放之前的cursor,相当于关闭了cursormCursorAdapter.changeCursor(null);
}

  我专门附上CursorAdapter的changeCursor()方法源码,让大家看的更清楚,免得不放心changeCursor(null)方法:

/*** Change the underlying cursor to a new cursor. If there is an existing cursor it will be* closed.** @param cursor The new cursor to be used*/
public void changeCursor(Cursor cursor) {Cursor old = swapCursor(cursor);if(old != null) {old.close();}
}/*** Swap in a new Cursor, returning the old Cursor.  Unlike* {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>* closed.** @param newCursor The new cursor to be used.* @return Returns the previously set Cursor, or null if there wasa not one.* If the given new Cursor is the same instance is the previously set* Cursor, null is also returned.*/
public Cursor swapCursor(Cursor newCursor) {if(newCursor == mCursor) {returnnull;}Cursor oldCursor = mCursor;if(oldCursor != null) {if(mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);if(mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);}mCursor = newCursor;if(newCursor != null) {if(mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);if(mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");mDataValid = true;// notify the observers about the new cursornotifyDataSetChanged();}else{mRowIDColumn = -1;mDataValid = false;// notify the observers about the lack of a data setnotifyDataSetInvalidated();}returnoldCursor;
}

6.  实战AsyncQueryHandler中Cursor的关闭问题
   AsyncQueryHandler是一个很经典很典型的分析Cursor的例子,不仅一阵见血,能举一反三,而且非常常见,为以后避免。
   AsyncQueryHandler文档参考地址:
http://developer.android.com/reference/android/content/AsyncQueryHandler.html
   下面这段代码是Android2.3系统中Mms信息主页面ConversationList源码的一部分,大家看看Cursor正确关闭了吗?

private final class ThreadListQueryHandler extends AsyncQueryHandler {public ThreadListQueryHandler(ContentResolver contentResolver) {super(contentResolver);}@Overrideprotected void onQueryComplete(int token, Object cookie, Cursor cursor) {switch(token) {caseTHREAD_LIST_QUERY_TOKEN:mListAdapter.changeCursor(cursor);setTitle(mTitle);... ...break;caseHAVE_LOCKED_MESSAGES_TOKEN:long threadId = (Long)cookie;confirmDeleteThreadDialog(newDeleteThreadListener(threadId, mQueryHandler,ConversationList.this), threadId == -1,cursor != null&& cursor.getCount() > 0,ConversationList.this);break;default:Log.e(TAG,"onQueryComplete called with unknown token " + token);}}
}@Override
protected void onStop() {super.onStop();mListAdapter.changeCursor(null);
}

   大家觉得有问题吗?
   主要是两点:
   (1). THREAD_LIST_QUERY_TOKEN分支的Cursor正确关闭了吗?
   (2). HAVE_LOCKED_MESSAGES_TOKEN分支的Cursor正确关闭了吗?
   根据前面的一条条分析,答案是:
   (1). THREAD_LIST_QUERY_TOKEN分支的Cursor被传递到了mListAdapter了,而mListAdapter在onStop里面使用changeCursor(null),当用户离开当前Activity,这个Cursor被正确关闭了,不会泄露。
   (2). HAVE_LOCKED_MESSAGES_TOKEN分支的Cursor(就是参数cursor),只是作为一个判断的一个条件,被使用后不再使用,但是也没有关掉,所以cursor泄露,在StrictMode监视下只要跑到这个地方都会抛出这个错误:

E/StrictMode(639): A resource was acquired at attached stack trace but never released. See java.io.Closeableforinformation on avoiding resource leaks.
E/StrictMode(639): java.lang.Throwable: Explicit termination method 'close'not called
E/StrictMode(639): at dalvik.system.CloseGuard.open(CloseGuard.java:184)
... ...

 在Android4.0 JellyBean中谷歌修正了这个泄露问题,相关代码如下:

private final class ThreadListQueryHandler extends ConversationQueryHandler {public ThreadListQueryHandler(ContentResolver contentResolver) {super(contentResolver);}@Overrideprotected void onQueryComplete(int token, Object cookie, Cursor cursor) {switch(token) {caseTHREAD_LIST_QUERY_TOKEN:mListAdapter.changeCursor(cursor);... ...break;caseUNREAD_THREADS_QUERY_TOKEN:// 新增的UNREAD_THREADS_QUERY_TOKEN分子和HAVE_LOCKED_MESSAGES_TOKEN分支也是类似的情况,cursor在jellybean中被及时关闭了int count = 0;if(cursor != null) {count = cursor.getCount();cursor.close();}mUnreadConvCount.setText(count > 0 ? Integer.toString(count) : null);break;caseHAVE_LOCKED_MESSAGES_TOKEN:@SuppressWarnings("unchecked")Collection<Long> threadIds = (Collection<Long>)cookie;confirmDeleteThreadDialog(newDeleteThreadListener(threadIds, mQueryHandler,ConversationList.this), threadIds,cursor != null&& cursor.getCount() > 0,ConversationList.this);// HAVE_LOCKED_MESSAGES_TOKEN分支中的cursor在jellybean中被及时关闭了if(cursor != null) {cursor.close();}break;default:Log.e(TAG,"onQueryComplete called with unknown token " + token);}}
}@Override
protected void onStop() {super.onStop();mListAdapter.changeCursor(null);
}

  是不是小看了AsyncQueryHandler,谷歌在早期的版本里面都有一些这样的代码,更何况不注意的我们呢,实际上网上很多使用AsyncQueryHandler举例中都犯了这个错误,看完这篇文章后,以后再也不怕AsyncQueryHandler的cursor泄露了,还说不定能解决很多你现在应用的后台strictmode的cursor not close异常问题。

7. 小结
   虽然我觉得还有很多cursor未关闭的情况没有说到,但是根本问题都是及时正确的关闭cursor。
   内存泄露cursor篇是我工作经验上的一个总结,专门捋清楚后对我自己对大家觉得都很有帮助,让复杂的问题本质化,简单化!


这篇关于android中使用Cursor时防止内存泄露的几个方面的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/982004

相关文章

Conda与Python venv虚拟环境的区别与使用方法详解

《Conda与Pythonvenv虚拟环境的区别与使用方法详解》随着Python社区的成长,虚拟环境的概念和技术也在不断发展,:本文主要介绍Conda与Pythonvenv虚拟环境的区别与使用... 目录前言一、Conda 与 python venv 的核心区别1. Conda 的特点2. Python v

Spring Boot中WebSocket常用使用方法详解

《SpringBoot中WebSocket常用使用方法详解》本文从WebSocket的基础概念出发,详细介绍了SpringBoot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消... 目录一、WebSocket基础概念1.1 什么是WebSocket1.2 WebSocket与HTTP

C#中Guid类使用小结

《C#中Guid类使用小结》本文主要介绍了C#中Guid类用于生成和操作128位的唯一标识符,用于数据库主键及分布式系统,支持通过NewGuid、Parse等方法生成,感兴趣的可以了解一下... 目录前言一、什么是 Guid二、生成 Guid1. 使用 Guid.NewGuid() 方法2. 从字符串创建

Python使用python-can实现合并BLF文件

《Python使用python-can实现合并BLF文件》python-can库是Python生态中专注于CAN总线通信与数据处理的强大工具,本文将使用python-can为BLF文件合并提供高效灵活... 目录一、python-can 库:CAN 数据处理的利器二、BLF 文件合并核心代码解析1. 基础合

Python使用OpenCV实现获取视频时长的小工具

《Python使用OpenCV实现获取视频时长的小工具》在处理视频数据时,获取视频的时长是一项常见且基础的需求,本文将详细介绍如何使用Python和OpenCV获取视频时长,并对每一行代码进行深入解析... 目录一、代码实现二、代码解析1. 导入 OpenCV 库2. 定义获取视频时长的函数3. 打开视频文

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Python内置函数之classmethod函数使用详解

《Python内置函数之classmethod函数使用详解》:本文主要介绍Python内置函数之classmethod函数使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 类方法定义与基本语法2. 类方法 vs 实例方法 vs 静态方法3. 核心特性与用法(1编程客

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空