ndk-build를 이용한

Native Code 빌드 및 사용방법

 

다음과 같은 이유로 Android App에서 Native Code(C/C++등)로 코드를 작성하여 사용해야 할 경우가 존재합니다.

  • 처리 속도 및 성능향상
  • C/C++로 만들어진 코드 재사용
  • Native Code로만 개발 가능한 기능 구현등

 

본 포스팅에서는 Android Studio에서 Native Code를 작성하고 빌드하는 방법에 대해 "최대한 많은 스크린 샷"을 통해 설명하고자 합니다.

작성 예제는 Native코드를 포함한 라이브러리(ndklib)와 이 라이브러리를 사용하는 app(ndklibdemo)으로 모듈을를 구분하여 작성 하도록 하겠습니다.

 

프로젝트 및 라이브러리 생성

1. 프로젝트 생성

프로젝트 타입을 'Empty Activity'로 선택합니다.

 

적당한 프로젝트 이름을 지정합니다. "ndklibdemo"로 설정했습니다.

 

2.라이브러리 모듈생성

[File>New>New Module..] 메뉴를 통해 새로운 라이브러리 모듈 추가

(Native Code가 작성될 모듈입니다.)

"Android Library"를 선택합니다.
적당한 라이브러리 명을 지정합니다. "ndklib"로 지정했습니다.
프로젝트에 ndklib 라이브러리가 추가 되었습니다.

 

Native Code 빌드를 위한 툴킷 설치

1. NDK  설치

1) [Tool>SDK Manager] 메뉴를 통해 프로젝트 설정 창을 열고 [SDK Tools]탭을 선택후,

[NDK] 항목을 체크해서 설치합니다.

(1) Android NDK는 Native Development Kit의 약자로, C / C++과 같은 Native 언어를 사용하여 앱의 일부를 구현할 수있게 해주는 개발 툴킷입니다.

 

NDK 빌드환경 구성 및 코드 작성

1. C/C++ 소스 폴더 생성

1) 프로젝트 표시창의 뷰타입을 "Project"로 변경 후,

ndklib 라이브러리의 [ src/main ] 하위에 jni 폴더를 생성합니다.

2. NDK 빌드환경 구성

1) [File>Setting] 메뉴을 통해 설정 다이얼로그를 연후, [Tool>External Tools] 항목을 선택합니다.

본 스크린 샷에는 이미 설정된 3개의 구성이 이미 존재합니다. 이 3개의 구성을 아래의 설명대로 생성합니다.

2) NDK Group에 세가지 환경을 추가합니다. 각각의 역할은 아래와 같으며 각 설정방법은 아래에 기술합니다.

(1) ndklib-javah

- Java Class를 기반으로 Jni규격에 맞게 헤더파일을 생성해주는 역할

(2) ndklib-build

- Native(C/C++) 코드를 빌드하는 구성

(3) ndklib-clean

- Native(C/C++) 코드의 빌드 결과물을 지워주는 구성 

3) [ + ] 버튼을 눌러 각 구성을 추가합니다.

1) ndklib-javah

(1) Group: NDK

(2) Program: javah.exe의 경로를 적어 줍니다.

$JDKPath$\bin\javah.exe

(3) Arguments: javah.exe 실행시 전달되는 파라미터 입니다. 그냥 아래 내용대로 적어 줍니다.

-classpath "$Classpath$" -v -jni $FileClass$

(4) Working directory: Native Code 소스파일을 작성할 폴더 경로를 적어 줍니다.

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main\jni

- 위에서 생성한 jni폴더를 지정합니다.

- 지정한 경로에 헤더파일 생성됩니다.

2) ndklib-build

(1) Group: NDK

(2) Program: ndk-build.cmd의 경로를 적어 줍니다.

$JDKPath$\bin\javah.exeC:\Users\unodi\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd

(3) Working directory: Library module의 src/main 폴더 경로를 적어 줍니다. 

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main

(3) ndklib-clean

(1) Group: NDK

(2) Program: ndk-build.cmd의 경로를 적어 줍니다.

$JDKPath$\bin\javah.exeC:\Users\unodi\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd

(3) Arguments:

clean

(4) Working directory: Library의 main 폴더 경로를 적어 줍니다. 

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main

 

Native Code 작성

1. Native Wrapping Java Class 작성

1) [라이브러리>java>패키지]에 Native API를 Wrapping하는 Java Class를 작성합니다.

Class 이름을 "NativeWrapper"로 지정했습니다.

2) 생성된 Wrapping class에 라이브러리를 로드하는 기본 코드를 구현합니다.

static {

   System.loadLibrary("ndklib");

}

"ndklib"부분은 생성된 라이브러리의 이름을 기술 합니다.

public native int nativeSum(int a, int b);

native로 구현할 함수의 원형을 기술 합니다.

이 선언을 기반으로 javah가 Jni규격에 맞는 함수명을 생성하게 됩니다.

2. Native 코드 작성

1) Header 파일 생성

위에서 생성한 클래스에서 오른쪽 마우스를 누른후, [NDK>ndklib-javah] 메뉴를 선택해서 헤더파일을 생성합니다.

아래와 같이 class파일을 찾지 못한다고 표시되는 경우, 프로젝트를 한번 빌드한후 다시 시도합니다.

정상적으로 수행되면 아래와 같이 *.h 파일이 생성됩니다.

생성된 header파일은 [jni] 폴더에서 확인 할 수 있습니다.(ndklib-javah 환경 구성에서 지정한 경로 입니다.)

Jni 규격에 맞게 함수명과 파라미터가 작성됩니다.

2) cpp 파일 구현

헤더파일이 위치한 [jni]폴더에 cpp파일을 생성하여 함수내용을 구현합니다.

#include "com_thirteenrain_ndklib_NativeWrapper.h"

JNIEXPORT jint JNICALL Java_com_thirteenrain_ndklib_NativeWrapper_nativeSum
(JNIEnv *env, jobject obj, jint a, jint b)
{
     return a+b;
}

 

Gradle연동

1. android.mk 생성

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := ndklib

FILES := NativeSum.cpp

LOCAL_SRC_FILES := $(FILES)

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

"ndklib" - 라이브러리 이름을 기술합니다.

NativeSum.cpp - 빌드할 CPP 파일을 기술합니다.

2. NDK build - gradle 연동

1) Java Wrapping Class를 오른쪽 클릭하여 NDK Build를 Gradle과 연동합니다.

(1) Build System: ndk-build

(2) project Path: 위에서 생성한 Android.mk를 선택합니다.

이 작업을 통해 ndklib 모듈의 gradle에 NativeBuild 구문이 추가되어, Gradle빌드시 ndkBuild가 함께 진행되도록 설정됩니다.

android {
     externalNativeBuild {
          ndkBuild {
               path file('src/main/jni/Android.mk')
           }
     }
}

이상으로 Native Code 라이브러리 생성을 위한 설정이 완료되었습니다.

 

Native Code Library Build

1. Java Wrapping Class 우클릭 > NDK > ndklib-build 메뉴를 통해 Native Code를 빌드 할 수 있습니다.

2. ndklib/src/main/libs 하위에 라이브러리 파일(*.so)이 생성됩니다.

 

Native Library 사용

1. 코드 작성

1) 테스트를 위해 MainActivity의 onCreate 메소드에 ndklib의 NativeWapper.nativeSum을 호출하는 코드를 삽입합니다.

NativeWrapper nativeApi = new NativeWrapper();
int result = nativeApi.nativeSum(5, 10);

2. dependency 추가

1) [Alt+Enter] 단축키로 ndklib에 대한 dependency를 추가해 줍니다.

2) ndklib에 대한 dependency가 추가되었습니다.

디버깅

1) 디버깅 Tool설치

(1) LLDB

: Native Debugging을 위해서는 LLDB를 설치해야 합니다.(LLDB는 Android Studio에서 사용하는 Native Debugger입니다.)

2) Debug type

(1) Debug type이 [java]로 설정되어 있으면 Native Code에 대한 디버깅이 되지 않습니다.

