안드로이드의 블루투스 통신 예제입니다.

안드로이드의 블루투스 레퍼런스를 참고하고 싶으시다면 다음 링크로 들어가시면 됩니다.


레퍼런스 참고 -> 블루투스 API


1. 블루투스 통신 예제



이 예제를 활용하여 수정한다면 블루투스 통신을 자유자재로 사용할 수 있다.

이제부터 이 예제를 활용하여 블루투스 통신을 하는 예제를 만들어 보겠다.


2. 새 프로젝트 생성


액티비티에 버튼을 생성하여, 버튼을 눌렀을 경우 블루투스 연결을 할 수 있도록 만들어볼 예정이다.


메인화면 레이아웃은 다음과 같이 버튼과 현재 상황을 알려주는 TextView로 만들었다.

main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/btn_connect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="연결" />


    <TextView
        android:id="@+id/txt_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btn_connect"
        android:text="" />

</RelativeLayout>



기본 세팅이 끝났으니 이제부터 블루투스 통신에 관련된 코드를 작성해보겠다.


3. 권한 등록


제대로된 코딩을 시작하기 전에 권한등록(permission 등록)을 먼저 하도록 하자

블루투스 기능을 사용하기 위해서는 'BLUETOOTH'와 'BLUETOOTH_ADMIN'의 권한을 등록해줘야 한다.


BLUETOOTH : 커넥션 요구, 커넥션 수락, 데이터 전송 등의 블루투스 통신을 하기 위해 필요한 권한

BLUETOOTH_ADMIN : 디바이스 검색 시작, 블루투스 설정을 조작하기 위해 필요

(BLUETOOTH_ADMIN 권한을 사용하려면 BLUETOOTH 권한도 꼭 있어야 한다.)


Manifest 파일에 블루투스 권한 등록


AndroidManifest.xml

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


4. 새 Class 파일 생성


MainActivity에는 Button과 TextView만 배치해놓고 BluetoothService라는 Class를 만들도록 한다.

BluetoothService라는 Class에서 블루투스와 관련된 모든 작업을 처리할 예정이다. 

작업이 처리되는 과정은 핸들러를 통해서 MainActivity에 전달되어 상태값을 전달받아 TextView에 보여주거나 버튼의 상태를 변경해 주도록 할 것이다.


BluetoothService 생성자는 메인이 되는 MainActivity로 부터 Activity와 Handler 값을 받는다.


BluetoothService.java

public class BluetoothService {
	// Debugging
	private static final String TAG = "BluetoothService";
	
	private BluetoothAdapter btAdapter;
	
	private Activity mActivity;
	private Handler mHandler;
	
	// Constructors
	public BluetoothService(Activity ac, Handler h) {
		mActivity = ac;
		mHandler = h;
		
		// BluetoothAdapter 얻기
		btAdapter = BluetoothAdapter.getDefaultAdapter();
	}

MainActivity.java

private BluetoothService btService = null;

private final Handler mHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
		}
		
	};

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		setContentView(R.layout.main);
		
		// BluetoothService 클래스 생성
		if(btService == null) {
			btService = new BluetoothService(this, mHandler);
		}
	}


5. 블루투스 활성화


블루투스를 활성화 하기 위해 BluetoothAdapter클래스를 사용해서 다음 두 단계의 작업을 순서대로 진행한다.


① 블루투스 지원 확인

② 블루투스 활성화


① 블루투스 지원 확인

블루투스 통신을 사용하기 전에 디바이스가 블루투스를 지원하는지 확인할 필요가 있다. 디바이스가 블루투스를 지원하는지 확인해보도록 하자


BluetoothService.java

public boolean getDeviceState() {
		Log.d(TAG, "Check the Bluetooth support");
		
		if(btAdapter == null) {
			Log.d(TAG, "Bluetooth is not available");
			
			return false;
			
		} else {
			Log.d(TAG, "Bluetooth is available");
			
			return true;
		}
	}

getDeviceState() 라는 메소드를 만들어서 기기의 블루투스 지원여부를 확인 한다.

BluetoothAdapter가 null일 경우 블루투스 통신을 지원하지 않는 기기이다. (하지만 이러한 기기는 요즘 없을듯..?)


② 블루투스 활성화

getDeviceState()가 true를 반환할 경우 블루투스 활성화를 요청하도록 해보자.

