안드로이드 프레임워크 프로그래밍(2) [안드로이드 커널 다운로드 및 컴파일]

 지난 포스팅에서는 안드로이드 프레임워크 환경 구축에 대해 알아보았습니다. 이번에는 안드로이드 커널을 다운로드한 후 이를 컴파일하는 과정을 진행해 보도록 합니다.


 Ubuntu 12.04 환경에서 Terminal을 통해 안드로이드 커널을 구축해 보도록 하겠습니다. Terminal은 Ctrl+Alt+T 버튼의 조합으로 실행하실 수 있습니다.


 커널을 다운받기에 앞서 아래의 프로그램을 설치합니다. apt-get 명령어를 통해 설치하며 아래의 내용을 그대로 터미널에 적은 후 엔터를 입력하면 되겠습니다.


$ sudo apt-get install git gnupg flex bison gperf build-essential zip curl libc6-dev libncurses5-dev:i386 x11proto-core-dev   libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386 libgl1-mesa-dev g++-multilib mingw32 tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386


$ sudo ln -s /usr/lib/i386-linux-gnu/mesa/libGL.so.1 /usr/lib/i386-linux-gnu/libGL.so


위 패키지를 설치하던 도중 libgl1-mesa-glx:i386을 설치하려 할 때 의존성에 의한 오류가 발생하는 경우가 있습니다. 이 경우 다음과 같은 순서대로 실행을 해 주시면 되겠습니다.


$ sudo apt-get install git gnupg flex bison gperf build-essential zip curl libc6-dev libncurses5-dev:i386 x11proto-core-dev   libx11-dev:i386 libreadline6-dev:i386 libglapi-mesa:i386 libgl1-mesa-dev g++-multilib mingw32 tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386

(libgl1-mesa-glx:i386 의존성을 해결하기 위해 먼저 설치)


$ sudo apt-get install libgl1-mesa-glx:i386


 이제부터 본격적으로 안드로이드 커널을 설치하는 단계에 들어가도록 하겠습니다. 우선 사용자 폴더에 bin이라는 이름의 폴더를 생성합니다.

$ mkdir ~/bin


다음으로 해당 폴더에 설치될 repo를 다른 폴더에서 쉽게 사용할 수 있도록 PATH를 추가합니다.

$ export PATH=$PATH:~/bin


이제 repo를 설치해 보도록 하겠습니다.

$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo


다운로드한 repo에 실행권한을 추가합니다.

$ chmod a+x ~/bin/repo


이로서 repo를 설치하였습니다. 안드로이드 커널을 설치할 폴더를 만드신 후 해당 폴더로 이동합니다.

$ mkdir ~/lollipop

$ cd ~/lollipop


커널을 다운받을 폴더로 이동하신 후 repo를 초기화 합니다. 해당 초기화 내용은 폴더 내에 저장됩니다.

$ repo init -u https://android.googlesource.com/platform/manifest -b android-5.0.2_r1


맨 뒤에 굵은 글씨가 자신이 설치하고자 하는 안드로이드의 버전입니다. 자신이 설치하고자 하는 버전을 확인하기 위해서는 다음 사이트를 참조하시면 되겠습니다.

https://android.googlesource.com/platform/manifest



위 목록에서 자신이 설치하고자 하는 버전을 선택하신 후 실행하면 repo가 알아서 해당 버전만 설치해줍니다.


$ repo sync

 해당 명령어를 실행하자마자 바로 안드로이드 커널을 다운로드 하는 과정이 시작됩니다. 컴퓨터의 성능 및 인터넷 환경에 따라 빠르면 1시간내에 다운로드가 완료됩니다. 필자의 경우 약 3시간 30분 정도 소요되었습니다.



 다운로드가 완료된 안드로이드 커널(Lollipop)의 용량은 약 13.7GB를 차지하고 있습니다. 마지막으로 안드로이드 커널을 컴파일해 보도록 하겠습니다.


$ make -j4


 뒷부분의 -j4는 4개의 쓰레드를 수행하여 컴파일을 하겠다는 의미입니다. 쓰레드의 갯수는 자신의 컴퓨터의 코어 갯수로 설정하면 최적하게 컴파일이 수행될 수 있습니다. 


※가상머신 등 메모리의 크기나 Ubuntu 설치시 linux_swap 영역의 크기가 부족할 시 make 컴파일을 진행하는 도중에 실패로 종료되는 경우가 있습니다. 이를 해결하기 위해서는 두 가지의 용량을 더 크게 하거나 make 컴파일시 -j 옵션을 쓰지 않은 단일 thread로 컴파일을 하면 이를 해결할 수 있습니다. 단, 단인 thread로 컴파일 시 컴파일 속도가 현저하게 느려질 수 있습니다.(필자의 경우 약 2일 정도 소요된 것으로 확인되었습니다.)



