Android Drawing Process 1

Android 2019. 4. 15. 11:17

(App surface, SF Layer)출처: https://lastyouth.tistory.com/24 [자유로운날개]

 

학기가 끝나서, 지난학기간 한 것들을 포스팅하려고 한다. 언제 다 할지는 미지수지만 그래도 틈틈히 할 예정이다.

 

Smartphone display에 대해 공부를 하다보면 필연적으로 해당 Smartphone 상 동작하는 운영체제에서 어떻게 화면을 그려내는지를 알아야 할 시점이 오게 된다.

 

사실 이전에 수행한 프로젝트 덕분에 Android 운영체제에서 SurfaceFlinger 서비스가 하는 일은 개략적으로 알고 있었으나, SurfaceFlinger는 어찌보면 실제 화면 출력을 담당하는 DisplayController로 보내기 전에 출력될 Layer를 관리하고 필요에 따라서 합성도 해주는 준 최종 관문의 역할을 한다.

 

SurfaceFlinger를 이해하는 것이 분명 Android Drawing Mechanism 을 이해하는데 초석이 되는것은 분명하다. 하지만 SurfaceFlinger는 시스템 서비스로써 여러 Application과 상호작용 한다는 점을 떠올리면, 결국 드로잉의 시작은 Application에서 해야함을 생각할 수 있다.

 

이에 본 포스팅에서는 Application의 Drawing과 그것이 SurfaceFlinger 서비스에 의해 관리되는 과정 일부를 살펴 볼 예정이다.

 

 

위 그림은 사용자의 상호작용에 의해 앱의 화면이 변화할 때, 화면이 다시 갱신되는 과정을 각 계층적으로 나타낸 것이다.

 

앱이 다시 그린 화면은 SurfaceFlinger에 의해 합성되거나 Display Controller에 의해 합성되어 실제 Display에 의해 출력된다. 좀 더 자세히 알아보기 위해 SurfaceFlinger의 역할에 대해 소개한다.

 

 

SurfaceFlinger는 크게 3가지의 역할을 수행하는데, 각각 Layer Management, Vsync Processing 그리고 Screen Refreshing이다.

 

SurfaceFlinger는 하나의 프로세스로 존재하는 시스템 서비스로써 SurfaceFlinger 에 접근하기 위해서는 IPC를 이용해야 하며, 이를 위해 제공하는 인터페이스를 통해 SurfaceFlinger의 기능을 이용한다. SurfaceFlinger는 두 가지 종류의 인터페이스를 지원한다.

 

 

 

이 중 ISurfaceComposerClient는 Client 접미사에서 추측이 가능하듯이 각 어플리케이션마다 관리되는 객체를 컨트롤하기 위한 용도로 사용되며, 주된 기능은 각 어플리케이션 별 Surface를 생성하는 것이다.

 

ISurfaceComposer는 SurfaceFlinger에 의해 관리되는 객체를 컨트롤하기 위한 용도이다.

 

 

이 중 먼저 Layer가 어떻게 관리되는지를 살펴보겠다.

 

Drawing Process (App side)


Layer에 대해 살펴보기 전에 먼저 각 어플리케이션의 View가 어떻게 관리되는지를 알아볼 필요가 있다.

 

 

각 어플리케이션은 여러 뷰를 가질 수 있으며, DecorView를 최종 루트로 가지는 트리 구조로 관리된다.

 

DecorView는 그릴 것이 있는 경우, 할당받은 Surface에 그리게 된다. 따라서 Surface는 각 Application별로 최소 하나는 가지고 있게 된다.

 

그럼 여기서 Surface는 뭐고 Layer는 무엇인가 라는 질문을 할 수 있는데, 엄밀히 말하면 둘은 같은 개념이다. 다음을 보자.

 

 

SurfaceComposerClient로 createSurface 메서드를 호출하면, ISurfaceComposer 인터페이스를 이용해 SurfaceFlinger에 Layer를 하나 만드는 것을 볼 수 있다.

 

즉 Surface는 어플리케이션이 각 소유하는 개념이라면, Layer는 SurfaceFlinger에 의해 관리되는 개념이라고 보면 된다.

 

그러면 이제 각 어플리케이션은 적어도 하나의 Surface를 가지고, 이 Surface는 SurfaceFlinger에 의해 Layer로써 관리된다는 것을 알았다.

 

본격적인 Drawing Process를 설명하기 전 몇 가지 객체에 대해서 설명을 하도록 하겠다.

 

먼저 Choreographer 객체에 대한 설명을 하도록 한다. Choreographer에서는 SurfaceFlinger에서 송신되는 Vsync Event를 요청/수신하여 다음 작업들을 처리한다.

 

1. Input Event Handling

2. Self-Invalidation

3. Animation

 

다음으로 ViewRootImpl은 DecorView와 Choreographer를 연결해주는 일종의 핸들러 역할을 하는 객체로써, DecorView와 Choreographer 사이에서 둘의 요청을 처리하고 중계해주는 역할을 한다.

 

이제 어플리케이션의 Drawing Process를 살펴보도록 하자.

 

실제 그리기는 현재 foreground에 표시되고 있는 어플리케이션에서 그릴 것이 생김으로써 시작된다. 무언가 그릴 것이 생기면 ViewRootImpl 객체 내에 있는 scheduleTraversal 메서드가 호출된다.

 

scheduleTraversal 메서드 내부에서는 Choreographer 객체에게 다음 vsync를 예약해달라는 요청을 보내게 된다. 이 작업이 아래 그림에서 Invalidate와 Vsync scheduling에 해당된다.

 

 

 

 

Vsync 예약 요청을 받은 Choreographer는 SurfaceFlinger로 하여금 다음 Vsync 도착 시 알려달라고 요청한다.

 

다음 Vsync signal이 도착하면 Choreographer는 ViewRootImpl의 performTraversal 메서드를 호출한다. performTraversal 메서드 내부에서는 다시 그려야 될 부분을 measure하고, layout을 재 구성한 다음, performDraw 메서드를 호출하여 그리기를 수행한다.

 

 

ViewRootImpl 객체에서는 performDraw 메서드에 의해 호출되는 draw 메서드가 최종적으로 DecorView로 하여금 할당받은 Surface에 그리게 한다. 

 

이 때, HardwareRenderer를 이용하는 경우와 Software Reneder를 이용하는 경우로 나뉘게 된다.

 

전자는 GPU를 이용하여 Surface에 그리는 것이며, 후자는 CPU로 그리는 것이다. Android 3.0 버전 이후로 하드웨어 가속을 지원하면서 이후 Android에서는 GPU를 이용하여 그리는 것을 기본값으로 한다.

 

GPU든 CPU든 Surface에 View의 변한 부분을 그리는 것은 동일하지만, 그리는 방법이 다른데, 이 부분에 대한 구체적인 내용은 다음을 참조하면 된다.

 

https://developer.android.com/guide/topics/graphics/hardware-accel.html#model

 

간략히 살펴보면, CPU Drawing(Software Rendering)은 Decorview 에서 시작하여 자식 뷰로 내려가면서 그려내는 방법을 사용하고, GPU Drawing은 뷰가 직접 그리는 것이 아닌 DisplayList 라는 추가적인 객체를 이용하여 그리기 작업을 수행하는 것으로 보인다.

 

그리기가 완료되면, 할당받은 Surface가 변했으므로, 이를 SurfaceFlinger에게 통지해야 한다.

 

Layer Management


각 어플리케이션이 Surface를 생성하는 과정과, Surface에 필요한 부분을 그리는 방법을 설명하였다. 위에서도 언급했듯이, 각 어플리케이션은 적어도 하나의 Surface를 가지고, 이 Surface는 SurfaceFlinger에 의해 Layer로써 관리된다는 것을 알았다. 이를 다시 그림으로 나타내면 다음과 같다.

 

 

그러면 Layer와 Surface는 동등한 개념이지만, 존재하는 위치가 다르다. Surface는 어떻게 다시 그려진 내용을 Layer로 하여금 알게 하는 것일까?

 

이를 알기 위해서 실제 그림에 대한 데이터가 존재하는 위치는 다른 데 있다는 것을 알아야 한다. Surface와 Layer는 추상화된 객체일 뿐이며, 실제 화면에 대한 raw data가 존재하는 곳은 따로 있다. 이를 Buffer라고 한다.

 

USB 디버깅이 허용된 디바이스를 연결하고 쉘에 adb shell dumpsys SurfaceFlinger 를 입력하면 현재 표시되는 화면에 대한 SurfaceFlinger의 상태에 대해 표시가 되는데 여기에 보면 다음과 같은 부분을 찾을 수 있다.

 

 

이 부분이 할당된 Buffer로써 실제 화면에 대한 정보를 담고있다. 각 Surface가 Buffer를 하나씩 소유하게 되면, 메모리가 남아나지 않을 것이므로, 어느 정도의 Buffer를 할당해 두고 필요할 때 마다 획득하여 사용하도록 설계되었다. 즉 Producer, Consumer 모델을 따른다고 볼 수 있다.

 

각 Surface와 Layer를 이어주기 위해 BufferQueue가 사용된다. 이름에서 알 수 있듯이 BufferQueue에는 3개의 Buffer가 들어있다. 

 

Buffer는 누가 사용중이냐에 따라서 4가지의 상태를 가지는데, 각각 다음과 같다.

 

1. Free : Queue 안에 존재하며, Surface 혹은 Layer에 의해 사용되고 있지 않으며, 그려지지도 않은 상태

 

2. dequeued : Queue에서 나왔으며, Surface에 의해 점유 중인 상태

 

3. queued : Surface에 의해 쓰여진 buffer가 Queue 안에 있는 상태

 

4. acquired : queued 상태인 buffer가 Layer에 의해 점유 중인 상태

 

그러면 이제 실제로 Surface와 Layer가 어떻게 통신하는지 그림으로 보면서 알아보자.

그림에는 SurfaceFlinger가 소유한 Layer는 표시하지 않았다.

 

초기 상태에는 free buffer가 queue에 3개 들어있는 상태로 시작한다. queue에는 buffer 자체가 들어있는 것이 아니라 buffer의 handle이 들어가 있다.

 

 

여기서 어플리케이션이 소유한 surface가 invalidate 되면, surface는 buffer의 핸들을 획득한다. 이 시점에서 buffer의 상태는 dequeued가 된다.

 

