카테고리 없음

NDK를 이용한 Android 예제

53프로 2012. 2. 28. 15:21
서너달 만에 다시 안드로이드를 할려기 기억이 가물 가물하다.
예전에 하던거 쉽게 까먹는다 그래서 예전에 보고 하던 실습 예제 스크립해 둔다. 까먹어도 쉽게 찾기 위해서...

http://viiiin.tistory.com/12

1. 개발환경

Host OS
  - Windows 7 Ultimate x86

Cygwin
  - version 2.712
  - 
http://www.cygwin.com

Eclipse
  - Eclipse IDE for Java Developers : Galileo Packages(ver 3.5)
  - http://www.eclipse.org/downloads

JDK
  - Java SE Development Kit 6u21
  - http://www.oracle.com/technetwork/java/javase/downloads/index.html

Android SDK & NDK
  - Android SDK Package : android-sdk_r06-windows.zip
  - Android NDK Package : android-ndk_r4b-windows.zip
  - Android SDK and AVD Manager(Packages and Archives) : http://dl-ssl.google.com/android/eclipse
  - http://developer.android.com/sdk/index.html


2. Cygwin 설치 및 환경설정

Cygwin을 사용하는 이유는 기본적으로 Android 개발 환경이 Linux 기반으로 되어 있기 때문이고, ndk-build 명령으로 빌드하기 위함입니다. 개인적으로 MinGW를 사용하시는 분은 MinGW에서 하셔도 좋습니다.

Cygwin 설치 시에 Select Packages 단계에서 'make'를 체크 후 설치해 줍니다.


저는 이미 설치되어 있기 때문에 'Keep'이라고 나오는 것입니다. 'Skip'부분을 클릭하면 'Install'로 바꿀 수 있습니다. 같은 방법으로 Search 기능을 이용하여 'vim'을 검색 후, Editors 카테고리의 vim을 'Install'로 바꾼 후 설치해 줍니다. 혹시 나중에 추가로 필요한 패키지가 생길 경우 다운받은 Cygwin 폴더의 setup.exe를 실행시켜 패키지를 추가할 수 있습니다.

다음으로 Cygwin을 실행하여 ndk-build 명령을 어디서든 실행할 수 있게 하기 위해 .bash_profile 파일의 PATH 환경변수에 경로를 추가시켜 줍니다.


/cygdrive 는 윈도우의 '내컴퓨터'와 같은 경로입니다. 저의 경우에는 다운받은 NDK 폴더가 아래의 경로에 있기 때문에 그림과 같이 PATH설정을 해 준 것입니다. 본인의 경로에 맞게 수정해주면 됩니다.

D:\UTIL\etc\Android_NDK_r4b\android-ndk-r4b

그럼 이제 cygwin은 모두 준비가 끝난 상태입니다.


3. Eclipse Android Project 생성

이제 Eclipse를 이용하여 안드로이드 프로젝트를 만들어 보도록 하겠습니다.

먼저, [File]->[New]->[Project]를 선택하면 다음과 같은 창이 나옵니다.



Android Project를 선택한 후, Next를 클릭합니다.

아래 그림과 같이 빈 곳을 채워준 다음 Finish를 클릭합니다. 이 때, 저는 "Use default location"을 체크해제한 후, cygwin의 홈디렉토리에 프로젝트 폴더를 만든 후 이곳의 경로를 지정해 주었습니다.



다음 그림과 같이 프로그래밍할 준비를 마쳤습니다.




4. 예제 프로그래밍

그럼 이제 본격적으로 안드로이드 App 예제를 실행하기 위한 프로그래밍을 해 보도록 하겠습니다.

① Activity Layout 구성
위치 : [HelloAndroid]-[res]-[layout]-[main.xml]
텍스트뷰와 버튼을 각각 하나씩 추가합니다.
01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03.android:orientation="vertical"
04.android:layout_width="fill_parent"
05.android:layout_height="fill_parent"
06.>
07.<Button
08.android:id="@+id/callbtn"
09.android:layout_width="wrap_content"
10.android:layout_height="wrap_content"
11.android:text="Call JNI"
12./>
13.<TextView 
14.android:id="@+id/rettxt"
15.android:layout_width="fill_parent"
16.android:layout_height="wrap_content"
17.android:textSize="15pt"
18./>
19.</LinearLayout>


② Activity 구현
위치 : [HelloAndroid]-[src]-[ssu.os.android]-[NdkTestActivity.java]
버튼 클릭으로 jni를 호출하는 소스를 onCreate에 구현하였습니다.
혹시 에러가 나거든 [Ctrl+Shift+O]를 이용해 자동으로 import를 수행해 줍니다.
01.package ssu.os.android;
02. 
03.import android.app.Activity;
04.import android.os.Bundle;
05.import android.util.Log;
06.import android.view.View;
07.import android.view.View.OnClickListener;
08.import android.widget.Button;
09.import android.widget.TextView;
10. 
11.public class NdkTestActivity extends Activity {
12./** Called when the activity is first created. */
13.private Button bv;
14.private TextView tv;
15. 
16.@Override
17.public void onCreate(Bundle savedInstanceState) {
18.super.onCreate(savedInstanceState);
19.setContentView(R.layout.main);
20. 
21.bv = (Button)findViewById(R.id.callbtn);
22.tv = (TextView)findViewById(R.id.rettxt);
23. 
24.bv.setOnClickListener(new OnClickListener() {
25.public void onClick(View v) {
26.NativeCall nativeCall = new NativeCall();
27.int ret = nativeCall.add(10, 20);
28.String retStr = nativeCall.stringFromJNI();
29.Log.i("TAG", retStr + ret);
30.tv.setText(retStr);
31.}
32.});
33.}
34.}


