byte[] 바이트 배열을 socket을 통해 쉽게 전송하는 방법

 C/C++을 통해 파일을 socket 통신으로 전송하는 경우 데이터를 char 배열을 통해 buffer의 크기를 고려하면서 전송을 해야 하기 때문에 프로그램을 설계할 때 상당히 많은 부분을 고려해야 되어 골치가 아프지요. 그러한 면에서 보았을 때 Java에서 제공하는 Socket 통신 기능들이 상당히 편해서 프로그래머들에게도 상당히 큰 부담을 줄여주는 점이 맘에 들곤 합니다. 이번 포스팅에서는 C/C++에서는 다루는 것이 다소 번거로운 byte 배열을 손쉽게 전송하는 방법에 대해 알아보도록 하겠습니다.


 안드로이드 프로그래밍을 하다 보면 이미지나 파일을 Socket을 통해 전송해야 되는 경우들이 많습니다. 만약 수신측이 C/C++로 짜여져 있으면 정해진 buffer로 나누어서 전송을 해야 하기 때문에 파일을 byte[] 배열 형식으로 변환한 후 socket에 실어서 전송해야 합니다. 프로그램을 설계할 때 도중에 자료가 누락되는 경우 원본의 손실 또한 발생하기 때문에 신중하게 프로그래밍을 해야 합니다.

 안드로이드 카메라의 경우 takePicture() 함수에 callback 함수를 통해 카메라에 찍힌 이미지를 아래와 같은 방식으로 byte[] 배열로 제공합니다. 프로그래머는 이를 통해 파일로 저장하거나 화면에 띄우는 등의 작업을 수행하게 됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
        private Camera.PictureCallback picb_remote = new Camera.PictureCallback() {
 
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                // TODO Auto-generated method stub
 
                ....
 
                camera.startPreview();
            }
        };
cs

 위의 이미지 형식의 데이터인 byte[] 배열을 Java 기반의 서버와 socket을 통해 어떤 방식으로 통신을 하면 가장 간편할까요? Java에서는 직렬화 된 Object 혹은 byte[] 배열을 손쉽게 전송할 수 있는 ObjectOutputStreamObjectInputStream을 제공합니다. 이를 사용하는 방식에 대해 좀 더 자세히 알아보겠습니다. 소스코드에서 각 중요한 내용을 주석을 통해 설명하였습니다.

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
        private Camera.PictureCallback picb_remote = new Camera.PictureCallback() {
 
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                // TODO Auto-generated method stub
                try {
                    //IP주소와 포트번호를 입력하여 Socket 통신을 시작합니다.
                    Socket sock = new Socket("127.0.0.1"8200);
                    //Socket으로부터 outputStream을 얻습니다.
                    OutputStream os = sock.getOutputStream();
                    //등록한 OutputStream을 ObjectOutputStream 방식으로 사용합니다.
                    ObjectOutputStream oos = new ObjectOutputStream(os);
 
                    //byte[] 파일을 object 방식으로 통째로 전송합니다.
                    oos.writeObject(data);
                
                    oos.close();
                    os.close();
                    sock.close();
                        
                } catch (UnknownHostException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
 
                camera.startPreview();
            }
        };
cs

 위의 소스코드에서 보이는 바와 같이 생성된 byte[] 배열을 그대로 writeObject() 매서드의 인자값에 등록을 하면 Java에서는 이를 그대로 Server 쪽으로 전송해줍니다. 아래는 byte[]를  수신하는 Server 측의 소스코드입니다. 중요한 부분은 주석으로 설명합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
            int port = 8200;
            //Server측에서 사용할 포트번호를 설정한 후 Socket 서버를 개방합니다.
            ServerSocket sock = new ServerSocket(port);
            //Client로부터 소켓 신호를 기다립니다.
            Socket socket = sock.accept();
            //Socket로부터 InputStream을 등록합니다.
            InputStream is = socket.getInputStream();
            //등록한 InputStream을 ObjectInputStream방식으로 사용합니다.
            final ObjectInputStream ois = new ObjectInputStream(is);
            
            //전송된 byte[] 데이터를 수신합니다.
            byte[] data = (byte[])ois.readObject();
            
            System.out.println("data size : " + data.length);
            
            ois.close();
            is.close();
            socket.close();
            sock.close();
