티스토리 툴바


저작권 안내 (펌 하실 때)
  • 저작권자표시 Yes, 상업적이용 No , 컨텐츠변경 No

최근 수행중인 프로젝트에서 JPA의 구현체로 하이버네이트를 사용하고 있다. 매핑 설정에 따라 다르겠지만, 이런 JPA 구현체들은 Lazy 로딩을 구현하기 위해 프록시 객체를 사용하고 있는데, 이와 관련해서 한 가지 주의해야 할 것이 있다. 그것은 바로 this를 리턴하는 메서드와 관련된 것이다.


현재 수행중인 프로젝트에는 상속을 매핑한 코드가 있다. 예를 들면 다음과 같은 구조이다.


public class Content { ... 최상위 }

public class VideoContent extends Content { .... }

public class MovieContent extends VideoContent { ... }

public class BookContent extends Content { ... }


그리고, 특정 클래스과 다음과 같이 Content를 *-to-One의 형식으로 레퍼런스하고 있다.


public class Order {

    private Content content;

    public Content getContent() {

        return content;

    }

}


Order 객체를 구한 뒤에 Content 객체에 접근해야 하는 경우가 있으며, 특히 Content 타입이 아닌 실제 타입에 접근해야 할 때가 있다. 이런 경우에 아주 단순하게 생각하면 다음과 같이 Content에 자기 자신을 리턴하는 코드를 넣는 방법을 생각해 볼 수 있을 것이다.


public Content {

    public Content self() {

        return this; // 자기 자신을 리턴

    }

}


Order o = getOrder(xxx);

Content proxy = o.getContent(); // Content는 프록시

Content real = proxy.self(); // self() 메서드는 자기 자신을 리턴하므로 프록시가 아닌 실제 대상 객체???

if (real instanceof MovieContent) { // 하지만, real은 프록시 객체이므로 항상 false

    ...

}


하지만, 위와 같은 방법은 통하지 않는다. 그 이유는 프록시 객체는 대상 객체가 대상 객체 자신을 리턴하는 경우 프록시 객체를 리턴하도록 만들어지기 때문이다. 

  • proxy.self() 메서드는 대상 객체의 self() 메서드를 호출한다.
  • 대상 객체의 self() 메서드는 자기 자신을 리턴한다.
  • proxy.self() 메서드는 대상 객체가 리턴한 객체가 대상 객체와 동일한지 확인한다.
  • 동일하다면 프록시 객체 자신을 리턴한다.
그렇다면, 왜 프록시 객체가 대상 객체를 리턴하지 않고 자기 자신을 리턴하는 것일까? 그 이유는 간단하다. 대상 객체에 직접 접근하게 될 경우 ORM이 제공하는 Dirty Checking이나 Lazy Loading 등이 적용되지 않기 때문이다.

이런 이유로 실제 대상 객체에 접근해서 뭔가를 하고 싶다면, 대상 객체가 자기 자신을 리턴하도록 하지 말고, Double dispatch를 사용해야 한다. Double dispatch란 다음과 같은 것이다.
  • A 객체의 a() 메서드는 B 인터페이스를 파라미터로 갖는다.
  • B 인터페이스의 b() 메서드는 A를 파라미터로 갖는다.
  • A 객체의 a() 메서드는 파라미터로 전달받은 B 인스턴스의 b() 메서드를 호출할 때 자기 자신을 전달한다.
위 내용을 코드로 다시 살펴보면 다음과 같다.
public interface B {
    public void b(A a);
}

public class A {
    public void a(B b) {
        b.b(this);
    }
}

A aobj = new A();
B bobj = new BImpl();
aobj.a(bobj); // 내부적으로 bobj.b(a)가 호출됨

위 코드를 보면 aobj.a() 메서드를 호출하면, a() 메서드는 내부적으로 다시 파라미터로 전달받은 bobj의 b() 메서드를 호출한다. 이렇게 두 객체간의 호출이 두 번 이루어지기 때문에 이를 Double Dispatch라고 표현한다.

그럼, 요놈을 앞서 Content에 적용해 보자. 다음과 같이 변경해 주면 된다.

public interface Accessor {
    public void access(Content c);
}

public class Content {
    public void access(Accessor access) {
        accessor.access(this);
    }
}

위 코드에서 Accessor의 구현체는 Content의 프록시 객체가 아닌 실제 객체에 접근할 수 있게 된다.

Order o = getOrder(xxx);

Content proxy = o.getContent(); // Content는 프록시