획득한 buffer의 핸들이 가리키는 곳에 변경된 내용을 그린다. 그리는 주체는 앞서 설명한 CPU 혹은 GPU 중 하나가 된다.

 

 

변경된 buffer 핸들을 다시 queue에 넣는다. 이 시점에서 buffer의 상태는 queued가 된다.

 

다음 vsync가 도달하면, Surface는 다시 그리기 작업을 수행하게 되고, SurfaceFlinger는 queued된 buffer가 queue 내에 존재하게 되므로, 이제 Layer Composing 을 수행 하기 위해 해당 buffer를 획득한다.



출처: https://lastyouth.tistory.com/24 [자유로운날개]

블로그 이미지

kuku_dass

,

Android 그래픽 시스템의 발전 과정.


허니컴 이전에는 Surface Flinger 에서만 GPU 사용


허니컴이 Tablet 용 Framework 이다보니 늘어난 pixel 에 대응하기 위해서 GPU 사용이 필요하게 됨.

onDraw() 이후에 실제 그리는 부분을 CPU 에서 하는 것이 아니라 이제는 GPU 에서 하게 됨.


[android] TextureView 에 대한 이야기


기존 View 는 한 View 가 invalidate 가 되면, dirty check를 한 후, parent 로 올라가면서 invalidate 를 쭉 호출하게 되고, 다시 dirty check 된 녀석까지 draw 를 수행하여 그리게 된다.


[android] TextureView 에 대한 이야기


* 기존 View 의 문제

1. UI 스레드에서만 그릴 수 있다.

2. View 의 계층 구조를 타야 한다.

3. 실시간으로 그리기 어렵다.





* 대안으로 등장한 녀석이 SurfaceView

SurfaceView 의 경우 확대, 축소, 비트맵 캡쳐가 안된다.


* 또 다시 대안으로 등장한 녀석이 GLSurfaceView

GLSurfaceView 는 빨리 전환하게 되면 죽는다.


* 또 다른 대안은 RenderScript

RenderScript 는 젤리빈부터 deprecated 되었다.




TextureView 의 등장


SurfaceTexture, TextureView, SurfaceTextureListener 가 한 팀( Set )으로 작동.

이 중 SurfaceTexture 는 framework 가 관리한다.

TextureView 는 일반 View 처럼 사용한다.

Listener 만 잘 override 해서 구현하면 사용이 간편하다.


TextureView 는 Camera 나 OpenGL 에 주로 사용한다.



출처: http://aroundck.tistory.com/2075 [돼지왕 왕돼지 놀이터]

블로그 이미지

kuku_dass

,

Touch Event 와 관련된 메소드는 dispatchTouchEvent, onTouchEvent 가 있다.


가장 최상위에 있는 dispatchTouchEvent가 호출이 되고, 해당 메소드는 하위의 dispatchTouchEvent를 호출한다.


예)

 Activity

  ViewGroup

   View


이런식으로 되어있다고하면, Activity.dispatchTouchEvent -> ViewGroup.dispatchTouchEvent -> View.dispatchTouchEvent

이렇게 호출을 아래로 내려간다.


가장 최하위 View 의 dispatchTouchEvent 까지 도착하게 되면, 이때 View.onTouch , View.onTouchEvent 가 호출된다.

View.onTouch, onTouchEvent 에서 들어온 이벤트를 보고, 처리할지 말지를 판단한 후, 처리를 할거면 (intercept) return true.

처리하지 않을거면 return false 를 한다.

return 된 값은 View.dispatchTouchEvent 로 가게되고 여기서는 Touch 메소드로 부터 받은 리턴값을 그대로 리턴한다.


ViewGroup.dispatchTouchEvent 로 결과값이 넘어오면, 결과값이 true면 이미 View 쪽에서 이벤트가 처리되었음을 의미하기때문에

자신도 그대로 true를 리턴한다.


만약 View 에서 처리되지 않았다면, (return false) 자신의 onTouch, onTouchEvent로 이벤트를 보낸다.




[출처] : http://dktfrmaster.blogspot.kr/2016/09/blog-post_26.html

[안드로이드] 터치 이벤트 흐름

안드로이드는 뷰의 터치 이벤트를 처리하는 3가지 방법이 있다.
 - 엑티비티에서 onTouchEvent 메서드 재정의
 - 이벤트를 처리하고자 하는 뷰의 서브클래스에서 onTouchEvent 메서드 재정의
 - 처리하고자 하는 뷰에 View.OnTouchListener 리스너 인터페이스를 등록

이벤트 처리의 흐름

엑티비티에서의 이벤트 처리

엑티비티는 onTouchEvent 메서드를 재정의하면 모든 뷰의 터치이벤트를 받을 수 있다.

뷰에서의 이벤트 처리

onTouchEvent 메서드를 재정의하거나, OnTouchListener 인터페이스를 등록함으로써, 해당 뷰의 이벤트만 처리할 수 있다.
 - onTouchEvent 메서드를 재정의하려면 상속받아서 서브클래스를 구성해야 하기에 번거롭다.
 - OnTouchListener를 등록하면 인터페이스를 구현한 메서드에서 터치이벤트를 받을 수 있으므로 상속이 필요없다.
만약 2가지 방법이 다 구현되어 있다면??
 - OnTouchListener의 메서드(onTouch) -> onTouchEvent의 순서로 호출된다.
 - onTouch 메서드가 true를 리턴하면, onTouchEvent는 이벤트를 받지 못한다. 

Activity, ViewGroup, View 사이의 이벤트 처리

화면이 다음과 같이 구성되어 있을 때, 기본적인 터치 이벤트의 흐름은 다음과 같다.
Activity(dispatchTouchEvent)              : 하위 dispatchTouchEvent 호출
  ViewGroup(dispatchTouchEvent)       : onInterceptTouchEvent 호출
    ViewGroup(onInterceptTouchEvent) : 하위 dispatchTouchEvent 호출
    View(dispatchTouchEvent)              : 자신의 이벤트 핸들러 메서드 호출
      View(onTouch)                           : 이벤트 처리 & 처리여부 리턴
      View(onTouchEvent)                    : 이벤트 처리 & 처리여부 리턴
    View(dispatchTouchEvent)              : 자신의 이벤트 핸들러 메서드의 결과값을 리턴
    ViewGroup(onTouch)                     : 이벤트 처리 & 처리여부 리턴
    ViewGroup(onTouchEvent)             : 이벤트 처리 & 처리여부 리턴
  ViewGroup(dispatchTouchEvent)       : 자신 or 하위 레이어의 결과값을 리턴
  Activity(onTouchEvent)                    : 이벤트 처리 & 처리여부 리턴
Activity(dispatchTouchEvent)              : 자신 or 하위 레이어의 결과값을 리턴

- Aictivity, ViewGroup, View는 모두 dispatchTouchEventonTouchEvent 메서드를
  가지고 있다.
- ViewGroup에는 추가적으로 onInterceptTouchEvent 메서드가 있다.
- 이벤트가 발생하면 각 레이어의 dispatchTouchEvent가 가장 먼저 호출된다.
  dispatchTouchEvent의 역할은 하위 레이어의 dispatchTouchEvent를 호출하고,
  하위 레이어가 터치 이벤트를 처리했는지 결과를 받아서 처리하지 않은 경우
  자기 자신의 이벤트 처리 메서드(onTouch, onTouchEvent) 메서드로 이벤트를 보낸다.
  그리고는 이벤트 처리 여부를 상위 dispatchTouchEvent에 리턴한다.
- onInterceptTouchEvent는 ViewGroup의 dispatchTouchEvent의 로직을 대신 담당하여,
  자신에게 속한 하위뷰에게 이벤트를 전달할지 결정한다.
- 모든 메서드는 리턴값이 boolean형인데 true일 경우 이벤트를 처리했음을 의미하고,
  false일 경우 이벤트가 처리되지 않았음을 의미한다.


블로그 이미지

kuku_dass

,

Android Lollipop (API 21) 버전 부터 기존 Camera API는 deprecated 되고 Camera2 API가 적용되었는데, Pipeline Stream 기반으로 기존에 비해 세밀한 세팅이 가능하고 비동기 루틴이 많아 Google의 샘플 소스 android-Camera2Basic을 봐서는 분석이 쉽지 않다. Google I/O 2014 프레젠테이션에서 Camera2 API에 대한 적용 흐름이 잘 설명되어 있어 해당 루틴을 기준으로 분석 및 이해를 목적으로 코드를 작성하였으며 실제 프로젝트에 적용하기에는 적절치 않다.

Architecture

source: https://source.android.com/devices/camera/index.html#architecture

Camera HAL(Hardware Abstraction Layer)에서 Application Framework단 사이에 구현된 스택. 왼쪽의 기존 Camera API와 비교하여 CameraService단에 Callback, Listener C++ Binder Interface가 보인다.

source: https://source.android.com/devices/camera/camera3_requests_hal.html

코드를 통해 다시 설명하겠지만, App - Camera2 API - HAL(Hardware Abstraction Layer)모델의 API 적용 프로세스를 표현한 그림.

Process

source: https://www.youtube.com/watch?v=92fgcUNCHic&feature=youtu.be&t=2130

Google I/O 2014 프레젠테이션에서 캡쳐한 Camera2 API 시퀀스. 다음은 영상의 프로세스 시퀸스 다이어그램.

  1. CameraManager
    • 사용가능한 카메라를 나열하고, CameraDevice를 취득하기 위한 Camera2 API의 첫번째 클래스.
  2. CameraCharacteristics
    • CameraManager에 의에 나열된 Camera 하드웨어, 사용가능 세팅등에 대한 정보 취득.
  3. CameraDevice
    • 실질적인 해당 카메라를 나타내는 클래스.
    • CameraManager에 의해 비동기 콜백으로 취득.
  4. CameraCaptureSession
    • CameraDevice에 의해 이미지 캡쳐를 위한 세션 연결.
    • 해당 세션이 연결될 surface를 전달.
  5. CaptureRequest
    • CameraDevice에 의해 Builder패턴으로 생성하며, 단일 이미지 캡쳐를 위한 하드웨어 설정(센서, 렌즈, 플래쉬) 및 출력 버퍼등의 정보(immutable).
    • 해당 리퀘스트가 연결될 세션의 surface를 타겟으로 지정.
  6. CaptureResult
    • CaptureRequest가 수행되고 비동기 CameraCaptureSession.CaptureCallback으로 취득.
    • 해당 세션의 리퀘스트 정보 뿐만 아니라 캡쳐 이미지의 Metadata정보도 포함.