블루투스 활성화 요청을 위해서 enableBluetooth()라는 메소드를 만들었다. 


BluetoothService.java

public void enableBluetooth() {
		Log.i(TAG, "Check the enabled Bluetooth");
		
		
		if(btAdapter.isEnabled()) {		
			// 기기의 블루투스 상태가 On인 경우
			Log.d(TAG, "Bluetooth Enable Now");
			
			// Next Step
		} else {		
			// 기기의 블루투스 상태가 Off인 경우
			Log.d(TAG, "Bluetooth Enable Request");
			
			Intent i = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
			mActivity.startActivityForResult(i, REQUEST_ENABLE_BT);
		}
	}

기기의 블루투스 상태가 On일 경우 다음 단계를 실행하면 되고, 

기기의 블루투스 상태가 Off일 경우 블루투스 활성화를 요청하는 알림창을 띄운다.


알림창에서 확인/취소를 선택할 경우 결과는 MainActivityonActivityResult()메소드로 들어온다. 

그 전에 startActivityForResult의 개념에 대해서 알고 있어야 이해하기 편한데, 이 것은 구글링을 통해서 알아보는것을 추천한다.


알림창의 확인/취소 결과는 MainActivity에 onActivityResult()메소드로 전달되는데 소스를 보면 다음과 같다.


MainActivity.java

public void onActivityResult(int requestCode, int resultCode, Intent data) {
        
        switch (requestCode) {          
        case REQUEST_ENABLE_BT:
            // When the request to enable Bluetooth returns
            if (resultCode == Activity.RESULT_OK) {
                // 확인 눌렀을 때
                //Next Step
            } else {
                // 취소 눌렀을 때
                Log.d(TAG, "Bluetooth is not enabled");
            }
            break;
        }
}


이제 MainActivity에서 버튼을 클릭했을 때 블루투스 활성화를 위한 이벤트 처리만 해주면 된다.


MainActivity.java

@Override
	public void onClick(View v) {
		if(btService.getDeviceState()) {
			// 블루투스가 지원 가능한 기기일 때
			btService.enableBluetooth();
		} else {
			finish();
		}
	}

여기까지 블루투스를 활성화 하는 방법에 대해서 알아보았다.

블루투스가 활성화 되었을 때(resultCode가 Activity.RESULT_OK일때) 기기 검색을 통해 페어링을 시켜주면 되는데, 이 부분은 다음장에 포스팅 하도록 하겠다.


전체 소스는 다음과 같고, 프로젝트를 첨부파일로 올린다.


EX_Bluetooth(1).zip



MainActivity.java

public class MainActivity extends Activity implements OnClickListener {
	// Debugging
	private static final String TAG = "Main";
	
	// Intent request code
	private static final int REQUEST_CONNECT_DEVICE = 1;
	private static final int REQUEST_ENABLE_BT = 2;
	
	// Layout
	private Button btn_Connect;
	private TextView txt_Result;
	
	private BluetoothService btService = null;
	
	
	private final Handler mHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
		}
		
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Log.e(TAG, "onCreate");
		
		setContentView(R.layout.main);
		
		/** Main Layout **/
		btn_Connect = (Button) findViewById(R.id.btn_connect);
		txt_Result = (TextView) findViewById(R.id.txt_result);
		
		btn_Connect.setOnClickListener(this);
		
		// BluetoothService 클래스 생성
		if(btService == null) {
			btService = new BluetoothService(this, mHandler);
		}
	}

	@Override
	public void onClick(View v) {
		if(btService.getDeviceState()) {
			// 블루투스가 지원 가능한 기기일 때
			btService.enableBluetooth();
		} else {
			finish();
		}
	}
	
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d(TAG, "onActivityResult " + resultCode);
        
        switch (requestCode) {

        case REQUEST_ENABLE_BT:
            // When the request to enable Bluetooth returns
            if (resultCode == Activity.RESULT_OK) {
            	
            } else {

                Log.d(TAG, "Bluetooth is not enabled");
            }
            break;
        }
	}

}


BluetoothService.java

public class BluetoothService {
	// Debugging
	private static final String TAG = "BluetoothService";
	
	// Intent request code
	private static final int REQUEST_CONNECT_DEVICE = 1;
	private static final int REQUEST_ENABLE_BT = 2;
	
	private BluetoothAdapter btAdapter;
	
	private Activity mActivity;
	private Handler mHandler;
	