참고문헌 : 인사이드안드로이드, 송형주 외 3인 저, 위키북스, 2010


300x250

안드로이드 프레임워크 프로그래밍(1) [입문 및 동작환경 구축]

 안드로이드 프로그래밍에 처음 입문하는 경우 거의 대부분의 분들이라면 안드로이드 SDK를 활용하여 애플리케이션을 제작하는 분들이 많으실 것이라 생각합니다. 물론 애플리케이션을 제작하는 것 만으로도 상당히 흥미있는 작업이라고 생각합니다. 물론 임베디드 시스템에 관심있으신 분들이라면 이보다 더 깊이 공부하고 싶어 하시는 분들도 계시리라 생각합니다.

 이번 포스팅은 안드로이드의 애플리케이션 제작에서 만족하지 않고 한 걸음 더 들어가 프레임워크를 다루는 것에 대해 이야기해 볼까 합니다.




 위 그림은 안드로이드 운영체제의 내부 구조입니다. 가장 아래 부분에는 잘 알려져 있는 대로 리눅스 커널로 구성되어 있으며 상단쪽이 바로 흔히 SDK와 Java를 활용한 애플리케이션이 동작하는 부분입니다. 안드로이드의 프레임워크는 애플리케이션 바로 아래쪽에 위치하여 애플리케이션이 요구하는 기능들을 제공해 주는 역할을 한다고 보실 수 있습니다. 이번에 우리들이 다루고자 하는 목표가 바로 위의 붉은 네모로 표시한 프레임워크를 다루는 데에 초점을 맟출 것입니다.


※준비 사항

- 리눅스가 설치된 컴퓨터(Ubuntu 12.04 버전 추천)

→ Gingerbread 이후 버전부터 64bit 환경의 리눅스를 사용해야 합니다. Froyo 이전의 버전은 32bit 사용이 가능합니다.

- Java Developer Kit

→ 안드로이드 애플리케이션을 제작할 때 쓰는 그 JDK입니다. Froyo 이전의 버전은 JDK 6, Gingerbread 이후 버전은 JDK 7 버전을 사용해야 합니다.

- 안드로이드 어플리케이션 개발 IDE(Eclipse)


※시작하기에 앞서

-본 포스팅은 작성 당시 안드로이드 최신 버전인 Lollipop(5.02)를 기준으로 설명합니다. Lollipop의 경우 64bit의 환경에서 진행해야 수월하게 진행하실 수 있습니다.


-자신의 컴퓨터가 Windows 환경이신 분의 경우 리눅스 운영체제를 동작시킬 수 있는 가상머신을 사용해야 합니다. 흔히 사용되는 가상머신으로 VirtualBox와 VMware가 있습니다. 가상머신을 사용하는 방법에 대해서는 아래의 포스팅을 참고해 주시기 바랍니다.


 가상머신으로 프레임워크를 구축하기 위해서는 하드웨어의 여유 공간이 최소 50GB(우분투 설치 공간 포함 약 60GB)정도는 확보하셔야 합니다. 최초 설치시 용량을 확보해 두지 못하면 나중에 다시 용량을 날려야 할 때 굉장히 고생하게 됩니다. 만일을 대비해서 최대 100GB정도 확보하시면 문제 없이 사용하실 수 있습니다.


Ubuntu 12.04(32bit)

http://ftp.daum.net/ubuntu-releases/precise/ubuntu-12.04.5-desktop-i386.iso


Ubuntu 12.04(64bit)

http://ftp.daum.net/ubuntu-releases/precise/ubuntu-12.04.5-desktop-amd64.iso


Virtualbox

https://www.virtualbox.org/wiki/Downloads


VMware

https://my.vmware.com/web/vmware/free#desktop_end_user_computing/vmware_player/6_0|PLAYER-604|product_downloads


VirtualBox에 Ubuntu 설치하기

VMware에 Ubuntu 설치하기



다음으로 JDK를 설치하는 과정이 필요합니다. Lollipop의 경우 JDK 1.7 버전을 설치하셔야 합니다.

Ubuntu에 JDK 설치하기


끝으로 이클립스를 설치합니다.


http://www.eclipse.org/downloads/?


자신의 Ubuntu의 버전에 맞는 프로그램을 다운로드 받습니다.




다운로드 받은 파일 내에는 이클립스 폴더가 있습니다. 입축을 해제하시면 바로 사용하실 수 있습니다.

다음으로 우분투에 리눅스용 SDK 패키지를 설치한다.