: Java - Java Code만 디버깅 합니다.

: Auto - 디버깅 대상을 자동으로 판단합니다.

: Native - Native Code만 디버깅 합니다.

: Dual - Java, Native 둘다 디버깅 합니다.

 

실행확인

1) cpp 소스에 중단점을 설정합니다.

2) 툴바의 디버그 버튼 혹은 [Shit+F9] 단축키를 눌러 디버그를 진행합니다.

3) 중단점에 적중되어 Native Code 디버깅이 가능합니다.

 

  1. dd 2020.04.24 00:52

    javah.exe 파일이 없네요 ㅠㅠ

https://git-scm.com/book/ko/v1/Git%EC%9D%98-%EA%B8%B0%EC%B4%88-%EB%A6%AC%EB%AA%A8%ED%8A%B8-%EC%A0%80%EC%9E%A5%EC%86%8C

 

Git - 리모트 저장소

.5 Git의 기초 - 리모트 저장소 리모트 저장소 리모트 저장소를 관리할 줄 알아야 다른 사람과 함께 일할 수 있다. 리모트 저장소는 인터넷이나 네트워크 어딘가에 있는 저장소를 말한다. 저장소는 여러 개가 있을 수 있는데 어떤 저장소는 읽고 쓰기 모두 할 수 있고 어떤 저장소는 읽기 권한만 있을 수도 있다. 간단히 말해서 다른 사람들과 함께 일한다는 것은 리모트 저장소를 관리하면서 데이터를 거기에 Push하고 Pull하는 것이다. 리모트 저장소를 관리한

git-scm.com

 

Repository 복사

git clone https://github.com/account/repogitory/test.git test

 

전역 설정

1. 계정 설정

<전역 설정>

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

<대상  repository만>

git config  user.email "you@example.com" 
git config  user.name "Your Name" 

2. 라인인코딩

기본으로 lf를 사용

git config --global core.eol lf

git config --global --list|grep core.eol 

저장소에서 소스를 pull하거나 push할때 line encoding  자동 변경
git config --global core.autocrlf true

git config --global --list|grep core.autocrlf

3. LFS - Large File Storage

git lfs install

git lfs track "*.zip"

 

소스관리

상태확인

git status

파일추가

git add

되돌리기

git reset --soft

git reset --mixed

git reset --hard

비교

git diff

커밋

git commit

git commit -m "modify AAA"

삭제 

git rm

로그확인

git log

리모트 저장소

git remote

git remote -v

되돌리기

git checkout -- file or folder

브랜치변경

git checkout <branch>

브랜치변경 - remote

git remote update : branch 정보 업데이트

git branch -a : branch 전체 확인

git branch -r : remote branch 확인

git checkout -t origin/{branch name} : 브랜치 전환

 

PUSH

git push origin master

PULL

git pull origin master

CardView

 

CardView는 아래와 같이 곡선 테두리와 그림자(음영)를 표시할 수 있는 형태의 레이아웃입니다.

 

종속성

CardView는 v7 Support library에 포함된 위젯으로

CardView를 사용하기 위해서는 Bundle.gradle(Module: app)에 다음과 같은 종속성을 추가해 주어야 합니다.

dependencies {
...
implementation 'com.android.support:cardview-v7:28.0.0'

...
}

 

CardView 속성

app:cardBackgroundColor - CardView의 배경색 지정

 

app:cardCornerRadius - 코너 radius 지정

 

app:cardElevation

- 음영 elevation 지정, elevation값이 클수록 음영을 표시하기 위한 공간이 더 필요하다.

app:cardMaxElevation

- 음영표시 영역 max값 지정이라고 되어 있는데, 왜 적용이 안되지?

 

app:contentPadding - CardView 패딩 지정

 

app:cardUseCompatPadding="true"

- 음영이 표시될 자동으로 확보 합니다. cardElevation 값이 클 수록 공간이 많이 확보됩니다.

 

app:cardPreventCornerOverlap - ?

 

 

그림자(음영) 표시

그림자(음영)이 표시되기 위해서는 CardView 주변으로 추가적인 공간이 필요합니다.