	// Constructors
	public BluetoothService(Activity ac, Handler h) {
		mActivity = ac;
		mHandler = h;
		
		// BluetoothAdapter 얻기
		btAdapter = BluetoothAdapter.getDefaultAdapter();
	}
	
	/**
	 * Check the Bluetooth support
	 * @return boolean
	 */
	public boolean getDeviceState() {
		Log.i(TAG, "Check the Bluetooth support");
		
		if(btAdapter == null) {
			Log.d(TAG, "Bluetooth is not available");
			
			return false;
			
		} else {
			Log.d(TAG, "Bluetooth is available");
			
			return true;
		}
	}
	
	/**
	 * Check the enabled Bluetooth
	 */
	public void enableBluetooth() {
		Log.i(TAG, "Check the enabled Bluetooth");
		
		
		if(btAdapter.isEnabled()) {		
			// 기기의 블루투스 상태가 On인 경우
			Log.d(TAG, "Bluetooth Enable Now");
			
			// Next Step
		} else {		
			// 기기의 블루투스 상태가 Off인 경우
			Log.d(TAG, "Bluetooth Enable Request");
			
			Intent i = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
			mActivity.startActivityForResult(i, REQUEST_ENABLE_BT);
		}
	}
	
}


다음 글 : 블루투스 기기 검색하기 글 보기



 안드로이드 프로그래밍에서 어플만의 UI를 갖는것은 필수라고 생각한다.

 앱의 기능은 구글링을 통해서 어느정도 구현을 할 수가 있는데, 똑같은 기능을 갖고 있는 앱 중에서 UI가 이쁜 앱과 UI가 평범한 앱중에는 당연히 UI가 이쁜 앱을 쓰지 않을까??

 독특한 특색을 갖는 UI를 꾸미기 위해서 이번에는 액션바를 활용해 보겠다. (커스텀하게 액션바를 만드는 내용은 다음에 시간날때 올리도록 하겠다..)


 글쓴이의 경우 앱에 액션바를 사용하였는데 액션바에 배경이미지를 넣고 사용하기 위해 커스텀하게 구현을 하였다. 버튼역시 테마에 맞게 이미지를 넣어 구현을 하였다. 


 이때 문제가 발생하였는데 이미지를 넣은 버튼을 배치했을 때 디바이스에 따라 해상도가 모두 다르기 때문에 버튼의 크기가 제 각각 이었다. 

 액션바의 크기는 모든 디바이스에서 비율이 똑같을 것이라고 생각하고 크기를 절대값으로 조절하였더니 문제가 생긴것이다. 해상도에 따라서 액션바의 크기도 제각각이었다. 그래서 액션바의 크기를 구해서 버튼크기를 조절하려고 했다. 그러나 액션바의 크기가 0으로 나와서 크기를 알 수 가 없었다.

 그렇다.. 액션바의 크기는 0이었던 것이다.. 

 하지만 액션바의 크기를 구할 수 있는 방법이 있다!!

 액티비티가 실행될 때 onCreate 안에서 액션바의 크기를 구하면 0이 나오지만, 스레드를 이용하면 크기를 구할수 있다.

 일단 Action Bar 와 Status Bar 크기를 구하는 코드부터 살펴보자


private void getStatusBarSize() {
	Rect rectgle = new Rect();
	Window window = getWindow();
	window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
	int StatusBarHeight = rectgle.top;
	int contentViewTop = window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
	int TitleBarHeight = contentViewTop - StatusBarHeight;
	
	Log.i("getHeight", "StatusBar Height= " + StatusBarHeight + " TitleBar Height = " + TitleBarHeight);
}

위의 메소드를 onCreate 안에서 호출하지말고 스레드를 통해서 호출한다.


@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);

	LinearLayout layout = new LinearLayout(this);
	layout.post(new Runnable() {
		@Override
		public void run() {
			getStatusBarSize();
		}
	});
}


스레드를 통해서 getStatusBarSize를 호출하면 크기를 구할 수 있다.


여기서 또 한가지 팁은 액션바 사이즈를 구해서 액션바 사이즈를 이용해서 액션바에 들어가는 버튼이나 이미지 크기를 조절하려면 onCreate안에서 계산하면 안된다. 스레드를 통해서 크기를 구하기 때문에 onCreate가 끝나고 크기가 구해질 수 가 있기 때문에 액션바에 들어가는 버튼의 크기를 조절하고자 한다면 스레드 안에서 getStatusBarSize()를 호출하고 난 뒤에 크기를 조절해 주도록 하자!


