Android Studio NDK 入門教程(1)--來自C 語言的String

Wastrel_xyz發表於2016-08-08

概述

本文講述如何使用Android Studio 進行NDK開發,適合略有了解C語言的人以及NDK入門開發。

環境配置

  1. 安裝Android Studio,目前版本是2.1.2。可以去官方下載最新安裝包,也可以百度上下載安裝之後線上更新,這部分網上教程比較多,學習NDK開發的道友應該不會卡在這裡。
  2. Android Studio安裝完成之後Android SDK也包含在其中,但是不包含NDK。
    • 開啟File->Settings->Appearance & Behavior->System Settings->Android SDK
    • 選擇SDK Tools ,等待檢查更新完成,然後勾上LLDB2.1、NDK、CMAKE工具,然後點選Apply,等待下載安裝完成,完成之後ndk-bundle在你Android SDK資料夾下。
      這裡寫圖片描述
      注意:如果使用其他位置的NDK,需要在專案設定裡面配置NDK路徑,如果沒有CMake可以不下載,CMake並不是NDK的必須外掛

建立一個Android程式

1.建立一個名為HelloJni的Android應用程式。此部分和建立正常的Android應用沒有區別,過程略。
2.新建HelloJNI.java;

 package com.example.wastrel.hellojni;
/**
 * Created by wastrel on 2016/8/4.
 */
public class HelloJNI {

    static {
        System.loadLibrary("helloJNI");
    }
    //Native方法
    public static native String getFormCString();
}

3.利用javah生成C語言標頭檔案。在執行javah命令之前,應先編譯java檔案為class檔案,執行選單欄上的Building->Make Project即可。然後開啟Android Studio底部的Terminal執行命令:

javah -d src\main\jni\ -classpath build\intermediates\classes\debug com.example.wastrel.hellojni.HelloJNI
注意:最後一個引數改成你對應的包名,也可以參照我另外一篇文章配置一鍵生成標頭檔案:http://blog.csdn.net/venusic/article/details/51087945
4.完成第三步之後將在jni目錄下生成對應的.h標頭檔案。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_wastrel_hellojni_HelloJNI */

#ifndef _Included_com_example_wastrel_hellojni_HelloJNI
#define _Included_com_example_wastrel_hellojni_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_wastrel_hellojni_HelloJNI
 * Method:    getFormCString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_wastrel_hellojni_HelloJNI_getFormCString
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

注:我們可以看到生成.h檔案方法上面的註釋包含了Class、Method、Signature三個屬性值,函式名命名也有非常嚴格的規則,包名+方法名的形式命名的。

5.正常情況下生成的標頭檔案開啟會有錯誤,提示找不到jni.h,並且編輯器上方會有英文提示“ndk support is an experimental feature…”。這是因為正式版gradle不能很好的支援NDK開發(事實上AS1.5版本的時候沒有問題的,升級到2.0才出現的毛病)。這時候我們需要把gradle版本換成experimental版本的。

  • 開啟Project的build.gradle,修改gradle為如下版本。
classpath 'com.android.tools.build:gradle-experimental:0.7.0'
  • 這個版本的gradle配置和正式版的有很大區別,開啟app的build.gradle修改如下:
apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.2"
        defaultConfig {
            //包名
            applicationId "com.example.wastrel.hellojni"
            minSdkVersion.apiLevel 15
            targetSdkVersion.apiLevel 23
            versionCode 1
            versionName "1.0.1"
        }
    }
    android.ndk {
        //生成so檔案的名字,不需要lib字首
        moduleName "helloJNI"
    }
    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles.add(file("proguard-rules.txt"))
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
}

注:可以把上面的模板複製過去修改一下即可。
詳情請參考:http://tools.android.com/tech-docs/new-build-system/

6.編寫cpp檔案,上面已經用javah工具生成了標頭檔案。接下來我們要實現標頭檔案裡面的方法。在jni目錄下新建檔案HelloJNI.cpp:

#include "com_example_wastrel_hellojni_HelloJNI.h"

JNIEXPORT jstring JNICALL Java_com_example_wastrel_hellojni_HelloJNI_getFormCString
        (JNIEnv *env, jclass clazz){
        //通過env裡面的轉換方法,將字串轉成UTF格式的jstring
    return env->NewStringUTF("I am from C String!");
}

JNIEnv是JNI的核心資料,env指向JNIEnv結構的指標。可以理解為JNIEnv提供了一些列方法來完成JAVA與Native之間的資料轉換與傳遞工作。

7.在Activity中佈局一個TextView用來顯示字串,佈局程式碼就不貼了。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView  tv= (TextView) findViewById(R.id.text);
        tv.setText(HelloJNI.getFormCString());
    }

8.執行程式,不出現編譯錯誤,手機上將會看到結果圖:
這裡寫圖片描述

可能遇到的問題

  1. 為什麼我生成的C函式引數是(JNIEnv , jobject)而不是(JNIEnv , jclass);
    答:這和java程式碼中對Native函式的宣告有關,宣告為static,這裡的引數就是jclass,即代表該函式所在的類(如HelloJNI.getFromString(),這是jclass接收的是HelloJNI.class)。如果沒有宣告static,這裡的引數就是jobject,表示呼叫者的實體物件,(如HelloJNI t=new HelloJNI();t.getFromCString(),這裡jobject就代表就是t這個物件)。
  2. 報java.lang.UnsatisfiedLinkError錯誤;
    答:此類錯誤一般後面有詳細的解釋,此處列出常見的錯誤:
    • could’t find “xxx.so” 此類錯誤表示找不到對應的so檔案,請檢視自己的gradle配置是否有ndk標籤,函式實現檔案要用標準的字尾.cpp或.c,不然gradle編譯ndk的時候不會加入編譯的。
    • Native method not found 此類問題表明你在JAVA中宣告的函式沒有對應的Native實現,如果已經實現了,請確保執行了System.loadLibary(“xxx.so”);一般來說用javah生成的函式定義是不會出現簽名不一致的情況的。
    • Can’t load 64-bit .so on 32-bit platform 也就是說不能在32位環境中載入64位的so。一般來說把so移到armeabi-v8a 資料夾下即可。該資料夾存放的是arm64位so檔案。

相關文章