2009-09-04 3 views
19

이 애플리케이션의 콘텐츠 제공 업체를 만들고 있는데 콘텐츠 제공 업체에서 데이터베이스 연결을 열고 쿼리를 실행하고 결과 커서를 호출 프로그램에 반환하고 있습니다. 공급자에서이 데이터베이스 연결을 닫으면 커서에 결과가 없습니다. 내가 열어두면 DDMS 로그에 "leak found"오류가 발생합니다. 내가 여기서 무엇을 놓치고 있니? 데이터베이스 결과의 커서를 반환하는 깨끗하고 적절한 방법은 무엇입니까?Android Content Provider 데이터베이스 누출 문제

+2

데이터베이스를 닫을 필요가없는 것으로 보입니다. 설명서에 따르면 데이터베이스가 캐시되어 전체 응용 프로그램 수명 동안 하나의 데이터베이스 인스턴스 만 열리게됩니다. http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html#getWritableDatabase() –

답변

5

데이터베이스 연결을 앱의 전체 런타임에 열어두면 완벽하게 정상적으로 끝난 후에 커서를 닫아야합니다.

활동에서 커서를 쿼리하고 사용하고 있다고 가정합니까? cursor.close(); 메서드를 호출하여 커서를 닫는 지 확인하는 경우 Activity에서 커서를 닫지 않고 다른 쿼리를 실행할 때 이러한 누출 메시지를 가져 오는 다른 Activity로 이동하는 경우 알 수 있습니다.

귀하의 활동에서 onDestroy 메소드를 무시하고 그 안에있는 모든 커서를 닫는 것이 가장 좋습니다.

+0

managedQuery 호출을 사용하여 공급자를 호출하고 있습니다. 필자는 커서 수명주기를 처리 한 것처럼 느껴졌지 만 다시 확인해 보겠습니다. 이 누출 오류를 무시하는 것이 안전합니까? – MattC

+0

전 수명주기에 대해서도 동일한 가정을했습니다. 일단 커서를 닫으면 오류 메시지가 사라지는 것을 보았습니다. 때로는 디버깅 할 때 닫히지 않은 커서로 인해 예외가 발생하고, 디버그 세션이 종료되었을 때 즉시 이러한 오류와 예외가 즉시 throw되지 않습니다. –

13

AFAIK가 누락되었습니다. Android에 ContentProvider에 대해 onDestroy() (또는 이에 상응하는 금액)이 누락되었습니다. 이 영역의 소스 코드에는 SDK에 표면화되지 않은 onDestroy() 일종의 것이 있습니다. 그들은 심지어 당 API 통화 기준으로 데이터베이스 오브젝트를 작성

당신이 AlarmProviderLauncherProvider에 대한 소스 코드를 보면, (예를 들어, 그들은 insert() 얻을 때마다, 그들은 그들이 결코 가까운 쓰기 가능한 데이터베이스 핸들을 엽니 다).

+0

아아 즉, 나는 열린 데이터베이스와 함께 살아야한다는 것을 의미한다. ( – Janusz

+0

"API- 호출 단위 [...] 절대 닫지 않는 데이터베이스 개체를 만들 수도있다"참조, SQLiteOpenHelper # getWritableDatabase' : "한번 열어 본다. 성공적으로 데이터베이스가 캐시됩니다. "동의하지만, 그 한 인스턴스는 잠시 동안 닫혀 야합니다 ... – TWiStErRob

11

내 콘텐츠 제공 업체 중 하나가 여러 데이터베이스를 관리하며 동일한 스키마가 다른 데이터 세트로 관리됩니다. SQLiteCursor가 여러 선택 항목을 남겨 둠에도 불구하고 가비지 콜렉션이 콘텐츠 공급자에 열려있는 데이터베이스가 있음을 발견했을 때이를 방지하기 위해 SQLiteCursor가 여러 선택 항목을 남겨 두었더라도 :

1) SQLiteDatabase 개체를 열어 둡니다. 그것을 다시 사용할 수 없도록 컬렉션에 배치하십시오.

