1. 程式人生 > >Android Studio建立JNI專案(一)

Android Studio建立JNI專案(一)

最近博主在研究JNI,在Android Studio中開發JNI中遇到一些坑,記錄下來,希望給需要的人提供一些解決方法.

JNI(Java Native Interface) Java本地介面.其實就是一種協議,只要實現這種協議,就可以實現Java,C程式碼的互相呼叫

提供了Java與其他的語言的進行互動的能力,增強Java的功能(適用場景):

  1.使用C語言的優秀開源框架 ffmpeg(視訊opengl(影象)等.

  2.Java操作硬體效率問題.

  3.安全性,java程式碼反編譯太過簡單,可以直接看到Java程式碼(在C語言加密等,使用者檢驗)等等.

但是:java
語言不在跨平臺,因為不同的平臺遵循的c語言的標準是不同的,如果要使用的話,需要在不同的平臺重新編譯java本地方法,生成不同的so檔案.armeabi.armeabi-v7a.x86.x86_64等等.

那麼Android Studio如何建立一個JNI專案呢.

先看下最終的目錄結構:


接下來:

  1.建立一個含本地的方法的Java類,不推薦在MainActivity中直接建立,一是javah生成標頭檔案的時候會提示MainActivity的父類無法找到(因為MainActivity的父類的class檔案並不再我們的專案中)二是增加程式碼的耦合性.

package com.example.administrator.yinhangapp;

/**
 * Created by Administrator Youngkaka on 2016/8/18.
 * 我的心願是:世界和平
 */
public class NdkUtils {
    public native int getResYinHangC(int pass,int word);
}

  2.建立好類檔案後,重新build這個工程(Build-->Rebuild Project),這裡我們可以看到Rebuild後,在app/build/intermediates/classes/debug/生成NdkUtils的.class檔案,我們進入Terminal模式使用javah生成.h的標頭檔案(這裡要注意的是Jdk1.8 生成.h檔案的時候,需要指定classpath的位置)

目錄結構

  

使用javah生成標頭檔案,命令執行完畢後,會在debug的目錄下,生成 包名_類名_本地方法名 的標頭檔案,這時我們在/src/main目錄下,新建一個jni目錄下,將生成的.h檔案拷貝該資料夾下.

生成的標頭檔案內容:

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

#ifndef _Included_com_example_administrator_yinhangapp_NdkUtils
#define _Included_com_example_administrator_yinhangapp_NdkUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_administrator_yinhangapp_NdkUtils
 * Method:    getResYinHangC
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_example_administrator_yinhangapp_NdkUtils_getResYinHangC
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif
我們今天先不過多討論Jni協議裡面的內容,現在只要清楚,我們生成的標頭檔案中,有一個的本地C方法(引數,返回值同時也包括在內)

     3.在jni目錄下,新建一個my.c 實現這個本地c方法.

// Created by Administrator on 2016/8/18.
//
#include "com_example_administrator_yinhangapp_NdkUtils.h"
int login(int num,int pass){
   if(num==1234 && pass==1234){
       return 1;
    }else{
       return 0;
    }
}
JNIEXPORT jint JNICALL Java_com_example_administrator_yinhangapp_NdkUtils_getResYinHangC
  (JNIEnv * env, jobject obj, jint num, jint pass){
   jint res=login(num,pass);  //呼叫本地的C方法.在C語言驗證使用者名稱和密碼
   return res;
  }
4.接下來就是坑的開始,因為Android Studio使用Gradle編譯,因此我們要配置Gradle,同時還要手動配置ndk_build的工作路徑,我們先在/src/main/新建一個資料夾jniLibs

我的build.gradle內容如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.1"

    defaultConfig {
        applicationId "com.example.administrator.yinhangapp"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        ndk {
            moduleName "JniLibName"         //生成的so名字
            abiFilters "armeabi", "x86", "armeabi-v7a" //輸出指定三種abi體系結構下的so庫。目前可有可無。
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets { main {
            jni.srcDirs = []
            jniLibs.srcDirs=['src/main/jniLibs']
    } }

    tasks.withType(JavaCompile) {    //這裡指定了ndkBuild的工作命令,以及拷貝so的命令
        compileTask -> compileTask.dependsOn 'ndkBuild', 'copyJniLibs'
    }
}

task ndkBuild(type: Exec) {//設定新的so的生成目錄
    def ndkBuildingDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder().absolutePath
    commandLine ndkBuildingDir + "/ndk-build.cmd", '-C', 'src/main/jni',   
            "NDK_OUT=$buildDir/intermediates/ndk/obj",
            "NDK_APP_DST_DIR=$buildDir/intermediates/ndk/libs/\$(TARGET_ARCH_ABI)"
}

task copyJniLibs(type: Copy) {//將新生成的so拷貝到jniLibs目錄
    from fileTree(dir: file(buildDir.absolutePath + '/intermediates/ndk/libs'), include: '**/*.so')
    into file('src/main/jniLibs')
}

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

NDK-Build編譯C檔案生成.so檔案需要Android.mk檔案以及Application.mk檔案,我在build.gradle檔案中已經指定我的.mk在/src/main/jni檔案下,因此我們要在jni目錄下,新建

5.Android.mk檔案,Application.mk檔案內容如下:

Andorid.mk內容:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JniLibName       //build.gradle指定的modulename
LOCAL_SRC_FILES := my.c      //我的C檔名稱
include $(BUILD_SHARED_LIBRARY)
Application.mk內容:
APP_ABI		:= all
APP_PLATFORM:= android-19
APP_OPTIM	:= release

重新Rebuild後會在jniLibs資料夾中生成so檔案,如何沒有的話,從app/build/intermediates/ndk/libs拷貝一個.

6.最後在我們的MainActivity中加入靜態程式碼塊

 static {
        System.loadLibrary("JniLibName");
    }
7  .執行

----------------------------------------------------------------------------------------------------------------------------華麗的分割線

可能會遇到的問題:


答:你的ndk版本不適合


答:刪除你的檢查的你的c原始碼,以及Android.mk檔案,C檔名稱是否錯誤,檢查無誤後,重新編譯


答:你的so檔案並沒有生成,或者沒有存在在/src/main/jniLibs/目錄下,拷貝並且檢查你的build.gradle相關是否正確.


答:手動使用ndk-build時候,需要進入到jni目錄下,並且設定Android.mk檔案,然後使用ndk-build命令,或者使用配置gradle自動編譯


答:jdk1.8的問題,手動設定classpath以及不能再MainActivity的中編寫native方法.

我目前就遇到這些問題,歡迎互相交流.

反向編譯後,只能看到native的宣告呼叫,但是看不到本地方法具體實現.