Salina SDK – FeedbackDialog의 구현 대안

프로토타입으로 작성한 Feedback Dialog에서는 서비스에서 Dialog를 띄울 수가 없기 때문에 WindowManager에 뷰를 추가하는 방식으로 구현했었는데, 이 경우 XML을 이용할 수가 없어서 순수 자바코드만으로 뷰를 구성해야 했고 이미지 리소스 또한 사용할 수 없다는 문제가 있다.

Pro Android 4 책을 살펴보던 중 Library Project에 관한 내용이 나왔고, 이를 이용하면 기존에 소스코드 상에서 구현한 Feedback Dialog의 문제점을 해결할 수 있을 것 같아 몇가지 테스트를 해보았다.

개발자가 SDK를 사용할 때 최소의 코드만으로 기능을 사용할 수 있도록 하는 것이 SDK의 구현 목표이기 때문에 사용자 프로젝트의 AndroidManifest.xml 등에 SDK 사용을 위해 다른 것들을 추가하는 것을 최소화하는 것 또한 포함된다.

따라서 라이브러리 프로젝트의 Activity를 사용함에 있어서 이를 사용자 프로젝트에서 Activity 등록을 해주어야 하는지를 확인해봤는데, 라이브러리의 매니페스트에 액티비티를 추가했더라도 사용자 프로젝트에서는 그것을 인식할 수 없다.
책에서는 암시적/명시적 차이만 있을 뿐이라고 했지만 버전에 따라 다르거나 책이 쓰여진 시점과 지금과 차이가 있는 모양이다. 결론은 라이브러리 프로젝트의 액티비티를 사용하려면 사용자 프로젝트에서 해당 액티비티의 패키지 경로를 포함한 액티비티 이름을 이용해서 매니페스트에 등록해주어야 한다.

두번째로 라이브러리 프로젝트의 리소스를 그대로 사용할 수 있는가?
결론부터 말하자면 리소스를 사용할 수 있다. 다만 리소스 식별을 위한 id가 라이브러리 프로젝트와 사용자 프로젝트가 중복되는 경우 사용자 프로젝트의 리소스에 우선순위가 있거나 애초에 라이브러리 프로젝트의 리소스는 등록이 되지 않는 것 같다.
xml 레이아웃의 경우 파일 이름을 id로 사용하므로 파일 이름이 같은 경우에 id 충돌이 발생하고 (런타임이나 컴파일 타임에 예외는 발생하지 않는다.) 리소스의 경우에는 @+id를 이용해 등록한 id가 같다면 충돌이 발생한다. (포럼에서 해당 이슈에 대해 대화를 나누는 것을 보면 이 문제를 비롯해 라이브러리 프로젝트에 있어서 아직 문제가 많고 계속해서 개선이 진행되고 있다고 한다.)

안드로이드 프레임워크 레벨에서 리소스 id에 대한 네임스페이스를 구분할 수 없으므로 간단하게 네이밍 컨벤션을 통해 이름 충돌이 일어나지 않도록 해야겠다.
간단하게는 salina_ 접두어를 사용하는 쪽으로 진행해야겠다.

혹은 이러한 문제를 역이용해서 사용자가 피드백 다이얼로그의 룩앤필을 바꿀 수도 있을 것 같다.
즉, 특정 이미지 리소스에 대한 파일 이름과 사이즈 규격을 가이드를 통해 알려주면 이를 사용자 프로젝트에 추가하게 되면 사용자 프로젝트의 리소스가 우선이므로 가능성이 있을 듯하나.. 아직은 라이브러리 프로젝트가 지속적으로 개선이 되고 있으므로 추후 변동사항이 생기는 경우에는 해결이 안되므로 좀 더 고려해봐야 할 문제이다.

무결성 제약조건을 성립하는 DB 설계 이슈

