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


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



300x250

안드로이드 기기 간에 Wifi Socket 통신하기


※해당 포스팅의 내용은 Java로 작성되어 있습니다. 본 내용을 Kotlin으로 다시 설계하여 보다 향상된 버전의 내용을 확인하고 싶으신 분들은 아래의 주소로 이동해주세요.

https://elecs.tistory.com/345


 최근 사물인터넷이 주목을 받게 되면서 휴대전화 이외의 기기에 안드로이드 OS가 적용되는 사례가 증가하고 있습니다. 이로 인해 기존 휴대기기에서 사용되지 않던 안드로이드 Server라는 개념이 등장하기도 합니다. 이번 포스팅에서는 Wifi를 활용한 간단한 Wifi 통신에 대해 다루어볼까 합니다. 아래의 예제를 통해서 안드로이드 기기에서 Wifi socket 통신을 즐겨보는 기회를 가져보도록 하겠습니다.


먼저 AndroidMenifest.xml에 통신 관련 권한을 추가해 줍니다.


AndroidMenifest.xml

1
2
3
4
5
6
7
    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
cs


string.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<resources>
 
    <string name="app_name">FW</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="ip">IP</string>
    <string name="port">PORT</string>
    <string name="name">NAME</string>
    <string name="button1">Connect!</string>
    <string name="button2">Disconnect!</string>
    <string name="button3">Set Server!</string>
    <string name="button4">close Server!</string>
    <string name="button5">View info!</string> 
    <string name="button6">Msg</string> 
 
</resources>
cs


activity_main.xml

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    android:orientation="vertical" >
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <TextView
            android:id="@+id/textView1"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:text="@string/ip" />
 
        <EditText
            android:id="@+id/editText1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint=""           
            />
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <TextView
            android:id="@+id/textView2"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:text="@string/port" />
        
        <EditText
            android:id="@+id/editText2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint=""           
            />
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <TextView
            android:id="@+id/textView3"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:text="@string/name" />
        
        <EditText
            android:id="@+id/editText3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint=""           
            />
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button1" />
 
        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button2" />
 
        <Button
            android:id="@+id/button6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button6" />
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button3" />
 
        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button4" />
 
        <Button
            android:id="@+id/button5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button5" />
 
    </LinearLayout>
 
    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />
 
</LinearLayout>
cs

MainActivity.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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package com.example.fw;
 
import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.text.format.Formatter;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
 
public class MainActivity extends Activity {
    private EditText et1, et2, et3;
    private TextView tv4;
    private Socket socket;
    private DataOutputStream writeSocket;
    private DataInputStream readSocket;
    private Handler mHandler = new Handler();
 
    private ConnectivityManager cManager;
    private NetworkInfo wifi;
    private ServerSocket serverSocket;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        et1 = (EditText) findViewById(R.id.editText1);
        et2 = (EditText) findViewById(R.id.editText2);
        et3 = (EditText) findViewById(R.id.editText3);
 
        tv4 = (TextView) findViewById(R.id.textView4);
 
        cManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
 
    }
 
    @SuppressWarnings("deprecation")
    public void OnClick(View v) throws Exception {
        switch (v.getId()) {
            case R.id.button1:
                (new Connect()).start();
                break;
            case R.id.button2:
                (new Disconnect()).start();
                break;
            case R.id.button3:
                (new SetServer()).start();
                break;
            case R.id.button4:
                (new CloseServer()).start();
                break;
            case R.id.button5:
                wifi = cManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
                if (wifi.isConnected()) {
                    WifiManager wManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
                    WifiInfo info = wManager.getConnectionInfo();
                    tv4.setText("IP Address : " + Formatter.formatIpAddress(info.getIpAddress()));
                } else {
                    tv4.setText("Disconnected");
                }
                break;
            case R.id.button6:
                (new sendMessage()).start();
        }
    }
 
    class Connect extends Thread {
        public void run() {
            Log.d("Connect""Run Connect");
            String ip = null;
            int port = 0;
 
            try {
                ip = et1.getText().toString();
                port = Integer.parseInt(et2.getText().toString());
            } catch (Exception e) {
                final String recvInput = "정확히 입력하세요!";
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
                });
            }
            try {
                socket = new Socket(ip, port);
                writeSocket = new DataOutputStream(socket.getOutputStream());
                readSocket = new DataInputStream(socket.getInputStream());
 
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast("연결에 성공하였습니다.");
                    }
 
                });
                (new recvSocket()).start();
            } catch (Exception e) {
                final String recvInput = "연결에 실패하였습니다.";
                Log.d("Connect", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
 
    class Disconnect extends Thread {
        public void run() {
            try {
                if (socket != null) {
                    socket.close();
                    mHandler.post(new Runnable() {
 
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            setToast("연결이 종료되었습니다.");
                        }
                    });
 
                }
 
            } catch (Exception e) {
                final String recvInput = "연결에 실패하였습니다.";
                Log.d("Connect", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
 
    class SetServer extends Thread {
 
        public void run() {
            try {
                int port = Integer.parseInt(et2.getText().toString());
                serverSocket = new ServerSocket(port);
                final String result = "서버 포트 " + port + " 가 준비되었습니다.";
 
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(result);
                    }
                });
 
                socket = serverSocket.accept();
                writeSocket = new DataOutputStream(socket.getOutputStream());
                readSocket = new DataInputStream(socket.getInputStream());
 
                while (true) {
                    byte[] b = new byte[100];
                    int ac = readSocket.read(b, 0, b.length);
                    String input = new String(b, 0, b.length);
                    final String recvInput = input.trim();
                    
                    if(ac==-1)
                        break;
 
                    mHandler.post(new Runnable() {
 
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            setToast(recvInput);
                        }
 
                    });
                }
                mHandler.post(new Runnable(){
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast("연결이 종료되었습니다.");
                    }
                    
                });
                serverSocket.close();
                socket.close();
            } catch (Exception e) {
                final String recvInput = "서버 준비에 실패하였습니다.";
                Log.d("SetServer", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
    
    class recvSocket extends Thread {
 
        public void run() {
            try {
                readSocket = new DataInputStream(socket.getInputStream());
 
                while (true) {
                    byte[] b = new byte[100];
                    int ac = readSocket.read(b, 0, b.length);
                    String input = new String(b, 0, b.length);
                    final String recvInput = input.trim();
                    
                    if(ac==-1)
                        break;
 
                    mHandler.post(new Runnable() {
 
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            setToast(recvInput);
                        }
 
                    });
                }
                mHandler.post(new Runnable(){
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast("연결이 종료되었습니다.");
                    }
                    
                });
            } catch (Exception e) {
                final String recvInput = "연결에 문제가 발생하여 종료되었습니다..";
                Log.d("SetServer", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
 
    class CloseServer extends Thread {
        public void run() {
            try {
                if (serverSocket != null) {
                    serverSocket.close();
                    socket.close();
 
                    mHandler.post(new Runnable() {
 
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            setToast("서버가 종료되었습니다..");
                        }
                    });
                }
            } catch (Exception e) {
                final String recvInput = "서버 준비에 실패하였습니다.";
                Log.d("SetServer", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
    
    class sendMessage extends Thread {
        public void run() {
            try {
                byte[] b = new byte[100];
                b = "Hello, World!".getBytes();
                writeSocket.write(b);
                
            } catch (Exception e) {
                final String recvInput = "메시지 전송에 실패하였습니다.";
                Log.d("SetServer", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
 
    void setToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    
    // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}
 
 
cs




※자신의 안드로이드기기를 Server로 설정하고 싶을떼

1.PORT에 자신이 설정하고자 하는 PORT 번호를 입력한다.

2.'Set Server!' 버튼을 누르면 해당 안드로이드 기기가 Server의 역할을 맡게 된다.

3.'View info!' 버튼을 눌러 해당 안드로이드 기기의 IP 주소를 확인한다.


※자신의 안드로이드 기기를 Client로 설정하고 싶을때

1.위에서 확인한 Server로 설정한 안드로이드 기기의 IP주소를 확인한 후 IP 칸에 해당 주소를 입력한다.

2.Server에서 설정한 Port 번호를 입력한다.

3.'Connect!'버튼을 눌러 접속을 시도한다.

300x250

USB를 연결한 후 Logcat이 바로 보이지 않을 때 해결방법

 간혹가다 안드로이드 기기를 USB에서 분리하였다가 연결한 후 Logcat을 보려 하였을 때 Logcat이 보이지 않는 경우가 종종 있습니다. 이 경우 DDMS를 키신 후 자신의 Device를 선택하는 메뉴에서 클릭을 하시면 손쉽게 해결하실 수 있습니다.




 eclipse 오른쪽 윗 부분의 아이콘을 보시면 Open Perspective라는 메시지가 보이는 아이콘이 있습니다. 해당 아이콘을 클릭하신 후


여기서 DDMS를 선택하신 후 확인 버튼을 누르시면 DDMS 선택메뉴가 추가된 것을 확인하실 수 있습니다. DDMS를 선택하시면 현재 컴퓨터에 연결된 기기의 리스트를 보실 수 있습니다. 만약 이 때 자신의 기기가 보이지 않을 경우 아래와 같이 view Menu를 클릭하신 후 'Reset adb'를 선택하시면 접속된 안드로이드 기기 리스트가 갱신되는 것을 확인하실 수 있습니다.



 이후 자신이 Logcat을 보고자 하는 기기를 선택하시면 Logcat이 정상적으로 동작하고 있는 모습을 확인하실 수 있습니다.



300x250

xml graphical layout가 정상적으로 동작하지 않을 때

 안드로이드 애플리케이션을 개발하던 도중 XML을 편집하고 graphicl layout을 확인한 순간 다음과 같은 메시지를 보게 되었습니다.



Exception raised during rendering:java.util.Locale.toLanguageTag()Ljava/lang/String;

Exception details are logged in Window > Show View > Error Log


이러한 경우 대개 2가지 경우로 보실 수 있습니다.

1.자신이 Target으로 한 버전의 SDK가 설치되어 있지 않아 Graphical Layout가 지원되지 않는 경우

2.다른 무언가에 의해 충돌이 될 경우


 거의 대부분의 경우가 1번의 경우가 많은 것으로 보입니다. 이를 해결하는 방법은 상당히 간단합니다. SDK Manager를 실행하여 자신의 프로젝트에 해당되는 프로젝트를 다운로드하여 설치하신 뒤 Eclipse를 다시 실행하시면 Graphical Layout가 정상적으로 동작하는 것을 확인해 보실 수 있습니다.



300x250

XML의 Graphical Layout이 보이지 않는 경우 해결법

 오랜만에 Linux에서 안드로이드 작업을 하려고 새로운 프로젝트를 생성한 후 작업을 하려 했더니 아래와 같은 에러가 발생하면서 아무런 화면도 뜨지 않는 현상이 발생하였습니다.



The rendering target (Android 5.0.1) is still loading.

The layout will refresh automatically once the process is finished.


 처음에는 다소 당황했었으나 차분히 원인을 분석해 본 결과 안드로이드 버전을 낮은 버전으로 설정하는 바람에 Graphical Layout가 해당 버전과 맞지 않아 위와 같은 에러가 발생하였더군요 해결 방법은 간단합니다.


1. 먼저 에러가 발생한 xml 파일을 끈 후 다시 엽니다.


2. Graphical Layout을 클릭한 후 xml 창의 왼쪽 위 부분을 자세히 보면 아래쪽 화살표 모양의 버튼이 보입니다 해당 버튼을 클릭하면 현재 설정된 버전의 번호가 보입니다 해당 버전의 번호를 클릭하면 설정할 수 있는 버전 명단이 뜨는데 그 중 자신의 프로젝트에 해당하는 버튼을 클릭합니다.



 위의 과정을 거치만 아래와 같이 Grahpical Layout가 정상적으로 출력 되는 것을 확인하실 수 있습니다.




300x250

Extract contour area using OpenCV in Android(OpcnCV에서 검출된 영역의 넓이 구하기)

※이 프로그램은 OpenCV의 예제파일인 OpenCV Sample - color-blob-detection을 수정한 자료임을 알립니다.


OpenCV의 색상 검출 프로그램을 돌려보면 원하는 영역이 아래와 같이 붉은 테두리로 나타나는 것을 보실 수 있을 것입니다.



위 그림에서 보시는 이 빨간 테두리를 '등고선(Contour)'라 부릅니다. Android OpenCV에서는 이 빨간 테두리가 쳐진 부분의 넓이를 추출하는 기능을 가지고 있습니다. 이를 적용하는 방법은 아래외 같이 하시면 되겠습니다.


ColorBlobDetectionActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ColorBlobDetectionActivity extends Activity implements OnTouchListener, CvCameraViewListener2 {
    private static final String  TAG              = "OCVSample::Activity";
 
    private boolean              mIsColorSelected = false;
    private Mat                  mRgba;
    private Scalar               mBlobColorRgba;
    private Scalar               mBlobColorHsv;
    private ColorBlobDetector    mDetector;
    private Mat                  mSpectrum;
    private Size                 SPECTRUM_SIZE;
    private Scalar               CONTOUR_COLOR;
 
    private CameraBridgeViewBase mOpenCvCameraView;
    List<MatOfPoint>             contours;
    
}

 위의 코드에서 contour 변수는 원래 onCameraFrame() 함수 내에 있는 변수입니다. 이를 다른 곳에서 사용하기 위해서는 위의 코드에서 보시는 것 처럼 전역변수로 선언하는 것이 사용하기에 편합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
        mRgba = inputFrame.rgba();
 
        if (mIsColorSelected) {
            mDetector.process(mRgba);
            contours = mDetector.getContours();
            Log.e(TAG, "Contours count: " + contours.size());
            Imgproc.drawContours(mRgba, contours, -1, CONTOUR_COLOR);
 
            Mat colorLabel = mRgba.submat(4, 68, 4, 68);
            colorLabel.setTo(mBlobColorRgba);
 
            Mat spectrumLabel = mRgba.submat(4, 4 + mSpectrum.rows(), 70, 70 + mSpectrum.cols());
            mSpectrum.copyTo(spectrumLabel);
        }
        
        return mRgba;
    }

 onCameraFrame() 함수 내에 있던 contours 변수를 외부에 전역으로 선언하였으므로 이 함수 안에서는 그냥 변수에 값을 넣는 개념으로 이해하시면 되겠습니다.


 끝으로 Contour의 면적을 구하는 부분을 다음과 같이 설정하시면 Contour의 Area값을 얻으실 수 있습니다.
1
2
3
4
5
6
7
8
if(contours != null){
        double d;
        d=0;
        for(int i=0; i<contours.size();i++){
            d += Imgproc.contourArea(contours.get(i));
        }
            
}

 프로그램을 처음 구동할 때 contours의 값이 null일 경우가 있으므로 if문을 통해 null일 경우 예외처리를 해줍니다.

그 다음으로 contours 내의 모든 등고선의 값을 d에 저장을 하시면 화면에 표시되는 등고선(Contour)의 면적값들을 구하실 수 있습니다.


 위에서 보시는 바와 같이 contours는 List 변수로 내부에는 화면에 표시된 등고선의 수 만큼의 Mat 값들이 있는 것을 확인하실 수 있습니다. 이러한 contours를 반복문을 통해서 각 하나의 contour값을 확인하는 과정을 거칩니다.


 Imageproc.contourArea() 함수는 해당 Contour의 값을 구하는 기능을 가지고 있습니다. 이를 통하여 해당 contour의 넓이를 구해 모든 값을 더하게 되면 화면에 표시된 붉은 영역의 총 면적을 구할 수 있게 됩니다.




300x250

Color detection using Android openCV(안드로이드 OpenCV로 특정 색깔 인식)

※이 프로그램은 OpenCV의 예제파일인 OpenCV Sample - color-blob-detection을 수정한 자료임을 알립니다.


1.먼저 자신이 찾고자 하는 색깔의 Hsv를 알아내야 합니다. 만약 자신이 찾는 색깔의 Hsv를 모르는 경우 해당 색깔의 RGB를 Hsv로 변환하는 사이트를 이용합니다.

http://www.rapidtables.com/convert/color/rgb-to-hsv.htm




위의 RGB 색상 변환으로 나오는 HSV의 값을 아래와 같이 입력하시면 되겠습니다.

new Scalar(235, 75.2, 45.9, 0.0);


위에서 처리한 값을 이제 아래의 onCameraViewStarted() 함수에 입력해주시면 프로그램이 실행하자마자 해당 색상을 검출하는 것을 확인하실 수 있습니다.


ColorBlobDetectionActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void onCameraViewStarted(int width, int height) {
        mRgba = new Mat(height, width, CvType.CV_8UC4);
        mDetector = new ColorBlobDetector();
        mSpectrum = new Mat();
        mBlobColorRgba = new Scalar(255);
        SPECTRUM_SIZE = new Size(200, 64);
        CONTOUR_COLOR = new Scalar(255,0,0,255);
        
        
        mBlobColorHsv = new Scalar(235, 75.2, 45.9, 0.0);
        mDetector.setHsvColor(mBlobColorHsv);
        mIsColorSelected = true;
        
    }


300x250

Use front camera with OpenCV 2.4.9 for android(안드로이드 OpenCV에서 전면카메라 적용 방법)

 굳게 마음을 먹고 OpenCV를 사용하여 프로그래밍을 해보려고 하는데 SurfaceView를 통해 전면 카메라를 사용하는 방법과 다른 방법을 쓰는 것 같아 찾아보니 쉽지가 않더군요.


그래서 직접 OpenCV 라이브러리를 찾아보면서 적용방법을 찾아본 결과 다음과 같은 결과를 얻을 수 있었습니다.

안드로이드용 OpenCV에서 아래의 클래스가 카메라를 담당하는 것으로 보였습니다.


private CameraBridgeViewBase mOpenCvCameraView;

위 클래스를 이것 저것 적용하다보니 사용법을 찾아낼 수 있었습니다.

아래의 코드와 같이 onCreate()에서 굵게 표시한 부분을 코드에 삽입하면 OpenCV가 전면카메라를 사용하는 것을 확인하실 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "called onCreate");
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
 
        setContentView(R.layout.color_blob_detection_surface_view);
 
        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.color_blob_detection_activity_surface_view);
        mOpenCvCameraView.disableView();
        mOpenCvCameraView.setCameraIndex(CameraBridgeViewBase.CAMERA_ID_FRONT);
        mOpenCvCameraView.setCvCameraViewListener(this);
    }



300x250

블루투스를 통해 이미지를 바이트로 전송하기

 대부분의 안드로이드 폰에는 블루투스가 기본적으로 내장되어 있습니다. 블루투스 기능을 활용하면 굳이 인터넷이나 기지국을 거치지 않고도 안드로이드 기기간에 파일 전송이 가능하지요.

 이번 포스팅에서는 간단하게 다른 안드로이드 폰으로 사진을 전송하는 방법에 대해 알아보겠습니다.


 시작하기에 앞서 아래에 있는 블루투스 채팅 프로그램 프로젝트를 다운로드하여 Import 합니다.

 

BluetoothChat (1).zip





 위의 예제는 안드로이드 폰 상호간에 간단한 문자 채팅을 할 수 있는 프로그램입니다. 이 프로젝트에 이미지를 전송할 수 있는 기능을 넣어보도록 하겠습니다.


먼저 AndroidManifest.xml 파일에 다음과 같은 퍼미션을 추가 해줍니다.

1
2
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH" />


다음으로 메인 레이아웃인 main.xml에 그림을 띄울 ImageView와 그림을 전송하기 위한 Button을 다음과 같이 추가합니다.

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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <ListView android:id="@+id/in"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:stackFromBottom="true"
        android:transcriptMode="alwaysScroll"
        android:layout_weight="1"
    />
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
 
        <ImageView
            android:id="@+id/getimage"
            android:layout_width="match_parent"
            android:layout_height="150dp"
            android:src="@drawable/app_icon"
            android:scaleType="centerInside"
            />
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >
 
            <EditText
                android:id="@+id/edit_text_out"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:layout_weight="1" >
 
                <requestFocus />
            </EditText>
 
            <Button
                android:id="@+id/button_send"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/send" />
 
            <Button
                android:id="@+id/button_image"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/send_image" />
        </LinearLayout>
 
    </LinearLayout>
 
</LinearLayout>
 



다음으로 BluetoothChat.java를 수정해 보도록 하겠습니다.

먼저 추가된 Button과 ImageView에 대한 함수를 생성합니다.
1
2
    private Button mSendImage;
    private ImageView iv;


다음으로 setupChat() 함수 내에 다음과 같이 추가합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    private void setupChat() {
        
        ............
    
        iv = (ImageView)findViewById(R.id.getimage);
        mSendImage = (Button) findViewById(R.id.button_image);
        mSendImage.setOnClickListener(new OnClickListener(){
 
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_PICK,
                                               android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                startActivityForResult(intent,10);
            }
            
        });
    }


위에서 보는 것과 같이 이미지 갤러리를 Intent를 통해 불러오는 것을 볼 수 있습니다.

Intent를 통해 선택된 사진은 onActivityResult() 함수를 통해 얻을 수 있습니다.


onActivityResult() 함수에 다음과 같이 값을 추가합니다.

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
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        
        ............
        
        switch (requestCode) {
            
            ............
            
            case 10:
            if(resultCode==RESULT_OK && data != null){
                try {
                    Uri selectedImage = data.getData();
                Bitmap bp = Images.Media.getBitmap(this.getContentResolver(), selectedImage);
                    
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                bp.compress(CompressFormat.JPEG, 80, baos);
                byte[] imageByte = baos.toByteArray();
                
                iv.setImageBitmap(BitmapFactory.decodeByteArray(imageByte, 0, imageByte.length));
 
                if(mChatService.getState() == BluetoothChatService.STATE_CONNECTED){                        
                    int len;
                    final int size = 512;
                    byte[] sendByte = new byte[size];
                    ByteArrayInputStream bais = new ByteArrayInputStream(imageByte);
                    mConversationArrayAdapter.add("이미지 전송을 시작합니다.");
    
                        
                    sendByte[0] = 6;
                    sendByte[1] = 26;
                    sendByte[2] = 18;
                    mChatService.write(sendByte);
                    while( (len=bais.read(sendByte)) != -1){                            
                        if(len<512){
                            byte[] EOF = new byte[len];
                            for(int eof=0 ; eof<EOF.length; eof++){
                                EOF[eof] = sendByte[eof];
                            }
                            mChatService.write(EOF);
                            
                        }else{
                            mChatService.write(sendByte);
                        }
                    }
                    
                    sendByte[0] = 26;
                    sendByte[1] = 6;
                    sendByte[2] = 18;
                    mChatService.write(sendByte);
                        
                    mConversationArrayAdapter.add("이미지 전송이 완료되었습니다!");
                    mConversationArrayAdapter.add("Image Size : " + imageByte.length);
                }
                    
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
           }
           break;
     }
    }



마지막으로 Handler를 통해 전송된 사진의 값을 수정하는 부분을 다음과 같이 수정해 줍니다.


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
....
    boolean imageTransfer = false;
    boolean imageTransferW = false;
    ByteArrayOutputStream ReceiveImage;
 
    // The Handler that gets information back from the BluetoothChatService
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MESSAGE_STATE_CHANGE:
                if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
                switch (msg.arg1) {
                case BluetoothChatService.STATE_CONNECTED:
                    mTitle.setText(R.string.title_connected_to);
                    mTitle.append(mConnectedDeviceName);
                    mConversationArrayAdapter.clear();
                    break;
                case BluetoothChatService.STATE_CONNECTING:
                    mTitle.setText(R.string.title_connecting);
                    break;
                case BluetoothChatService.STATE_LISTEN:
                case BluetoothChatService.STATE_NONE:
                    mTitle.setText(R.string.title_not_connected);
                    break;
                }
                break;
            case MESSAGE_WRITE:
                byte[] writeBuf = (byte[]) msg.obj;
                String writeMessage = new String(writeBuf);
                mConversationArrayAdapter.add(writeBuf.length+" "+(int)writeBuf[0]
                                                +" "+(int)writeBuf[1]);
                mConversationArrayAdapter.add("나 :  " + writeMessage);
                
                break;
            case MESSAGE_READ:
                byte[] readBuf = (byte[]) msg.obj;
                // construct a string from the valid bytes in the buffer
                if(msg.arg1 > 2 && readBuf[0]==6 && readBuf[1]==26 && readBuf[2== 18){
                    imageTransfer = true;
                    ReceiveImage = new ByteArrayOutputStream();
                    mConversationArrayAdapter.add("Image Transfer Start!");
                    break;
                }
                
                if(msg.arg1 > 2 && readBuf[0]==26 && readBuf[1]==6 && readBuf[2== 18){
                    imageTransfer = false;
                    mConversationArrayAdapter.add("Image Transfer End!");
                    mConversationArrayAdapter.add("Image Size : " + ReceiveImage.size());
                    byte[] getImage = ReceiveImage.toByteArray();
                                        
                    iv.setImageBitmap(BitmapFactory.decodeByteArray(getImage, 0, getImage.length));
                    
                    break;
                }
                
                if(imageTransfer){
                    try {
                        ReceiveImage.write(readBuf);
                        if(readBuf.length==512)
                        mConversationArrayAdapter.add("Size : " + readBuf.length +", "
                                                    +(short)readBuf[0]+", " + (short)readBuf[511]);
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }else{
                    String readMessage = new String(readBuf, 0, msg.arg1);
                    mConversationArrayAdapter.add(mConnectedDeviceName+":  " + readMessage);
                }
                break;
            case MESSAGE_DEVICE_NAME:
                // save the connected device's name
                mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
                Toast.makeText(getApplicationContext(), "Connected to "
                               + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
                break;
            case MESSAGE_TOAST:
                Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
                               Toast.LENGTH_SHORT).show();
                break;
                
            case 6:
                //Toast.makeText(getApplicationContext(), "WTF", Toast.LENGTH_SHORT).show();
                
                break;
            }
        }
    };
....
cs


300x250

[NDK] openCV jni 소스 헤더파일이 include 되지 않을 때 해결방법

 안드로이드로 openCV를 해보기 위해 NDK를 설치한 후 직접 소스를 수정해보기 위해 소스파일을 연 순간 다음과 같은 장면이 펼쳐졌습니다.



 분명 직접 빌드까지 하였던 프로젝트임에도 이렇게 헤더파일이 잡히지 않아 당황하였습니다. 혹시 프로젝트를 빌드에 성공하지 못하셨던 분이라면 아래의 블로그를 참조해 주시길 바랍니다.


 http://adppark.tistory.com/303



 그럼 프로젝트의 헤더파일이 연결될 수 있도록 해보겠습니다.


1. 자신이 원하는 jni 프로젝트 폴더를 오른쪽 클릭 -> Properties -> C/C++ General -> Paths and Symbols를 선택합니다.



 2. 다음과 같은 화면이 나오면 Includes 탭 내의 Languages 내에서 C/C++ 둘중 Include directories가 있는 경우 모두 추가해줍니다.

오른쪽의 Add를 선택합니다.



3. File System...를 선택하신 후



4. "자신의 안드로이드 openCV 버전/sdk/native/jni/include"를 선택하신 후 확인 버튼을 누른 후



5. OK버튼을 눌러 Include directories에 추가합니다.



 6. 추가한 Include 디렉토리를 상단 두번째에 위치시킵니다. 오른쪽의 Move Up를 클릭하여 이동이 가능합니다.



 8. jni 폴더 내에 있는 Android.mk 파일을 연 후 include 부분에 자신의 OpenCV의 절대 경로로 수정합니다.



 9. 시작버튼을 눌러 '고급 시스템 설정'을 검색하여 나오는 항목을 클릭합니다.



10. 다음과 같이 시스템 설정 메뉴가 나오는 것을 보실 수 있습니다. 아래 환경변수(N)을 클릭합니다.



11. 새로 만들기(W)를 선택하신 후



12. 자신의 Android NDK의 환경변수를 설정합니다.

변수 이름은 'NDKROOT'로 설정하시고

변수값에는 자신의 NDK가 있는 위치를 입력하신 후 확인버튼을 누릅니다.



 위와 같은 과정을 거치시면 다음과 같이 헤더파일이 정상적으로 동작하는 것을 확인하실 수 있습니다.




300x250

<Project name> does not specify a android.test.InstrumentationTestRunner instrumentation or does not declare uses-library android.test.runner in its AndroidManifest.xml

 블로그에 올라와 있는 안드로이드 프로젝트 파일을 받은 후 이를 실행하기 위해 실행을 하려 했더니 다음과 같은 에러가 뜨면서 프로그램이 실행되지 않는 상황을 맟게 되었습니다.


 "<Project name> does not specify a android.test.InstrumentationTestRunner instrumentation or does not declare uses-library android.test.runner in its AndroidManifest.xml"



이렇게 나오는 경우 다음과 같이 진행해 주시면 되겠습니다.



해당 프로젝트 폴더 위에 마우스 우클릭 -> Run As(또는 Debug As) -> 1 Android Application


다음과 같이 프로그램을 실행하면 정상적으로 동작되는 것을 확인하실 수 있습니다.


만약 위와 같은 방법으로 실행이 되지 않으신 분들은

Run As -> Run Configurations... 를 선택하신 후

동작 시킬 프로그램을 선택하신 후 수동으로 프로그램을 실행할 타겟을 선택합니다.

300x250

MediaPlayer, VideoView 재생이 끝났을 때 이벤트 처리하기 (setOnCompletionListener)

public void setOnCompletionListener (MediaPlayer.OnCompletionListener listener)


 안드로이드를 통해 동영상이나 mp3 파일 등을 재생한 후 해당 미디어를 모두 재생이 되었을 때 설정하는 이벤트입니다. 사운드를 재생하는 MediaPlayer라던가 동영상을 재생하는 VideoView에서 사용할 수 있습니다.


 함수의 인자로 OnCompletionListener()가 사용됩니다. 이 함수는 인터페이스로 new 로 함수를 새로 선언함으로서 적용할 수 있습니다.


 아래는 MediaPlayer에 setOnCompletionListener 함수를 적용한 예제입니다. 음악이 모두 재생된 후 이벤트가 정상적으로 발생되는 것을 확인하실 수 있습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MediaPlayer mp;
mp = MediaPlayer.create(this, R.raw.melody);
mp.start();
mp.setOnCompletionListener(new OnCompletionListener(){
 
        @Override
        public void onCompletion(MediaPlayer mp) {
            // TODO Auto-generated method stub
            l1.setBackgroundColor(Color.TRANSPARENT);
            l2.setBackgroundColor(Color.TRANSPARENT);
            l3.setBackgroundColor(Color.TRANSPARENT);
            pb1.setVisibility(View.INVISIBLE);
            pb2.setVisibility(View.INVISIBLE);
            pb3.setVisibility(View.INVISIBLE);
            tv4.setText("");
            tag = 0;
        }
        
    });


300x250