Linux(우분투)에서 Android Studio 빠르게 실행하는 방법(바로가기 추가하기)

 안드로이드가 주로 애플리케이션 제작 도구로서 사용해오던 Eclipse에 대한 지원을 종료하게 됨에 따라 앞으로 안드로이드 Application을 개발하기 위해서는 안드로이드에서 자체적으로 제공하고 있는 IDE인 안드로이드 스튜디오(Android Studio)를 사용하는 것이 불가피하게 되었습니다.



안드로이드 애플리케이션 제작에 최적화 된 IDE인 Android Studio.

Eclipse에 익숙한 사용자들도 다양한 편의 기능에 개발 환경이 개선되었음을 체감할 수 있다.


 Windows 버전의 경우 필수 설치 프로그램만 설정되어 있다면 바로 설치 후 이용하실 수 있습니다만 Linux의 경우 studio.sh 파일 방식으로 실행을 해야 하기 때문에 Terminal을 사용해야 한다는 불편한 점이 있습니다.



기존 Eclipse에 익숙한 Linux 사용자에게도 Terminal을 통한 실행 방식은 번거롭고 불편하다.

 그렇다면 Linux 환경에서 Android Studio를 좀 더 쉽게 이용할 수 있는 방법은 없는 것일까요? 아래는 Terminal을 사용하지 않고 바로 Android Studio를 실행할 수 있는 방법들에 대해 다룬 내용입니다.


1) Dash Home을 통해 Android Studio 실행하기

 이 방법은 Android Studio에서 직접적으로 제공하는 방식으로서 별도의 설정 없이 바로 적용이 가능합니다.

- Android Studio를 실행한 후 Tools -> Create Desktop Entry... 를 실행합니다.



 실행을 하게 되면 아래와 같은 안내문이 나타납니다. 만약 다른 사용자가 사용할 수 있게 하고 싶다면 박스를 체크하신 후 OK 버튼을 클릭합니다.



 위의 과정을 완료한 후 Dash Home에 'android'를 검색하면 아래와 같이 Android Studio를 바로 실행할 수 있는 아이콘이 나타나는 것을 확인하실 수 있습니다.



2) 바탕화면(Desktop)에 바로가기 추가하기


 이 방법을 사용하면 바탕화면에 있는 아이콘을 더블클릭하여 바로 Android Studio를 실행할 수 있게 합니다.


$ vi android_studio.desktop


 android_studio.desktop 파일을 vi로 아래와 같은 내용을 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
[Desktop Entry]
Version=1.0
Type=Application
Name=Android Studio
Exec="/home/자신의 폴더 경로/android-studio/bin/studio.sh" %f
Icon=/home/자신의 폴더 경로/android-studio/bin/studio.ico
Categories=Developement;IDE;
Terminal=false
StartupNotify=true
StartupWMClass=jetbrains-android-studio
Name[en_G0]=android-studio.desktop
cs

위와 같이 작성한 후 다음과 같은 명령어를 입력해줍니다.


$ chmod +x android_studio.desktop

$ mv ~/Desktop/ 또는 $ mv ~/바탕화면/


 위와 같은 과정을 거치신 후 자신의 바탕화면을 확인하면 Android Studio 바로가기 아이콘이 생성되어 있음을 확인할 수 있습니다. 실행하면 Android Studio가 정상적으로 실행됨을 확인하실 수 있습니다.




300x250

같은 공유기에 접속중인 안드로이드 기기 탐색 및 통신하기

 안드로이드 디바이스와 같이 휴대용 기기의 경우 Wi-Fi를 사용할 때 장소 및 환경에 따라 항상 같은 공유기를 사용할 수 만은 없지요. 또한 무선공유기(라우터)에 접속할 때 마다 할당 받는 IP 주소 또한 달라질 수 있기 때문에 Socket 통신을 하게 될 때 IP 주소를 고정 시킬 수 없어 변경된 IP 주소를 일일이 변경하여야만 합니다.


 그렇다면 번거롭게 IP 주소를 확인해야만 하는 걸까요? JAVA를 기반으로 하는 서버를 통한 통신 프로그래밍을 설계한다면 UDP 방식을 활용한 Broadcast 방식으로 공유기에 접속한 안드로이드 기기의 IP 주소를 알아낸 후 해당 IP를 통해 TCP 소켓 통신을 하는 방법이 있습니다.


 이번 포스팅에서는 같은 공유기(라우터)에 접속중인 서버가 UDP를 통해 현재 접속 중인 안드로이드 기기들을 탐색한 후 이에 응답을 한 안드로이드 기기의 IP 주소를 통해 TCP 방식의 Socket 통신이 이루어지는 과정에 대해 알아보도록 하겠습니다.


 본 포스팅의 소스코드는 이전에 작성하였던 아래 포스팅의 소스코드를 사용하였음을 밝힙니다. 혹시 서버에서의 UDP 통신에 대해 좀 더 알고 싶으신 분은 아래의 포스팅을 참조해 주시면 도움이 될 것입니다.


[JAVA] 같은 공유기에 접속중인 기기의 IP 주소 확인하는방법

http://elecs.tistory.com/153


-Server측(PC)

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
109
110
111
112
113
114
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
 
public class Main {
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
 
        RecvMesage rm = new RecvMessage();
        rm.start();
 