cs


 위와 같은 방식으로 Socket 프로그램을 설계하시면 byte[] 배열 방식으로 되어있는 데이터 값을 Java 상에서 손쉽게 전송할 수 있습니다.

300x250

IP v4 주소 0.0.0.0의 의미

공대생의 팁 2015. 9. 27. 23:42

 IP주소는 여러분들이 흔히 아시듯이 인터넷에 접속하기 위해 사용되는 자신의 기기에 할당되는 일종의 주소와 같은 역할을 합니다. 이 중 몇몇 IP주소의 경우 특수 용도로 쓰입니다.


 가장 대표적인 것이 0.0.0.0과 255.255.255.255입니다. 255.255.255.255는 한정된 범위 내에 접속된 모든 기기에 패킷을 보내는 용도로 사용됩니다. 그렇다면 0.0.0.0은 어떤 용도로 사용되는 것일까요?



 IP주소 0.0.0.0은 IPv4 패킷을 전송하고자 하는 컴퓨터가 자신의 IP주소를 모르는 경우 통신을 하기 위해 사용됩니다. 보통 자신의 IP주소를 모르는 컴퓨터는 부트스트랩(컴퓨터의 전원을 킬 때나 재부팅할 때)이 진행되는 도중에 위 주소를 사용합니다.


 이 신호를 보낸 컴퓨터는 자신의 주소를 알기 위해 이 주소를 발신지 주소로 설정하고 목적지 주소로 255.255.255.255로 설정한 IP 패킷을 DHCP서버로 전송합니다. DHCP서버는 신호를 받은 후 해당 PC에 IP 주소를 알려주며 PC는 해당 주소를 자신의 IP 주소로 사용합니다.


보다 더 자세한 내용을 알고 싶으신 분은 아래 링크를 참조해 주시길 바랍니다.

http://www.netmanias.com/ko/post/blog/5348/dhcp-ip-allocation/understanding-the-basic-operations-of-dhcp


300x250

Android에서 VSync 동작 원리 및 초기화 과정(5)

안드로이드/프레임워크 2015. 9. 20. 17:37

 이전 포스팅을 통해 안드로이드의 VSync( Vertical Synchronization, 수직동기화)가 초기화 되어가는 과정을 차근차근 살펴보았습니다. 이번 시간에는 C++ 단계에서 Java Framework 단계로 VSync가 동작하는 과정을 살펴보도록 하겠습니다. 본 포스팅의 이해를 위해 이전에 작성해 왔던 포스팅들을 참조해 주시길 바랍니다.


Android에서 VSync 동작 원리 및 초기화 과정(3)

http://elecs.tistory.com/134