Code Project

분석 및 이해를 용이하게 하기 위해 Camera2 APIs는 클래스 파일(Camera2APIs.java)하나만 작성하여 몰아 넣고 MainActivity에서 해당 클래스만 사용.

AndroidManifest.xml

카메라 디바이스 접근을 위한 권한. Android 6.0 이상에서는 권한요청 코드(requestPermission)가 추가되어야 한다.

<uses-permission android:name="android.permission.CAMERA" />

activity_main.xml

Preview 화면을 위한 TextureView 추가. CaptureSession의 surface로 사용.

<TextureView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textureView"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true" />

Camera2APIs.java

CameraManager

카메라 시스템 서비스 매니저 리턴.

public CameraManager CameraManager_1(Activity activity) {
    CameraManager cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    return cameraManager;
}

CameraCharacteristics

사용가능한 카메라 리스트를 가져와 후면 카메라(LENS_FACING_BACK) 사용하여 해당 cameraId 리턴. StreamConfiguratonMap은 CaptureSession을 생성할때 surfaces를 설정하기 위한 출력 포맷 및 사이즈등의 정보를 가지는 클래스. 사용가능한 출력 사이즈중 가장 큰 사이즈 선택.

public String CameraCharacteristics_2(CameraManager cameraManager) {
    try {
        for (String cameraId : cameraManager.getCameraIdList()) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                Size[] sizes = map.getOutputSizes(SurfaceTexture.class);

                mCameraSize = sizes[0];
                for (Size size : sizes) {
                    if (size.getWidth() > mCameraSize.getWidth()) {
                        mCameraSize = size;
                    }
                }

                return cameraId;
            }
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }

    return null;
}

참고로, INFO_SUPPORTED_HARDWARE_LEVEL키값으로 카메라 디바이스의 레벨을 알 수 있는데 LEGACY < LIMITED < FULL < LEVEL_3 순으로 고성능이며 더 세밀한 카메라 설정이 가능하다. LEGACY 디바이스의 경우 구형 안드로이드 단말 호환을 위해 Camera2 API는 기존 Camera API의 인터페이스에 불과하다. 즉, 프레임 단위 컨트롤 등의 Camera2 기능은 사용할 수 없다.

CameraDevice

비동기 콜백 CameraDevice.StateCallback onOpened()로 취득. null파라미터는 MainThread를 이용하고, 작성한 Thread Handler를 넘겨주면 해당 Thread로 콜백이 떨어진다. 비교적 딜레이가 큰(~500ms) 작업이라 Thread 권장.