        for (int i = 0; i < 100; i++) {
            try {
                new SearchDevice("255.255.255.255"8200).start();
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        rm.closeServer();
 
    }
 
    static class SearchDevice extends Thread {
        InetAddress ia;
        int port;
 
        SearchDevice(String IPaddr, int Port) {
            try {
                ia = InetAddress.getByName(IPaddr);
                port = Port;
            } catch (UnknownHostException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        public void run() {
            String msg = "Hello, ELECS!";
            try {
                DatagramSocket ds = new DatagramSocket();
                int length = msg.length();
                byte[] msgbyte = msg.getBytes();
                DatagramPacket dp = new DatagramPacket(msgbyte, length, ia, port);
                //UDP 방식으로 공유기에 접속중인 모든 기기에 Packet을 전송합니다.
                ds.send(dp);
                ds.close();
 
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
 
    static class RecvMessage extends Thread {
        boolean ready = true;
        Socket socket;
        InputStream is;
        ObjectInputStream ois;
        String clientIp;
        ServerSocket sockserver;
        Object lock = new Object();
 
        public void closeServer() {
            try {
                synchronized (lock) {
                    sockserver.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        public void run() {
            try {
                sockserver = new ServerSocket(8000);
                while (ready) {
                    //안드로이드 디바이스로부터 TCP 소켓 통신을 시작합니다.
                    socket = sockserver.accept();
                    synchronized (lock) {
                        is = socket.getInputStream();
                        ois = new ObjectInputStream(is);
 
                        //안드로이드 기기로부터 IP주소를 받아옵니다.
                        clientIp = (String) ois.readObject();
 
                        System.out.println("Client IP : " + clientIp);
                        ois.close();
                        is.close();
                        socket.close();
                    }
                }
 
            } catch (SocketException e) {
                System.out.println("ServerSocket is closed!");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
cs


-Client측(안드로이드 디바이스)

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
public class MainActivity extends AppCompatActivity {
    TextView tv1;
    Handler mHandler = new Handler();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        tv1 = (TextView)findViewById(R.id.textview1);
        (new RecvUDP()).start();
    }
 
    class RecvUDP extends Thread{
        String text;
        String serverIp;
        DatagramPacket dp;
 
        public void run(){
            int port = 8200;
            byte[] message = new byte[1000];
            dp = new DatagramPacket(message, message.length);
            try {
                
                DatagramSocket ds = new DatagramSocket(port);
                //서버로부터 전달되는 Packet을 수신합니다.
                ds.receive(dp);
                text = new String(message, 0, dp.getLength());
                ds.close();
                //수신된 Packet으로부터 서버의 IP를 저장합니다.
                serverIp = dp.getAddress().getHostAddress();
 
                mHandler.post(new Runnable(){
 
                    @Override
                    public void run() {
                        tv1.setText(text);
                    }
                });
 
                WifiManager wifiMgr = (WifiManager) getSystemService(WIFI_SERVICE);
                WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
                int ip = wifiInfo.getIpAddress();
 
                String androidIp = Formatter.formatIpAddress(ip);
 
                //서버의 IP를 통하여 Socket 통신을 시작합니다.
                Socket sock = new Socket(serverIp, 8000);
                DataOutputStream os = new DataOutputStream(sock.getOutputStream());
                ObjectOutputStream oos = new ObjectOutputStream(os);
                oos.writeObject(androidIp);
                oos.close();
                os.close();
                sock.close();
 
            } catch (Exception e) {
                e.printStackTrace();
            }
 
        }
    }
}
cs


-결과



300x250

[JAVA] 같은 공유기에 접속중인 기기의 IP 주소 확인하는방법

프로그래밍 팁 2015. 10. 15. 01:50

 TCP를 통해 Socket 통신을 하기 위해서는 접속하고자 하는 기기에 할당된 IP주소를 알고 있어야 합니다. 다시 말하자면, IP 주소를 알지 못할 경우 TCP를 통해서는 기기간의 통신이 불가능하다는 것입니다.


 그렇다면 상대의 IP 주소를 알아낼 수 있는 방법은 없는 것일까요? IP 프로토콜 규약에 의하면 특수 IP 주소를 사용하면 다른 기기와 통신이 가능하도록 설정되어 있습니다. 예를 들어 '255.255.255.255' 주소로 패킷을 보내면 같은 라우터(공유기) 내에 연결된 모든 기기들이 이를 수신하게 됩니다. 이를 Broadcast라고 칭하며 이를 사용하기 위해서는 TCP가 아닌 UDP 방식을 사용하여야 합니다.


 아래의 소스코드는 UDP 방식을 사용하여 서버가 IP주소 '255.255.255.255'를 통해 같은 라우터(공유기) 내에 접속중인 기기들에게 패킷을 보낸 후 이를 수신한 기기가 자신의 IP 주소를 서버에 알려주는 방식입니다.


 - DatagramPacket가 상대에게 전송될 때 해당 패킷 안에는 패킷을 보낸 측의 IP 주소가 담겨 있습니다. 이를 통해 서버측 IP와 클라이언트측 IP를 서로 알 수 있습니다.


Server측

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
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
 
public class Main {
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub       
        RecvServer rm = new RecvServer();
        rm.start();
 
        for (int i = 0; i < 10; i++) {
            try {
                new SearchDevice("255.255.255.255"8200).start();
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        rm.closeServer();
 
    }
 
    static class SearchDevice extends Thread {
        InetAddress ia;
        int port;
 
        SearchDevice(String IPaddr, int Port) {
            try {
                ia = InetAddress.getByName(IPaddr);
                port = Port;
            } catch (UnknownHostException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        public void run() {
            String msg = "Hello, ELECS!";
            try {
                DatagramSocket ds = new DatagramSocket();
                int length = msg.length();
                byte[] msgbyte = msg.getBytes();
                DatagramPacket dp = new DatagramPacket(msgbyte, length, ia, port);
                ds.send(dp);
                ds.close();
 
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
 
    static class RecvServer extends Thread {
        String text;
        String clientIp;
        DatagramPacket dp;
        DatagramSocket ds;
        Object lock = new Object();
 
        public void closeServer() {
            synchronized (lock) {
                ds.close();
            }
        }
 
        public void run() {
            int port = 8000;
            byte[] message = new byte[1000];
            dp = new DatagramPacket(message, message.length);
            try {
                ds = new DatagramSocket(port);
                ds.receive(dp);
                synchronized (lock) {
                    text = new String(message, 0, dp.getLength());
                    ds.close();
 
                    clientIp = dp.getAddress().getHostAddress();
 
                    System.out.println("Client IP : " + clientIp);
                }
 
            } catch (Exception e) {
                e.printStackTrace();
            }
 
        }
    }
}
 
 
cs


Client측

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
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
 
public class Main {
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String text;
        String serverIp;
        DatagramPacket dp;
        int port = 8200;
        byte[] message = new byte[1000];
        dp = new DatagramPacket(message, message.length);
        try {
            DatagramSocket ds = new DatagramSocket(port);
            ds.receive(dp);
            text = new String(message, 0, dp.getLength());
            ds.close();
            serverIp = dp.getAddress().getHostAddress();
            new SearchDevice(serverIp, 8000).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
 
    }
 
    static class SearchDevice extends Thread {
        InetAddress ia;
        int port;
 
        SearchDevice(String IPaddr, int Port) {
            try {
                ia = InetAddress.getByName(IPaddr);
                port = Port;
            } catch (UnknownHostException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        public void run() {
            String msg = "Hello, ELECS!";
            try {
                DatagramSocket ds = new DatagramSocket();
                int length = msg.length();
                byte[] msgbyte = msg.getBytes();
                DatagramPacket dp = new DatagramPacket(msgbyte, length, ia, port);
                ds.send(dp);
                ds.close();
 
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
 
}
 
 
cs


결과


300x250

[JAVA] Socket 서버 구현시 안전하게 SocketServer를 종료하는 방법

프로그래밍 팁 2015. 10. 14. 23:18

 안드로이드를 활용한 다양한 소켓 프로그래밍들의 에제를 둘러보다 보면 Server 측의 Socket을 다루는 데 종종 난해한 경우가 있습니다. 가령 Client측으로부터 Socket 통신이 한창 진행중인 상황에서 서버측 Socket을 닫아버리면 진행중이던 통신 관련 작업이 모두 끝나기도 전에 서버가 종료되어 버리는 심각한 상황이 발생할 수도 있는 것이지요.


 본 포스팅에서는 Java를 활용한 서버측의 Socket을 좀 더 안정적으로 종료시키는 방법에 대해 알아보도록 하겠습니다.


- Client와 Socket 통신이 진행중인 상황에서 ServerSocket이 강제로 종료되지 않도록 하기 위해 synchronized를 사용합니다. 이는 C/C++에서 제공하는 Mutex와 유사한 역할을 합니다. synchronized() 의 인자(Argument)를 가지고 있는 쪽에서 실행을 하다가 종료가 되었을 때 이를 다른 쪽에서 점유한 후 실행이 종료될 때 까지 다른 부분에서는 실행되지 않도록 설정합니다.


- ServerSocket이 accpt()를 실행하던 중에 종료되었을 때 SocketException이 발생합니다. 이 때 try-catch를 통해 해당 Exception을 catch한 후 Server가 안전하게 종료되었음을 확인합니다.


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
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
 
public class Main {
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
 
        RecvMessage rm = new RecvMessage();
        //Server Thread를 실행합니다.
        rm.start();
 
        ....
 
        //ServerSocket를 닫음으로서 Server Thread를 종료합니다.
        rm.closeServer();
 
    }
 
    static class RecvMessage extends Thread {
        boolean ready = true;
        Socket socket;
        InputStream is;
        ObjectInputStream ois;
        String clientIp;
        ServerSocket sockserver;
 
        //Mutex로 사용할 Object 변수를 선언합니다.
        Object lock = new Object();
 
        public void closeServer() {
            try {
                //Client와 Socket 통신이 진행중일 경우 종료될 때까지 기다립니다.
                synchronized (lock) {
                    sockserver.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        public void run() {
            try {
                sockserver = new ServerSocket(8200);
                while (ready) {
                    socket = sockserver.accept();
                    //Client와의 통신이 종료될 때 까지 SocketServer의 종료를 보류시킵니다.
                    synchronized (lock) {
                        is = socket.getInputStream();
                        ois = new ObjectInputStream(is);
 
                        clientIp = (String) ois.readObject();
 
                        System.out.println("Client IP : " + clientIp);
                        ois.close();
                        is.close();
                        socket.close();
                    }
                }
 
            } catch (SocketException e) {
                //ServerSocket가 종료되었을 때 실행됩니다.
                System.out.println("ServerSocket is closed!");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
 
}
 
 
cs


300x250

[컴퓨터비전]이미지 변환시 DoF(Degrees of Freedom)에 대한 고찰

공대생의 팁 2015. 10. 13. 00:13

 영상처리와 컴퓨터비전 분야에서 가장 중요한 요소로 Matrix 변환을 기반으로 하는 선형대수(Linear Algebra)가 있습니다. 특히 화면에 나타나는 이미지를 변환할 때 (Geometric Transformations를 하기 위해 사용되는 행렬), DoF라는 개념이 언급됩니다. 본 포스팅에서는 이 DoF에 대해 이야기해 보려 합니다.


-DoF(Degrees of Freedom)란?

 DoF(Degrees of Freedom), 우리말로 '자유도'라 일컫는 요소로 이미지가 변환이 될 때 최소한으로 필요로 하는 독립변수의 수라고 정의할 수 있습니다. 이에 대해 각 Geometric Transformations들을 통해 알아보도록 하겠습니다.


-Translation


 이미지 변환중 가장 단순하다고 할 수 있는 변환 중 하나입니다. 이를 Matrix로 나타내면 다음과 같습니다.


위의 식에서 보이는 바와 같이 총 2개의 변수에 의해 이미지가 변환되고 있는 것을 알 수 있습니다. 각 변수는 이미지의 x축과 y축을 이동하는 식으로서 이는 Translation의 DoF가 2임을 알 수 있습니다.

 또한 Translation의 결과물은 원본 이미지에서 큰 변화가 일어나지 않습니다.


-Rigid(Euclidean)

위에서 보았던 Translation을 수행한 후 이미지를 일정 각도로 회전시킨 결과입니다. 이를 Matrix로 나타내면 다음과 같습니다.


위의 식에서 보는 바와 같이 x축으로의 이동, y축으로의 이동, 회전각도 Θ로 DoF는 3임을 알 수 있습니다.

 위 Rigid의 최종 결과물은 원본 이미지의 높이 및 너비와 같은 길이가 보존되는 것을 알 수 있습니다.


-Similiary

위의 Rigid Transform에서 결과물의 사진 크기가 변하였음을 확인할 수 있습니다. 이를 Matrix로 나타내면 다음과 같습니다.


 위의 식에서 보는 바와 같이 x축으로의 이동, y축으로의 이동, 회전각Θ, 크기변수s 로 DoF는 4임을 알 수 있습니다.

 위 Similary의 최종 결과물은 원본 이미지의 직각 부분이 유지되는 것을 알 수 있습니다.


-Affine

원본 이미지에 Affine Matrix가 적용되면 이미지가 위에서 보는 바와 같이 일그러진 모습을 하고 있는 것을 보실 수 있습니다. 이를 Matrix로 나타내면 다음과 같습니다.


Affine Matrix의 DoF는 6임을 확인하실 수 있습니다.

 위 Affine의 최종 결과물은 원본 이미지의 두 변의 평행이 보존됨을 확인하실 수 있습니다.


-Projective

 원본이미지에 Projectiive가 젹용되면 마치 이미지가 정사영에 투영된 듯이 이미지를 기울여서 보는 듯한 느낌이 들 겁니다. 이를 Matirx로 나타내면 다음과 같습니다.


Projective Matrix의 DoF는 8임을 알 수 있습니다.

 위 Projective의 최종 결과물은 원본 이미지의 특정 두 변의 평행이 유지되는 것을 알 수 있습니다.

300x250

Error: The SDK Build Tools revision is too low for project

 Eclipse에서 사용하던 프로젝트를 Android Studio로 가져왔을 때 아래와 같은 상황의 에러가 발생하였습니다.


Error: The SDK Build Tools revision (19.0.0) is too low for project. Minimum required is 19.1.0


 위 에러는 프로젝트가 기존 Eclipse에서 사용하던 Build Tools가 Android Studio에 설치된 Build Tools보다 버전이 낮아 발생하는 에러 입니다. 위 에러를 해결하기 위해서는 아래와 같은 과정을 수행합니다.


1.에러가 발생한 프로젝트의 폴더 -> build.grade 를 실행합니다.

2.android 부분에서 BuildToolsVersion 부분을 최소 요구 버전으로 설정합니다. 본 예제의 경우 버전을 19.1.0으로 수정합니다.



 위와 같이 적용한 후 Android Studio를 종료한 후 다시 실행하면 제대로 적용되어 있는 것을 확인하실 수 있습니다.




300x250

저장된 사진및 파일이 보이지 않을 때 미디어스캐닝(Media Scanning) 방법 [Kitkat 이후의 버전에서 적용방법]

 Socket을 활용하여 안드로이드 프로그래밍을 하는 분들이라면 누구나 한 번 즈음은 난관에 부딪치는 경우가 하나 있습니다. 그 중 하나가 분명 소켓 통신을 통해 받은 이미지나 동영상 파일을 저장하였는데 갤러리를 통해 확인해 보려 하면 보이지 않는 경우이지요! 희한하게도 안드로이드 기기의 전원을 끈 후 다시 확인해보면 안보이던 사진이 버젓이 보인다는 사실!


갤러리에 내가 다운로드 받은 파일이 누락되어 있다면 상당히 당황스러울 것이다.


 실제로 안드로이드 기기는 파일을 바로 저장한 상태로는 이를 기기가 바로 인식을 하지 못합니다. 그렇다면 기껏 다운로드 받은 파일을 보기 위해서 안드로이드  기기를 리셋 하는 방법밖에 없는 걸까요?



 안드로이드 기기를 쓰다가 위와 같이 미디어스캐닝(Media scanning)이 진행중인 것을 종종 보실 수 있습니다. 이것이 바로 종적을 감추어버린 파일들의 위치를 다시 찾는 기능을 하는 녀석입니다. 종종 개발자들이 애플리케이션을 개발하던 도중 다운로드가 완료된 후 미디어스캐닝을 해주지 않게 될 경우 파일을 볼 수 없는 상황이 벌어지는 것입니다.


 이번 포스팅에서는 소스코드를 통하여 미디어스캐닝을 수행하는 방법에 대해 알아볼 것입니다. 또한 본 포스팅은 Kitkat에서부터 변경된 미디어 저장 방식을 반영한 방법에 대해 다루어 보도록 할 것입니다.


 아래의 소스코드는 카메라로부터 촬영된 사진 데이터를 저장하는 과정을 나타낸 소스코드입니다.

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
        private Camera.PictureCallback picb = new Camera.PictureCallback() {
 
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                // TODO Auto-generated method stub
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                long time = System.currentTimeMillis();
                SimpleDateFormat day = new SimpleDateFormat("yyyymmddhhmmssSSS");
                String output = day.format(new Date(time));
 
                String folder = Environment.getExternalStorageDirectory().getAbsolutePath()
                        + "/DCIM/FrameworkTest";
                String file = folder + File.separator + output + ".jpg";
 
                File FolderPath = new File(folder);
                if (!FolderPath.exists()) {
                    FolderPath.mkdirs();
                    Log.d("MKDIR", folder);
                }
 
                try {
                    FileOutputStream out = new FileOutputStream(file);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 50out);
                    out.close();
                    sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, 
                            Uri.parse("file://"+file)));
                } catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
 
                camera.startPreview();
            }
        };
cs

  위 소스코드의 내용대로 byte[] 형식으로 들어온 사진 데이터가 FileOutputStream 클래스를 통해 저장되는 과정을 나타내고 있습니다. 이를 위해 위의 소스코드 몇 줄을 확인해 보도록 하겠습니다.


String folder = Environment.getExternalStorageDirectory().getAbsolutePath();


 저장할 파일의 폴더명을 설정해 줍니다. 위의 소스코드를 통해 안드로이드 기기의 외부저장소의 최상단에서의 절대주소를 얻을 수 있습니다.


sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+file)));


 FileOutputStream을 통해 저장된 파일 하나를 나타내기 위해 해당 파일에 대해 Media Scanning을 수행합니다. Uri.parse() 의 인자값으로 해당 파일의 절대주소를 입력하며, Intent의 첫번째 인자인 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE에 주의하시면 해당 소스코드를 사용하는 데에 큰 문제는 없을 것입니다. 위와 같이 설정된 Intent 클래스를 sendBroadcast() 매서드를 통해 전달하면 저장된 파일에 대한 Media Scanning이 정상적으로 동작되에 갤러리를 통해 해당 파일을 확인하실 수 있을 것입니다.

300x250

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

안드로이드 프레임워크 프로그래밍(25) [Native 단계에서 Looper의 동작 원리]

안드로이드/프레임워크 2015. 9. 16. 23:01

 SurfaceFlinger의 동작 원리에 대해 좀 더 정확히 이해하기 위해서는 Native 단계에서 Looper의 동작 방식을 파악할 필요가 있습니다. 비록 Looper는 안드로이드의 System Library에 있지만 Native 단계에서의 Framework에서 상당히 중요하므로 간단하게 짚고 넘어가도록 하겠습니다.


 Looper란 폴링이 이루어지고 있는 루프(loop)로서 File Discriptor 이벤트를 관찰하는 역할을 하고, 등록된 Callback 함수를 호출하는 역할을 수행하기도 합니다. 실제로 Native 단계에서의 Looper는 UNIX 명령어 집합 epoll() 함수를 사용합니다.


/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
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    int wakeFds[2];
    int result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0"Could not create wake pipe.  errno=%d", errno);
 
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
 
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0"Could not make wake read pipe non-blocking.  errno=%d",
            errno);
 
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0"Could not make wake write pipe non-blocking.  errno=%d",
            errno);
 
    mIdling = false;
 
    // Allocate the epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0"Could not create epoll instance.  errno=%d", errno);
 
    struct epoll_event eventItem;
    memset(& eventItem, 0sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0"Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
}
cs


 Looper 클래스의 생성자 소스코드를 살펴보시면 IPC 함수들이 눈에 띄는 것을 보실 수 있습니다. Looper의 Pipe File Descriptor를 Non-block로 설정되고 있는 모습 또한 확인하실 수 있습니다.


mEpollFd = epoll_create(EPOLL_SIZE_HINT);

 Looper에 등록된 File Descriptor를 관리할 epoll 인스턴스를 생성합니다. 인자로 인스턴스의 크기를 설정하며 return 값으로 해당 인스턴스의 FD를 돌려받습니다.


result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);

  epoll 인스턴스인 mEpollFd에 제어하고자 하는 File Descriptor인 mWakeReadPipeFd를 등록합니다.


 위의 과정을 거쳐 등록된 Looper는 이후 외부 이벤트를 통해 File Descriptor를 제어하게 됩니다. Looper가 생성된 이후 부터는 addFd()함수를 통해 File Descriptor가 등록됩니다.


/system/core/include/utils/Looper.h

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
/**
 * A looper callback.
 */
class LooperCallback : public virtual RefBase {
protected:
    virtual ~LooperCallback() { }
 
public:
    /**
     * Handles a poll event for the given file descriptor.
     * It is given the file descriptor it is associated with,
     * a bitmask of the poll events that were triggered (typically ALOOPER_EVENT_INPUT),
     * and the data pointer that was originally supplied.
     *
     * Implementations should return 1 to continue receiving callbacks, or 0
     * to have this file descriptor and callback unregistered from the looper.
     */
    virtual int handleEvent(int fd, int events, void* data) = 0;
};
 
....
 
/**
 * A polling loop that supports monitoring file descriptor events, optionally
 * using callbacks.  The implementation uses epoll() internally.
 *
 * A looper can be associated with a thread although there is no requirement that it must be.
 */
class Looper : public ALooper, public RefBase {
protected:
    virtual ~Looper();
 
....
 
private:
    struct Request {
        int fd;
        int ident;
        sp<LooperCallback> callback;
        void* data;
    };
 
    struct Response {
        int events;
        Request request;
    };
 
    struct MessageEnvelope {
        MessageEnvelope() : uptime(0) { }
 
        MessageEnvelope(nsecs_t uptime, const sp<MessageHandler> handler,
                const Message& message) : uptime(uptime), handler(handler), message(message) {
        }
 
        nsecs_t uptime;
        sp<MessageHandler> handler;
        Message message;
    };
 
    const bool mAllowNonCallbacks; // immutable
 
    int mWakeReadPipeFd;  // immutable
    int mWakeWritePipeFd; // immutable
    Mutex mLock;
 
    Vector<MessageEnvelope> mMessageEnvelopes; // guarded by mLock
    bool mSendingMessage; // guarded by mLock
 
    // Whether we are currently waiting for work.  Not protected by a lock,
    // any use of it is racy anyway.
    volatile bool mIdling;
 
    int mEpollFd; // immutable
 
    // Locked list of file descriptor monitoring requests.
    KeyedVector<int, Request> mRequests;  // guarded by mLock
 
    // This state is only used privately by pollOnce and does not require a lock since
    // it runs on a single thread.
    Vector<Response> mResponses;
    size_t mResponseIndex;
    nsecs_t mNextMessageUptime; // set to LLONG_MAX when none
 
    int pollInner(int timeoutMillis);
    void awoken();
    void pushResponse(int events, const Request& request);
 
    static void initTLSKey();
    static void threadDestructor(void *st);
};
cs


/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
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
int Looper::addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data) {
    return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data);
}
 
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p"this, fd, ident,
            events, callback.get(), data);
#endif
 
    if (!callback.get()) {
        if (! mAllowNonCallbacks) {
            ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
            return -1;
        }
 
        if (ident < 0) {
            ALOGE("Invalid attempt to set NULL callback with ident < 0.");
            return -1;
        }
    } else {
        ident = ALOOPER_POLL_CALLBACK;
    }
 
    int epollEvents = 0;
    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
 
    { // acquire lock
        AutoMutex _l(mLock);
 
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.callback = callback;
        request.data = data;
 
        struct epoll_event eventItem;
        memset(& eventItem, 0sizeof(epoll_event)); // zero out unused members of data field union
        eventItem.events = epollEvents;
        eventItem.data.fd = fd;
 
        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.add(fd, request);
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) {
                ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
                return -1;
            }
            mRequests.replaceValueAt(requestIndex, request);
        }
    } // release lock
    return 1;
}
cs

 Looper 이외의 클래스로부터 File Descriptor를 등록받게 되변 epoll을 통해 해당 File Descriptor를 등록하게 되고 해당 File Descriptor가 epoll에 의해 실행이 요청되었을 때, callback 함수가 동작할 수 있도록 Vector 변수인 mRequests에 등록됩니다.


/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
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
int Looper::pollInner(int timeoutMillis) {
 
....
 
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
 
    // No longer idling.
    mIdling = false;
 
    // Acquire lock.
    mLock.lock();
 
    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error, errno=%d", errno);
        result = ALOOPER_POLL_ERROR;
        goto Done;
    }
 
    // Check for poll timeout.
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout"this);
#endif
        result = ALOOPER_POLL_TIMEOUT;
        goto Done;
    }
 
    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds"this, eventCount);
#endif
 
    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);
            }
        }
    }
Done: ;
 
....
 
}
cs

 Looper에서 pollInner()함수가 호출되면 epoll을 통해 등록되었던 File Descriptor를 통해 이벤트가 들어왔는지 확인한 후 이를 처리하도록 합니다.


int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

 epoll에 등록된 File Descriptor 중 파일 읽기가 준비된 FD의 갯수를 return 합니다. 이후 준비된 이벤트를 처리하는 과정을 거치게 됩니다.


awoken();

 만약 준비된 File Descriptor가 Looper의 것일 경우 위 함수를 실행합니다.


/system/core/libutils/Looper.cpp

1
2
3
4
5
6
7
8
9
10
11
void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ awoken"this);
#endif
 
    char buffer[16];
    ssize_t nRead;
    do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}
cs


pushResponse(events, mRequests.valueAt(requestIndex));

 만약 준비된 File Descriptor가 addFd에 의해 등록된 것의 경우 위 함수를 실행합니다.


/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


 위에서 보시는 바와 같이 mResponses에 이벤트가 등록되는 것을 확인하였습니다. 이제 해당 이벤트의 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


 위 함수를 통해 Looper에 등록된 Callback 함수가 실행됨을 확인할 수 있습니다.

300x250

SurfaceFlinger에서 DispSyncSource의 초기화 과정

안드로이드/프레임워크 2015. 9. 12. 23:40

 DisplaySyncSource 클래스는 SurfaceFlinger.cpp 내에 소스코드가 존재합니다. 이는 이후 안드로이드의 VSync(수직동기화)를 수행하기 위해 따로 만들어지는 EventThread에서 적합하게 사용될 수 있도록 설계되어 있습니다. 전반적인 소스코드를 분석해 보도록 하겠습니다.


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

1
2
3
4
5
6
7
8
9
10
11
    // start the EventThread
    sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            vsyncPhaseOffsetNs, true);
    mEventThread = new EventThread(vsyncSrc);
    sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync,
            sfVsyncPhaseOffsetNs, false);
    mSFEventThread = new EventThread(sfVsyncSrc);
    mEventQueue.setEventThread(mSFEventThread);
 
    mEventControlThread = new EventControlThread(this);
    mEventControlThread->run("EventControl", PRIORITY_URGENT_DISPLAY);
cs

 위의 DispSyncSource 생성자 내의 mPrimaryDispSync 변수는 DispSync 클래스의 변수입니다. DispSync 클래스에 대해 자세히 알고 싶으신 분은 아래 포스팅을 참조해 주시기 바랍니다.


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

http://elecs.tistory.com/134


 위의 소스코드를 통해 DisySyncSource 클래스가 VSync를 수행하기 위한 Thread를 위해 실행되고 있는 것을 알 수 있습니다. 위에서 생성되고 있는 DispSyncSource 클래스를 살펴보도록 하겠습니다.


/frameworks/native/services/surfaceflinger/EventThread.h
1
2
3
4
5
6
7
8
9
10
11
12
class VSyncSource : public virtual RefBase {
public:
    class Callback: public virtual RefBase {
    public:
        virtual ~Callback() {}
        virtual void onVSyncEvent(nsecs_t when) = 0;
    };
 
    virtual ~VSyncSource() {}
    virtual void setVSyncEnabled(bool enable) = 0;
    virtual void setCallback(const sp<Callback>& callback) = 0;
};
cs


/frameworks/native/services/surfaceflinger/DispSync.h

1
2
3
4
5
6
7
8
9
10
11
12
13
class DispSync {
 
public:
 
    class Callbackpublic virtual RefBase {
    public:
        virtual ~Callback() {};
        virtual void onDispSyncEvent(nsecs_t when) = 0;
    };
 
....
 
}
cs


/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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class DispSyncSource : public VSyncSourceprivate DispSync::Callback {
public:
    DispSyncSource(DispSync* dispSync, nsecs_t phaseOffset, bool traceVsync) :
            mValue(0),
            mPhaseOffset(phaseOffset),
            mTraceVsync(traceVsync),
            mDispSync(dispSync) {}
 
    virtual ~DispSyncSource() {}
 
    virtual void setVSyncEnabled(bool enable) {
        // Do NOT lock the mutex here so as to avoid any mutex ordering issues
        // with locking it in the onDispSyncEvent callback.
        if (enable) {
            status_t err = mDispSync->addEventListener(mPhaseOffset,
                    static_cast<DispSync::Callback*>(this));
            if (err != NO_ERROR) {
                ALOGE("error registering vsync callback: %s (%d)",
                        strerror(-err), err);
            }
            ATRACE_INT("VsyncOn"1);
        } else {
            status_t err = mDispSync->removeEventListener(
                    static_cast<DispSync::Callback*>(this));
            if (err != NO_ERROR) {
                ALOGE("error unregistering vsync callback: %s (%d)",
                        strerror(-err), err);
            }
            ATRACE_INT("VsyncOn"0);
        }
    }
 
    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);
        }
    }
 
    int mValue;
 
    const nsecs_t mPhaseOffset;
    const bool mTraceVsync;
 
    DispSync* mDispSync;
    sp<VSyncSource::Callback> mCallback;
    Mutex mMutex;
};
cs

 위 과정을 거쳐 이후 EventThread를 통해 해당 내용들이 등록되게 됩니다. EventThread에 대한 자세한 사항은 아래 포스팅을 참조해 주시기 바랍니다.