액션바를 커스텀하게 만드는 방법은 다음에 자세히 포스팅 하도록 하겠다.


android-ndk-r4-windows-crystax-4 이다.

NDK를 사용하고 안드로이드에 OpenCV를 올리기 위해서는 정식 NDK 버전이 아닌 crystax ndk 버전이 필요하다고 한다?? 정식 NDK로 잘 안되는 경우는 이 파일을 받아서 사용하길 바란다.


다운로드

안드로이드 앱의 폴더와 서버의 폴더 생성 시간을 비교하고 변경하여 동기화 하는 작업을 수행하는 도중 setLastModified() 메소드가 제대로 작동하지 않았다. 

폴더의 생성시간에 접근을 하는 메소드는 setLastModified()가 맞는데 몇번을 수행하여도 폴더의 시간이 변경되지 않았다.


setLastModified()의 반환형이 boolean 형임을 파악하고 Log를 계속 찍어보았다.

return 값은 계속해서 false...


실행한 디바이스는 갤럭시 탭 10.1로 안드로이드 4.0 버전이었다.


분명히 폴더시간이 바뀌어야 되는데 바뀌지 않았다. 구글링을 한 결과 setLastModified() 메소드가 일부 디바이스에서 작동하지 않는다는 답변이 있었다. 


아마도 삼성제품에 대다수 해당되는 것 같았다. 해킹이나 버그를 방지하기 위해서 접근하지 못하게 해놓았다는 답변이다. 정말 거지같다..


결국 SDcard의 폴더 생성시간은 변경할 수 없다.


폴더 생성시간을 변경을 하지는 못하고, 서버의 폴더 생성 시간과 비교를 하기 위해서는 따로 DB를 만들거나 또 다른 편법을 써야할 것 같다..


이 문제로 고민하는 분이 없길 바란다...

영상처리에서 RGB 색상모델을 이용하여 간단하게 색상을 검출하는 방법에 대해서 알아보겠다.


RGB 색상모델을 실제 영상처리에서 사용하는 일은 드물다 RGB 색상모델은 조명에 영향을 많이 받기 때문에 정확도가 많이 떨어진다. 그러나 간단하게 색을 인식하는 용도로 RGB 색상모델을 쓰기도 한다. 


영상처리의 기초로 간단하게 붉은색을 인식하는 예제를 알아보도록 하겠다.


소스를 먼저 보고 설명을 하도록 하겠다.


#include <opencv\highgui.h>

int main() {
	IplImage *frame;	// 원본영상
	IplImage *red;		// 색 검출 영상

	int width, height;	// 창의 넓이, 높이
	int i, j, index;
	unsigned char R, G, B;

	CvCapture* capture = cvCaptureFromCAM(0);
	frame = cvQueryFrame(capture);

	// frame 에서 width와 height 정보를 가져온다.
	width = frame->width;
	height = frame->height;
	
	cvNamedWindow("Original",0);
	cvNamedWindow("Red Tracking",0);
	cvResizeWindow("Original", width, height);
	cvResizeWindow("Red Tracking", width, height);

	// frame와 같은 크기를 갖는 3채널 영상 생성
	red = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3);

	while(capture) {
		frame = cvQueryFrame(capture);

		for(i = 0; i < (frame->height); i++) {
			for(j = 0; j < (frame->widthStep); j += (frame->nChannels)) {
				index = i * frame->widthStep + j;

				R = frame->imageData[index+2];	// R영역
				G = frame->imageData[index+1];	// G영역
				B = frame->imageData[index+0];	// B영역

				// 붉은색으로 예상되는 범위 설정
				if(R > 200 && G < 200 && B < 200) {
					// 흰색으로
					red->imageData[(i*frame->widthStep)+j + 0] = 255;	
					red->imageData[(i*frame->widthStep)+j + 1] = 255;
					red->imageData[(i*frame->widthStep)+j + 2] = 255;
				}
				else {
					// 검정색으로
					red->imageData[(i*frame->widthStep)+j + 0] = 0;
					red->imageData[(i*frame->widthStep)+j + 1] = 0;
					red->imageData[(i*frame->widthStep)+j + 2] = 0;
				}
			}
		}

		cvShowImage("Original",frame);
		cvShowImage("Red Tracking",red);

		if(cvWaitKey(33) >= 27)
			break;
	}
	cvReleaseCapture(&capture);
	cvReleaseImage(&red);
	cvDestroyWindow("Original");
	cvDestroyWindow("Red Tracking");

	return 0;
}