데이터 베이스를 만들다보면 사실 모든 컬럼을 하나의 테이블에 갖고 있는 경우가 편하게 느껴질때가 있다. 하지만 무결성 제약조건은 표면적으로 드러나지 않는 문제에 대해서 중복이나 갱신이상등이 발생할 수 있기때문에 정교하게 설계해야되는 것이 원칙이다. 이번에 설계했던 스키마에서의 테이블 수는 그렇게 많지 않았지만 무결성 제약조건을 가급적 지키려고 테이블을 설계했다. 아래는 무결성 제약조건이다.

[도메인 제약조건]

각 애트리뷰트 값이 반드시 원자값이어야 하며, 데이터 형식을 통해 값들의 유형을 제한하고, 애트리뷰트의 디폴트 값을 지정하고, 애트리뷰트에 저장되는 값들의 범위를 제한할 수 있다. 또한 릴레이션을 정의할 때 애트리뷰트 선언에 ‘NOT NULL’ 구문을 붙이면 모든 튜플에 해당 애트리뷰트의 값이 존재하도록 보장한다.

-> null값이 많아지면 데이터가 의미가없어지는 경우가 많다. 실제 테이블안에 들어간 null의 의미는 ‘모른다’ 라는 의미이고 같은 컬럼에서 어떤것은 null이고 어떤 값은 null이 아니라면 이 데이터는 의미가 없어지는 데이터이다. salina 스키마에서는 user_id라는 어쩔수 없이 모르는 데이터에 대해서만 null을 허용했다.

 

[키 제약조건]

키 애트리뷰트에 중복된 값이 존재해서는 안된다는 것이다. 릴레이션을 정의할 때 기본 키로 정의한다.

->순차적인 프라이머리키가 아닌, 실제 칼럼에 의미있는 값들이 주키가 되고, 중복되지 않는다.

[기본 키와 엔티티 무결성 제약조건]

기본 키를 구성하는 애트리뷰트가 널값을 가지면 튜플들을 고유하게 식별할 수 없게 되므로 엔티티 무결성 제약조건은 릴레이션의 기본 키를 구성하는 어떤 애트리뷰트도 널값을 가질 수 없다는 것이다.

[외래 키와 참조 무결성 제약조건]

두 릴레이션의 연관된 튜플들 사이의 일관성을 유지하는데 사용된다. 관계 데이터베이스가 포인터 없이 릴레이션들로만 이루어지고, 릴레이션 사이의 관계들이 다른 릴레이션의 기본 키를 참조하는 것을 기반으로 하여 묵시적으로 표현되기 때문에 외래 키의 개념이 중요합니다.

<무결성의 종류>

1. 엔티티(개체) 무결성★: 릴레이션에서 기본키를 구성하는 속성은 널값이나 중복 값을 가질 수 없음

2. 참조 무결성 : 외래키 값을 널이거나 참조 릴레이션의 기본키 값과 동일해야함. 즉, 릴레이션은 참조할 수 없는 외래키 값을 가질 수 없음

3. 널 무결성 : 릴레이션의 특정 속성값이 널이 될 수 없도록 하는 규정

4. 고유 무결성 : 릴레이션의 특정 속성에 대해서 각 튜플이 갖는 값들이 서로 달라야 한다는 규정

5. 도메인 무결성 : 특정 속성의 값이 그 속성이 정의된 도메인에 속한 값이어야 한다는 규정

6. 키 무결성 : 하나의 릴레이션에는 적어도 하나의 키가 존재해야 한다는 규정

7. 관계 무결성 : 릴레이션에 어느 한 튜플의 삽입 가능 여부 또는 한 릴레이엿노가 다른 릴레이션의 튜플들 사이의 관계에 대한 적절성 여부를 지정한 규정

 

salina 스키마는 무결성 제약조건을 성립하고, 3nf과 bcnf를 사용하여 갱신이상 삭제이상 중복이상등의 문제를 최소화 시켰다.

클라이언트에서 보내준 json log를 어떻게 쉽게 받을 수 있을까?