http://developer.android.com/sdk/index.html


 다운로드 사이트에 접속하신 후 계속 아래로 스크롤 하시면 SDK Tools Only 메뉴를 보실 수 있습니다. 이 곳에서 Linux 전용을 클릭하도록 합니다.



 압축파일을 확인하시면 다음과 같은 내용을 보실 수 있습니다. 해당 폴더를 적절한 곳에 압축을 해제하시면 되겠습니다.


 다음으로 설치한 Eclipse에 안드로이드 플러그인을 설치하도록 하겠습니다.



메뉴에서 Help ->Eclipse Marketplace...를 선택합니다.




 검색창에 'android development tools'를 검색하면 Google에서 제공하는 ADT for Eclipse 메뉴를 보실 수 있습니다. 오른쪽의 Install을 클릭하여 설치합니다.




위와 같이 나왔을 때 라이센스에 동의한다고 한 후 Finish 버튼을 눌러줍니다.



 다음과 같은 창이 나타나면 설치가 정상적으로 완료된 것입니다. Yes 버튼을 누르면 Eclipse가 다시 시작됩니다.



 이클립스를 다시 실행하면 위에서 보는 바와 같은 경고문이 등장합니다. 앞에서 설치하였던 SDK가 아직 이클립스와 연동이 되지 않은 상황에서 다음과 같이 발생합니다. Open Preferences를 클릭하여 SDK 폴더의 위치를 설정합니다.



 Browse...에서 위에서 압축을 풀었던 SDK 도구의 폴더를 선택하신 후 OK를 눌러주세요. 만약 경고문이 뜰 경우 아래쪽 이미 존재하는 SDK 사용 메뉴를 선택하시면 되겠습니다.




 이클립스 메뉴에서 Window -> Android SDK Manager 를 클릭하면



 우리들이 Windows에서 흔히 사용했던 Android SDK Manager를 보실 수 있습니다. 여기서 자신이 사용하고자 하는 안드로이드의 버전을 설치하면 이로서 프레임워크 구축 환경을 다루기 위한 구성요소의 준비가 끝납니다.


다음 포스팅에서는 안드로이드 커널을 다운로드 하는 방법과 이를 컴파일 하는 방법에 대해 다루어 보도록 하겠습니다.




참고문헌 : 인사이드안드로이드, 송형주 외 3인 저, 위키북스, 2010

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

안드로이드 Native(NDK) 소스를 간단하게 보는 방법

안드로이드/프레임워크 2014. 11. 17. 01:04

 안드로이드의 카메라 기능에 대해 알아보고 싶어서 직접 안드로이드 라이브러리 내부에 있는 클래스를 열어 이리저리 해집고 다니다가 다음과 같은 코드를 목격하게 되었습니다.




 JNI를 접해본 경험이 없으신 분들께서는 아마 난생 처음 보는 녀석인 native를 보고 당황하실 분들이 있으시리라 생각합니다.

 JNI란 Java Native Interface의 약자로, 아주 간단하게 설명드리자면 안드로이드를 C/C++로 구현할 수 있는 기능입니다! Java 소스코드는 안드로이드 내에 있는 Dalvic머신(Lolipop 이후부터는 ART)에서 구동됩니다만 안드로이드 폰 내의 센서나 커널 등을 다루게 될 때는 JNI를 통해 C/C++로 접근하여야 합니다.

 위의 경우도 Camera 센서를 구동하기 위해 NDK를 통해 해당 함수가 구현된 것을 native로 나타내고 있다고 볼 수 있습니다. 그렇다면 이 Native 코드는 직접 볼 수 있는걸까요?



 가장 확실한 방법은 안드로이드의 OS 커널을 직접 다운받아 해당 소스코드를 직접 찾아보는 방법입니다. 커널은 위 사이트에서 다운로드 하실 수 있습니다.


http://source.android.com/source/downloading.html


 하지만 커널을 직접 구경할 일이 없는 분들께 이 방법은 번거롭기만 합니다. 게다가 자신이 원하는 소스코드를 바로 찾는 것도 상당히 불편합니다. 그렇다면 좀 더 편하게 안드로이드 native 소스코드를 볼 수 잇는 방법은 없는걸까요?


 

 위 사이트는 안드로이드 OS에 있는 Android와 Kernel의 소스코드를 인터넷으로 볼 수 있도록 되어 있습니다. 검색 기능을 제공하고 있어 자신이 찾고자 하는 소스코드를 더 쉽게 찾을 수 있습니다.