proxy.access(new Accessor() {
    public void access(Content c) {
        // c는 proxy의 대상 객체인 실제 객체
        if (c instanceof MovieContent) {
            ...
        }
    }
});

Content 및 Content의 자식 클래스들은 상속 관계에 있으므로 사용하므로 Visitor 패턴을 사용하면 instanceof 연산자를 사용하지 않고 실제 타입으로 바로 접근할 수도 있어 더 보기 좋은 코드를 만들어 낼 수 있을 것이다.



저작자 표시 비영리 변경 금지
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 권남 2012/08/29 23:10  댓글주소  수정/삭제  댓글쓰기

    저도 비슷한 상황에서 Visitor 패턴으로 해결한 적이 있습니다.
    하지만 이런일 자체가 안일어나게 만드는게 제일 좋을 것 같습니다.
    예전에 Visitor 패턴으로 베베꽈가면서 만든 기능을 이번 프로젝트에서는 각 엔티티가 특정 interface를 구현하게 해서 간단히 해결했던게 생각나네요.
    즉, 예제에서 보여주신 Accessor가 할 일을 각 엔티티가 인터페이스를 구현해서 알아서 하게 만든거죠.
    상속구조니까 Accessor가 할일을 인터페이스 메소드로 빼고 @Override를 계속해가면서 어쩌면 쉽게 해결 될수도 있어보입니다. 하지만 프로javascript:;젝트 실제 요구사항이 어떤지를 모르니 속단은 금물이겠죠.

    • madvirus 2012/08/30 09:22  댓글주소  수정/삭제

      상속 구조에서 하위 타입이 특정 인터페이스를 상속받는다 하더라도, 최상위 타입에 대한 프록시 객체를 사용하기 때문에, 해당 인터페이스를 사용할 수 없는 상황이었습니다.
      그렇다고 해당 인터페이스를 최상위 타입이 상속받도록 할 수도 없었습니다. 왜냐면, 최상위 타입에는 해당 인터페이스에 대한 역할을 필요가 없었기 때문이다.
      또한, 각 엔티티의 역할이 아니면서 엔티티의 타입에 따라서 다르게 동작해야 하는 것들이 있었습니다.
      그래서 위와 같이 double dispatch되는 방식의 코드를 사용하게 되었습니다.

페이스북 친구들과 댓글을 공유하고 싶다면 아래를 이용해주세요.

동영상 스트리밍을 위해 최초에 선택한 방법은 다음과 같았다.

  • PC 웹 브라우저: RTMP 프로토콜 이용 스트리밍/플래시 플레이어 이용.
  • 안드로이드: RTSP 프로토콜 이용 스트리밍, 안드로이드 MediaPlayer 이용.

미디어 서버로는 Wowza를 선택했는데, 그 이유는 위 두 가지 프로토콜을 모두 지원하기 때문이었다. 


안드로이드와 RTSP의 나쁜 궁합


그런데, 기능 구현을 진행하다보니 안드로이드에서 다음의 문제점들이 드러났다.

  • 스트리밍 품질
  • seeking 기능의 문제
우선, 스트리밍 자체의 품질이 좋지 않았다. RTSP 자체가 데이터 송수신에 UDP를 사용하는 것에서 비롯되는 것도 있겠지만, 안드로이드 2.2 기반 폰, 3.2 기반 태블릿, 4.0 기반 폰, 4.1 기반 넥서스 7에서 화면이 일부 깨지거나 하는 등의 현상이 발생했다.

특히 문제되는 부분은 시간바의 이동, 즉 seeking 기능에 있었다. seek bar를 이동하는 동안 플레이어가 해당 시점으로 이동하고 버퍼링을 하는 등의 작업을 하는데, RTSP의 경우 시간 이동과 버퍼링 등이 신속하게/원활하게 동작하지 않는 경우가 많았다. 특히, 허니콤 기반의 갤럭시 탭은 시간 이동이 안 될 정도로 문제가 심각했다. (갤럭시 탭을 아이스크림으로 업그레이드 해야 그나마 seek bar 이동이 동작했다.)

안드로이드의 MediaPlayer와 RTSP와의 궁합이 그닥 좋지 않다는 점, 특히 RTSP를 사용할 때 seeking이 부드럽게 되지 않는다는 점 때문에 스트리밍 방식을 교체하기로 결정했다.

HTTP 기반 스트리밍으로의 전환

