一篇掌握JNI

NDK開發流程

  1. 在Java裏面寫native代碼
  2. 在main目錄下創建jni目錄,寫C代碼—生成頭文件
  3. 配置動態鏈接庫的名稱
  4. 加載動態鏈接庫
   //把.so加載進來
   System.loadLibrary("hello");
  1. 使用

交叉編譯

  1. 在一個平臺上編譯出在另外一個平臺上可以運行的本地代碼

  2. 平臺CPU平臺:x86、arm、mips

  3. 操作系統平臺:Windows、Linux、Mac os、Unix

  4. NDK:模擬另外一個平臺的特性進行編譯

NDK壓縮包下的文件

  1. sources:NDK相關的源代碼

  2. platforms:根據不同的api-level 有多個文件夾 每個文件夾中都有不同CPU架構的文件夾

  3. include:跟Android下做jni開發相關的頭文件

  4. lib:Google編譯好的jni開發可能用到的函數庫

  5. toolchains:交叉編譯工具鏈

  6. build/tools .sh linux的批處理命令 通過這些批處理命令調用交叉編譯工具鏈中的編譯工具

  7. ndk-build.cmd:通過這個命令可以開啓交叉編譯的過程 如果想通過命令行來編譯可以在Android上運行的本地代碼 要吧ndk-build放到環境變量中
    在這裏插入圖片描述
    看到這個錯說明配置成功了

JNI的理解

在這裏插入圖片描述
現在是Java是程序入口,所以C代碼不用寫main方法,只用寫函數對native方法的具體實現

  cd /d:直接進入到後面的目錄下

  cd /d E:\AndroidDemo\JNIDemo

JNI_Helloworld的步驟

  1. 首先新建一個NDKDemo的項目,我們在包名目錄下建立一個調用C方法的類JNI
    在這裏插入圖片描述

  2. 給AS配置關聯NDK

    1). local.properties中添加配置

    ndk.dir=G\:\\android-ndk-r10(=號後面爲ndk的解壓路徑)
    

在這裏插入圖片描述
2). gradle.properties中添加配置
在這裏插入圖片描述
兼容老的ndk(老的版本):android.useDeprecatedNdk=true

	android.useDeprecatedNdk=true
  1. 在 JNI 中聲明一個native方法,native方法不用實現
   //通過native關鍵字 聲明瞭一個本地方法 本地方法不用實現,需要用jni調用C的代碼來實現
   public native String helloInc();

native關鍵字表明該Java方法由非Java語言實現