예전 ETbike 프로젝트 할때는 Spring과 안드로이드 조합으로 프로젝트를 진행했었다. 이런경우 쉽제 Gson을 사용하여 변수명만 일치시켜주면 쉽게 json을 객체로 맵핑할수 있었다. 하지만 Gson은 자바 라이브러리 이기떄문에 장고에선 사용할 수 없다. 아무리 찾아도  장고내에 Gson같은 라이브러리없기때문에 Pson이라는 모듈을 만들었다.

def dic_to_obj(dic, obj):

dic_key_list = dic.keys()
# if obj instance have user model, it is saved
# but if user model doesn’t exist in user, it will not save
for key in dic_key_list :
setattr(obj, key, dic[key])

return obj

위는 json을 쉽게 객체로 바꿔주는 함수이다. 위의 함수같이 짜게 되면 HttpRequest를 파이썬 딕션어리로 바꾸고, for 문을 돌면서 setattr메서드를 사용해서 해당객체의 property와 같은 이름의 json value값을 가져와 해당 객체에 저장해준다.

setattr를 사용하여 쉽게 Gson을 대체하였지만 Json객체 안에 또다시 재귀적으로 객체가 있는 경우는 좀더 연구하여야 할것같다.

Django에서 어떻게 클래스 다이어그램을 그리는가 ?

장선진 멘토님께서 소프트웨어를 보는 관점에는 5개의 뷰를 기반으로 볼수 있다고 했다.(2개의 동적인 뷰, 2개의 정적인 뷰, 하나의 use case diagram)

설계하는 도중에 파이썬은 하나의 모듈이 클래스와 매핑되지 않기때문에 클래스다이어그램을 그리기에 너무 부적절한것 같았다.(실제로 상속이나 association도 일어나는 경우도 적었다.)

이 부분은 배권한 멘토님께 물어보았고, 장고의 철학은 데이터나 프로그램의 구조를 쉽고 빠르게 바꿀수 있다는 데 있기때문에 먼저 설계하고 그리는게 아니라, 구현한후에  빠르게 바꾸고 완전히 fix되었을때 reverse하는 방법이 있다고 한다.

자바와 전혀다른 장고의 철학과 하루 빨리 익숙해 져야될것같다.

 

서버와 Http 요청 시 500에러.

서버에 Http 요청을 보내면 서버측에서 500 Status Code를 보내온다.

Post 테스트용 HTML 페이지에서 테스트를 해도 마찬가지

두형이가 원인을 찾아본 결과 CSRF 공격 대비를 위해 템플릿 페이지에 <% csrf_token %>을 넣어주게 되는데, HTML 페이지에서는 해당 토큰을 삽입하지 않았고,
안드로이드에서 보낼 때는 서버로부터 토큰을 발급받을 수 없으니 임시 방편으로 운천이 형이 알려준 컨트롤러 메서드에서 @csrf_exempt를 붙여 해당 메서드에서는 토큰에 관해 처리하지 않아도 되도록 했다.

테스트의 편의를 위해서 애노테이션을 삽입하긴 했지만 다른 방안이 필요할 듯 하다.

Gson의 StackOverflowError는 훼이크고, 실수.

Gson을 이용해서 json 스트링으로 변환하는데 Gson 쪽에서 StackOverflowError가 계속해서 발생했다. 자바쪽에서 테스트를 해보니 정상적으로 돌아간다.

구글링을 해보니 자기 참조의 형태가 되서 순환되는 경우가 아니라면 발생하지 않는다고 하는데, 클래스 구조를 아무리봐도 순환이 발생할 수 있는 구조는 아니었다.

조금 헤매다가 문제가 있는 부분을 발견했다. 데이터를 래핑하는 클래스에서 맵으로 데이터들을 추가하는데, 여기서 원래 넣어야 할 데이터는 feedback인데, 여기에 자기 자신을 추가해버리니 순환이 되어버렸다. 신중하게 코드를 작성할 필요가 있겠다.

안드로이드 SQLite ORM Library 몇가지.

ActiveAndroid

