소스를 디버깅하다 보면 분명 SQLiteCursor의 finalize 메소드 코드에 중단점(break point)를 걸지 않았음에도 불구하고 자꾸 브레이크가 걸리는 경우가 있습니다.


저는 처음에 이 현상의 원인이 프레임워크 라이브러리 빌드시 어떤 식으로든 중단점 정보가 잘못 포함되어 걸리는 줄로 짐작하고 대수롭지 않게 생각했습니다. 근데 그게 아니더군요. 구글신에게 물어보니 아래와 같은 답변이 나왔습니다.

The implication is that you haven't used cursor.close() and left an 
"open" cursor around when your activity/service was destroyed.   You 
either need to do this manually, or make sure you're using a managed
cursor. 

If you're not using a managed cursor, you should wrap every cursor 
creation with a try/finally expression. 

그러니까 코드 어딘가에 close 되지 않은 커서가 있는 것이니 명시적으로 finally절에서 close해줘야 한다는 겁니다. 불행히도 close 되지 않은 커서가 어느 클래스의 어느 메소드에서 open 됐는지는 디버깅 창의 스택트레이스에 나와있지 않습니다. 

하지만 한가지 힌트는 있습니다. SQLiteCursor 객체의 mQuery 멤버변수에는 해당 커서가 실행했던 쿼리 정보가 담겨 있습니다. 이 쿼리를 Variables 창에서 확인할 수 있습니다. 이제 close되지 않는 커서가 실행했던 쿼리가 무엇인지 알았으니 대충 소스의 어느 부분에서 open된 건지 찾아낼 실마리를 얻은 겁니다.


* 내용추가(2010/08/12)
cursor가 어느 클래스의 어느 메소드에서 open 된 것인지 로그캣에 출력할 수 있습니다. SQLiteCursor.finalize()의 아래 코드에 방법이 나옵니다.

if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
    Log.d(TAG, message + "\nThis cursor was created in:");
    for (StackTraceElement ste : mStackTraceElements) {
        Log.d(TAG, "      " + ste);
    }
}

즉 SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION 이 true이면 스택트레이스를 DEBUG 레벨로 로깅해줍니다. 
DEBUG_ACTIVE_CURSOR_FINALIZATION 을 true로 설정하는 방법은 Log.isLoggable() 메소드에 나오는 아래 설명을 참조하여,
Checks to see whether or not a log for the specified tag is loggable at the specified level. The default level of any tag is set to INFO. This means that any level above and including INFO will be logged. Before you make any calls to a logging method you should check to see if your tag should be logged. You can change the default level by setting a system property: 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>' Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPRESS will turn off all logging for your tag. You can also create a local.prop file that with the following in it: 'log.tag.<YOUR_LOG_TAG>=<LEVEL>' and place that in /data/local.prop.
다음처럼 명령을 날립니다.

$ adb shell
# setprop log.tag.SQLiteCursorClosing VERBOSE

이제 로그캣에 닫히지 않은 커서의 스택트레이스가 나올 것입니다. (그러나 저는 안나오더군요. 왜 안나오는지 모르겠습니다.ㅠㅠ)

이 방법을 응용하여 SQLite가 실행하는 모든 쿼리를 로그캣에 찍을 수 있습니다.
android.database,sqlite.SQLiteDebug 클래스의 DEBUG_SQL_STATEMENTS 옵션을 한번 볼까요.

    /**
     * Controls the printing of SQL statements as they are executed.
     */
    public static final boolean DEBUG_SQL_STATEMENTS =
            Log.isLoggable("SQLiteStatements", Log.VERBOSE);

아하 "SQLiteStatements" 군요. 아래의 명령어를 실행해주면 모든 쿼리가 로그캣에 디버그 레벨로 로깅됩니다.

$ adb shell
# setprop log.tag.SQLiteStatements VERBOSE

신고
Posted by 에코지오

댓글을 달아 주세요

  1. 영식 2010.06.21 13:37 신고  댓글주소  수정/삭제  댓글쓰기

    프로요에서 어플 돌리면 좀더 상세한 exception이 떨어져서 어서 났는지 대번에 알수 있어요..ㅎㅎ

  2. 나진수 2010.06.30 21:24 신고  댓글주소  수정/삭제  댓글쓰기

    m쿼리에서 보면 쿼리문이 나오는군요 !_! 덕분에 금방 고쳤습니다.
    좋은글 잘보고 갑니다. 퍼갈게요 ~_~

안드로이드 애플리케이션 소스를 디버깅하는 최선의 방법은 이클립스에서 [Debug As > Android Application]을 실행하는 것입니다. 그러나 어떤 경우에는 이 메뉴를 이용할 수 없거나 또는 이용할 필요가 없는 상황도 생깁니다.  
예를들어 Debug As 메뉴를 실행했더니 바빠 죽겠는데 어디가 꼬였는지(?) 계속 아래와 같은 에러가 발생한다면?