2) SQLiteDatabase 개체를 열어두고 같은 데이터베이스에 액세스 할 때 데이터베이스 개체를 다시 사용할 수있는 캐시를 만듭니다.

3) 커서가 닫히면 데이터베이스를 닫습니다.

해결책 1은 내 판단에 위배됩니다. 솔루션 1은 리소스 유출의 또 다른 형태입니다. 그것의 1 개의 저축은 시스템을 화나게하지 않는다는 것이다. 나는이 선택을 즉시 다 판결했다.

해결책 2은 최선의 해결책이라고 생각했습니다. 동시에 데이터베이스 연결을 다시 열지 않아도되므로 런타임을 줄여 리소스를 절약 할 수 있습니다. 이 솔루션의 단점은 캐시를 작성해야 응용 프로그램의 크기가 증가한다는 것입니다. 크기는 정말로 문제가 아니었지만 쓰는 시간은 그랬습니다. 나는이 해결책을 현 시대에 통과 시켰고, 나중에 그것을 다시 볼 수있다.

해결책 3은 함께 가기로 결정한 것입니다. 첫째, 나는 그것이 간단 할 것이라고 생각했다; 액티비티에서 필요한 것은 SQLiteCursor에 콘텐츠 공급자가 반환 한 커서를 다시 작성하는 것입니다. 그런 다음 getDatabase() 메서드를 호출하고 데이터베이스에서 close()를 호출 할 수 있습니다.

이것은 불가능합니다. 콘텐츠 공급자로부터 반환 된 커서는 실제 Cursor 개체에 직접 액세스하지 못하게하는 래퍼 클래스 내에 있음을 알 수 있습니다. 래퍼는 수신 한 메서드 호출을 Cursor에 위임합니다. 파생 된 유형으로 커서를 되돌릴 기회가 없습니다.

그래서 데이터베이스를 닫는 작업을 수행하는 대신 다른 방법을 사용했습니다. SQLiteCursor 클래스를 확장하여 내 자신의 Cursor 클래스를 파생 시켰습니다. 파생 된 클래스에 두 개의 데이터 멤버를 추가했습니다. 하나는 데이터베이스를 참조하고 다른 하나는 디버깅에 사용할 ID입니다. 클래스의 ctor는 SQLiteCursor 매개 변수와 동일한 서명을 가졌습니다.이 매개 변수는 ID 값을 설정하기 위해 끝에 추가 된 매개 변수와 함께 추가되었습니다. ctor는 데이터베이스 데이터 멤버를 설정하여 커서가 닫히기 전에 가비지 수집이 발생하면 무언가가 데이터베이스를 참조하고 있는지 확인합니다.

커서가 닫히고 참조가 null이 아닌 경우 데이터베이스를 닫을 수 있도록 close() 메소드를 오버로드했습니다.

toString() 메서드를 오버로드하여 ID 번호가 문자열에 추가되도록했습니다. 이를 통해 로그 파일에서 열려있는 커서와 열었던 커서를 추적 할 수있었습니다.

또한 콘텐츠 공급자가 매번 새 데이터베이스 연결을 열지 않고도 여러 쿼리에 대해 데이터베이스를 다시 사용할 수 있도록 closeForReuse() 메서드를 추가했습니다.

또한 SQLiteDatabase.CursorFactory 인터페이스를 구현 한 클래스를 만들었습니다. 그것은 새로운 파생 된 커서 클래스를 만들고 각각에 전달 된 ID 값을 관리합니다.

이 솔루션은 여전히 ​​모든 활동이 커서를 사용하여 완료 될 때 전달 된 커서를 닫을 것을 요구합니다. 이것은 좋은 프로그래밍 방식이므로 걱정할 필요가 없습니다.