public void CameraDevice_3(CameraManager cameraManager, String cameraId) {
    try {
        cameraManager.openCamera(cameraId, mCameraDeviceStateCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

onOpened()에서 취득한 CameraDevice로 CaptureSessionCaptureRequest가 이뤄지는데 Camera2 APIs 처리과정을 MainActivity에서 일원화하여 표현하기 위해 인터페이스로 처리.

private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice camera) {
        mCameraDevice = camera;
        mInterface.onCameraDeviceOpened(camera, mCameraSize);
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice camera) {
        camera.close();
    }

    @Override
    public void onError(@NonNull CameraDevice camera, int error) {
        camera.close();
    }
};

CaptureSession

일단 세션이 생성(비동기)된 후에는 해당 CameraDevice에서 새로운 세션이 생성되거나 종료하기 이전에는 유효.

public void CaptureSession_4(CameraDevice cameraDevice, Surface surface) {
    try {
        cameraDevice.createCaptureSession(Collections.singletonList(surface), mCaptureSessionCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

생성된 세션에 카메라 프리뷰를 위한 CaptureRequest정보 설정. 프리뷰 화면은 연속되는 이미지가 보여지기 때문에 CONTROL_AF_MODE_CONTINUOUS_PICTURE로 포커스를 지속적으로 맞추고, setRepeatingRequest로 해당 세션에 설정된 CaptureRequest세팅으로 이미지를 지속적으로 요청.

setRepeatingRequest의 null파라미터는 MainThread를 사용하고, 작성한 Thread Handler를 넘겨주면 해당 Thread로 콜백이 떨어진다. 프리뷰 화면은 지속적으로 화면 캡쳐가 이뤄지기 때문에, MainThread 사용 시 Frame drop이 발생할 수 있다. Background Thread 사용 권장.

Google의 android-Camera2Basic샘플 코드에서는 CaptureRequest를 먼저 수행하는데, 여기서는 Google 프레젠테이션 자료 및 모델 그림의 프로세스를 기준으로 작성하기 위해 CaptureSession을 먼저 수행.

private CameraCaptureSession.StateCallback mCaptureSessionCallback = new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(CameraCaptureSession cameraCaptureSession) {
        try {
            mCaptureSession = cameraCaptureSession;
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            cameraCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {

    }
};

캡쳐된 이미지 정보 및 Metadata가 넘어오는데, 프리뷰에서는 딱히 처리할 작업은 없다. 사진 촬영의 경우라면, onCaptureCompleted()에서 촬영이 완료되고 이미지가 저장되었다는 메세지를 띄우는 시점. 캡쳐 이미지와 Metadata 매칭은 Timestamp로 매칭가능하다.

private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
    @Override
    public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) {
        super.onCaptureProgressed(session, request, partialResult);
    }

    @Override
    public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
    }
};

CaptureRequest

카메라 프리뷰(CameraDevice.TEMPLATE_PREVIEW)를 위한 Builder 패턴의 CaptureRequest생성. 예를 들어, 사진 촬영의 경우에는 CameraDevice.TEMPLATE_STILL_CAPTURE로 리퀘스트를 설정한다. surface는 해당 세션에 사용된 surface를 타겟으로 설정.

public void CaptureRequest_5(CameraDevice cameraDevice, Surface surface) {
    try {
        mPreviewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        mPreviewRequestBuilder.addTarget(surface);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

이상으로 프리뷰를 위한 Camera2 APIs 코드 작성은 완료.

Camera2APIs.java 전체 코드.

public class Camera2APIs {

    interface Camera2Interface {
        void onCameraDeviceOpened(CameraDevice cameraDevice, Size cameraSize);
    }

    private Camera2Interface mInterface;
    private Size mCameraSize;

    private CameraCaptureSession mCaptureSession;
    private CameraDevice mCameraDevice;
    private CaptureRequest.Builder mPreviewRequestBuilder;

    public Camera2APIs(Camera2Interface impl) {
        mInterface = impl;
    }

    public CameraManager CameraManager_1(Activity activity) {
        CameraManager cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
        return cameraManager;
    }

    public String CameraCharacteristics_2(CameraManager cameraManager) {
        try {
            for (String cameraId : cameraManager.getCameraIdList()) {
                CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {
                    StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    Size[] sizes = map.getOutputSizes(SurfaceTexture.class);

                    mCameraSize = sizes[0];
                    for (Size size : sizes) {
                        if (size.getWidth() > mCameraSize.getWidth()) {
                            mCameraSize = size;
                        }
                    }

                    return cameraId;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        return null;
    }

    private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            mInterface.onCameraDeviceOpened(camera, mCameraSize);
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            camera.close();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            camera.close();
        }
    };

    public void CameraDevice_3(CameraManager cameraManager, String cameraId) {
        try {
            cameraManager.openCamera(cameraId, mCameraDeviceStateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraCaptureSession.StateCallback mCaptureSessionCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(CameraCaptureSession cameraCaptureSession) {
            try {
                mCaptureSession = cameraCaptureSession;
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                cameraCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {

        }
    };

    public void CaptureSession_4(CameraDevice cameraDevice, Surface surface) {
        try {
            cameraDevice.createCaptureSession(Collections.singletonList(surface), mCaptureSessionCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    public void CaptureRequest_5(CameraDevice cameraDevice, Surface surface) {
        try {
            mPreviewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) {
            super.onCaptureProgressed(session, request, partialResult);
        }

        @Override
        public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
        }
    };

    public void closeCamera() {
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }

        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }
}

MainActivity.java

준비

TextureView의 surface가 사용가능할 때 카메라 오픈을 위한 Listener 및 MainActivity에서 CaptureSessionCaptureRequest호출을 위한 Camera2Interface 설정.

public class MainActivity extends AppCompatActivity
        implements Camera2APIs.Camera2Interface, TextureView.SurfaceTextureListener {

    private TextureView mTextureView;
    private Camera2APIs mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextureView = (TextureView)findViewById(R.id.textureView);
        mTextureView.setSurfaceTextureListener(this);

        mCamera = new Camera2APIs(this);
    }
}

Open Camera

openCamera()만 호출하면 5단계 과정이 전부 수행되며, 프리뷰가 이뤄진다.

private void openCamera() {
    CameraManager cameraManager = mCamera.CameraManager_1(this);
    String cameraId = mCamera.CameraCharacteristics_2(cameraManager);
    mCamera.CameraDevice_3(cameraManager, cameraId);
}

@Override
public void onCameraDeviceOpened(CameraDevice cameraDevice, Size cameraSize) {
    SurfaceTexture texture = mTextureView.getSurfaceTexture();
    texture.setDefaultBufferSize(cameraSize.getWidth(), cameraSize.getHeight());
    Surface surface = new Surface(texture);

    mCamera.CaptureSession_4(cameraDevice, surface);
    mCamera.CaptureRequest_5(cameraDevice, surface);
}

Surface Texture가 준비 완료된 콜백을 받으면, 카메라 오픈.

@Override
protected void onResume() {
    super.onResume();

    if (mTextureView.isAvailable()) {
        openCamera();
    } else {
        mTextureView.setSurfaceTextureListener(this);
    }
}

/* Surface Callbacks */
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
    openCamera();
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
    return true;
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

}

Close Camera

private void closeCamera() {
    mCamera.closeCamera();
}

@Override
protected void onPause() {
    closeCamera();
    super.onPause();
}

실행화면

References

블로그 이미지

kuku_dass

,

안드로이드 UI는 기본적으로 싱글 스레드 모델로 작동하므로, 이 영향을 고려해 개발하지 않으면 애플리케이션의 성능이 저하될 수 있습니다. 따라서 메인 스레드에서 긴 작업을 하는 것을 피하기 위해 여분의 스레드를 사용해야 합니다. 다른 스레드에서 UI 스레드로 접근할 수 있도록 안드로이드에서 제공하는 스레드 간 통신 방법을 소개합니다.


소개

안드로이드의 애플리케이션을 실행하면 시스템은 메인 액티비티를 메모리로 올려 프로세스로 만들며, 이 때 메인 스레드가 자동으로 생성됩니다. 메인 스레드는 안드로이드의 주요 컴퍼넌트를 실행하는 곳이자 UI를 그리거나 갱신하는 일을 담당할 수 있는 유일한 스레드이므로 UI 스레드라고도 불립니다.

안드로이드 화면을 구성하는 뷰나 뷰그룹을 하나의 스레드에서만 담당하는 원칙을 싱글 스레드 모델이라고 합니다. 싱글 스레드 모델의 규칙은 첫째, 메인 스레드(UI 스레드)를 블럭하지 말 것, 둘째, 안드로이드 UI 툴킷은 오직 UI 스레드에서만 접근할 수 있도록 할 것, 이 두 가지입니다. 이런 싱글 스레드 모델의 영향을 고려하지 않으면 애플리케이션의 성능이 저하될 수 있습니다. 긴 시간이 걸리는 작업을 메인 스레드에서 담당한다면 애플리케이션의 반응성이 낮아질 수 있고, 급기야 사용자의 불편함을 방지하고자 시스템이 애플리케이션을 ANR(Appication Not Responding) 상태로 전환시킬 수도 있습니다. 따라서 시간이 걸리는 작업을 하는 코드는 여분의 스레드를 사용하여 메인 스레드에서 분리해야 하고, 자연스럽게 메인 스레드와 다른 스레드가 통신하는 방법이 필요하게 됩니다.

다른 스레드에서 메인 스레드로 접근하기 위해 Looper와 Handler를 사용할 수 있으며, 안드로이드는 Java의 Thread를 좀 더 쉽게 사용할 수 있도록 래핑한 HandlerThread, 더 나아가 Thread나 Message Loop 등의 작동 원리를 크게 고려하지 않고도 사용이 가능한 AsyncTask 등의 클래스를 제공합니다. 이 글에서는 먼저 Thread-Looper-Handler의 개념을 이해하고, 나아가 HandlerThread와 AsyncTask에 대해 정리해보도록 하겠습니다.

이런 개발 뉴스를 더 만나보세요

 

Looper와 Handler의 사용 목적

왜 안드로이드는 메인 스레드에서만 UI 작업이 가능하도록 제한할까요? 메인 스레드가 아닌 스레드가 병렬적으로 실행되고 있을 때, 메인 스레드와 다른 스레드, 두 개 이상의 스레드가 동시에 같은 텍스트뷰에 setText()를 시도하는 경우를 생각하면 간단합니다.

둘 중 어느 스레드의 setText()가 적용될지 예측할 수 없고, 사용자는 둘 중 하나의 값만을 볼 수 있어 다른 한 스레드의 결과는 버려집니다. 이같이 두 개 이상의 스레드를 사용할 때의 동기화 이슈를 차단하기 위해서 Looper와 Handler를 사용하게 됩니다.

Looper와 Handler의 작동 원리

먼저 스레드와 Looper, Handler가 어떻게 작동하는지 알아볼까요? 메인 스레드는 내부적으로 Looper를 가지며 그 안에는 Message Queue가 포함됩니다. Message Queue는 스레드가 다른 스레드나 혹은 자기 자신으로부터 전달받은 Message를 기본적으로 선입선출 형식으로 보관하는 Queue입니다. Looper는 Message Queue에서 Message나 Runnable 객체를 차례로 꺼내 Handler가 처리하도록 전달합니다. Handler는 Looper로부터 받은 Message를 실행, 처리하거나 다른 스레드로부터 메시지를 받아서 Message Queue에 넣는 역할을 하는 스레드 간의 통신 장치입니다.

이제 Handler와 Looper, Message Queue에 대해 좀 더 자세히 살펴보겠습니다.

Handler

Handler는 스레드의 Message Queue와 연계하여 Message나 Runnable 객체를 받거나 처리하여 스레드 간의 통신을 할 수 있도록 합니다. Handler 객체는 하나의 스레드와, 해당 스레드의 Message Queue에 종속됩니다. 새로 Handler 객체를 만든 경우 이를 만든 스레드와 해당 스레드의 Message Queue에 바인드됩니다. 다른 스레드가 특정 스레드에게 메시지를 전달하려면 특정 스레드에 속한 Handler의 post나 sendMessage 등의 메서드를 호출하면 됩니다. 앞서 Message Queue는 전달받은 Message를 선입선출 형식으로 보관한다고 설명했지만, 전달 시점에 다른 메서드를 사용하여 Queue의 맨 위로 보내거나, 원하는 만큼 Message나 Runnable 객체의 전송을 지연시킬 수도 있습니다. 자주 쓰이는 Handler의 메서드를 아래 표에 정리했습니다.

리턴값메서드명인자설명
voidhandleMessageMessage msgLooper가 Message Queue에서 꺼내준 Message나 Runnable 객체를 처리 
(상속 시 구현 필수)
final booleanpostRunnable rMessage Queue에 Runnable r을 전달
final booleansendMessageMessage msgMessage Queue에 Message msg를 전달
final booleanpostAtFrontOfQueueRunnable rMessage Queue의 맨 앞에 Runnable r을 전달
final booleansendMessageAtFrontOfQueueMessage msgMessage Queue의 맨 앞에 Message msg를 전달
final booleanpostDelayedRunnable r, long delayMillisdelayMillis만큼 지연 후 Message Queue에 Runnable r을 전달
final booleansendMessageDelayedMessage msg, long delayMillisdelayMillis만큼 지연 후 Message Queue에 Message msg를 전달

외부, 혹은 자기 스레드로부터 받은 메시지를 어떤 식으로 처리할 지는 handleMessage() 메서드를 구현하여 정합니다. sendMessage()나 post()로 특정 Handler에게 메시지를 전달할 수 있고, 재귀적인 호출도 가능하므로 딜레이를 이용한 타이머나 스케줄링 역할도 할 수 있어 편리합니다.

Looper와 Message Queue

Looper는 무한히 루프를 돌며 자신이 속한 스레드의 Message Queue에 들어온 Message나 Runnable 객체를 차례로 꺼내서 이를 처리할 Handler에 전달하는 역할을 합니다. 메인 스레드는 Looper가 기본적으로 생성돼 있지만, 새로 생성한 스레드는 기본적으로 Looper를 가지고 있지 않고, 단지 run 메서드만 실행한 후 종료하기 때문에 메시지를 받을 수 없습니다. 따라서 기본 스레드에서 메시지를 전달받으려면 prepare() 메서드를 통해 Looper를 생성하고, loop() 메서드를 통해 Looper가 무한히 루프를 돌며 Message Queue에 쌓인 Message나 Runnable 객체를 꺼내 Handler에 전달하도록 합니다. 이렇게 활성화된 Looper는 quit()이나 quitSafely() 메서드로 중단할 수 있습니다. quit() 메서드가 호출되면 Looper는 즉시 종료되고, quitSafely() 메서드가 호출되면 현재 Message Queue에 쌓인 메시지들을 처리한 후 종료됩니다.

Message와 Runnable

Message란 스레드 간 통신할 내용을 담는 객체이자 Queue에 들어갈 일감의 단위로 Handler를 통해 보낼 수 있습니다. 일반적으로 Message가 필요할 때 새 Message 객체를 생성하면 성능 이슈가 생길 수 있으므로 안드로이드가 시스템에 만들어 둔 Message Pool의 객체를 재사용합니다. obtain() 메서드는 빈 Message 객체를, obtain(Handler h, int what …)은 목적 handler와 다른 인자들을 담은 Message 객체를 리턴합니다. Runnable을 설명하려면 스레드를 만드는 두 가지 방법부터 말씀드려야 합니다. 새 스레드는 Thread() 생성자로 만들어서 내부적으로 run()을 구현하던지, Thread(Runnable runnable) 생성자로 만들어서 Runnable 인터페이스를 구현한 객체를 생성하여 전달하던지 둘 중 하나의 방법으로 생성하게 됩니다. 후자에서 사용하는 것이 Runnable로 스레드의 run() 메서드를 분리한 것입니다. 따라서 Runnable 인터페이스는 run() 추상 메서드를 가지고 있으므로 상속받은 클래스는 run()코드를 반드시 구현해야 합니다. 앞서 언급한대로 Message가 int나 Object같이 스레드 간 통신할 내용을 담는다면, Runnable은 실행할 run() 메서드와 그 내부에서 실행될 코드를 담는다는 차이점이 있습니다.

HandlerThread

Looper에서 언급했듯이 안드로이드의 스레드는 Java의 스레드를 사용하기 때문에 안드로이드에서 도입한 Looper를 기본으로 가지지 않는다는 불편함이 있습니다. 이 같은 불편함을 개선하기 위해 생성할 때 Looper를 자동으로 보유한 클래스를 제공하는데, 이것이 바로 HandlerThread입니다. HandlerThread는 일반적인 스레드를 확장한 클래스로 내부에 반복해서 루프를 도는 Looper를 가집니다. 자동으로 Looper 내부의 Message Queue도 생성되므로 이를 통해 스레드로 Message나 Runnable을 전달받을 수 있습니다.

AsyncTask

AsyncTask는 스레드나 메시지 루프 등의 작동 원리를 몰라도 하나의 클래스에서 UI작업과 backgrond 작업을 쉽게 할 수 있도록 안드로이드에서 제공하는 클래스입니다. 캡슐화가 잘 되어 있기 때문에 사용시 코드 가독성이 증대되는 장점이 있으며, 태스크 스케쥴을 관리할 수 있는 콜백 메서드를 제공하고, 필요할 때 쉽게 UI 갱신도 가능하며 작업 취소도 쉽습니다. 따라서 리스트에 보여주기 위한 데이터 다운로드 등 UI와 관련된 독립된 작업을 실행할 경우 AsyncTask로 간단하게 구현할 수 있습니다.

그림: AsyncTask의 구조

그러나 AsyncTask를 사용해서 스케줄링 할 수 있는 작업 수의 제한이 있고, 몇 초 정도의 짧은 작업에서만 이상적으로 동작한다는 한계가 있습니다. 또한, 안드로이드의 버전 별로 병렬 처리 동작이 다르므로 허니콤 이후 버전에서 멀티 스레드로 병렬적인 동작을 원한다면 AsyncTask를 실행할 때 AsyncTask.THREAD_POOL_EXECUTOR 스케줄러를 지정해야 합니다. 한편 앞서 살펴본 Handler와 Looper를 사용한다면 작동 원리를 고려해야 하며 구현을 직접 해야 하고 코드가 복잡해져서 가독성을 저해한다는 단점이 있지만 그만큼 개발 범위가 자유롭습니다. 또한 UI 스레드에서만 작업하지 않아도 되므로 보다 많은 자율성을 가지고 코드를 제어하기를 원한다면 Handler나 HandlerThread 사용을 고려해 보세요.

도움된 사이트

제목에 링크된 안드로이드 개발자 사이트를 정독하면 해당 클래스나 인터페이스의 개념을 정밀하게 이해하고 사용법을 익힐 수 있습니다. 그 밖에도 다음 사이트들이 크게 도움됐습니다.


블로그 이미지

kuku_dass

,

출처 : http://javacan.tistory.com/entry/Android-Baisc-Resolution-and-DP


안드로이드를 좀 더 잘 해 보기 위해 기초를 다지고 있는데, 그 중 첫 번째로 공부하고 있는 부분이 해상도 및 레이아웃과 관련된 내용이다. 


안드로이드의 주요 단위


안드로이드 기기들이 해상도와 물리적인 크기가 저마다 다르기 때문에, UI 레이아웃을 기기별로 깨지지 않게 만들어주려면 주요 단위에 대한 이해가 필요하다. 다음은 안드로이드 개발시 알아야 하는 용어/단위를 정리한 것이다.


용어 및 단위

설명 

Pixel

화면상의 픽셀 

해상도(Resolution)

픽셀 단위의 화면 크기. 예를 들어, 갤럭시노트 10.1의 해상도는 1280*800인데, 이는 픽셀이 1280개 및 800개임을 의미한다.

DPI (Dots Per Inch) / 밀도

물리적인 1 인치 당 포함되는 픽셀 개수. 예를 들어, 160 DPI는 1인당 픽셀이 160개 포함된다는 것을 의미한다. 주요 DPI는 다음과 같다.

- LDPI (low) : 120 DPI

- MDPI (medium) : 160 DPI

- TVDPI : 213 DPI

- HDPI (high) : 240 DPI

- XHDPI (extra high) : 320 DPI

스크린 크기

물리적인 크기의 종류를 나타낸다. 다음의 4종류가 존재한다.

- X-Large: 주로 10.1 인치 이상의 디바이스

- Large: 주로 5인치 이상의 디바이스

- Normal: 3인치에서 5인치 미만의 사이의 디바이스

- Small: 3인치 미만의 디바이스

 px

픽셀 기반의 단위 

 dip (density-independent pixels) 또는 dp

밀도 독립 단위로, 장치의 밀도에 상관없이 물리적으로 (거의) 동일한 크기를 갖는다. 

 sp (scale-independent pixels)

스케일 독립 픽셀 단위로 , dip와 유사하며, 글꼴 크기를 지정할 때 주로 사용된다.


실제 테스트 해 볼 수 있는 기기별로 확인해보니 주요 값은 다음과 같았다.


 

 갤럭시노트 10.1

옵LTE 2 

넥서스7 

옵Q 

해상도 (픽셀단위)

800 x 1280 

720 x 1280 

800 x 1280 

480 x 800 

해상도 (DP 단위)

800 x 1280 

360 x 640

600 x 961 

320 x 533 

DPI

160 DPI (mdpi)

320 DPI (xhdpi)

213 DPI 
(tvdip, hdip) 
240 DPI 

스크린 크기

xlarge 

normal 

large 

normal 

밀도 비율

(DPI / 160)

1.331250

1.5


안드로이드의 기준 DPI는 중간 수준인 160 DPI이다. 160 DPI를 기준으로 DPI가 크면 밀도가 높아지고, DPI가 작으면 밀도가 낮아진다. 또한, 160 DPI인 경우 밀도독립 단위인 DP(DIP)와 픽셀이 같은 크기를 갖는다. 즉, 160 DPI에서 1 DP는 1 PX이 된다.


PX와 DP


옵LTE2와 넥서스7 그리고 갤럭시노트 10.1에서 트위터를 실행해보면, 기기의 크기는 다르지만, 상단 바 부분의  물리적 높이가 동일한 것을 확인할 수 있다. 또한, 글자 크기도 동일한 것을 확인할 수 있다.


[옵티머스LTE2(좌)와 넥서스7(우)에서 트위터를 실행한 화면. 상단 바와 메뉴의 높이가 (거의) 같다]


위 그림에서 두 기기의 높이 해상도는 1280이지만, 물리적인 크기는 넥서스7이 더 크다. 따라서, 위 그림에서 실제 px 단위의 높이 값은 좌측의 옵티머스LTE2가 넥서스7보다 커야 위와 같이 물리적으로 동일한 크기로 표시된다. 모든 기기마다 물리적으로 동일한 높이를 갖는 px 값을 구해서 계산한다는 것은 매우 힘든데, dp 단위를 사용하면 위 그림처럼 기기의 크기에 상관없이 물리적으로 동일한 크기로 레이아웃을 구성할 수 있다.


XML 레이아웃 설정 파일에서 dp 단위로 크기를 지정하면, 안드로이드는 내부적으로 알맞은 px 단위로 값을 변환해서 크기를 구성한다. 따라서, 개발자는 dp 단위를 사용해서 물리적으로 동일한 크기를 갖는 레이아웃을 구성할 수 있다.


코드에서 직접 크기를 설정하는 경우에는 픽셀 단위로 지정하게 되는데, 이 경우 다음의 공식을 이용해서 dp 단위의 값을 px 단위의 값으로 변환할 수 있다.


px = dp * (DPI / 160)


기기의 DPI 구하기


dp 단위의 값으로부터 px 단위의 값을 구하려면 기기의 DPI를 구해야 하는데, 다음의 코드를 이용하면 DPI를 구할 수 있다.


Display dis = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();

DisplayMetrics metrics = new DisplayMetrics();

dis.getMetrics(metrics);

// 해상도: dis.getWidth() * dis.getHeight() / metrics.widthPixels * metrics.heightPixels

// DPI: metrics.densityDpi

// 밀도비율 (DPI / 160) : metrics.density


참고자료

  • 기기별 DPI/해상도/크기 등: http://developer.android.com/tools/revisions/platforms.html
  • Supporting Multiple Screens: http://developer.android.com/guide/practices/screens_support.html



출처: http://javacan.tistory.com/entry/Android-Baisc-Resolution-and-DP [자바캔(Java Can Do IT)]

블로그 이미지

kuku_dass

,

http://www.nexpert.net/606


    글 싣는 순서
 1. SSO의 개요
 2. SAML 2.0 Deepdive (상)
 3. SAML 2.0 Deepdive (하) 

시작하며
SSO에 대한 기본적인 개념을 바탕으로 SAML 2.0 프로토콜에 대해 살펴보겠습니다. SSO에 대한 관심이 많다보니 은근히 인기를 끄는 글입니다. 


SAML 2.0 의 개요
SAML 은 Security Assertion Markup Language 의 약어로 같은 네트워크 내의 인증과 권한에 관한 데이타를 서로 교환하기 위한 표준입니다. SAML을 이용하면 사용자를 인증을 위한 IdP 서버와 SP간에 안전하게 인증 정보를 교환할 수 있습니다. 요즘 많은 기업에서 SAML 2.0 기반의 SSO를 구현하는 이유는 다음과 같은 이점이 있기 때문입니다.  

  • 암호 피로 (Password fatigue) 감소 

    사용자가 여러 개의 비밀번호와 유저 네임의 쌍을 외워야 하는 정신적인 피로도를 의미하는 암호피로의 감소  

  • 인증 위임 
    다수의 Service Provider 와 단 하나의 IdP 간의 CoT를 생성하여 IdP로 인증 위임되므로 사용자는 단 한번의 로그인으로 다수의 Service Provider를 사용할 수 있음

  • 안전한 인증 정보의 보호 
    IdP, SP, 사용자 사이에서 교환되는 인증정보를 암호화 

  • 개인 생산성 향상 
    지속적으로 패스워드를 재입력하는 과정이 생략되고, 패스워드 재설정 및 복구에 따른 과정이 필요없음 


시스코의 UC 애플리케이션의 경우 SAML 2.0을 지원하는 대부분의 IdP와 연동이 가능하지만, 제조사에서 직접 테스트 IdP는 다음과 같습니다. 

  • Microsoft Active Directory Federation Services (ADFS) 2.0
  • Open Access Manager (OpenAM) 11.0
  • PingFederate 6.10.0.4 
  • F5 BIP-IP 11.6




SAML 기반의 SSO 주요 요소
SAML 의 동작 방식을 이해하기 위해서 먼저 몇가지 구성 요소를 살펴보겠습니다. . 



  • 클라이언트 
    웹브라우저 또는 단말 

  • Service Provider
    클라이언트가 접근하려는 애플리케이션 또는 서비스 (웹엑스 또는 재버)

  • Identity Provider(IdP)
    사용자 크리덴셜 (사용자명과 패스워드)을 인증하고 SAML Assertion을 발행 

  • SAML Assertion
    사용자 인증을 위해 IdP로 부터 SP로 전달되는 보안 정보 
    사용자명, 권한 등의 내용을 적은 XML 문서로 변조를 막기위해 전자서명됨 

  • SAML Request (인증 요청)
    Service Provider 가 생성하는 인증 요청으로 IdP로 인증 위임

  • Circle of Trust (CoT)
    하나의 IdP를 공유하는 Service Provider 들로 구성

  • Metadata
    SSO를 활성화하는 Service Provider 및 IdP 가 생성하는 XML 파일
    메타데이타의 교환으로 신뢰 관계 설립

  • Assertion Consumer Service (ACS) URL
    ACS URL은 IdP가 특정 URL로 최종 SAML 응답을 포스트하도록 요구한다.  


SAML SSO 은 Assertions, 프로토콜, 바인딩 그리고 프로파일의 특별한 결합입니다. 여러가지 Assertion은 프로토콜과 바인딩을 사용하는 애플리케이션과 사이트 사이에서 교환됩니다. 이 Assertion은 사이트간의 사용자들을 인증합니다.


SAML 2.0 에서 Trust Agreement 
SAML SSO는 IdP와 SP 간이 프로비져닝 과정에서 인증서와 메타데이타(metadata)를 교환하므로서 CoT (신뢰 체인, Circle of Trust)를 설립합니다. 이를 통해 Service Provider는 IdP의 사용자 정보를 신뢰합니다. 

SAML 2.0 에서 IdP와 Service Provider 간에 신뢰관계를 맺기 위해서는 각각의 메타데이타를 교환해야 합니다. 각각의 메타데이타를 생성하면 다음과 같은 XML문서로 나타납니다.  

 


Service Provider의 메타데이타는 <md:EntityDesciptor>로 시작하며, IdP의 메타데이타는 <md:IDPSSODescriptor>로 시작합니다. 실제 교환은 관리자에 의해 수동으로 (Copy & Paste)으로 전달되는 가장 확실한 방식을 주로 사용합니다. 

 

SAML 2.0 기반의 인증 과정 
IdP와 Service Providor 간에 메타데이타를 교환하여 신뢰 관계 설립한 후 SAML 2.0 은 다음과 같이 동작합니다. 


SAML 기반의 인증 과정은 다음과 같습니다.


  1. Access Resource (애플리케이션 접속)
    사용자가 웹브라우저에서 최초로 Service Provider 로 접속합니다. 아직 웹브라우저가 세션을 활성화한 상태는 아닙니다. 

  2. Redirect with SAML Authentication Request (인증 요청)
    Service Provider 는 인증요청(Authentication Request)를 생성하여 클라이언트로 전송합니다. 
    IdP URL은 SAML 메타데이타 교환 과정에서 사전 구성됩니다. SP는 iDP와 직접 통신하지는 않고, 사용자의 웹브라우저가 iDP로 인증 요청을 redirect 하도록 합니다. 

    인증 요청은 다음과 같이 보입니다. 

    HTTP/1.1 302 Found

    Location: https://pingsso.home.org:9031/idp/SSO.saml2?SAMLRequest=nZLNbtswEITveQqCd1m0pKoWYRlwYxQ1kDZK5OaQG02tYwISqXLJtH37kkra%2FBjwodflcPab3V2iGPqRr7076lv44QEdIb%2BGXiOfXmrqreZGoEKuxQDIneTt%2BusVz2aMj9Y4I01PL7abmmJWVCxnku07sYCqFAu2KGWVdaycV1AWRbnPPjJZlDkld2BRGV3TYEPJFtHDVqMT2oUSm%2BcJq5Ks2LGK5x84K%2B8p2QQ0pYWbfh2dG5Gn6aj0A6KZHc0AM2MfeACYp6ob07a9nsUEGSWfjZUwJazpQfQIsWEjENUj%2FKs0z1E%2BKd0F0%2FO5908i5F92uyZprtsdJWtEsJHu0mj0A9gW7KOS8P326oVXejkk4F94F0WRpyEBjmmkjdip6JXAEyldXSyjhE%2FDsq%2BWdJ5V%2FOWiq%2FeWy%2FSV4bP9yL8Fi%2B2mMb2Sv%2F%2FnFuK8B%2BHOq2NFdclhknJnhUYF2lHSNrH%2FjQ9DOCiwNT2ZA1n3vfl5aUG4sD5nPdDVU5K37CFQenrdqz8%3D&RelayState=s249030c0bda8e96a8086c92d0619e6446b270c463

    인증 요청을 아래와 같이 디코딩하였습니다.

    <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"

    ID="s249030c0bda8e96a8086c92d0619e6446b270c463"

    Version="2.0"

    IssueInstant="2013-09-19T09:35:06Z"

    Destination="https://pingsso.home.org:9031/idp/SSO.saml2"

    ForceAuthn="false"

    IsPassive="false"

    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"

    AssertionConsumerServiceURL="https://cucm-eu.home.org:8443/ssosp/saml/SSO/alias/cucm-eu.home.org">

      <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">cucm-eu.home.org</saml:Issuer>

      <samlp:NameIDPolicy xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"

    Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"

    SPNameQualifier="cucm-eu.home.org"

    AllowCreate="true"

    />

    </samlp:AuthnRequest>


  3. GET with SAML Authentication Request (인증 요청 획득)
    클라이언트는 인증 요청을 그대로 IdP로 전송합니다. 아직 사용자의 브라우저는 IdP와 세션이 활성화되지 않았습니다. 
     

  4. Challenge for Credential (로그인 요청) 
    IdP는 사용자 웹브라우저에게 로그인을 요청합니다. 인증방식은 사용자명과 패스워드, PKI 등 다양하게 사용할 수 있습니다. 

  5. Credential (로그인 성공)
    클라이언트는 요구되는 인증방식으로 인증을 시도합니다. 이과정에서 Service Provider는 개입하지 않습니다. IdP가 LDAP 서버로 인증을 다시 위힘할 경우에는 LDAP으로 경로가 증가하지만 여기서는 생략합니다. 

  6. Signed response in hidden HTML form 
    IdP는 SP를 위해 SAML response를 생성하여 사용자의 웹브라우저로 전송합니다. SAML Response는 SAML assertion이 포함됩니다. iDP는 웹브라우저에  Session cookie (세션 쿠키)를 설정하고, 이정보는 웹브라우저에 캐싱됩니다.    

  7. POTS signed response
    클라이언트는 Service Provider 의 ACS URL에 Assertion을 포스팅합니다. 

  8. Supply Resoures (자원 접속)
    SP는 POST로 부터 SAML assertion을 추출합니다. 

 

  

마치며
SAML 2.0 Flow에 있는 프로토콜 절차는 IdP-initiated SSO일까요? SP-intiated SSO일까요? 답을 안다면 SAML 에 대한 기본적인 이해는 된 것입니다. 다음 글에서는 세션 쿠키에 대해 좀 더 자세하게 살펴보겠습니다. 나중에 개인적으로 여유가 된다면 SMAL 2.0을 CUCM에서 활성화하는 과정을 데모로 정리해보곘습니다.   



참조자료
Cisco.com : SAML SSO Deployment Guide for Cisco UC Applications 11.0  
위키피디아 : SAML 2.0
OASIS : SAML 2.0 Technical Overview 



'Web' 카테고리의 다른 글

Single Sign On (SSO)의 이해 - 1. SSO의 개요  (0) 2017.03.13
SSO 인증방식.  (0) 2017.03.13
쿠키(Cookie) 와 세션(Session) 개념  (0) 2017.03.13
bluebird 모듈의 promisify 조건은??  (0) 2017.01.11
Promise.promisify  (0) 2017.01.06
블로그 이미지

kuku_dass

,

http://www.nexpert.net/605


   글 싣는 순서
 1. SSO의 개요
 2. SAML 2.0 Deepdive (상)
 3. SAML 2.0 Deepdive (하) 



시작하며
몇 년전부터 SSO를 이용한 인증 프로세스를 도입하는 기업이 늘어나고 있습니다. SSO는 사용자들이 직접 접속하는 애플리케이션을 다루는 엔지니어들에게는 익숙하지만, 네트워크나 협업 솔루션 엔지니어에게는 생소한 단어입니다. 협업 솔루션 구축 시 SSO와 연동을 요구하는 기업이 점차 늘어나고 있으므로 엔지니어들이 SSO 연동 정도는 손쉽게 할 수 있어야 하는 시대가 되었습니다.  

이번 연재는 SSO에 대한 전반적인 기술적인 이해를 시작으로 시스코 협업 솔루션에서 어떻게 구현하였지까지 다룰 수 있도록 하겠습니다.  


SSO의 개요 
사람들이 자주 혼동하는 인증과 권한에 대해 살펴보기 위해 아래 그림을 참조 하겠습니다. 해외여행을 가면  호텔에서 체크인(Check-in)을 합니다. 리센셥의 안내원은 투숙객에게 여권(패스포트)를 요구합니다. 안내원은 여권의 사진과 당신의 얼굴을 비교하여 동일인임을 확인하고 호텔방키를 줍니다. 이제 당신은 호텔방키를 이용하여 자신의 호텔방에도 들어가고, 호텔 수영장이나 헬쓰장도 이용합니다.  즉, 체크인 이후부터는 호텔 직원들이 호텔방키를 확인하지 여권을 확인하지 않습니다.   



기술적으로 정리해 보면, 여권을 이용하여 인증을 진행하고, 호텔방키를 이용하여 권한이 부여된 것입니다.여권을 사용자명과 패스워드 조합으로 보고 호텔방키를 권한 토큰 (Authorization Token)이라 한다면, 우리는 매우 편리하게 여러 자원을 이용할 수 있습니다.  

호텔에서 단 한번의 체크인으로 호텔 내의 모든 서비스를 이용한다는 개념을 생각해 보면 사용자 편의성과 보안 측면에서 효율성을 생각할 수 있습니다. SSO를  이해하기 어려운 순간에는 호텔의 체크인 개념을 떠올리시길 바랍니다. 


SSO의 정의 
SSO는 Single Sign On의 약어로 사용자가 단 한번의 인증 절차만으로 다수의 애플리케이션에 접속할 수 있도록 해주는 인증 프로세스입니다. 정해진 시간 동안 여러 애플리케이션을 이동하더라도 사용자는 추가적인 인증을 요구 받지 않아 사용자 편의성을 증대시키고, 애플리케이션마다 인증절차를 거치지 않으므로 다수의 패스워드를 기억필요가 없으므로 보안을 강화합니다. 

SSO를 기술적으로 자세히 이해하기 위해 프레임워크를 살펴보겠습니다. 

SSO를 구현하기 위해서는 세 가지 컴포넌트가 유기적으로 동작해야 합니다.

  • RP (Relying Party) : 서비스
    사용자가 접근하려는 서비스 또는 애플리케이션으로 SP(service provider) 라고도 한다. 

  • IdP (Identity Provider) : 인증 대행
    사용자 인증을 대행한 후 이름, 역할, 주소 등의 속성과 같은 정보 (Token)를 할당한다. 


사용자는 RP에 접근할 때마다 사용자 인증을 수행하는 것이 아니라 IdP와 사용자 인증을 수행한 후 받은 토큰을 이용하여 RP에 접속합니다. 


SP initiated  SSO와 IdP initiated  SSO
SSO 를 구현하는 기본적인 방식은 두 가지입니다. 비슷하지만, 인증 요청의 시작을 어디서 하느냐의 차이입니다. 


  • IdP-Initiated SSO (Unsolicited WebSSO)
    인증 요청이 IdP에서 시작되는 페더레이션 프로세스입니다. 사용자는 IdP로 직접 로그인을 시도 한 후에 SP에 접속하을 시도하면 SAML Response을 IdP로 부터 받아서 SP의 ACS URL에 포스팅하여 접속합니다.    


  • SP-intiated SSO
    인증 요청이 SP에서 시작되는 페더레이션 프로세스입니다. 사용자는 SP에 접속하면 자동으로 인증과정이 IdP 로 리다이렉트됩니다. 사용자는 IdP가 보내는 인증 양식에 따라 로그온을 한 후 SAML Response를 
    SP의 ACS URL에 포스팅하여 접속합니다.   



SSO를 구현을 위한 프로토콜
SSO 구현할 때 일반적으로 사용하는 프로토콜은 두 가지 입니다. 

  • SAML (Security Assertion Markup Language)
    보안 도메인 간에 인증과 권한 데이타를 교환하기 위해 만들어진 프로토콜입니다. SAML 2.0은 iDP와 SP 사이에 사용자에 대한 권한 정보가 들어 있는 보안 토큰을 이용하여 보안정보를 교환하는 XML 기반의 표준 프로토콜입니다. 

  • OATH 
    권한 부여를 위한 표준 프로토콜입니다. 일반적으로 패스워드를 드러내지 않고 마이크로소프트, 구글, 페이스북 또는 트위터 계정을 이용하여 다른 웹사이트에 로그온 하려고 할때 주로 사용합니다. 

기업에서 사용하는 애플리케이션에 대한 SSO를 구현하기 위해서는 SAML을 주로 사용합니다. 시스코의 협업 애플리케이션은 SAML 2.0을 지원합니다. 


마치며
지금까지 SSO의 개요와 주요 컴포넌트에 대해 살펴보았습니다. 다음 글에서는 SAML을 기준으로 어떻게 동작하는 지를 자세히 살펴보겠습니다. 
 



라인하 유씨누스 (CCIEV #18487)  _________________________________________________________
ucwana@gmail.com (라인하트의 구글 이메일) 
http://twitter.com/nexpertnet (넥스퍼트 블로그의 트위터, 최신 업데이트 정보 및 공지 사항) 
http://groups.google.com/group/cciev (시스코 UC를 공부하는 사람들이 모인 구글 구룹스) 
http://groups.google.com/group/ucforum (UC를 공부하는 사람들이 모인 구글 구룹스) 
세상을 이롭게 하는 기술을 지향합니다. ________________________________________________________

'Web' 카테고리의 다른 글

Single Sign On (SSO)의 이해 - 2. SAML 2.0 Deepdive (상)  (0) 2017.03.13
SSO 인증방식.  (0) 2017.03.13
쿠키(Cookie) 와 세션(Session) 개념  (0) 2017.03.13
bluebird 모듈의 promisify 조건은??  (0) 2017.01.11
Promise.promisify  (0) 2017.01.06
블로그 이미지

kuku_dass

,

SSO 인증방식.

Web 2017. 3. 13. 14:54

출처 : http://mercurii.tistory.com/227


SSO의 종류

요약 :

SSO 에이전트가 대행해주는 Delegation(인증 대행) 방식과

SSO 시스템과 신뢰관계를 토대로 사용자를 인증한 사실을 전달받아 SSO를 구현하는 Propagation(인증정보 전달) 방식으로 구분된다.

 propagation 방식중 같은 도메인내에 있다면 one cookie domain sso를 이용하면 손쉽게 sso를 구현할수 있고 이때는 보안을 위해 128bit암호화 알고리즘이나 유효시간 제한등의 방법을 사용한다.


내용 :

일반적으로 사용되는 SSO 시스템은 두 가지 모델로 구분된다. SSO 대상 애플리케이션에서 사용되는 사용자 인증 방법을 별도의 SSO 에이전트가 대행해주는 Delegation(인증 대행) 방식과 SSO 시스템과 신뢰관계를 토대로 사용자를 인증한 사실을 전달받아 SSO를 구현하는 Propagation(인증정보 전달) 방식으로 구분된다.

<그림 1> SSO Delegation Model

△Delegation 방식: 대상 애플리케이션의 인증 방식을 변경하기 어려울 때 많이 사용된다. 대상 애플리케이션의 인증 방식을 전혀 변경하지 않고, 사용자의 대상 애플리케이션 인증 정보를 에이전트가 관리해 사용자 대신 로그온 해주는 방식이다. 즉 Target Server 1을 로그온 할 때 User1이 alice/alice라는 ID/ PWD가 필요하다면, 에이전트가 이 정보를 가지고 있고, User1이 Target Service 1에 접근할 때 에이전트가 대신 alice/alice ID/PWD 정보를 전달해서 로그온 시켜준다. △Propagation 방식: 통합 인증을 수행하는 곳에서 인증을 받아 대상 애플리케이션으로 전달할 토큰(Token)을 발급 받는다. 대상 애플리케이션에 사용자가 접근할 때 토큰을 자동으로 전달해 대상 애플리케이션이 사용자를 확인할 수 있도록 하는 방식이다. 웹 환경에서는 쿠키(Cookie)라는 기술을 이용해 토큰을 자동으로 대상 애플리케이션에 전달할 수 있다. 이러한 웹 환경의 이점으로 웹 환경에서의 SSO는 대부분 이 모델을 채택하고 있다.

<그림 2> SSO Propagation Model

△Delegation & Propagation 방식: 웹 환경이라고 하더라도 Propagation 방식이 모두 적용될 수는 없다. 특히 웹 애플리케이션의 변경이 전혀 불가능하고 사용자 통합이 어려운 경우 Delegation 방식을 사용하게 된다. 또한 대상 애플리케이션들이 많이 있고 애플리케이션의 특성들이 다양한 경우 각 애플리케이션에 Delegation 방식과 Propagation 방식을 혼용해서 전체 시스템의 SSO을 구성한다.

△Web 기반 One Cookie Domain SSO: SSO 대상 서비스와 응용 애플리케이션들이 하나의 Cookie Domain안에 존재할 때 사용된다. 일반적인 기업 내부의 컴퓨팅 환경이다. 통합인증을 받은 사용자는 토큰을 발급받게 되고, 이 토큰은 Cookie Domain에 Cookie로 설정되어 Cookie Domain 내의 다른 서비스로 접근할 때 자동으로 토큰을 서비스에 제공하게 된다. 서비스에서 동작되는 SSO 에이전트는 토큰으로부터 사용자 신원을 확인하고 요청된 자원에 대한 접근을 허가 해준다.

Cookie를 통한 SSO구현시 Cookie 보안 방법

토큰은 쿠키를 통해 전달되므로 외부에 노출되는 정보이다. 완벽한 보안을 위해서는 토큰이 네트워크에서 노출되어서는 안되지만, 비용 및 관리상의 이유로 허용되고 있다. 하지만 토큰을 통해 토큰이 포함하고 있는 정보까지 외부에 노출하는 것은 심각한 결함을 제공한다. 토큰의 네트워크 구간에서의 정보 노출 및 위·변조를 방지하기 위해 다음과 같은 보안기술이 사용된다.

△Data Confidentiality: 토큰은 주요 암호 알고리즘(AES, SEED)과 128bit 이상의 키로 암호화돼 보호되어야 한다. △Data Integrity: 토큰은 MAC

(Message Authentication Code) 등을 포함해 데이터의 무결성을 보장해야 한다. △Replay Attack Protection: 토큰은 사용자와 대상 애플리케이션 사이에 전달되는 인증 정보이다. 일반적으로 토큰은 네트워크에 노출되며, 노출된 토큰을 사용해 다른 사용자가 인증을 받고 들어올 수 있다(Replay Attack). 특히 웹 환경에서 이러한 문제점이 중요한 이슈로 등장하고 있다. 이러한 문제점을 근본적으로 해결하기 위해서는 토큰을 네트워크에 노출시키지 않아야 한다.
토큰을 네트워크에 노출시키지 않기 위해서는 항상 사용자와 대상 애플리케이션 사이에 암호 채널을 형성해야 하며, 이 채널을 통해 토큰을 전달해야 한다. 그러나 SSL과 같은 채널 암호를 사용하는 데에는 매우 많은 비용이 요구되어 실제로 많이 사용되고 있지는 않다.
SSL과 같은 암호채널을 사용하지 않으면서 Replay Attack이 발생할 수 있는 상황을 줄일 수 있도록 다음과 같은 보안 기술들이 사용된다.

△사용자 주소 제한: 토큰이 발행될 때 접속한 사용자 주소 정보를 토큰 내부나 토큰을 발행한 서버에서 기억함으르써 Token이 제출된 사용자 주소와 최초 발행시 기억된 주소를 비교하여 접속한 곳 이외에서의 접속을 제한할 수 있다. 사용자 주소가 업무진행 중에 자주 변경되지 않는 시스템일 경우 유효하다. 예를 들어 회사내의 인터넷 환경일 경우 사용될 수 있다. 인터넷 환경의 경우에는 사용자 주소가 특정 범위에서 자주 바뀔 수 있는 환경도 있기 때문에 불특정 다수를 위한 일반적인 인터넷 서비스에 사용하기에는 부적합하다.

△유효시간 제한: 토큰의 유효시간을 매우 짧게 줌으로써 Replay Attack에 사용될 수 있는 시간을 제한한다. 유효시간내에 임계시간(예: 유효시간의 1/2)을 넘으면 자동으로 토큰을 재 발행하여 사용자는 의식하지 못하고 서비스를 계속 사용하게 한다. 지금까지 사용자가 각 애플리케이션별로 별도의 인증을 받지 않고, 한번의 통합 인증만으로 각 애플리케이션들을 사용할 수 있도록 하는 SSO 기술에 대해 살펴보았다. 대상 애플리케이션의 수정을 최소화할 수 있는 Delegation 모델과 토큰을 생성해 통합 인증 서비스를 제공하는 Propagation 모델 그리고 양자의 장점을 조합한 Delegation & Propagation 방식을 이해한다면 현재 도입 대상 기업의 IT 인프라와 진행중인 서비스 및 애플리케이션의 특성에 적합한 EAM 시스템 도입을 결정하는데 도움이 될 것이다.

 

 URL암호화를 통한 SSO구현

가장 큰 분류로는 아마도 sso 해야할 사이트가 같은 도메인 그룹인지 여부를 파악해야 할것 같네요.
예를 들어서 aaa.xxx.combbb.xxx.com 은 둘다 xxx.com 도메인 그룹안에 포함되어 있기 때문에 이 경우 쿠키를 이용한 sso가 가능합니다.
이 같은 도메인 그룹의 경우 굳이 세션을 이용하지 않고 쿠키를 이용하는 이유는 aaa.xxx.com은 PHP로 제작된 사이트 bbb.xxx.com은 ASP로제작된 사이트..즉 각 서버의 플랫폼이 틀리다면 세션 공유가 안되는것은 잘 아실겁니다. 그래서 플랫폼에 상관없이 클라이언트에 의존되는 쿠키를 이용하게 됩니다.

반면에 서로 도메인이 틀리다면 어쩔수 없이 url로 암호화 시켜서 이리저리 주고 받으면서 sso인증 시켜줘야 합니다.

쿠키를 이용한 방법이나 url로 이용한 방법이나 서로 원리는 똑같습니다. 그래서 그나마 설명하기 쉬운 url을 이용한 방법으로 말씀드리겠습니다. 아무래도 예제를드는것이 간단할듯 -_-...

A사이트 : test.com
B사이트 : abcd.com

aaa란 유저가 A사이트에서 로그인후에 B사이트로 가게 되면 B사이트가 자동으로 로그인 되어있게 하는거잖아요.
(물론 aaa란 계정이A사이트에도 있고 B사이트에도 있어야겠지요)

이럴때 단순히 B사이이트로 자동 로그인 시키고자 한다면 아무래도 아이디/비밀번호가 필요할겁니다. 아래와 같이요

abcd.com/logijn.asp?id=aaa&pwd=1234

헌데 이 id=aaa&pwd=1234 이 부분이 url에 노출되면 상당히 위험하지요 그래서 이 부분을 암호화 시키는 겁니다. 제 홈피에 몇가지 암호화 하는 구문들이 있는데 대부분 URL 정보 노출시키고 싶지 않아서 암호화 시킨다고 적어둔 것들입니다.(제가적은것은 아니고 여기저기 돌아다니다가 -_-.. )

그래서 암호화 시키면 아래처럼 url 변하겠지요

abcd.com?/login.asp?암호된값=dpZe-==234s

요렇게 변할겁니다..(대충 적은것임 -_-) 요 login.asp 페이지에서는 아래와 같이

암호된값 = Request("암호된값")
디코딩된값 = 디코딩(암호된값)    ' 암호된값을 디코딩 함수에 넣어서 암호된값을 id=aaa&pwd=1234 형식으로 보이도록 한다.

id=aaa&pwd=1234 이 값들 보이면 다 끝난거지요..요것 그냥 split로 이리저리 분할하면 id는 aaa , pwd=1234란 값 쉽게 취할수 있습니다.


음..대충 이해가 가셨는지..위에서 설명한 암호화 함수는 그냥 일반 하나의 함수처럼 얘기했는지 제 홈피에 있는 암호화 함수는 암호할때/풀때 어떤 키값으로 풀어야 합니다. 즉 암호할때 a란 키값을 통해서 암호 시켰다면 풀때도 a란 키값으로 암호를 풀어야 합니다. 좀 더 보안상 좋지요..

암호화 방식도 좋지만 ID/pass 가 노출된다는 단점이 있기때문에, 서버가 다를경우 인증키를 생성하는 function 을
만들어서 각 서버에서 생성된 인증키를 비교하는 방식도 괜찮은 방식이겠죠.

쿠키를 이용한 방법은 잠깐 설명드리자만 aaa.com사이트에 로그인 하면 쿠키를 던져둡니다. 던진 값은 2개 입니다 하나는 키값 다른 하다는 암호화된 값 그래서 abcd.com 으로 접속하면 일단  그 abcd.com의 login.asp 페이지에서 던져진 쿠키가 있는지 검사합니다. 그래서 있다면 그 던져진 값들을 이용해서 암호푸는 겁니다. 그리곤 로그인 시키는 거지요. 



참고 URL


정리하면
SSO 는 Delegation 방식(솔루션 존재.)과 Propagation 방식(직접구현)이 있고.
Propagation 방식에는 Cookie 를 이용하는 방법과 URL 인증 방법 이 있겠다.
대부분의 SSO 는 Propagation 방식을 많이 사용하는듯하다!!

블로그 이미지

kuku_dass

,

출처 : http://hoonihoon.tistory.com/entry/%EC%BF%A0%ED%82%A4Cookie-%EC%99%80-%EC%84%B8%EC%85%98Session-%EA%B0%9C%EB%85%90



HTTP 개념

1. HTTP 는 비연결 구조로 사용자의 연결을 계속 유지 하지 않는 방식

2. 사용자의 요청에 대한 응답을 한 후 연결을 해제


아래글은 쿠키와 세션을 이해하기 쉽게 그림으로 설명 해 놓은 글입니다. 


로그인 정보나 장바구니 정보 같은 클라이언트 기반 정보들은 어떻게 저장할까?

다음 그림을 보자.

HTTP는 연결 보장을 하지 않으므로 또 다시 요청이 들어 온다면 서버는 해당 브라우저의 세션정보를 찾아줘야 할 것이다.

그런데 서버입장에서 생각해보면 서버에 요청해놓은 브라우저들이 엄청 많기에 세션정보를 어떻게 줄까?


그래서 자신의 세션 정보를 찾기 위해 자신의 세션을 증명하는 ID 가 필요하다.

보통의 사이트에서는 ID를 쿠키로 가지고 있게 된다. 

쿠키란 브라우저 상에서 저장되는 객체이다.

더쉽게 말하면 쿠키는 사용자 컴퓨터의 하드디스크에 저장되는 작은 텍스트 파일이며 세션은 서버의 메모리상에 존재하는 객체이다.

사용자 컴퓨터에서 세션정보처럼 많은 정보를 저장하기 힘든 관계로 많은 정보는 세션에 저장하고 그 세션 아이디만 web browser(사용자pc) 에 쿠키 형태로 저장을 해두는 것이다.


이런식으로 하면 세션아이디로 로그인 정보를 꺼내 줄 수 있다.

그럼 한번 이 세션아이디로 정말 로그인이 가능한지 테스트 해볼까?



일단 쿠키를 수정하기 위해 크롬 ad-on "Edit This Cookie" 를 설치한다.



원하는 사이트에 로그인 한 후 Edit This Cookie 를 실행해 보면



앵간하면 SESSION ID 라고 써있는 쿠키를 발견할 수 있을 것이다.


SESSION ID 라고 써있는 쿠키를 발견했다면 그 값을 잠시 메모장에 저장해 둔다.


그러고선 SESSION ID 를 지워보자



브라우저를 리플래쉬하면??




당연히 세션ID를 잃어버렸으니까 서버에서 자기 세션을 찾지 못하므로 서버는 이놈이 로그인 안된줄 알고 로그인 하라는 메시지를 보낼 것이다.


그렇다면 다시 아까 메모장에 저장해둔 세션 아이디를 입력하고 리프래쉬 해보자!


어떻게 되는가?




멀쩡하게 로그인 한것과 같은 화면이 되버린다.


아직 서버 안에 세션객체가 죽지 않았기 때문에


알맞는 세션 아이디만 있으면 다시 그 세션(로그인 정보와 장바구니 정보)를 물려줄 것 이다.



이쯤되면 아~ 로그아웃을 꼭 누르고 나와야 겠구나 할것이다.


왠만한 사이트의 로그아웃 기능은 쿠키의 세션아이디를 지워버리는 역할을 한다.


만약 피씨방 같은곳에서 로그아웃을 하지 않고 브라우저 창만 닫고 나온다면?


 


피씨에 저장된 쿠키의 세션 아이디값을 읽어다가 


브라우저에 쿠키를 설정하고 해당 사이트에 들어가면 그대로 로그인이 되버리므로 조심 조심


공공장소에서는 항상 로그아웃버튼을 누르고 나오시길!



[추가정리]
1. 쿠키
- 서버에 접속시 접속한 클라이언트 정보를 클라이언트에 저장한다.
-이후에 서버로 전송되는 요청에는 쿠키가 가지고 있는 정보가 같이 포함되어서 전송
-크기는 4096 Byte 이하
-클라이언트에 총 300개 까지 쿠키를 저장할 수 있다.
-하나의 도메인 당 20개의 값만 가질수 있다.
-쿠키는 이름,값,유효기간,도메인,경로 등으로 구성

사용예)
방문했던 사이트에 다시 방문 하였을 때 아이디와 비밀번호 자동 입력
팝업에서 오늘 이창을 다시 보지 않음체크

2. 세션
-웹서버 쪽 웹 컨테이너에 상태를 유지하기 위한 정보를 저장
-저장 데이터에 제한이 없음
-웹서버는 각각의 웹브라우저로부터 발생한 요청에 대하여 특정한 식별자를 우여하여 같은 브라우저인지 구별
-세션쿠키라고도 한다.
-브라우저를 닫거나, 서버에서 이 이쿠키를 삭제 했을때만 삭제가 되므로 비교적 지속성이 쿠키보다 안전하다
- 각각의 클라이언트마다 고유의 ID를 부여
-세션 객체마다 저장해 둔 데이터를 이용하여 서로 다른 클라이언트의 요구에 맞게 서비스제공

차이점은 : 
저장되는 곳이 다르다는 것.
= (쿠키는 클라이언트, 세션은 서버)
저장형태 
=(쿠키 텍스트형식, 세션은 Object형으로 메모리에 저장)
만료기간도 다르다는 것
= (쿠키는 저장시 설정하는데 설정하지 않으면 브라우저 종료시 소멸, 세션은 정확한 시점을 알 수 없음)
자원
=(쿠키는 클라이언트자원사용, 세션은 서버의 자원 사용)
용량
=(쿠키는 한 도메인에 20개, 쿠키당 4KB, 총 300개, 세션은 서버가 허용하는 한 용량에 제한 없음)

출저: 

 http://jhoonslife.tistory.com/354

http://marcof.tistory.com/16

http://bllizz.tistory.com/15

'Web' 카테고리의 다른 글

Single Sign On (SSO)의 이해 - 1. SSO의 개요  (0) 2017.03.13
SSO 인증방식.  (0) 2017.03.13
bluebird 모듈의 promisify 조건은??  (0) 2017.01.11
Promise.promisify  (0) 2017.01.06
[Redux] 내가 만든 세미나 자료 파일 (리덕스정리)  (0) 2016.11.29
블로그 이미지

kuku_dass

,