android下NDK開發環境搭建及TestJNI入門實例完整過程

1.先搭建好基本的ndk的開發環境

在windows下安裝下面兩個軟件

1、 Android NDK 安裝
2、 安裝Cygwin與使用NDK編譯

本文建立在已經完成Android開發環境搭建的基礎上。其基礎環境至少需要包含以下內容:

1、 JDK

2、 Eclipse

3、 Android SDK and ADT

一、Android NDK 安裝與配置

下載Android NDK。下載地址:http://developer.android.com/tools/sdk/ndk/index.html

下載後解壓縮到你的工作目錄,例如:D:\Java\android-ndk-r8,結果如下圖:
這裏寫圖片描述
注意:samples下面包含幾個實例開發演示項目,第一次接觸NDK開發,建議先從示例開始。

二、安裝Cygwin與使用NDK編譯

由於NDK開發大都涉及到C/C++在GCC環境下編譯、運行,所以在Windows環境下,需要用Cygwin模擬Linux編譯環境。

下載:

Cygwin的下載地址:http://www.cygwin.com/

點擊右上角的“setup.exe”即可下載。

安裝:

    第一步:運行setup.exe程序,直接點擊Next進入下一步。

這裏寫圖片描述
第二步:選擇安裝方式。第一次可以採用Direct Connection在線下載安裝,如有現成的離線包,可以選擇離線安裝(Install from Local Directory)。

第三步:選擇安裝目錄。比如D:\Java\Cygwin,注意此目錄是指Cygwin最終的安裝目錄,不是下載文件暫存目錄。

第四步:設置本地包暫存路徑。暫存目錄默認是放到setup.exe的同級目錄下,建議放到指定的文件夾,如D:\Cygwin_install_file。安裝完成後把這個文件夾打包備份,以後再配置時不用重新下載。

第五步:設置網絡連接方式。這個目前河蟹沒爬過來,選第一個即可。

第六步:選擇下載站點地址。據說國內163站點的速度不錯,我也是用的這個。

第七步:等待加載安裝項載入,選擇安裝項。點擊Devel-Default,使之變成Devel-Install,展開後可以看到其下的子項被選中了(網上多數教程都說選中某12個包,找起來太坑爹了,直接全下載了吧,全選多了150M左右)。此界面其他設置都不用動。
這裏寫圖片描述
第八步:等待下載完成。下載完成時間決定於你選擇的安裝包數量及網絡連接速度,安裝我安裝的版本,約983M,下載完成後會自動安裝到上文設置的安裝目錄,安裝也要時間的,總時間較長,去吃個飯沒啥問題。

提醒:第四步的備份建議,儘量去做。如果有備份,第二步中選擇離線安裝。

驗證:

運行安裝目錄下的“Cygwin.bat”,第一次運行時,它會自動創建用戶信息,用戶信息存放在“.\Cygwin\home”中。

在運行“Cygwin.bat”打開的命令行窗口輸入:“cygcheck -c cygwin”命令,會打印出當前Cygwin的版本和運行狀態,如果status是ok的話,則cygwin運行正常。

分別輸入:“make –v”和,“gcc –v”命令如果檢測成功,會有make和gcc相關版本信息打印出來。
這裏寫圖片描述
設置NDK路徑:

在windows的系統環境變量中添加NDK的路徑。使用“/cygdrive/d/Java/android-ndk-r8”這種Linux風格路徑,如果使用Windows下的“D:\Java\android-ndk-r8”,Cygwin在編譯時會發出警告。
這裏寫圖片描述
運行Cygwin命令行,可以直接使用此環境變量,當然也可以手動的cd到該目錄:
這裏寫圖片描述

三、新建TestJni工程,使用NDK編譯程序

1.創建Android工程
這裏寫圖片描述
2首先建立一個名爲TestJni的Android工程,包名默認爲com.example.hellojni,src目錄下自動創建MainActivity.java。
設計JNI接口,新建一個java文件,TestJni.java.如圖
這裏寫圖片描述
—工程目錄圖
TestJni.java代碼如下

package com.example.hellojni;

public class TestJni {
    public native boolean init();
    public native int add(int x,int y);
    public native void destory();
}

