startManagingCursor()

2012. 2. 26. 19:28
아는만큼만 말할 수 있다. 2탄이다.
Android에서는 Cursor 사용을 잘해야한다.
Cursor는 query 결과물을 담고 있는 녀석으로 필요할 때마다 그 결과값을 손쉽게 가져올 수 있는 유용한 객체이다.

메모리를 사용했으면 해제해주는 것이 당연하듯,
Cursor 역시 사용후에는 꼭 close를 해줘야 한다. 그게 안되면 cursor가 담고 있는 데이터만큼 memory leak이 발생하게 된다.
지난번 규모있는 프로젝트 진행 중 빈번하게 memory leak이 발생했고 그 원인이 Cursor를 제대로 닫아주지 않아서 발생한 것이었다.

막바지에 이런 이슈가 나오게 되면 close되지 않은 cursor를 일일이 찾아내어 수정하기도 어렵다.
이 때 하나의 해결책이 될 수 있는 것이 startManagingCursor (cursor) 의 사용이다.
Activity life cycle에 따라 Framework단에서 release시켜주기 때문에 App단에서 close를 못 시킨 녀석이 있더라도 해제가 될 수 있다.

하지만 이 녀석도 문제는 있었다.
일단 ICS부터는 deprecated API 가 되었으니 사용을 안하는 것이 맞고 새롭게 제공되는 CursorLoader class를 이용해야 한다.

public void startManagingCursor (Cursor c)

Since: API Level 1

This method is deprecated.
Use the new CursorLoader class with LoaderManager instead; this is also available on older platforms through the Android compatibility package.

This method allows the activity to take care of managing the given Cursor's lifecycle for you based on the activity's lifecycle. That is, when the activity is stopped it will automatically call deactivate() on the given Cursor, and when it is later restarted it will call requery() for you. When the activity is destroyed, all managed Cursors will be closed automatically. If you are targeting HONEYCOMB or later, consider instead using LoaderManager instead, available viagetLoaderManager().

Warning: Do not call close() on cursor obtained from managedQuery(Uri, String[], String, String[], String), because the activity will do that for you at the appropriate time. However, if you call stopManagingCursor(Cursor)on a cursor from a managed query, the system will not automatically close the cursor and, in that case, you must call close().

Parameters
c The Cursor to be managed.


이번에 내가 힘들게 삽질을 한 경우를 정리해보자면

Provider와 해당 Provider를 사용하는 Activity가 한 Process에서 돌때는 이상없던 코드가
Provider를 다른 Process로 분리하고 나서 부터
android.database.StaleDataException: Attempted to access a cursor after it has been closed.를 던지는 것으로 시작되었다.

이미 닫힌 cursor에 또 access했다 머 이런 의미인 것 같은데

결론부터 말하자면 startManagingCursor의 사용때문이었다.

Provider가 다른 Process에서 돌고 있는 경우에 remote access를 해야하기 때문에
ContentProviderNative.ContentProviderProxy 를 통해서 query등을 진행하게 되고

그 결과값을 local process에서 사용할 수 있도록 BulkCursorToCursorAdaptor로 wrapping 후에 cursor값을 돌려준다.
여기까지는 전혀 문제가 없는데
Activity가 onResume될때 ManagingCursor의 경우 Activity의 life cycle에 따라서 자동으로 requery가 진행된다.
그런데 이때 Cursor가 이미 close된 경우에 문제가 발생한 다.

requery() 를 살펴보면, BulkCursor를 사용하는 Remote Provider인 경우에는 아래 함수를 통해서 StaleDataExcetpion을 던져버리고
    public boolean requery() {
        throwIfCursorIsClosed();

    private void throwIfCursorIsClosed() {
        if (mBulkCursor == null) {
            throw new StaleDataException("Attempted to access a cursor after it has been closed.");
        }
    }

기존 SQLiteCursor의 경우에는 이미 close인 경우라면 false를 return하도록 되어 있다.

    public boolean requery() {
        if (isClosed()) {
            return false;
        }


App단에서 할 수 있는 일은 startManagingCursor를 통하여 Activity Manager에 Cursor관리를 위임하지 않는 것이다.
자기가 만들었으면 자기가 닫아주는 게 제일 속 편하다는 것을 이번 삽질을 통해 알수 있었다. ㅡㅜ

'관심거리 > Android' 카테고리의 다른 글

UI는 UI Thread에게.  (1) 2012.08.20
아는만큼만 말할 수 있다  (0) 2012.02.26
android - Resource string읽어오기  (1) 2011.11.29

+ Recent posts