GitHub-https://github.com/pardom/ActiveAndroid
SqliteORM – A Lightweight ORM For SQLite in Java
OrmLite – Lightweight Object Relational Mapping (ORM) Java Package
sTorm – a lightweight DAO generator for Android SQLite
설명 – http://turbomanage.wordpress.com/2012/12/11/storm-preview/
greenDAO – Android ORM for SQLite
★★★★★Sugar ORM
각 라이브러리의 위키를 참고해서 Getting Start 의 내용만 봤는데, 대부분 복잡하다.
마지막에 본 Sugar ORM이 그나마 간편하게 쓸 수 있도록 되어있다.
(매니페스트에서 애플리케이션을 지정하고 meta-data만 몇개 넣고 entity 클래스만 만들면 쓸 수 있다.)

요약하면 onCreate 전에 System Service를 사용할 수 없다고 한다.

03-04 01:33:26.700: E/AndroidRuntime(18074): FATAL EXCEPTION: main
03-04 01:33:26.700: E/AndroidRuntime(18074): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{nnoco.android.orm.test/nnoco.android.orm.test.MainActivity}: java.lang.IllegalStateException: System services not available to Activities before onCreate()
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1983)
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.app.ActivityThread.access$600(ActivityThread.java:130)
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.os.Handler.dispatchMessage(Handler.java:99)
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.os.Looper.loop(Looper.java:137)
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.app.ActivityThread.main(ActivityThread.java:4745)
03-04 01:33:26.700: E/AndroidRuntime(18074): at java.lang.reflect.Method.invokeNative(Native Method)
03-04 01:33:26.700: E/AndroidRuntime(18074): at java.lang.reflect.Method.invoke(Method.java:511)
03-04 01:33:26.700: E/AndroidRuntime(18074): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
03-04 01:33:26.700: E/AndroidRuntime(18074): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
03-04 01:33:26.700: E/AndroidRuntime(18074): at dalvik.system.NativeStart.main(Native Method)
03-04 01:33:26.700: E/AndroidRuntime(18074): Caused by: java.lang.IllegalStateException: System services not available to Activities before onCreate()
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.app.Activity.getSystemService(Activity.java:4410)
03-04 01:33:26.700: E/AndroidRuntime(18074): at nnoco.android.orm.test.BookAdapter.<init>(BookAdapter.java:16)
03-04 01:33:26.700: E/AndroidRuntime(18074): at nnoco.android.orm.test.MainActivity.<init>(MainActivity.java:19)
03-04 01:33:26.700: E/AndroidRuntime(18074): at java.lang.Class.newInstanceImpl(Native Method)
03-04 01:33:26.700: E/AndroidRuntime(18074): at java.lang.Class.newInstance(Class.java:1319)
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.app.Instrumentation.newActivity(Instrumentation.java:1053)
03-04 01:33:26.700: E/AndroidRuntime(18074): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1974)
03-04 01:33:26.700: E/AndroidRuntime(18074): … 11 more

그렇다고 한다..

안드로이드 UI 실험 중…

액티비티 실행 후 Service를 시작하여 Service 쪽에서 WindowManager에 피드백 레이블 추가.

액티비티 종료 시 서비스를 종료하도록 하고, 서비스 종료 시(onDestroy)에는 WindowManager에서 피드백 레이블을 제거하도록 했으나, 액티비티의 onDestroy 메서드가 호출이 되지 않는다.

액티비티 라이프 사이클을 잘못 이해하고 있었나 싶어서 안드로이드 책을 참고해도 알고 있는 그대로인데.. 추측이지만 Service가 실행 중인 경우에는 onDestroy로는 넘어가지 않는 것 같다. 그런 이유로 stopService는 onDestroy가 아닌 onPause쪽에서 호출하도록 수정해서 앱을 종료했을 때는 피드백 버튼이 사라졌다.

다음, 피드백 레이블을 터치했을 때 다이얼로그를 띄우려고 했다.
커스텀 다이얼로그를 만든 후 띄우려고 했으나, RuntimeException 발생, Service에서는 액티비티의 Context 인스턴스를 사용할 수 없고, 시스템에서 막혀있다고 한다. 이에 대한 대안으로는 액티비티에 다이얼로그 테마를 적용해서 액티비티를 호출하는 방법이 있으나 이 경우 SDK를 사용하는 개발자의 입장에서 매니페스트에 액티비티를 추가해야 하는 번거로움이 생긴다.