SurfaceFlinger에서 DispSyncSource의 초기화 과정`

http://elecs.tistory.com/142


/frameworks/native/services/surfaceflinger/DispSync.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
class DispSyncThread: public Thread {
 
....
 
    void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
        for (size_t i = 0; i < callbacks.size(); i++) {
            callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime);
        }
    }
 
....
    
};
cs

 DispSyncSource를 통해 설정하였던 Callback 함수인 onDispSyncEvent()를 호출합니다.


/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

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
class DispSyncSource : public VSyncSource, private DispSync::Callback {
public:
    DispSyncSource(DispSync* dispSync, nsecs_t phaseOffset, bool traceVsync) :
            mValue(0),
            mPhaseOffset(phaseOffset),
            mTraceVsync(traceVsync),
            mDispSync(dispSync) {}
 
    virtual ~DispSyncSource() {}
 
....
 
    virtual void setCallback(const sp<VSyncSource::Callback>& callback) {
        Mutex::Autolock lock(mMutex);
        mCallback = callback;
    }
 
private:
    virtual void onDispSyncEvent(nsecs_t when) {
        sp<VSyncSource::Callback> callback;
        {
            Mutex::Autolock lock(mMutex);
            callback = mCallback;
 
            if (mTraceVsync) {
                mValue = (mValue + 1) % 2;
                ATRACE_INT("VSYNC", mValue);
            }
        }
 
        if (callback != NULL) {
            callback->onVSyncEvent(when);
        }
    }
 
....
 
    DispSync* mDispSync;
    sp<VSyncSource::Callback> mCallback;
    Mutex mMutex;
};
 
 
cs

등록된 Callback 함수 onVSyncEvent()를 통해 EventThread 가 실행됩니다.


/frameworks/native/services/surfaceflinger/EventThread.cpp

1
2
3
4
5
6
7
8
void EventThread::onVSyncEvent(nsecs_t timestamp) {
    Mutex::Autolock _l(mLock);
    mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
    mVSyncEvent[0].header.id = 0;
    mVSyncEvent[0].header.timestamp = timestamp;
    mVSyncEvent[0].vsync.count++;
    mCondition.broadcast();
}
cs

EventThread의 Condition 클래스를 통해 broadcast()가 호출되어 wait() 상태에 있던 동작이 다시 재개됩니다.


/frameworks/native/services/surfaceflinger/EventThread.cpp

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
// This will return when (1) a vsync event has been received, and (2) there was
// at least one connection interested in receiving it when we started waiting.
Vector< sp<EventThread::Connection> > EventThread::waitForEvent(
        DisplayEventReceiver::Event* event)
{
    Mutex::Autolock _l(mLock);
    Vector< sp<EventThread::Connection> > signalConnections;
 
    do {
 
    ....
 
        // find out connections waiting for events
        size_t count = mDisplayEventConnections.size();
        for (size_t i=0 ; i<count ; i++) {
            sp<Connection> connection(mDisplayEventConnections[i].promote());
            if (connection != NULL) {
                bool added = false;
                if (connection->count >= 0) {
                    // we need vsync events because at least
                    // one connection is waiting for it
                    waitForVSync = true;
                    if (timestamp) {
                        // we consume the event only if it's time
                        // (ie: we received a vsync event)
                        if (connection->count == 0) {
                            // fired this time around
                            connection->count = -1;
                            signalConnections.add(connection);
                            added = true;
                        } else if (connection->count == 1 ||
                                (vsyncCount % connection->count) == 0) {
                            // continuous event, and time to report it
                            signalConnections.add(connection);
                            added = true;
                        }
                    }
                }
 
                if (eventPending && !timestamp && !added) {
                    // we don't have a vsync event to process
                    // (timestamp==0), but we have some pending
                    // messages.
                    signalConnections.add(connection);
                }
            } else {
                // we couldn't promote this reference, the connection has
                // died, so clean-up!
                mDisplayEventConnections.removeAt(i);
                --i; --count;
            }
        }
 
        ....
 
        // note: !timestamp implies signalConnections.isEmpty(), because we
        // don't populate signalConnections if there's no vsync pending
        if (!timestamp && !eventPending) {
            // wait for something to happen
            if (waitForVSync) {
 
            ....
 
            } else {
                // Nobody is interested in vsync, so we just want to sleep.
                // h/w vsync should be disabled, so this will wait until we
                // get a new connection, or an existing connection becomes
                // interested in receiving vsync again.
                mCondition.wait(mLock);
            }
        }
    } while (signalConnections.isEmpty());
 
    // here we're guaranteed to have a timestamp and some connections to signal
    // (The connections might have dropped out of mDisplayEventConnections
    // while we were asleep, but we'll still have strong references to them.)
    return signalConnections;
}
cs

wait 상태에서 멈추어있던 waitForEvent() 함수가 broadcast 함수의 신호를 받고 다시 동작하며 waitForEvent() 함수의 실행을 종료시킵니다.


/frameworks/native/services/surfaceflinger/EventThread.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool EventThread::threadLoop() {
    DisplayEventReceiver::Event event;
    Vector< sp<EventThread::Connection> > signalConnections;
    signalConnections = waitForEvent(&event);
 
    // dispatch events to listeners...
    const size_t count = signalConnections.size();
    for (size_t i=0 ; i<count ; i++) {
        const sp<Connection>& conn(signalConnections[i]);
        // now see if we still need to report this event
        status_t err = conn->postEvent(event);
 
    ....
 
    }
    return true;
}
cs

waitForEvent() 함수가 동작되는 동안 수신된 이벤트에 대해 postEvent() 함수를 호출합니다.


/frameworks/native/services/surfaceflinger/EventThread.cpp

1
2
3
4
5
status_t EventThread::Connection::postEvent(
        const DisplayEventReceiver::Event& event) {
    ssize_t size = DisplayEventReceiver::sendEvents(mChannel, &event, 1);
    return size < 0 ? status_t(size) : status_t(NO_ERROR);
}
cs

DisplayEventReceiver 클래스 내에 있는 sendEvent() 함수를 호출합니다.


/frameworks/native/libs/gui/DisplayEventReceiver.cpp

1
2
3
4
5
ssize_t DisplayEventReceiver::sendEvents(const sp<BitTube>& dataChannel,
        Event const* events, size_t count)
{
    return BitTube::sendObjects(dataChannel, events, count);
}
cs

sendObjects() 함수를 통해 BitTube 클래스가 Pipe로 Looper와 연결한 File Descriptor에 값을 입력합니다.


/frameworks/native/libs/gui/DisplayEventReceiver.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ssize_t BitTube::sendObjects(const sp<BitTube>& tube,
        void const* events, size_t count, size_t objSize)
{
    const char* vaddr = reinterpret_cast<const char*>(events);
    ssize_t size = tube->write(vaddr, count*objSize);
 
    // should never happen because of SOCK_SEQPACKET
    LOG_ALWAYS_FATAL_IF((size >= 0) && (size % objSize),
            "BitTube::sendObjects(count=%d, size=%d), res=%d (partial events were sent!)",
            count, objSize, size);
 
    //ALOGE_IF(size<0, "error %d sending %d events", size, count);
    return size < 0 ? size : size / objSize;
}
cs

write() 함수를 통해 Looper에 값을 입력합니다.


/frameworks/native/libs/gui/DisplayEventReceiver.cpp

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
void BitTube::init(size_t rcvbuf, size_t sndbuf) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) == 0) {
        size_t size = DEFAULT_SOCKET_BUFFER_SIZE;
        setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
        setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
        // sine we don't use the "return channel", we keep it small...
        setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
        setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
        fcntl(sockets[0], F_SETFL, O_NONBLOCK);
        fcntl(sockets[1], F_SETFL, O_NONBLOCK);
        mReceiveFd = sockets[0];
        mSendFd = sockets[1];
    } else {
        mReceiveFd = -errno;
        ALOGE("BitTube: pipe creation failed (%s)", strerror(-mReceiveFd));
    }
}
 
....
 
ssize_t BitTube::write(void const* vaddr, size_t size)
{
    ssize_t err, len;
    do {
        len = ::send(mSendFd, vaddr, size, MSG_DONTWAIT | MSG_NOSIGNAL);
        // cannot return less than size, since we're using SOCK_SEQPACKET
        err = len < 0 ? errno : 0;
    } while (err == EINTR);
    return err == 0 ? len : -err;
}
cs

 File Descriptor를 통해 데이터를 전송합니다. 이는 이후 Looper에서 감지하여 이벤트를 처리하게 됩니다. Looper에 DisplayEventReceiver에서 wirte를 시도하였을 때 이를 읽어들이는 File Descriptor는 아래의 방법으로 등록됩니다.


/frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
status_t NativeDisplayEventReceiver::initialize() {
    status_t result = mReceiver.initCheck();
    if (result) {
        ALOGW("Failed to initialize display event receiver, status=%d", result);
        return result;
    }
 
    int rc = mMessageQueue->getLooper()->addFd(mReceiver.getFd(), 0, ALOOPER_EVENT_INPUT,
            this, NULL);
    if (rc < 0) {
        return UNKNOWN_ERROR;
    }
    return OK;
}
cs


/frameworks/native/libs/gui/DisplayEventReceiver.cpp

1
2
3
4
5
6
int DisplayEventReceiver::getFd() const {
    if (mDataChannel == NULL)
        return NO_INIT;
 
    return mDataChannel->getFd();
}
cs


/frameworks/native/libs/gui/BitTube.cpp

1
2
3
4
int BitTube::getFd() const
{
    return mReceiveFd;
}
cs

위의 과정을 통해 Looper에 File Descriptor가 등록되며 이는 Looper 내에서 epoll_wait()함수에 의해 감지되어 이벤트를 등록합니다.


/system/core/libutils/Looper.cpp

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
int Looper::pollInner(int timeoutMillis) {
 
....
 
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
 
....
 
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
 
....
 
}
cs

epoll_wait()에 의해 감지된 File Descriptor는 이후 등록된 이벤트와 함께 pushResponse() 함수를 통해 등록됩니다.


/system/core/libutils/Looper.cpp

1
2
3
4
5
6
void Looper::pushResponse(int events, const Request& request) {
    Response response;
    response.events = events;
    response.request = request;
    mResponses.push(response);
}
cs

pushResponse() 함수에 의해 등록된 이벤트는 이후 가지고 있는 Callback 함수를 수행합니다.


/system/core/libutils/Looper.cpp

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
int Looper::pollInner(int timeoutMillis) {
 
....
 
    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == ALOOPER_POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd);
            }
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = ALOOPER_POLL_CALLBACK;
        }
    }
    return result;
}
cs

DisplayEventReceiver 클래스를 통해 등록하였던 Callback 함수인 handleEvent() 함수를 실행합니다.


/frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp

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
int NativeDisplayEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
        ALOGE("Display event receiver pipe was closed or an error occurred.  "
                "events=0x%x", events);
        return 0// remove the callback
    }
 
    if (!(events & ALOOPER_EVENT_INPUT)) {
        ALOGW("Received spurious callback for unhandled poll event.  "
                "events=0x%x", events);
        return 1// keep the callback
    }
 
    // Drain all pending events, keep the last vsync.
    nsecs_t vsyncTimestamp;
    int32_t vsyncDisplayId;
    uint32_t vsyncCount;
    if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
        ALOGV("receiver %p ~ Vsync pulse: timestamp=%lld, id=%d, count=%d",
                this, vsyncTimestamp, vsyncDisplayId, vsyncCount);
        mWaitingForVsync = false;
        dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
    }
 
    return 1// keep the callback
}
cs

Callback 함수로서 handleEvent()함수가 실행되며 이는 Java Framework에 있는 함수를 호출합니다.


/frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp


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
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
 
    ALOGV("receiver %p ~ Invoking vsync handler."this);
    env->CallVoidMethod(mReceiverObjGlobal,
            gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, id, count);
    ALOGV("receiver %p ~ Returned from vsync handler."this);
 
    mMessageQueue->raiseAndClearException(env, "dispatchVsync");
}
 
....
 
int register_android_view_DisplayEventReceiver(JNIEnv* env) {
    int res = jniRegisterNativeMethods(env, "android/view/DisplayEventReceiver",
            gMethods, NELEM(gMethods));
    LOG_FATAL_IF(res < 0"Unable to register native methods.");
 
    FIND_CLASS(gDisplayEventReceiverClassInfo.clazz, "android/view/DisplayEventReceiver");
 
    GET_METHOD_ID(gDisplayEventReceiverClassInfo.dispatchVsync,
            gDisplayEventReceiverClassInfo.clazz,
            "dispatchVsync""(JII)V");
    GET_METHOD_ID(gDisplayEventReceiverClassInfo.dispatchHotplug,
            gDisplayEventReceiverClassInfo.clazz,
            "dispatchHotplug""(JIZ)V");
    return 0;
}
cs

/frameworks/base/core/java/android/view/DisplayEventReceiver.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    /**
     * Called when a vertical sync pulse is received.
     * The recipient should render a frame and then call {@link #scheduleVsync}
     * to schedule the next vertical sync pulse.
     *
     * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
     * timebase.
     * @param builtInDisplayId The surface flinger built-in display id such as
     * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
     * @param frame The frame number.  Increases by one for each vertical sync interval.
     */
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
    }
 
....
 
    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
        onVsync(timestampNanos, builtInDisplayId, frame);
    }
cs




300x250