Handler와 Message를 활용하여 콜백함수 구현하기

 안드로이드 프레임워크를 분석하던 도중 흥미로운 부분을 발견하게 되어 이를 소개하고자 합니다. 물론 이는 안드로이드의 Application 단계에서도 쉽게 구현될 수 있는 기능이기에 안드로이드 애프리케이션 제작에 어느 정도 경험이 있으신 분들이라면 쉽게 이해하실 수 있으시리라 생각합니다.


 이번에 다루고자 하는 핵심적인 개념은 바로 Callback 입니다. 그럼 여기서 Callback 이란 무엇인지 간단하게 설명하도록 하겠습니다.


Callback이란?

 일반적으로 우리들이 프로그래밍을 설계할 때 Method와 같은 함수를 구현합니다. 특히 API와 같이 원하는 기능이 미리 구현되어 있어 해당 함수를 호출하는 것으로  원하는 기능을 실행하기도 하지요. Callback 또한 일반적인 함수들과 비슷하게 구성되어 있습니다. Android의 경우 프로그래머가 구현하고자 하는 기능을 Listener Interface를 통해 Callback 기능을 등록해줍니다.


 Callback 함수가 일반 함수와 가장 큰 차이점으로 호출되는 시점에 있습니다. 일반적인 함수의 경우 프로세스가 해당 함수를 호출하면 호출되는 즉시 해당 기능을 수행하게 됩니다. 반면 Callback 함수의 경우 프로세서가 호출을 요청할 경우 일반 함수처럼 즉시 호출될 수도 있지만 프로세스의 동작과는 독립적으로 동작하는 것이 가능하여 해당 프로세스가 수행을 종료한 후에 Callback 함수를 실행시킬 수 있습니다.


 안드로이드 Framework에서 Runnable Interface를 통해 Callback 기능을 구현한 방법이 있어 해당 기능을 분석해 보았습니다. 특이하게도 안드로이드에서 지원하는 Looper와 Message를 활용해서 구현하였다는 점인데요 쉽게 설명을 드리자면 실행하고자 하는 함수를 Runnable Interface를 통해 구현한 후 이를 Mssage를 통해 해당 함수를 예약해 두었다가 이후 프로세스가 작업을 종료하게 되었을 때 해당 함수를 호출하는 방식입니다.


 자세한 구현 내용을 실제 소스코드를 통해 확인해보도록 합시다.



/framework/base/core/java/android/view/Choreographer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
public final class Choreographer {
 
    ....
    // Choreographer를 초기와 합니다. 인자로 Choreographer의 Looper를 넘겨줍니다.
    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            return new Choreographer(looper);
        }
    };
 
    ....
 
    private final Looper mLooper;
    private final FrameHandler mHandler;
 
    // The display event receiver can only be accessed by the looper thread to which
    // it is attached.  We take care to ensure that we post message to the looper
    // if appropriate when interacting with the display event receiver.
    private final FrameDisplayEventReceiver mDisplayEventReceiver;
 
    ....
 
    private Choreographer(Looper looper) {
    //Constructor를 통해 Looper를 받는다.
        mLooper = looper;
    //Constructor를 통하여 얻게 된 Looper를 FrameHadler에 등록한다.
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
 
        ....
 
        }
    }
 
    ....
 
}
 
 
//Callback 기능을 수행할 Hndler입니다.
//Callback 기능 구현시 handlerMessage(Message msg) 함수는 실행되지 않습니다.
private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }
 
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
}
 
//Callback 기능을 구현하고자 하는 부분입니다.
//Runnable Interface로 구현하고자 하는 Callback 함수를 run() 함수로 구성합니다.
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;
 
        public FrameDisplayEventReceiver(Looper looper) {
            super(looper);
        }
 
        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
 
        ....
 
            mTimestampNanos = timestampNanos;
            mFrame = frame;
 
            //Message를 통해 Callback 기능 구현
 
            //Callback 함수를 Message에 등록합니다.
            //mHandler    : Message를 받을 Looper를 갖고 있는 Handler
        //this    : 등록하고자 하는 함수. 해당 기능은 Runnable Interface를 통하여 run() 함수로 구성됨
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            //Message를 Handler에 전송합니다. Handler는 Message를 받은 후 현재 프로세스가 작업을
            // 종료하면 이후 해당 Callback 기능을 실행합니다.
            mHandler.sendMessageAtTime(msg, timestampNanos / NANOS_PER_MS);
        }
 
        //실행하고자 하는 Callback 함수
        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
}
cs


 위에서 구현된 함수를 이미지로 나타내면 다음과 같습니다.