사용자(SDK를 사용하는 개발자) 입장에서 코드를 최소한으로 삽입하여 의존성을 최소화하는 것을 SDK의 목표로 삼고 있으니 일단 액티비티를 호출하는 것은 제외. 다음은 피드백 레이블과 같이 WindowManager에 다이얼로그 뷰를 코드에서 생성해서 추가하는 방법. Layout XML 파일의 추가 없이 해야하는 작업이라 하드코딩이 많다. 어쨌거나 가능한 방법이긴 하지만 이렇게 해서 만든 피드백 다이얼로그에서는 KeyEvent를 받을 수 없다. 이 역시나 서비스를 사용하면서 생기는 제한점인데 보안상의 문제로 서비스에서는 KeyEvent는 받을 수 없도록 되어있다. 일반적인 소프트 키보드에서 발생하는 키 이벤트에 대한 처리는 굳이 사용할 일이 없겠지만 문제는 하드웨어 키 중에서 Back 키이다. UX 관점에서 생각하자면 사용자는 [닫기] 버튼을 이용해 닫을 수도 있지만 [Back] 키, 즉 하드웨어의 뒤로 버튼을 눌러 종료할 수도 있어야 하는데 그게 불가능하단 얘기가 된다.
어쩔 수 없이 WindowManager에 다이얼로그 뷰를 추가하는 방법으로 했으나 다른 대안이 있는지도 더 생각해야겠다.

저기까지 구현한 후에 유환이가 개발한 영어발음 트레이너 앱에 라이브러리 형태로 삽입해봤다.
처음엔 이클립스에서 클래스패스에 Salina Android SDK 프로젝트만 추가했더니 NoClassFoundException이 발생하여 jar 파일로 추출한 후 추가했더니 해결되었다. 신기한 것은 MainActivity의 onResume과 onPause메서드에만 피드백 레이블을 추가/제거를 등록했는데 모든 액티비티에서 동일하게 나타난다. 액티비티가 전환될 때는 살짝 사라졌다가 다시 다른 액티비티가 활성화되면 다시금 나타난다. 정확히 메인 액티비티와 동일하게 작동한다. 최종적으로는 이러한 형태로 나타나야 하겠지만 구현은 한 액티비티에서만 적용되도록 했는데, 어째서 이렇게 동작하는지 의문이다. 이 부분에 대한 이해는 액티비티 라이프사이클부터 좀 더 정확하게 이해를 할 필요가 있어보인다.

이외에도 클래스패스에 properties 파일을 추가하여 외부에서 속성을 수정할 수 있도록 하려고 했으나 당최 자바와 같은 방법으로 클래스패스의 properties 파일을 불러 올 수가 없다. 아니 이 전에 클래스패스에 properties 파일이 존재하는 지의 여부도 모르겠다. 물론 properties 파일이 있는 디렉토리를 소스 폴더를 지정했으에도 그러하다. 사용자가 속성을 변경가능해야 좀 더 원하는 형태가 될 수 있으므로 이 부분은 추후에 해결방안을 찾아야겠다. Asset이나 res의 raw 폴더에 추가한 파일은 쉽게 가져올 수 있으므로 이쪽으로 생각해봐야겠다.

Android Method Hooking을 키워드로 검색하다가 재밌는 프로젝트를 발견했다.
jQuery에서 영감을 얻은 Vapor라는 Android Framework인데 jQuery와 상당히 유사한 용법을 가지고 있다. javascript에서 쉽게 콜백메서드를 넘겨주는 기능과 유사한 기능도 있고, 자투리 시간을 이용해서 어떤 프레임웍인지 자세히 살펴봐야겠다.

2013-02-25 13.21.45 2013-02-25 13.21.52

2013-02-25 13.22.26 2013-02-25 13.22.33 2013-02-25 13.22.50 2013-02-25 13.23.02