SurfaceFlinger에서 EventThread의 초기화 과정

http://elecs.tistory.com/140


 이후 EventThread가 초기화가 이루어진 후 waitForEvent() 함수가 호출됩니다. 이 때, 해당 함수 내에 있는 enableVSyncLocked() 함수가 실행되면 DispSyncSource에 Callback이 등록됩니다.

 

/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
// 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 {
 
....
 
    // Here we figure out if we need to enable or disable vsyncs
        if (timestamp && !waitForVSync) {
            // we received a VSYNC but we have no clients
            // don't report it, and disable VSYNC events
            disableVSyncLocked();
        } else if (!timestamp && waitForVSync) {
            // we have at least one client, so we want vsync enabled
            // (TODO: this function is called right after we finish
            // notifying clients of a vsync, so this call will be made
            // at the vsync rate, e.g. 60fps.  If we can accurately
            // track the current state we could avoid making this call
            // so often.)
            enableVSyncLocked();
 
....
 
}
 
....
 
void EventThread::enableVSyncLocked() {
    if (!mUseSoftwareVSync) {
        // never enable h/w VSYNC when screen is off
        if (!mVsyncEnabled) {
            mVsyncEnabled = true;
            mVSyncSource->setCallback(static_cast<VSyncSource::Callback*>(this));
            mVSyncSource->setVSyncEnabled(true);
            mPowerHAL.vsyncHint(true);
        }
    }
    mDebugVsyncEnabled = true;
}
cs


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