붉은색 영역은 본인이 임의로 설정하였다. 먼저 결과부터 확인해 보겠다.


실행결과 :


추출된 범위를 흰색으로(255) 나타내고 그 외의 범위는 검정색으로(0)으로 나타내었다.


이번 포스팅은 색상 검출에 대한 내용이므로 다른 코드에 대한 설명은 나중에 하도록 하겠다.


색상 범위


색상 검출한 결과를 나타내기 위해서 red라는 3채널의 IplImage 구조체를 선언하였다. 

카메라에서 프레임을 받아오는 동안 (while문 안에서) for 2개를 이용하여 전체영상의 각 ImageData에 접근하여서 R,G,B의 크기를 판단하였다. 

R,G,B 영역의 범위는 본인이 임의로 설정하였는데, 빨간색을 검출한다고 해서 꼭 R이 255의 값을 갖고 G와 B값은 0을 갖는것이 아니다. 컴퓨터로 작업을 한 영상이라면 R=255, G=0, B=0 의 값을 갖을수는 있지만 실제 영상에서 빨간색은 R,G,B 각각의 색상모델의 조합으로 나타내진다. 또한 그 값은 조명에 의해, 주변 환경에 의해 충분히 달라질 수 있다. 그렇기 때문에 색상검출을 위해서 실제로 사용할 경우 정확도가 크게 떨어지게 된다. 이러한 단점을 해결하기 위해 다양한 색상모델이 있고 대표적으로 YCbCr 모델을 많이 사용하고 있다. YCbCr에 대해서는 나중에 알아보도록 하겠다. 


RGB영역에서 색 검출을 굳이 하고싶다면 색 검출을 위한 범위는 직접 지정하길 바란다. 위의 코드의 범위는 본인마음대로 대충 때려넣은 범위이다.


widthStep


while문 안에서 2개의 for 루프를 이용하여 영상의 넓이(width)와 높이(height)만큼의 크기의 배열에 접근하고 있다. 이때 두번째 for 루프에는 width가 아닌 widthStep이 들어가 있다. 분명 넓이는 width인데 왜 widthStep을 쓸까?


컬러영상은 다음과 같이 B, G, R 순서의 세 영역으로 이루어져 있다.


하나의 컬러 영상은 B평면(B값만 존재), G평면(G값만 존재), R평면(R값만 존재)에 존재하는 각 값들의 합으로 이루어 진다. 3개의 평면이 하나로 겹쳐지면서 컬러 영상이 만들어 지는것이다. 우리는 각 영역의 색상값을 알아내기 위해 하나의 평면을 갖는 컬러 영상을 3개의 평면으로 나누어 볼 필요가 있다. 그렇기 때문에 width 대신 widthStep을 이용하는데 widthStep은 (영상의넓이) x (채널의 갯수) 의 값을 갖고있다. 여기서는 채널이 3채널이기 때문에(width x 3)의 값을 갖고 있는 것이다. 

한번 for루프에 들어갈 경우 [index+2], [index+1], [index+0] 으로 컬러영상에서 같은 위치에 있는 RGB평면을 동시에 체크하기 때문에 for루프에서 증가값은 3이 되어야 한다. 그렇기 때문에 j값은 nChannel 만큼 증가하게 된다. 


다시한번 말하지만 RGB색상모델은 실제 영상처리에서 사용하기에 좋은 색상모델은 아니다. 




제 머리속에 있는 내용을 정리해 보았습니다! 틀린내용이 있을수도 있고 저의 머리속에 있는 내용을 글로 옮겼기 때문에 이해가 안되는 분들도 계실꺼 같습니다. 댓글을 남겨주신다면 친절히 답변해 드리겠습니다.

NDK를 사용할 시 ndk-build를 이클립스에서 편하게 할 수 있는 방법에 대해서 알아보도록 하겠다.

NDK-build를 하여 .so파일을 생성할 때 cmd창이나 cygwin 터미널을 이용하지 않고 이클립스에서 편하게 할 수 있다.