[2010-04-12 11:33:41 - Contacts]Installing Contacts.apk...
[2010-04-12 11:34:19 - Contacts]Installation error: INSTALL_FAILED_UPDATE_INCOMPATIBLE
[2010-04-12 11:34:19 - Contacts]Please check logcat output for more details.
[2010-04-12 11:34:19 - Contacts]Launch canceled!

또는 로컬의 애플리케이션 소스가 타겟에 설치된 버전과 소스가 동일해서 재설치하거나 덮어쓸 필요가 없는데, 소스는 디버깅하고 싶다면?
이럴 때는 Debug As 메뉴를 고집하지 말고 임시로 수동으로 디버깅하면 됩니다. 

수동으로 프로세스에 디버거 붙이기
디버깅하고자 하는 애플리케이션 프로세스에 디버거를 여러분이 직접 수동으로 붙여서(attach) 디버깅할 수 있습니다. 다음 과정을 따릅니다.

  1. 타겟(에뮬레이터)에서 해당 애플리케이션을 시작시킴 
  2. 이클립스 DDMS에서 해당 프로세스를 선택
  3. 미리 만들어둔 리모트 디버그 실행설정을 실행함 
  4. 이제 소스에 중단점 존재시 디버그 퍼스펙티브로 전환됨

* 이클립스에 리모트 디버그 실행 설정 만들기
  • 프로젝트 오른 클릭 > Debug As > Debug Configurations 실행
  • Remote Java Application 타입으로 새로운 launch 설정 생성
  • Port를 8700번으로 수정
  • Apply 클릭 (Debug 클릭하면 바로 실행됨)


프로세스에 디버거 붙이는 작업을 자동화하기

만약 수동으로 디버거를 붙이는 작업을 자꾸 반복해야 한다면, 이것은 당연히 비효율적인 일이 되고 맙니다. 

또한 디버거를 붙이는 작업을 사람이 수동으로 처리하다보니, 애플리케이션 시작부터 리모트 디버거를 붙이기까지 다소 시간이 걸릴 수 있습니다. 이렇게 되면 디버거가 연결되기 전에 액티비티의 onCreate() 같은 메소드에 찍어둔 중단점은 지나쳐 버릴 수가 있습니다 (아 물론, 이 문제는 에뮬레이터의 Dev Tools 애플리케이션의 Development Settins에서 아래처럼 Wait For Debugger 옵션을 체크하여 방지할 수 있습니다).



어쨋든 디버거 붙이는 과정은 가급적 스크립트로 만들어두고 자동으로 실행시키는 것이 좋습니다.


▶ 타겟(에뮬레이터)에서 해당 애플리케이션을 시작

am(Activity Manager) 명령어를 이용합니다. 타겟에서 지정된 액티비티를 디버그 모드로 시작하는 명령형식은 다음과 같습니다.

am start -D -n <package-name>/<package-name>.<class-name> 

자동으로 해당 액티비티 프로세스의 디버깅 포트가 8700 포트로 포워딩되며 만약 8700 포트로 붙는 디버거가 없으면 액티비티는 디버거가 붙을 때까지 대기합니다. 다음은 Contacts 네이티브 애플리케이션의 주소록 목록화면 액티비티 띄우는 예제입니다.

$ adb shell am start -D -n com.android.contacts/.ContactsListActivity


▶ 이클립스 DDMS에서 해당 프로세스를 선택 

이클립스 DDMS를 보지 않고 방금 띄운 프로세스의 디버깅 포트를 알아내는 것이 핵심입니다. 디버깅 포트는 이렇게 알아낼 수 있습니다.

(1) 해당 프로세스가 마지막에 뜬 프로세스인 경우

adb jdwp | tail -1

(2) 프로세스의 이름을 알고 있는 경우

# ps| grep 프로세스명 | awk '{print $2}'
# 다음은 프로세스명이 android.process.acore인 경우 예제
$ adb shell ps | grep android.process.acore | awk '{print $2}'

이제 리모트 디버깅 실행설정에서 설정해둔 8700 포트를 이렇게 알아낸 포트로 포워딩하면 됩니다.

# 먼저 위에서 알아낸 디버깅 포트를 $DEBUG_PORT 변수에 저장합니다
$ adb forward tcp:8700 jdwp:$DEBUG_PORT


▶ 이클립스 리모트 디버그 실행설정을 실행
이클립스에 만들어둔 '리모트 디버그 실행설정'도 스크립트을 통해서 실행하는 방법이 있으면 좋겠네요. 이 부분은 좀더 찾아봐야겠습니다.

신고
Posted by 에코지오