1
2
3
4
    virtual void setCallback(const sp<VSyncSource::Callback>& callback) {
        Mutex::Autolock lock(mMutex);
        mCallback = callback;
    }
cs


 위 과정을 통하여 VSyncSource에 callback 함수가 등록됩니다. 해당 callback 함수는 아래와 같습니다.


/frameworks/native/services/surfaceflinger/EventThread.h

1
2
3
4
5
6
7
8
9
10
11
12
class VSyncSource : public virtual RefBase {
public:
    class Callback: public virtual RefBase {
    public:
        virtual ~Callback() {}
        virtual void onVSyncEvent(nsecs_t when) = 0;
    };
 
    virtual ~VSyncSource() {}
    virtual void setVSyncEnabled(bool enable) = 0;
    virtual void setCallback(const sp<Callback>& callback) = 0;
};
cs

 

/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

 Callback이 등록된 후 setVSyncEnable() 함수를 통해 DispSync Thread에 Listener를 등록합니다.


/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
    virtual void setVSyncEnabled(bool enable) {
        // Do NOT lock the mutex here so as to avoid any mutex ordering issues
        // with locking it in the onDispSyncEvent callback.
        if (enable) {
            status_t err = mDispSync->addEventListener(mPhaseOffset,
                    static_cast<DispSync::Callback*>(this));
            if (err != NO_ERROR) {
                ALOGE("error registering vsync callback: %s (%d)",
                        strerror(-err), err);
            }
            ATRACE_INT("VsyncOn"1);
        } else {
            status_t err = mDispSync->removeEventListener(
                    static_cast<DispSync::Callback*>(this));
            if (err != NO_ERROR) {
                ALOGE("error unregistering vsync callback: %s (%d)",
                        strerror(-err), err);
            }
            ATRACE_INT("VsyncOn"0);
        }
    }