먼저 생성한 프로젝트의 [Properties] -> [Builders] -> [New] 를 선택한다.



Program 선택


이름은 NDK_Builder로 하였다. 

Location에는 Browse File System을 선택하여 ndk-build가 있는 위치를 지정해준다.

Working Directory는 현재 프로젝트의 위치를 지정해주면 되는데 Browse Workspace를 선택하여 해당 프로젝트를 선택해준다.


다음은 같은 창에서 Refresh를 선택하고 아래 그림과 같은 순서로 선택한다. 3번을 선택하면 새 창이 뜨는데 해당 프로젝트 파일의 jni 폴더를 선택해 준다.


다음은 Build Options로 넘어가겠다. 1번은 선택사항이다. 자동으로 빌드를 하는건데 컴퓨터가 느려지길 원하지 않는다면 선택을 하지 않아도 된다. 1번을 제외한 나머지 항목은 필수로 선택을 한다. 여기서는 해당 프로젝트의libs 폴더를 선택한다. 


설정이 완료 되었다. 이제 프로젝트를 빌드해보자.


콘솔창에 다음과 같이 나왔다면 빌드에 성공한것이다.


.c 혹은 .cpp파일의 내용이 변경되었을 경우는 [Project] -> [Clean] 에서 해당 프로젝트만 Clean을 해주고 다시 디버깅을 하면 된다.


이제 cmd창에 입력할 필요 없이 이클립스에서 clean -> Debug로 간단하게 ndk build를 할 수 있다.

하지만 이후에 다른 프로젝트에서 똑같은 방법으로 했을 경우 이름이 중복된다는 경고를 볼 수 있다. 그럴때는 Builder의 이름을 NDK_Builder(2) 이런식으로 바꿔주도록 하자. 왜 굳이 따로 또 만들어야 되는지는 모르겠다.. 


이 문제를 해결할 방법을 아시는분은 알려주세요~!^^



NDK를 이용한 기본 예제이다. 간단한 프로그래밍은 기본예제의 변형으로 가능할 것이다. 더 깊은 내용은 나중에 포스팅 하도록 하겠다.


NDK 설치 완료후 제대로 설치가 되었는지 이 예제를 통해서 확인해보길 바란다. 먼저 프로젝트를 하나 생성한다. 프로젝트명은 HelloJNI로 하였다.


프로젝트 생성후 jni라는 이름의 폴더를 하나 생성한다. 이 폴더안에 2개의 파일을 생성할 예정이다.

2개의 파일 이름은 Android.mkhello-jni.c 이다.


jni폴더 생성후 [마우스 오른쪽 클릭] -> [New] -> [File] 을 눌러 파일을 생성한다.




위의 그림의 방식처럼 hello-jni.c 와 Android.mk 파일을 만들었다면 두 파일에 내용을 넣는다. 먼저 hello-jni.c 파일부터 내용을 넣겟다.


hello-jni.c

#include <string.h>
#include <jni.h>

jstring Java_tjssm_hellojni_MainActivity_stringFromJNI( JNIEnv* env, jobject thiz )
{

    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

NDK 설치를 확인하기 위한 간단한 예제이기 때문에 자세한 설명은 생략하도록 하겠다. 

여기서 주의할 점은 jstring Java_tjssm_hellojni_MainActivity_stringFromJNI()이다.

jstring은 return 타입이고 그다음은 Java_패키지명1_패키지명2_class명_class에서만든메소드명() 이다.


ex) return타입이 jstring 이고 패키지명이 tjssm.hellojni이고 class명이 MainActivity이고, class에 만들어 놓은 메소드명이 stringFromJNI 이기때문에jstring Java_tjssm_hellojni_MainActivity_stringFromJNI() 이라고 쓴다.


여기서 주의할 점은 패키지명이나 class명에 '_'기호가 들어갈 경우 에러가 난다는 것이다. ndk를 이용한 프로그래밍에서는 '_'기호를 넣지 않도록 한다.


다음은 Android.mk 파일에 내용을 넣겠다.


Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

LOCAL_MODULE 은 c파일의 이름

LOCAL_SRC_FILES 에는 c파일의 이름에 .c를 붙여주면 된다.


다음은 JAVA소스이다. jni를 호출하는 MainActivity의 소스이다.


MainActivity.java

public class MainActivity extends Activity {
	
	private TextView tv;
	
	static {
		System.loadLibrary("hello-jni");
	}
	