댓글을 달아 주세요

  1. 대전글로벌리더십센터 2010.07.07 16:34 신고  댓글주소  수정/삭제  댓글쓰기

    (강의요청)안녕하세요 대전글로벌리더십센터입니다
    혹시 안드로이드 어플리케이션 개발에 대해 강의도 가능하신가요?
    가능하시다면 cute519@naver.com 로 연락 부탁드립니다.
    감사합니다^^

지난 글에서는 로컬의 네이티브 애플리케이션 소스를 이클립스를 통해 쉽게 디버깅할 수 있도록, 에뮬레이터에 내장된 것과 동일한 키로 서명하는 방법을 알아봤습니다. 그러나 굳이 내장된 것과 동일하게 서명하지 않고(즉 Custom Debug Keystore를 만들지 않고) 로컬 네이티브 애플리케이션을 디버깅할 수 있습니다. 

(1) 내장된 애플리케이션을 설치 제거하거나(uninstall)

(2) 내장된 패키지(apk) 파일을 내가 빌드한 apk 파일로 덮어쓴 후(overwrite)

그러고 나서 이클립스에서 Debug As로 디버깅하면 됩니다.

1. 네이티브 애플리케이션 uninstall하기

에뮬레이터에서 기존 네이티브 애플리케이션을 설치제거(uninstall)합니다. 

# 그냥 언인스톨하면 안됩니다
$ adb uninstall com.android.calculator2
Failure

# 아직 rm으로 파일을 삭제할 수 없습니다
$ adb shell rm /system/app/Calculator.apk
rm failed for /system/app/Calculator.apk, Read-only file system

# remount는 /system 파티션을 ro(read-only)에서 rw(read-write)로 바꿉니다
$ adb remount

# 이제 rm으로 apk를 삭제하고 uninstall합니다
$ adb shell rm /system/app/Calculator.apk
$ adb uninstall com.android.calculator2

Success

# 기존 데이터를 보존하고 uninstall하려면 -k 옵션 추가
$ adb shell pm uninstall -k com.android.calculator2

▶ 네이티브 애플리케이션 제거 순서 요약 : remount => remove apk => uninstall 


2. 또는 네이티브 패키지 파일을 덮어쓰기

내장된 애플리케이션 패키지 파일(apk)을 로컬에서 빌드된 파일로 강제로 덮어씌우고 디버깅할 수 있습니다.

# remount는 /system 파티션을 ro(read-only)에서 rw(read-write)로 바꿉니다
$ adb remount

# 내장된 apk를 로컬 apk로 overwrite합니다
$ adb push C:\Calculator\bin\Calculator.apk /system/app


이클립스에서 Debug As 실행

내장된 애플리케이션을 언인스톨하거나 로컬 패키지 파일로 덮어쓴 후에 이클립스에서 [프로젝트 오른 클릭 > Debug As > Android Application]을 실행합니다. 이제 로컬에서 빌드된 네이티브 애플리케이션이 타겟에 새롭게 설치되면서 소스를 디버깅할 수 있습니다. (이 경우 apk는 /data/app에 설치됩니다. /system/app에 설치되지 않습니다)


별거 없네요. 어때요? 참, 쉽죠? ^^

신고
Posted by 에코지오

댓글을 달아 주세요

  1. ... 2012.05.24 14:58 신고  댓글주소  수정/삭제  댓글쓰기

    루팅을 해야하는건가요??

처음으로 JMaker를 써보는지라 여러가지로 테스트하면서 역쉬나 삽질 중.

제우스 연동 플러그인이 설치된 JMaker에서 자바소스에 중단점 찍고

제우스 서버를 디버그 모드로 실행.

화면 띄우고 클릭.........짜짠....JMaker는 디버그 퍼스펙티브로 바뀌지 않고

중단점을 그냥 왕 무시.

뭐야 이거. 니가 용산행 직통열차냐? 중단점 무시하고 달리게? 헐....

알아보니 제우스 커뮤니티에서 다음 글귀를 발견하고는 허탈모드...

 

JMaker를 이용하여 debug 모드를 사용하려면 반드시 JEUSMain.xml container 이름이 "default"로 설정되어야 한다.



게다가 컨테이너 이름을 "default"로 설정하면 엔진 컨테이너가 제우스 서버와 동일한 VM에서

실행되므로,  컨테이너에 설정한 자바실행 옵션(command-option) 및

사용자 클래스패스(user-classpath)는 default 컨테이너에 적용되지 않음.

따라서 만약 컨테이너에 별도로 자바실행옵션이나 유저 클래스패스를 추가했다면,

JEUS 서버(jeus.cmd)에 자바실행 옵션 및 클래스패스를 다시 추가해주어야 한다능..



신고
Posted by 에코지오

댓글을 달아 주세요



티스토리 툴바