cs

 위의 과정을 통해 DisySync 클래스의 addEventListener()함수를 통해 DisySync의 callback 함수가 등록되는 것을 보실 수 있습니다. 소스코드는 다음과 같습니다.


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    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);
        }
    }
cs



/frameworks/native/services/surfaceflinger/DispSync.cpp
1
2
3
4
5
6
status_t DispSync::addEventListener(nsecs_t phase,
        const sp<Callback>& callback) {
 
    Mutex::Autolock lock(mMutex);
    return mThread->addEventListener(phase, callback);
}
cs

 위의 소스코드를 통해 DispSyncThread에 Event가 등록되는 것을 확인하실 수 있습니다.


/frameworks/native/services/surfaceflinger/DispSync.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
class DispSyncThread: public Thread {
 
....
 
    status_t addEventListener(nsecs_t phase, const sp<DispSync::Callback>& callback) {
        Mutex::Autolock lock(mMutex);
 
        for (size_t i = 0; i < mEventListeners.size(); i++) {
            if (mEventListeners[i].mCallback == callback) {
                return BAD_VALUE;
            }
        }
 
        EventListener listener;
        listener.mPhase = phase;
        listener.mCallback = callback;
 
        // We want to allow the firstmost future event to fire without
        // allowing any past events to fire.  Because
        // computeListenerNextEventTimeLocked filters out events within a half
        // a period of the last event time, we need to initialize the last
        // event time to a half a period in the past.
        listener.mLastEventTime = systemTime(SYSTEM_TIME_MONOTONIC) - mPeriod / 2;
 
        mEventListeners.push(listener);
 
        mCond.signal();
 
        return NO_ERROR;
    }
 
....
 
}
cs

