רוב האפליקציות ב Android משתמשות בספריות Java תחת ה JVM (ליתר דיוק Dalvik) ללא צרכים מיוחדים אבל בחלק מהמקרים זה לא מספיק ונדרשת התערבות עמוקה יותר, בדומה לאחד המאמרים בו הצגתי את JavaScript Interface גם פה מדובר בממשק שהמערכת נותנת שמאפשר לנו להתחבר לעולם ה Native במכשיר ולהפעיל דרכו ספריות בעזרת שפת C.
שימו לב!
- המאמר נכתב על Android Studio 1.0.1
- מערכת הפעלה Windows 7 64 Bit
אם לרגע חשבתם שמעכשיו תכתבו אפליקציות ב C אז כדאי שתעצרו (למרות שזה אפשרי אבל לא חבל..), לא בשביל זה יש את ה NDK אלא עבור מקרים מאוד מיוחדים כמו למשל מוגבלויות ה API של Java או יצירת ממשק עבור חומרה חדשה, יש שימוש רחב ב NDK בתעשיית המשחקים בגלל ממשק ל OpenGL רחב יותר,
דרישות
- Native Development Kit - NDK
- +Java Development Kit - JDK 1.8
- +Gradle 2.2
יש להוריד ולפרוס את ה NDK היכן שתרצו (מומלץ לשמור איפה שתיקיית ה SDK), כדאי מאוד לוודא שיש לכם את גרסת Java אחרונה, גרסאות ישנות מידי עלולות לגרום לבעיות שלא נעים להתעסק בהן, לאחר מכן יש להוסיף את הנתיבים לתיקיית ה Java וה NDK למשתנה ה Path הכללי של המערכת שניתן להגיע אליו דרך Environment Variables שנמצא ב Advanced במסך של ה System Properties.
ניצור פרויקט חדש, נגדיר פונקצית Native שאת המימוש שלה נעשה ב C:
public native String getStringFromNative();
לאחר מכן נפתח Terminal ב Android Studio ,צריך לוודא שאתם בתיקיית Main של הפרויקט ולהריץ את הפקודה הבא:
E:\NDKtest3\app\src\main>javah -d jni -classpath E:\Users\proxytype\AppData\Local\Android\sdk\platforms
\android-14\android.jar;..\..\build\intermediates\classes\debug legend.proxytype.ndktest3.MainActivity
הפקודה תיצור תיקייה בפרויקט בשם JNI בתוכה קובץ Header שמכיל את החתימה לפונקציה, ניצור בה 2 קבצים חדשים, הראשון נקרא Application.mk שמכיל את הקוד הבא:
APP_ABI := all
APP_PLATFORM := android-14
השורה הראשונה מגדירה לאיזו ארכיטקטורת מעבד תתואם הספריה החיצונית (ה NDK מגיע עם כל סוגי ה Tool Chain) במקרה של ALL תיווצר ספריה עבור כל ארכטיקטורה, השורה השנייה עבור סוג הפלטפורמה של ה Android.
הקובץ השני נקרא Android.mk שמכיל את ההגדרות של הספריה בדומה ל Makefile סטנדרטי:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := MyLib
LOCAL_SRC_FILES := main.c
include $(BUILD_SHARED_LIBRARY)
חשוב לזכור להמשך את 2 הפרמטרים הבאים, הראשון ה LOCAL_MODULE שהוא שם הספריה שנשתמש בו ב Java כפי שנראה בהמשך, ו LOCAL_SRC_FILES שמצביע לקובץ ה main.c שמכיל את המימוש לפונקציות בקובץ ה Header שנוצר בתחילת התהליך.
C
התוכנית תכיל פונקציה שתחזיר מחרוזת מספריית ה Native שנכתבה ב C לאפליקציה עצמה שנכתבה ב Java, יש ליצור קובץ main.c ולרשום בו את הקוד הבא:
//our generated header file
#include "legend_proxytype_ndktest3_MainActivity.h"
JNIEXPORT jstring JNICALL
Java_legend_proxytype_ndktest3_MainActivity_getStringFromNative(JNIEnv * env, jobject obj)
{
return (*env)->NewStringUTF(env,"Hello From JNI!");
}
הקוד מאוד פשוט וניתן לראות את החשיפה של הפונקציה עבור מנגנון ה JNI והאפשרות לקרוא לה מבחוץ, קיים באג ב Android Studio שמחייב ליצור קובץ נוסף ריק על מנת שנוכל לבנות את הספריה.
Java
בשביל להפעיל את הפונקציה יש לטעון את הספריה באפליקציה עם השם שנתנו ב Android.mk, אח"כ נקרא לה ונקבל ממנה מחרוזת:
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
//loading the library with the name from android.mk
static
{
System.loadLibrary("MyLib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView lbl_jni = (TextView)findViewById(R.id.lbl_jni);
//make the call to function
lbl_jni.setText(getStringFromNative());
}
public native String getStringFromNative();
}
Gradle
import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "legend.proxytype.ndktest3"
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
ndk
{
//same as the name in android.mk
moduleName "MyLib"
}
}
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = []
}
//set task for the compilation of the library
task ndkBuild(type: Exec) {
//hadling different host machine
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine('ndk-build.cmd', '-C' , file('src/main').absolutePath)
} else {
commandLine 'ndk-build', '-C', file('src/main').absolutePath
}
}
//the build of the application depend on the success of the JNI
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
}
דבר אחרון לפני סיום הוא לוודא שתיקיית ה NDK מוגדרת ב Local.Properties של Gradle:
ndk.dir=E\:\\Users\\proxytype\\AppData\\Local\\Android\\ndk
מבנה האפליקציה אחרי כל הבלאגן |
סיכום
הכנת סביבה לתמיכה ב NDK זה עסק לא פשוט, למרות שיש דיבורים להפוך אותו לחלק בלתי נפרד בגרסאות הבאות של Android Studio, מי יודע מתי זה יקרה, אבל האפשרויות שהוא נותן שווים את המאמץ ויש דברים שאפשר לעשות רק איתו כפי שנראה עוד בהמשך.
100% טבעי...