3.編譯JNI
在E:\workplace\TestJni\bin\classes\com\example\hellojni文件夾下會生成TestJni.class文件。
這裏寫圖片描述

在bin文件夾下,如果沒有則創建目錄:/com/example/jni,並把TestJNI.class複製到/bin/com/example/jni目錄下。然後在終端裏進入工程的bin目錄,輸入javah -jni com.example.jni.TestJNI,此時會生成一個com_example_jni_TestJNI.h文件。

注意:我在輸入javah -jni com.example.jni.TestJNI的命令時會出錯,改爲
javah -classpath E:/software_tool/android/adt-bundle-windows-x86_64-20140702/sdk/platforms/android-20/android.jar;bin/classes -d jni com.example.hellojni.TestJni

這裏寫圖片描述

4.用C/C++實現JNI
有了JNI的C/C++頭文件,就可以在C層實現JNI接口了。首先在工程目錄下創建一個jni目錄,這個目錄就是專門用來放C/C++代碼的。把com_example_hellojni_TestJni.h文件複製到jni目錄下,並在這裏創建一個com_example_jni_TestJni.cpp文件。

由於我想用C++來實現JNI,所以上面兩個文件我只是用來作爲動態鏈接庫的接口,具體的實現我希望放在一個類裏面來完成,因此我再添加兩個文件:Add.h和Add.cpp。如上操作結果見工程目錄圖

com_example_hellojni_TestJni.h

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

#ifndef _Included_com_example_hellojni_TestJni
#define _Included_com_example_hellojni_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_hellojni_TestJni
 * Method:    init
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_com_example_hellojni_TestJni_init
  (JNIEnv *, jobject);