DispSync에 등록된 이벤트들은 이후 DispSyncThread에서 VSync 이벤트 Callback 발생시 실행됩니다.


/frameworks/native/services/surfaceflinger/DispSync.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
class DispSyncThread: public Thread {
 
....
 
    Vector<CallbackInvocation> gatherCallbackInvocationsLocked(nsecs_t now) {
        Vector<CallbackInvocation> callbackInvocations;
        nsecs_t ref = now - mPeriod;
 
        for (size_t i = 0; i < mEventListeners.size(); i++) {
            nsecs_t t = computeListenerNextEventTimeLocked(mEventListeners[i],
                    ref);
 
            if (t < now) {
                CallbackInvocation ci;
                ci.mCallback = mEventListeners[i].mCallback;
                ci.mEventTime = t;
                callbackInvocations.push(ci);
                mEventListeners.editItemAt(i).mLastEventTime = t;
            }
        }
 
        return callbackInvocations;
    }
 
....
 
    void fireCallbackInvocations(const Vector<CallbackInvocation>& callbacks) {
        for (size_t i = 0; i < callbacks.size(); i++) {
            callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime);
        }
    }
 
....
 
}
cs

 위의 과정을 거쳐 EventThread의 Callback 함수를 수행하게 되면 broadcast()함수에 의해 EventThread 내의 Thread들이 모두 깨어나게 됩니다. 이는 이후 postEvent() 함수를 호출하게 됩니다.






300x250