http://androidxref.com/


 위 사이트에 접속하신 후 자신이 보고자 하는 안드로이드 버전을 클릭합니다. 여기서는 현 시점에서 최신버전인 Lolipop을 선택하였습니다.




 검색하기 원하는 버전을 선택하신 후 위 사진에서 붉은 네모칸으로 표시한 'select all'을 선택하신 후 왼쪽 'Full Search' 부분에 자신이 찾고자 하는 native 함수명을 입력합니다.



 다음과 같이 해당 함수명을 가지고 있는 소스코드들이 검색에 잡히는 것을 확인하실 수 있습니다. 해당 함수가 존재하는 소스코드는 다음과 같이 찾으실 수 있습니다.


 Java 소스코드에 있는 native 함수가 있는 클래스의 패지명이 소스코드의 명칭이 될 경우가 대부분입니다. 예를들어 Camera.java가 위치한 패키지는 android.hardware.Camera입니다. 즉, jni 내부의 소스코드의 명칭은 android_hardware_camera.cpp가 해당 코드라고 할 수 있습니다.



 Native 소스코드 내의 함수명은 '클래스의 패키지경로_함수명()'으로 구성되어 있습니다. 즉 android.hardware.Camera 클래스 내에 있는 native 함수인 native_setup 함수는 해당 소스코드 내에서는 

'android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, ...)'

와 같은 형식으로 구성되어 있음을 확인하실 수 있습니다.

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

안드로이드 SDK 업데이트 후 이클립스에서 실행이 안될 때(Android ADT 재설치)

 모처럼 안드로이드 SDK Manager를 실행시켜 업그레이드를 시켜준 후 이클립스를 실행시켰더니 다음과 같이 빨간 줄들이 난무하면서 실행이 되지를 않는군요.


 지금까지 열심히 만들어왔던 애플리케이션 프로젝트가 이렇게 갑작스럽게 실행조차 되지 않는다면 상당히 당황스러울 것이라 생각합니다. 보통 일반적으로는 이클립스를 업데이트 하는 것 만으로 해결이 되는 경우가 많습니다.


이클립스를 업데이트 하는 방법은 다음과 같습니다.

Menu->Help->Check for Updates


 그런데 종종 이렇게 이클립스를 업데이트 시켰음에도 불구하고 위의 화면과 같이 계속 먹통을 일으키는 경우가 발생합니다. 이 경우 이클립스에 설치되었던 Android ADT를 완전히 삭제한 후 다시 설치해야 합니다.



1. 메뉴에서 Help->Install New Software... 를 실행합니다.


2. 다음과 같은 화면이 나왔을 경우 오른쪽 윗 부분의 Add를 클릭합니다.



3. Name은 자신이 원하는 대로 작성하셔도 됩니다.

Location에는 다음과 같은 주소를 입력합니다.

https://dl-ssl.google.com/android/eclipse/

입력을 마친 후 OK 버튼을 클릭합니다.



4. 정상적으로 수행되었을 경우 위와 같이 Developer Tools 메뉴가 나타납니다.

확인후 위의 그림과 같이 빨간색으로 표시한 'already installed'를 클릭합니다.



5. 현재 이클립스에 설치된 소프트웨어들의 목록이 나타납니다.

Id 부분에서 안드로이드와 관련된 소프트웨어를 모두 선택하신 후 Uninstall을 클릭합니다.



6. 목록들을 다시 한 번 확인한 후 Finish를 눌러 모두 삭제해줍니다.



7. Yes를 누른 후 이클립스를 다시 실행합니다.



8. 다시 이클립스를 실행한 후 Help->Install New Software를 클릭한 후 위에서 수행하였던 Add버튼을 누른 후 안드로이드 ADT를 다시 설치합니다.


설치를 완료하면 아래와 같이 프로그램이 정상적으로 수행되고 있음을 확인하실 수 있습니다.




300x250

