美文网首页
关于Cursor的内存泄露问题总结(Android 9.0)

关于Cursor的内存泄露问题总结(Android 9.0)

作者: peter_RD_nj | 来源:发表于2018-08-23 23:03 被阅读0次

    前言

    Android中数据的持久化经常会使用到SqliteDatabase或是ContentProvider,查询数据的时候可以通过SqliteDatabase.query或是ContentResolver.query方法来获取一个Cursor对象,这个Cursor对象里面就包含了我们所要查找的数据。在之前的认知里Cursor在使用完毕后必须要主动调用close来关闭释放资源。最近发现项目里有很多地方并没有做回收处理,编译器也没有任何提示。为了搞清楚这个问题,把以前写的Demo找了出来,并结合ContentProvider和SqliteDatabase相关源码来分析一下具体原因。

    SqliteDatabase

    SqliteDatabase中提供的query开头的查询方法最后都会通过queryWithFactory方法来查询,而queryWithFactory最终会调用rawQueryWithFactory来执行查询,我们直接来跳转到rawQueryWithFactory方法。

        public Cursor rawQueryWithFactory(
                CursorFactory cursorFactory, String sql, String[] selectionArgs,
                String editTable, CancellationSignal cancellationSignal) {
            acquireReference();
            try {
                SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
                        cancellationSignal);
                return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
                        selectionArgs);
            } finally {
                releaseReference();
            }
        }
    

    发现最后返回的地方会去调用SQLiteDirectCursorDriver的query方法,继续跳。

        public Cursor query(CursorFactory factory, String[] selectionArgs) {
            final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
            final Cursor cursor;
            try {
                // 把所有数据绑定到SQLiteQuery的对象query中
                query.bindAllArgsAsStrings(selectionArgs);
                // 封装成Cursor对象
                if (factory == null) {
                    cursor = new SQLiteCursor(this, mEditTable, query);
                } else {
                    cursor = factory.newCursor(mDatabase, this, mEditTable, query);
                }
            } catch (RuntimeException ex) {
                query.close();
                throw ex;
            }
    
            mQuery = query;
            return cursor;
        }
    
    

    SQLiteDirectCursorDriver.query方法中会先把所有数据绑定到SQLiteQuery的对象query中,然后根据CursorFactory对象是否为空来决定如何创建Cursor对象,如果为空则直接new一个SQLiteCursor对象,如果非空则需要实现CursorFactory接口类的newCursor方法来返回一个Cursor对象。

        public interface CursorFactory {
            /**
             * See {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}.
             */
            public Cursor newCursor(SQLiteDatabase db,
                    SQLiteCursorDriver masterQuery, String editTable,
                    SQLiteQuery query);
        }
    

    最后,由SQLiteCursor类的构造方法把相关数据封装起来并创建Cursor对象。

        public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
            if (query == null) {
                throw new IllegalArgumentException("query object cannot be null");
            }
            if (StrictMode.vmSqliteObjectLeaksEnabled()) {
                mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
            } else {
                mStackTrace = null;
            }
            mDriver = driver;
            mEditTable = editTable;
            mColumnNameMap = null;
            mQuery = query;
    
            mColumns = query.getColumnNames();
        }
    

    Object类是类层次结构的根类,每个类都使用Object作为父类,Object类中定义了finalize方法,当Java触发GC就会回调finalize方法。SQLiteCursor类中重写了finalize方法,在这个方法中会主动调用Cursor.close方法来释放资源。

        /**
         * Release the native resources, if they haven't been released yet.
         */
        @Override
        protected void finalize() {
            try {
                // if the cursor hasn't been closed yet, close it first
                if (mWindow != null) {
                    if (mStackTrace != null) {
                        String sql = mQuery.getSql();
                        int len = sql.length();
                        StrictMode.onSqliteObjectLeaked(
                            "Finalizing a Cursor that has not been deactivated or closed. " +
                            "database = " + mQuery.getDatabase().getLabel() +
                            ", table = " + mEditTable +
                            ", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
                            mStackTrace);
                    }
                    close();
                }
            } finally {
                super.finalize();
            }
        }
    

    ContentProvider

    ContentProvider一般是用于存储应用间可共享的数据,应用可以通过ContentResolver提供的方法来对数据进行增删改查操作,同SqliteDatabase仍然从ContentResolver的query方法入手。

        public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
                @Nullable String[] projection, @Nullable Bundle queryArgs,
                @Nullable CancellationSignal cancellationSignal) {
                ...
                // Wrap the cursor object into CursorWrapperInner object.
                final IContentProvider provider = (stableProvider != null) ? stableProvider
                        : acquireProvider(uri);
                final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
                stableProvider = null;
                qCursor = null;
                return wrapper;
                ...
        }
    

    相较于SqliteDatabase的查找过程,使用ContentResolver查找相对简单,在ContentResolver.query方法中经过一些处理后会返回一个CursorWrapperInner类对象。从其构造方法可知,这个CursorWrapperInner对象包含了IContentProvider接口对象的信息。

            CursorWrapperInner(Cursor cursor, IContentProvider contentProvider) {
                super(cursor);
                mContentProvider = contentProvider;
                mCloseGuard.open("close");
            }
    

    同样,在CursorWrapperInner类中重写了Object类的finalize方法,在这个方法中同样会调用close方法来释放资源。

            @Override
            public void close() {
                mCloseGuard.close();
                super.close();
    
                if (mProviderReleased.compareAndSet(false, true)) {
                    ContentResolver.this.releaseProvider(mContentProvider);
                }
            }
    
            @Override
            protected void finalize() throws Throwable {
                try {
                    if (mCloseGuard != null) {
                        mCloseGuard.warnIfOpen();
                    }
    
                    close();
                } finally {
                    super.finalize();
                }
            }
    

    总结

    从对SqliteDatabase和ContentResolver相关源码的分析可以看出,两种方法最终返回的Cursor类对象都重写了Object类的finalize方法,即使不主动调用Cursor.close方法也不会造成内存泄露。虽然不会造成泄露,但是会消耗IO资源有可能导致native运行异常,所以最好还是手动调用Cursor.close方法及时回收内存。

    如果不小心忘记关闭Cursor了怎么办?有什么方法来快速确定是否有泄漏?可以通过一下几种方法来快速定位问题。

    • 编写代码的时候注意编译器提示

    如下图所示,如果我把cursor.close注销掉编译器就会提示我需要调用close()方法来释放资源。


    • 编写完代码跑一遍Lint

    跑Lint的时候,生成的报告中会有相应的警报,可以直接定位问题代码。


    • 在项目中集成leakcanary

    leakcanary github地址:https://github.com/square/leakcanary

    相关文章

      网友评论

          本文标题:关于Cursor的内存泄露问题总结(Android 9.0)

          本文链接:https://www.haomeiwen.com/subject/sppyiftx.html