最後類的樣式如下:
在這裏插入圖片描述
我們爲準備生成的so文件起名爲 Hello ,JNI 會預加載該庫,平時使用so庫的小夥伴一定不會陌生了,這是一個標準的使用方法,說明我們的native方法來自於Hello.so庫,其實JNI也是最後生成so庫供以使用。

  1. 生成提供該方法的C類(可以先看這個:https://blog.csdn.net/weixin_42814000/article/details/105279704

    那麼既然這樣,我們就要用c語言實現一個同名方法用以調用,首先我們根據這個類生成一個c的頭文件(這是c語言的內容如果不瞭解的話先照着做),在android studio下面terminal窗口執行如下命令:

    這裏我遇到了第一個坑,先把正確實現的步驟寫出來

生成頭文件的方式

點擊 Terminal 輸入命令,便可以生成一個JNI的 C 頭文件.

輸入第二個命令行提示錯誤的原因是註釋爲中文。所以在輸入命令行時不能出現註釋或者中文
在這裏插入圖片描述

javac  -h  ./  JNI.java

上面的命令的作用:根據Java中的 native 方法生成對應在 C 中的方法該怎麼寫(自動生成)

這樣爲正確的,輸入命令行沒問題
在這裏插入圖片描述
這樣會提示錯誤
在這裏插入圖片描述
下圖爲生成的頭文件
在這裏插入圖片描述
JNI的頭文件的代碼如下
在這裏插入圖片描述

  坑1(繞路):有些資料給出的步驟是去/build/intermediates/classes目錄下對.class文件執行javah,其實需要在生成頭文件的文件夾下(cd app/src/main/java/com/lwm/ndkdemo)執行就可以。
 
  坑2(找不到類文件):有些資料給出的命令是 javah -d jni 包名.類型,在不同的本地環境下可能出現找不到類文件的提示,網上有人給出的解決方法是在classpath中做配置,其實直接使用javah -d jni -classpath 命令就可以了。
 
  坑3(JNI目錄問題):注意JNI目錄的位置是在/src/main之下,很多同學的JNI目錄生成不對(比如使用了坑1方法生成再挪過來),位置錯誤導致最後運行時一直崩潰。

  1. 生成完頭文件之後我們需要在寫一個.c 文件並引用該頭文件(還是c的內容,不瞭解的同學直接copy),在項目根目錄下創建 jni 文件夾 在 jni 目錄下創建.c 的代碼(New–>c/c++SourceFile---->後綴選擇.c),取名 Hello.c:
    在這裏插入圖片描述
    C函數命名規則: Java_包名_native方法所在類名_native方法名(JNIEnv* env,jobject jobj)
   //
   // Created by 林偉茂 on 2020/4/2.
   //
   #include <stdio.h>
   #include <stdlib.h>
   #include <jni.h>
   
   // 因爲 sayHello() 方法返回的爲String類型所以對應JNI中的jstring類型
   // 然後把.改爲_,然後前面加一個 Java_
   /**
     jstring:返回值
     本地函數命名規則:Java_包名—_native函數所在類的類名_native方法名
     JNIEnv* env:相當於環境變量,裏面有很多方法
     jobject jobj:誰調用了這個方法就是誰的實例
     當前就是 JNI.this
   */
   // 第一個參數 JNIEnv* JNIEnv是結構體 JNINativeInterface 這個結構體的一級指針
   // env又是JNIEnv的一級指針 那麼 env就是 JNINativeInterface 的二級指針
   // 結構體 JNINativeInterface 定義了大量的函數指針,這些函數指針在 JNI 開發中十分常用
   // 第二個參數 jobject 就是調用當前 native 方法的 java 對象
   jstring Java_com_lwm_ndkdemo_JNI_sayHello(JNIEnv* env,jobject jobj){
   
   // jstring     (*NewStringUTF)(JNIEnv*, const char*);
       char* text = "I am from c";
       //通過 NewStringUTF方法把C的字符串轉換成java的jstring類型
       return (*env)->NewStringUTF(env,text);
   }
  1. 在jni目錄下創建一個Android.mk、Application.mk
    在這裏插入圖片描述
    Android.mk文件
   #makefile:作用就是向編譯系統描述 我要編譯的文件在什麼位置 要生成的文件叫什麼名字,是什麼類型
   #call my-dir:獲取當前的目錄,因爲放在jni這個目錄,所以就找當前的目錄
   LOCAL_PATH := $(call my-dir)
   #把上次編譯的時候的信息清空,但是call my-dir裏的內容不會被清除掉的
      include $(CLEAR_VARS)
   #在這裏指定最後生成的文件叫什麼名字
      LOCAL_MODULE := hello
   #要編譯的C的代碼的文件名
      LOCAL_SRC_FILES := hello.c
   #要生成的是一個動態鏈接庫( 通過BUILD_SHARED_LIBRARY指定存儲的擴展名爲 .so)
      include $(BUILD_SHARED_LIBRARY)

Application.mk 文件

APP_ABI := all
APP_STL := stlport_static

在工程上app目錄上右擊選擇link c++ project with Gradle,這個選項的意思是導入外部的c++工程,也就是把我們寫好的c當做外部工程導入:
在這裏插入圖片描述
在下拉框中選擇ndk-build,並選擇對應的Android.mk文件的位置:
在這裏插入圖片描述
這裏說明一下mk文件的作用,mk文件其實裏面也就是一些 ndk 的文件路徑配置、生成 so庫名字、生成平臺等的配置。

我們需要在 jni 目錄里加入如上兩個 mk 文件 Android.mk 和 Application.mk,如圖是最基本的配置,解釋下每個配置的注意的地方:

Android.mk:LOCAL_PATH 和兩個 include 照寫就行了,這三個配置 google 出來的意思比較生澀,解釋也比較難懂,我們要關注的是 LOCAL_MUDLE 和 LOCAL_SRC_FILES,LOCAL_MUDLE 其實就是我們之前配置的 moduleName,指定了生成 so 庫的名字,LOCAL_SRC_FILES則是我們引用的c文件位置。

Application.mk:APP_ABI 就是 abiFilters 了,所以之前我們做的配置都可以在這裏寫,賦值爲 all 表明全平臺生成,如果有多個用空格分開,APP_ABI:= armeabi-v7a armeabi。APP_STL指運行庫類型,通常都是stlport_static,表示以靜態鏈接方式使用的sttport版本的STL(寫出這個配置翻譯,是挺生澀的吧)。

項目build完之後,回想一下,我們選擇的mk文件是 Android.mk,Applicaiton.mk 還沒配置進去,打開app/build.gradle,你會發現多了一個配置:
在這裏插入圖片描述
所以Android.mk最後還是在gradle裏面引入,其實我們就可以直接在build.gradle里加入這句引入代碼,剛纔用的方式是可視化界面的操作方式。接着我們在裏面配置Application.mk:
在這裏插入圖片描述

externalNativeBuild {
            ndkBuild{
                // 指定 Application.mk 的路徑
                arguments "NDK_APPLICATION_MK=src/main/jni/Application.mk"
                //cFlags 和 cppFlags 是用來設置環境變量的,一般不需要動
                cFlags "-DTEST_C_FLAG1","-DTEST_C_FLAG2"
                cppFlags "-DTEST_CPP_FLAG2","-DTEST_CPP_FLAG2"
            }
        }

最後再build一下,終於大功告成了!
在這裏插入圖片描述
7. 通過ndk-build在項目根目錄下編譯.c文件 生成.so文件
在這裏插入圖片描述
如果生成了如上兩個文件,並且ndkBuild文件夾下有對應so文件則說明成功了。

但是其實還有最後一個坑

我發現Application.mk中APP_ABI的配置並不起作用,於是在build.gradle中做了最後一步修改:
在這裏插入圖片描述
ndkBuild下生成的so文件也變了,終於ok了!
在這裏插入圖片描述
JNI 的 sayHello 方法已經可以調用了,我們在c裏面讓它返回一個 I am from c 的字符串,趕快試試吧!

  1. 調用.so之前需要使用System.loadLibrary來加載.so文件
   //把.so加載進來
   System.loadLibrary("hello");
  1. 調用 C 的函數
public class MainActivity extends AppCompatActivity {

    private Button cid;
    private String result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        result = new JNI().sayHello();
        System.out.println("result" + result);
        initView();
    }

    private void initView() {
        cid = (Button) findViewById(R.id.cid);
        cid.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "result=" + result, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

在這裏插入圖片描述
部分轉載:https://www.jianshu.com/p/eae320ee9b2d

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章