그림자가 표시될 영역을 확보할 수 있는 방법은 다음의 세가지 방법이 있습니다.

 

1. cardUseCompatPadding 속성을 통해 지정

app:cardUseCompatPadding="true"로 설정하여 음영을 그릴 공간을 자동으로 확보하도록 설정 합니다.

다만 이경우, cardElevation값이 클 수로 생각보다 많은 공간이 확보되어 CardView 영역이 작아 집니다. 

app:cardElevation="40dp"으로 설정한 모습입니다.

아래의 두가지 방법을 통해 고정된 공간을 확보할 수는 있으나, 반대로 음영을 표시할 공각이 부족하면

연결부분이 약간 부자연 스럽게 표시될수 있습니다.

 

2. 상위 layout의 padding을 통해 지정

상위 layout에 padding을 지정하고, clip 속성을 false로 지정해야 합니다.

http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:orientation="vertical"
    android:padding="8dp">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:cardBackgroundColor="#ff0000ff"
        app:cardCornerRadius="10dp"
        app:cardElevation="5dp"
        app:cardPreventCornerOverlap="false"
        app:contentPadding="0dp">

 

3. CardView에 layout_marging을 지정합니다.

<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardBackgroundColor="#ff0000ff"
app:cardCornerRadius="10dp"
app:cardElevation="5dp"
app:cardPreventCornerOverlap="false"
app:contentPadding="0dp"
android:layout_margin="8dp">

 

기타 속성

[parent layout]

android:clipChildren"
android:clipToPadding

 

참고URL

https://developer.android.com/guide/topics/ui/layout/cardview

https://stackoverflow.com/questions/41615468/how-to-add-shadow-on-cardview-aligned-to-bottom-of-parent/46374054

http://devstory.ibksplatform.com/2018/05/android-cardview.html

View의 background 속성을 이용해 View의 모양 변경하기


layout xml의 적용 부분

<View
android:id="@+id/marker"
android:layout_width="match_parent"
android:layout_height="7dp"
android:background="@drawable/marker_bound_top" />


marker_bound_top.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item >
<shape android:shape="rectangle">
<corners android:topLeftRadius="10dp" android:topRightRadius="10dp" />
<solid android:color="@color/default_marker_color"/>
</shape>
</item>
</layer-list>


shape Background 지정한 상태에서 필요에 따라 배경색상 변경

*setBackgroundColor()로 색상을 변경할 경우, xml로 지정한 내용이 무효화 된다.

아래와 같은 코드로 모양은 유지하면서 배경색만 변경 가능하다.

public void setMarkerColor(@ColorInt int color) {

Drawable backgroundOff = vMarker.getBackground();
backgroundOff.setTint(color);
vMarker.setBackground(backgroundOff);

}


Error type 3: Activity class {xxxx.MainActivity} does not exist.



package rename 후 빌드는 정상적으로 되나, app 실행 안되는 현상


$ adb shell am start -n "com.x.y.z/com.c.b.a.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER

Error while executing: am start -n "com.x.y.z/com.c.b.a.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER

Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.x.y.z/com.c.b.a.MainActivity }

Error type 3

Error: Activity class {com.x.y.z/com.c.b.a.MainActivity} does not exist.


Error while Launching activity 



1. Android Studio 종료
2. .idea Directory  삭제
3. .iml 확장자 파일 삭제
4. Android Studio 재시작


http://1004lucifer.blogspot.com/2015/03/intellij-build-apk.html

  1. ㅇㅇ 2020.09.12 14:35

    굿 덕분에 해결했습니다. 근데 ideal 파일이 뭔가요 자동으로 생성되는데, ,

Activity & Fragment & ViewModel Lifecycle


Activity Lifecycle

Fragment


onCreateView() 와 onActivityCreated() 사이에 onViewCreated() 호출됨


https://developer.android.com/guide/components/fragments



ViewModel Lifecycle

https://developer.android.com/topic/libraries/architecture/viewmodel



런타임 변경처리

https://developer.android.com/guide/topics/resources/runtime-changes?hl=ko

+ Recent posts