③ Class 만들기
위치 : [HelloAndroid]-[src]-[ssu.os.android]-[NativeCall.java]
위의 위치에 NativeCall.java 파일을 새로 생성합니다.
native 함수가 들어있는 NativeCall 클래스를 생성합니다. native 키워드는 javah가 헤더파일을 생성할 때 참조하게 되는 키워드 입니다. 구현부는 없습니다. static 부분은 class의 객체가 생성될 때 (new) 되어질 때 호출됩니다. "my_lib"는 ndk-build로 생성된 라이브러리 이름입니다. 파일명은 libmy_lib.so 입니다.
01.package ssu.os.android;
02. 
03.public class NativeCall {
04.static {
05.System.loadLibrary("my_lib");
06.}
07.public native String stringFromJNI();
08.public native int add(int a, int b);
09.}


④ javah 실행
~/bin 디렉토리에서 javah를 실행합니다. javah는 내부적으로 ./ssu/os/android/NativeCall.class 파일을 참고하여 헤더파일을 생성하게 됩니다. 헤더 파일명은 패키지명과 클래스명을 참고해서 생성됩니다. -o 옵션을 사용하면 파일명을 임의로 지정할 수 있습니다. 프로젝트 폴더에 /jni 폴더를 생성하고, 만들어진 헤더파일을 옮겨줍니다.


- 생성 된 헤더파일(ssu_os_android_NativeCall.h) 내용
native 키워드로 되어있던 함수들이 jni 형태 함수 프로토타입을 확인할 수 있습니다.
복잡해 보이지만 C문법입니다.
01./* DO NOT EDIT THIS FILE - it is machine generated */
02.#include <jni.h>
03./* Header for class ssu_os_android_NativeCall */
04. 
05.#ifndef _Included_ssu_os_android_NativeCall
06.#define _Included_ssu_os_android_NativeCall
07.#ifdef __cplusplus
08.extern "C" {
09.#endif
10./*
11.* Class:     ssu_os_android_NativeCall
12.* Method:    stringFromJNI
13.* Signature: ()Ljava/lang/String;
14.*/
15.JNIEXPORT jstring JNICALL Java_ssu_os_android_NativeCall_stringFromJNI
16.(JNIEnv *, jobject);
17. 
18./*
19.* Class:     ssu_os_android_NativeCall
20.* Method:    add
21.* Signature: (II)I
22.*/
23.JNIEXPORT jint JNICALL Java_ssu_os_android_NativeCall_add
24.(JNIEnv *, jobject, jint, jint);
25. 
26.#ifdef __cplusplus
27.}
28.#endif
29.#endif


⑤ 함수 구현
위치 : [HelloAndroid]-[jni]-[my_lib.c]
/jni 폴더에 my_lib.c 파일을 만들어서 다음과 같이 함수를 구현합니다. 두 개의 함수가 있는데 단순히 string을 리턴해 주는 함수와 두 정수를 입력받아 더한 후 리턴하는 함수입니다.
01.#include "ssu_os_android_NativeCall.h"
02. 
03.JNIEXPORT jstring JNICALL Java_ssu_os_android_NativeCall_stringFromJNI(JNIEnv *env, jobject obj)
04.{
05.return (*env)->NewStringUTF(env, "Hello JNI!");
06.}
07. 
08.JNIEXPORT jint JNICALL Java_ssu_os_android_NativeCall_add(JNIEnv *env, jobject obj, jint a, jint b)
09.{
10.return a + b;
11.}


⑥ Android.mk 파일 작성
위치 : [HelloAndroid]-[jni]-[Android.mk]
다운받은 Android NDK 폴더의 /sample/hello-jni/jni 의 Android.mk 파일을 현재 프로젝트의 /jni 폴더에 복사한 후, 다음과 같이 수정하여 사용합니다. 수정 된 내용은 19, 20 라인의 LOCAL_MODULE 과 LOCAL_SRC_FILES 입니다.
01.# Copyright (C) 2009 The Android Open Source Project
02.#
03.# Licensed under the Apache License, Version 2.0 (the "License");
04.# you may not use this file except in compliance with the License.
05.# You may obtain a copy of the License at
06.#
08.#
09.# Unless required by applicable law or agreed to in writing, software
10.# distributed under the License is distributed on an "AS IS" BASIS,
11.# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12.# See the License for the specific language governing permissions and
13.# limitations under the License.
14.#
15.LOCAL_PATH := $(call my-dir)
16. 
17.include $(CLEAR_VARS)
18. 
19.LOCAL_MODULE    := my_lib
20.LOCAL_SRC_FILES := my_lib.c
21. 
22.include $(BUILD_SHARED_LIBRARY)


⑦ NDK Build
프로젝트 루트 혹은 /jni 디렉토리에서 "ndk-build" 명령을 실행합니다.


빌드가 끝나면 프로젝트 폴더 밑에 /libs 폴더가 생성되고, 라이브러리인 .so 파일이 생성 된 것을 확인할 수 있습니다.




⑧ 에뮬레이터 실행
위의 과정이 정상적으로 진행되었으면 Eclipse에서 에뮬레이터를 실행시켜 줍니다. 안드로이드 부팅 과정이 끝나면 우리가 작성한 App을 볼 수 있을 것입니다.


Call JNI" 버튼을 누르게 되면 jni가 호출되어 스트링을 가져와 화면(TextView)에 뿌려주게 됩니다.



<참고 사이트>
[1] http://shchoi82.springnote.com/pages/6150375