숨겨진 Activity 혹은 Fragment의 Thread를 종료시키는 방법

 요즘 나오는 안드로이드 앱의 구성을 보면 여러개의 Fragment를 사용하여 Action Activity나 PagerView Activity를 활용하는 경우가 많습니다. 위의 Activity를 적절히 사용하면 몇 줄 안되는 코드로 화려한 화면 연출을 구성할 수 있기 때문이겠죠.

 그러나 안드로이드 개발 초보자들의 경우 위의 구성을 한 Activity와 Fragment의 동작 원리를 잘 이해하지 못할 경우 앱이 원하는 대로 동작을 하지 않는 경우가 수두룩 합니다. 특히 Fragment 내의 Thread를 활용하던 중 갑자기 앱이 죽어버리는 광경을 많이 보셨을 것이라 생각합니다.


 


 위에 보시는 화면은 Fragment를 활용하여 Tab과  Pager를 적용한 어플리케이션입니다. 보시는 바와 같이 4개의 탭이 존재하고 각 탭마다 고유의 Fragment를 가지고 있습니다.

 위의 화면으로 보기에는 첫 번째 'Simple'의 Fragment만이 동작을 하는 것 처럼 보이지만 사실은 보이는 탭을 중심으로 왼쪽과 오른쪽의 Fragment 또한 동작을 하고 있는 상황입니다. Pager 형식으로 화면으로 오른쪽에서 왼쪽으로 드래그를 하면 Contacts의 Fragment 화면으로 넘어가게 되는데요. 이는 'Simple'의 Fragment가 떠 있는 상황에도 'Contacts'의 Fragment가 미리 로딩이 되어 자신의 Fragment로 넘어가기를 기다리기 때문에 바로 뜰 수 있는 것이지요.


 Fragment의 이러한 점은 상당히 합리적인 듯 하지만 만약 Fragment에서 메인 핸들러를 활용하여 Fragment의 UI를 계속 바꾸어 주고 있는 상황이면 어떻게 될까요? 해당 Fragment가 다른 Fragment로 전환되는 순간 자신의 Fragment에 돌리던 Thread가 종료되지 않고 계속 동작을 하다가 Fragment의 UI를 수정하는 작업에 접근하게 되고 이로인해 숨겨진 Fragment의 UI를 건드릴 경우 앱이 죽어버리는 일이 벌이집니다!


 그렇다면 해당 Thread를 어떻게 처리하면 좋을지의 방안을 생각해보면 다음과 같은 해결책을 세울 수 있습니다.


 1. Fragment 화면이 넘어가게 될 때 Thread에 Interrupt를 걸어준다.

 - 위의 방안대로 수행하게 되면 Fragment가 숨겨짐과 동시에 Thread에 인터럽트가 걸리면서 Thread가 종료됨을 볼 수 있습니다. 코드는 다음과 같이 작성해 주시면 되겠습니다.


1
2
3
4
public void onStop(){
        super.onStop();
        U.interrupt();
    }


 하지만 분명 이렇게 Thread에 Interrupt를 걸어 종료시켰음에도 불구하고 Fragment의 UI를 건드려 앱이 죽어버립니다. 이는 UI 핸들러의 Message가 Queue 방식의 구조로 되어 있어 Queue에 UI와 관련된 작업의 Message가 남아 있을 경우 Thread가 종료되었음에도 UI의 동작에 접근하게 되는 상황이 발생합니다. 이에 대한 대처 방식으로 다음과 같은 방안을 다시 마련할 수 있습니다.


 2. Flag 방식을 사용하여 UI를 건드리는 Handler의 Message를 막는다.

 - 위의 코드에서 Flag만 추가해주시면 됩니다. 


1
2
3
4
5
public void onStop(){
        super.onStop();
        state = "DeActive";
        U.interrupt();
    }


 다음은 Fragment의 Thread를 종료시키고 UI와 관련된 작업도 중단시키는 방식에 대해 다룬 코드입니다. 이렇게 설계한다면 숨겨진 Fragment의 UI와 간섭할 일을 최소한으로 줄이실 수 있을 것입니다.



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
public class FragmentTab3 extends Fragment {
    UIThread U;
 UIHandler u;
    String state;
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
 
        u = new UIHandler();
 
        View rootView = inflater.inflate(R.layout.fragmenttab3, container,
                false);
        
        state = "Active";
        U = new UIThread();
        U.start();
 
        return rootView;
    }
    
    private class UIThread extends Thread{
        Message msg;
        boolean loop = true;
 
        public void run() {
            try {
                while (loop) {
                    Thread.sleep(100);
 
                    if(Thread.interrupted()){ //인터럽트가 들어오면 루프를 탈출합니다.
                        loop = false;
                        break;
                    }
                    
                    msg = u.obtainMessage();
                    msg.arg1 = 1;
 
                    u.sendMessage(msg);
                }
 
            } catch (InterruptedException e) {//sleep 상태에서 인터럽트가 들어오면 exception 발생
                // TODO Auto-generated catch block
                loop = false;
            }
 
        }
    }
 
    private class UIHandler extends Handler {
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.arg1) {
            case 1:
                if(state.equals("DeActive")) //Fragment가 숨겨진 상태일 때
                    break;
                //Fragment의 UI를 변경하는 작업을 수행합니다.
            }
        }
    }
    
    public void onStop(){
        super.onStop();
        state = "DeActive";
        U.interrupt();
    }
    
    public void onResume(){
        super.onResume();
        state = "Active";
    }


300x250