	public native String stringFromJNI();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		
		tv = (TextView) findViewById(R.id.tv_result);
		tv.setText(stringFromJNI());
	}
}

System.loadLibrary에 이전에 만들었던 c파일의 이름인 hello-jni를 넣어준다. 

public native String stringFromJNI() 라는 메소드가 jni와 연결된다.

return 타입이 String 이므로 이전에 작성했던 hello-jni로부터 String을 return 받아 tv라는 TextView로 값이 전달된다.


위와 같이 소스를 작성하였다면 ndk-build를 실행한다. cygwin 폴더안의 android-ndk폴더를 환경변수로 등록을 하였다면 cmd 창을 실행하여 현재 작성한 프로젝트 파일의 경로로 이동한다. 그후에 ndk-build라는 명령어를 입력하면 현재 프로젝트의 libs->armeabi 라는 폴더가 생기며 폴더안에 파일이 생성되어 있을것이다.


이제 이 프로젝트를 실행시키면 다음과 같은 결과를 얻을 수 있다.



매번 cmd창을 이용하여 ndk build를 할 수는 없으니 다음에는 이클립스에서 ndk build하는 방법을 알아보도록 하겠다.



2014/01/27 - [Programming/안드로이드] - [안드로이드] Eclipse에서 NDK-build 하기



안드로이드 프로그래밍에서 C나 C++ 언어를 사용하기 위해서는 NDK를 설치하여야 한다. NDK 설치 방법에 대해 알아보도록 하자.


Cygwin 설치

NDK를 설치하기 위해서는 Cygwin을 먼저 설치하여야 한다.


다운받기


각자 컴퓨터 사양에 맞는 파일을 다운받는다.



다운이 완료된 설치파일을 실행한다.



인터넷에서 파일을 받는 항목 선택



경로는 C:\cygwin 으로 설정한다.



패키지 파일을 다운로드할 폴더를 지정한다. 어느 경로를 지정하던지 상관없다.



인터넷 연결 선택



다운로드 사이트를 선택한다. 전세계적으로 사이트가 있는데 한국사이트는 없는듯 하다 ㅠㅠ 가까운 일본 사이트를 선택하도록 하자



다음을 누르면 아래와 같은 화면이 뜨는데 항목중에 Devel 항목이 Default로 표시되어 있을것이다. 클릭하여 Install로 바꿔주고 다음을 누른다.



다음을 누르면 엄청난 시간이 걸린다.. 잠시 쉬도록 하자



완료가 되면 다음과 같은 창이 뜬다.



이렇게 Cygwin 설치가 완료되었다.


환경변수 등록

환경변수를 등록하자

[내컴퓨터] -> [속성] -> [고급] -> [환경변수] 에서 시스템 변수Path 값을 편집한다.

C:\cygwin\bin;

C:\cygwin\usr\include;

두개의 환경변수를 등록한다.



환경변수 등록후 Cygwin을 실행하면 다음과 같은 화면이 나올 것이다.

이렇게 Cygwin설치를 완료한다.



NDK 설치

이제 NDK를 다운받아 설치하도록 하자


다운받기


각자 컴퓨터에 맞는 사양의 파일을 다운받는다.



다운로드 받은 파일을 압축 해제후 폴더안의 android-ndk-r9c(android-ndk-버전) 폴더를 C:\cygwin\home\Administrator 폴더로 이동시킵니다. (C:\cygwin\home\사용자계정)



이제 같은 경로에 있는 .bashrc파일을 수정한다. .bashrc파일을 이클립스에 드래그해서 열고 맨 아래에 다음 코드를 추가한다.

export ANDROID_NDK_ROOT=/home/사용자계정/ndk버전

ex) export ANDROID_NDK_ROOT=/home/Administrator/android-ndk-r9c



NDK를 이용한 프로그램을 작성한뒤 ndk build를 하기위해 android ndk의 폴더의 경로를 환경변수로 설정한다.

C:\cygwin\home\Administrator\android-ndk-r9c; (C:\cygwin\home\사용자계정\android-ndk-r9c)


이렇게 안드로이드 NDK 개발환경 구축이 완료되었다.


2014/01/27 - [Programming/안드로이드] - [안드로이드] NDK 기본 예제


2014/01/27 - [Programming/안드로이드] - [안드로이드] Eclipse에서 NDK-build 하기


+ Recent posts