/*
 * Class:     com_example_hellojni_TestJni
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_example_hellojni_TestJni_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_example_hellojni_TestJni
 * Method:    destory
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_hellojni_TestJni_destory
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

com_example_hellojni_TestJni.cpp

#include <stdio.h>
#include <stdlib.h>
#include "com_example_hellojni_TestJni.h"
#include "Add.h"

CAdd *pCAdd = NULL;
JNIEXPORT jboolean JNICALL Java_com_example_hellojni_TestJni_init(JNIEnv *env,jobject obj) {
   if (pCAdd == NULL) {
        pCAdd = new CAdd;
    }
   return pCAdd != NULL;
}
JNIEXPORT jint JNICALL Java_com_example_hellojni_TestJni_add(JNIEnv *env, jobject obj,
        jint x, jint y) {
        int res = -1;
       if (pCAdd != NULL) {
          res = pCAdd->add(x, y);
        }
       return res;
}
JNIEXPORT void JNICALL Java_com_example_hellojni_TestJni_destory(JNIEnv *env, jobject obj)
{    if (pCAdd != NULL)
    {
        pCAdd = NULL;
    }
}

Add.cpp

#include "Add.h"
 CAdd::CAdd() {
 }
 CAdd::~CAdd() {
 }
 int CAdd::add(int x, int y) {
 return x + y;
}

Add.h

#ifndef _TEST_JNI_ADD_H_
#define _TEST_JNI_ADD_H_
class CAdd {
    public:
        CAdd();
        ~CAdd();
        int add(int x,int y);
};
#endif

到此C/C++代碼實現結束

5、創建Android.mk
JNI實現了之後就要把C/C++代碼編譯成動態鏈接庫.so文件,這樣Java程序才能調用JNI的接口。要編譯so文件,需要寫Android.mk和Application.mk兩個文件。我們先來寫Android.mk。

先在工程目錄的jni下創建一個Android.mk文件:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := TestJni
LOCAL_SRC_FILES := com_example_hellojni_TestJni.cpp
LOCAL_SRC_FILES += Add.cpp
include $(BUILD_SHARED_LIBRARY)

其中LOCAL_PATH是C/C++代碼所在目錄,也就是我們的jni目錄。
LOCAL_MODULE是要編譯的庫的名稱。編譯器會自動在前面加上lib,在後面加上.so。

LOCAL_SRC_FILES是要編譯的C/C++文件。

現在我們在工程的根目錄下創建一個Application.mk文件,並輸入如下內容:

APP_PROJECT_PATH := $(call my-dir)
APP_MODULES := TestJni

6、編譯動態鏈接庫(.so文件)
默認在Windows7下配置好了NDK開發環境,打開cygwin,進入到工程目錄
這裏寫圖片描述
編譯生成.SO庫
這裏寫圖片描述

編譯成功後會在工程目錄的libs/armeabi目錄下生成一個libTestJni.so文件。

7 在Java中調用JNI

現在我們的Android應用可以調用JNI計算加法的代碼,如下:
MainActivity.java

package com.example.hellojni;

import android.support.v7.app.ActionBarActivity;
import android.app.Activity;
import android.app.SearchManager.OnCancelListener;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;


public class MainActivity extends Activity{

    static{
        System.loadLibrary("TestJni");
    }
    EditText tvX = null;
    EditText tvY = null;
    EditText tvSum = null; 
    Button   btnAdd = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvX = (EditText)findViewById(R.id.et_x);
        tvY = (EditText)findViewById(R.id.et_y);
        tvSum = (EditText)findViewById(R.id.et_sum);
        btnAdd = (Button)findViewById(R.id.btn_add);
        btnAdd.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                int x = Integer.valueOf( tvX.getText().toString());
                int y = Integer.valueOf( tvY.getText().toString());
                int sum = 0;
                TestJni jni = new TestJni();
                boolean flag = jni.init();
                if(flag){
                sum = jni.add(x, y);
                }
                tvSum.setText(String.valueOf(sum));
            }
        });
    }





}

8.XML文件(activity_main.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    >

    <EditText
        android:id="@+id/et_x"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
         >

    </EditText>

    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"        
        android:text="+" >

     </TextView>
      <EditText
        android:id="@+id/et_y"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
         >
      </EditText>
      <Button 
          android:id = "@+id/btn_add"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="="
          />
       <EditText
        android:id="@+id/et_sum"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
         >

      </EditText>

</LinearLayout>

程序運行結果如圖
這裏寫圖片描述

總結一下JNI開發基本步驟
①編寫.java類
②生成.class文件
③生成.h文件
③查閱.h文件
  JNIEXPORT 和 JNICALL 是jni的宏
  函數前的註釋Signature: ()Ljava/lang/String;中的括號含義爲:表示函數的參數爲空,Ljava/lang/String表示函數的返回值是java的String對象
④編寫.c/cpp文件
⑤創建 Android.mk文件(和.c文件同級目錄)
⑥編譯生成.so文件
這裏寫圖片描述

NDK開發中的一些常見錯誤及解決辦法

一、常見的幾個錯誤及其解決辦法
1. android.mk文件不存在
錯誤代碼形如:
AndroidNDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk
/cygdrive/h/heima6/jni2/ziliao/android-ndk-r7b/build/core/add-application.mk:133:* Android NDK: Aborting… 。 停止。
解決辦法:
創建Android.mk文件。

2.android.mk文件的配置信息有錯誤
錯誤代碼形如:
***Android NDK: Missing LOCAL_MODULE before including BUILD_SHARED_LIBRARY injni/Android.mk 。 停止。
解決辦法:
修改Android.mk文件中的錯誤。有時候可能是一些看不見的特色字符導致,可以刪除後重新建立一個。

3.c代碼語法出現錯誤,編譯不通過Error1.
錯誤代碼形如:
make:* [obj/local/armeabi/objs/Hello/Hello.o]Error 1
解決辦法:
檢查C程序代碼

4.java層c代碼庫沒有找到
錯誤代碼形如:
Causedby: java.lang.UnsatisfiedLinkError:Library Hell0 not found靜態加載代碼庫的時候代碼庫沒有找到.(調用庫時出錯)
解決辦法:
檢查庫庫的名字,看是否寫錯了。看

5.c代碼函數簽名出現錯誤(函數名出錯)
錯誤代碼形如:
Causedby: java.lang.UnsatisfiedLinkError: hello_from_c
解決辦法:
C代碼中函數名不合特定規範,改過來就行了。

  1. 其他隱含錯誤
    如:在C代碼中視圖去訪問已經釋放了的內存空間。
發佈了31 篇原創文章 · 獲贊 8 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章