Flutter 강좌 - 플랫폼간 메소드 호출 #1 - 안드로이드에서 네이티브...

Flutter 강좌 - 플랫폼간 메소드 호출 #1 - 안드로이드에서 네이티브...

Flutter Code Examples 강좌를 추천합니다.

제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.

Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.

또한 모든 예제는 Flutter Code Examples 앱을 통해 테스트 가능합니다.

Flutter Code Examples 강좌로 메뉴로 이동

Flutter Code Examples 강좌 목록 페이지로 이동

Flutter Code Examples 앱 설치 | Google Play Store로 이동

이번 강좌부터는 Flutter에서 네이티브 코드를 사용하는 방법에 대해서 알아본다.

이번 강좌는 첫번째 강좌로, Flutter가 아닌 일반 Android 프로젝트에서 NDK를 이용해서 C/C++ API를 호출하는 방법에 대해서 알아본다.

Android Studio에서 Native C++ 프로젝트를 생성한다.

프로젝트 이름을 설정한다.

기본 툴체인으로 설정하고 Finish를 선택한다.

프로젝트 생성이 완료되면 바로 앱을 실행해 보자.

"Hello from C++"이란 문구가 출력되는 것을 확인할 수 있다. 이 문구는 우리가 작성한 것이 아니라 프로젝트 생성이 자동으로 작성된 코드 템플릿이다. 출력되는 내용으로 짐작하면, 이미 C++로 작성된 API가 호출되어 안드로이드 앱에 출력되고 있는 것으로 보인다.

그럼 소스를 좀 살펴보자.

MainActivity.java의 구현 내용은 다음과 같다.

package com.example.ndk_test; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI(); }

System.loadLibrary를 이용해서 native-lib라는 네이티브 라이브러를 로딩하였고, stringFromJNI 메소드를 통해 화면에 문구를 출력하고 있다. stringFromJNI 메소드에 native라는 키워드가 붙어있은 것으로 보아 이 메소드가 native-lib 안에 있는 네이티브 API를 호출한 것으로 보인다.

우선 native-lib가 어디 있는지부터 확인해 보자. Android Studio의 워크스페이스를 프로젝트로 변경한 후 app/build/intermediates/cmak/debug/obj 디렉토리의 내용을 확인해 보자.

각 아키텍쳐별로 libnative-lib.so 라는 파일들이 존재한다. 이 so 파일들이 MainActivity.java에서 로드한 native-lib에 해당한다. 상위에 cmake 디렉토리가 존재하는 것으로 보아 camke 툴체인을 이용해서 컴파일된 것으로 유추할 수 있다.

여기서 잠깐 정리하자.

MainActivity에서 사용한 stringFromJNI라는 네이티브 메소드는 cmake 툴체인으로 컴파일된 libnative-lib.so 라이브러리(native-lib) 내부의 API를 호출하고 있다.

그럼 다음으로 libnative-lib.so가 어떻게 생성되었는지 알아보자.

app/src/main 디렉토리를 살펴보면, MainActivity.java 파일이 존재하는 java 디렉토리와 동일한 수준으로 cpp 디렉토리가 존재하고 있고 그 디렉토래내에 native-lib.cpp 파일과 CMakeLists.txt 파일이 존재한다.

native-lib.cpp 파일은 Native C++ 프로젝트 생성 시 자동으로 생성되는 템플릿 코드로 stringFromJNI 함수가 존재한다.

#include #include extern "C" JNIEXPORT jstring JNICALL Java_com_example_ndk_1test_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }

내용을 보면 "Hello from C++" 문자열을 반환하는 기능을 가지고 있다.

함수의 구조를 조금 더 살펴보면,

JIN를 통해 호출되는 함수로 노출하기 위해서는 다음과 같이 함수를 선언한다. JNICALL 키워드 앞에는 리턴타입이 들어간다.

extern "C" JNIEXPORT jstring JNICALL

그리고 함수명은 다음과 같이 선언한다. Java_패키지명_호출하는 Activity명_함수명 형태로 선언한다.

Java_com_example_ndk_1test_MainActivity_stringFromJNI

다음으로 CMakeLists.txt 파일을 살펴보자.

cmake_minimum_required(VERSION 3.4.1) add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). native-lib.cpp ) find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} )

add_library 항목을 살펴보면 native-lib.cpp 파일을 공유라이브러리로 설정하고 그 이름을 native-lib로 한다는 내용이다. MainActivity에서 로드한 native-lib가 native-lib.cpp 파일의 컴파일 결과물임을 확인할 수 있다.

여기서도 잠시 정리하자.

MainActivity에서 로그하여 사용한 native-lib라는 라이브러리는 native-lib.cpp 파일의 컴파일 결과물이며, cmake 툴체인을 통해 공유라이브러리형태로 컴파일되고 있다.

자 마지막으로 native-lib 라이브러리가 어떻게 컴파일 되는지 확인해 보자.

app/build.gradle 파일을 살펴보자.

apply plugin: 'com.android.application' android { compileSdkVersion 29 defaultConfig { applicationId "com.example.ndk_test" minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { cppFlags "" } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" version "3.10.2" } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' }

app/build.gradle 파일은 안드로이드 프로젝트(Flutter 프로젝트도 동일)를 빌드하는 규칙을 정의하고 있다. 일반적인 안드로이드 프로젝트의 build.gradle 파일과 비교해보면 android 항목 안에 externalNativeBuild 서브 항목이 추가되어 있는 것을 확인할 수 있다.

내용을 살펴보면, src/main/cpp/CMakeLists.txt 파일에 정의된 규칙을 참고해서 외부 네이티브 빌드를 진행하라는 의미 이다.

만약 일반 안드로이드 프로젝트를 개발하던 중 네이티브 코드를 추가하여 네이티브 라이브러리를 이용하고자 않다면, 위에서 살펴본 것 처럼 cpp 디렉토리를 생성하여 cpp파일과 CMakeLists.txt 파일을 추가한 후 build.gradle 파일에 externalNativeBuild 항목을 추가해주면 된다.

이번 강좌에서는 Android Studio에서 제공하는 Native C++ 프로젝트를 통해 NDK를 이용하여 네이티브 라이브러가 호출되는 구조에 대해서 살펴보았다.

다음 강좌에서는 네이티브 API를 추가해 보고, 안드로이드에서 호출하는 방법에 대해서 실습해보도록 한다.

from http://here4you.tistory.com/257 by ccl(A) rewrite - 2020-04-20 15:00:52

댓글