여러 방법을 고민하다가 HTTP 기반의 스트리밍으로 처리하기로 결정했다. 필요한 건 다음과 같은 것들이다.
  • HTTP range 헤더를 지원하는 웹 서버. 아파치 httpd는 당연히 지원하므로, 아파치 웹 서버를 사용 (seeking과 관련)
  • 힌팅(hinting)된 MP4 파일 (비디오는 H.264, 오디오는 AAC로 인코딩 된 버전)
안드로이드의 MediaPlayer는 힌팅된 MP4 파일을 HTTP 기반으로 스트리밍으로 플레이 할 수 있는 기능을 제공하고 있다. 따라서, 아파치 웹 서버의 문서 디렉토리에 MP4 파일을 업로드 하고, MediaPlayer의 데이터 소스로 다음과 같이 MP4 파일에 대한 URL을 지정하면 해당 MP4를 플레이할 수 있게 된다.

path = "http://mediaserver/starwords_1.mp4";
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(path);
...

MediaPlayer의 Seeking 기능을 사용할 경우 HTTP의 range 헤더를 이용해서 원하는 위치로 빠르게 이동할 수 있게 된다. 즉, 프로그레시브 다운로드Progressive Download 방식의 비디오 플레이와 달리 원하는 위치로 빠르게 이동할 수 있게 된다.

안드로이드 앱에서 동영상 미디어를 스트리밍으로 플레이하는 기능이 필요하다면, 현재 수준에서는 폼질/안정성 측면을 고려한다면 여러모로 불안한 RTSP 보다는 HTTP 기반 스트리밍을 사용하는 것이 현명할 것이다.

참고:


저작자 표시 비영리 변경 금지
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 2012/10/31 21:04  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

페이스북 친구들과 댓글을 공유하고 싶다면 아래를 이용해주세요.

최근 수행중인 안드로이드 관련 프로젝트에서 구글의 Nexus 7을 사용할 일이 생겼다. Nexus 7을 윈도우 PC와 연결해서 디버그 모드로 앱을 실행하고 싶었으나, ADB 인터페이스에 Nexus 7 장치가 보이질 않았다.  디버그 모드 설정은 정상적으로 하고 관련 드라이버들도 정상적으로 설치되었으나 이것 저것 확인해도 특별히 잘못된 것이 없어 보였는데, 최종적으로 매우 허무하게 처리하였다.


Device에 보이도록 하려면 USB 연결시, USB 컴퓨터 연결을 미디어 기기(MTP)에서 카메라(PTP)로 변경한다. 이렇게 한다고 바로 되는 것은 아니고 드라이버 업데이트를 해 주어야 한다. 최초에 카메로(PTP)로 변경하면, 장치 관리자에 노란색으로 장치 연결에 문제가 있다고 표시가 된다. 이때 아래 사이트에서 다운로드 받은 드라이버로 업그레이드를 해 주면 된다.


http://dlcdnet.asus.com/pub/ASUS/EeePAD/Nexus7/usb_driver_r06_windows.zip


드라이버 설치가 완료되면 설치 끝! 이제 디바이스 목록에 Nexus 7이 표시될 것이다.



저작자 표시 비영리 변경 금지
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. jen 2012/10/10 10:54  댓글주소  수정/삭제  댓글쓰기

    USB 연결시, USB 컴퓨터 연결을 미디어 기기(MTP)에서 카메라(PTP)로 바꾸는건
    어디가서 해야하는지 알려주실수 있으신가요?ㅜㅜ

    • madvirus 2012/10/11 10:02  댓글주소  수정/삭제

      설정 -> 저장소 -> 상단 우측의 메뉴 표시 버튼 클릭 -> usb 컴퓨터 연결 메뉴 선택
      미디어 기기에서 카메라로 변경하면 됨

      빠르게 가려면, 상단의 공지 영역을 누르고 아래로 스크롤
      -> 그러면 미디어기기로 연결됨 표시 있음. 그걸 클릭하면 변경 화면으로 이동

  2. hjw6036 2013/04/13 23:49  댓글주소  수정/삭제  댓글쓰기

    그거깔아서 어떻게해요 깔아놓고 바탕화면에 놔두니까 똑같던데

    • 최범균 madvirus 2013/04/14 11:28  댓글주소  수정/삭제

      압축 풀면 드라이버 파일이 나오는데요, 장치관리자에서 노란색으로 표시되는 장치의 드라이버를 다운로드 받은 파일로 지정해주시면 됩니다.
      그럼, 안드로이드 개발할 때 Nexus 7이 ADB 인터페이스에 장치로 표시가 됩니다.

페이스북 친구들과 댓글을 공유하고 싶다면 아래를 이용해주세요.