AndroidStudio2.2+環境下的JNI環境搭建

[cpp] view plain copy
  1. 在Eclipse環境下進行JNI環境配置非常的複雜,需要記憶的東西很多,相比較,IDE變爲AS之後整個過程都變的更簡單,AS2.2版本發佈後讓我覺得很有用的更新內容之一就是NDK的支持更加便捷,之前的NDK使用需要在Android.mk等文件中進行諸多配置,在java代碼中寫明瞭native方法之後要生成cpp文件示例依賴於在終端中編寫的代碼(這一塊我沒有做過只是瞭解可能說明有錯誤的地方,等我看了這塊回來改正)  

現在在AS2.2中進行JNI開發更容易,它將原先的jni文件夾取消,取而代之的是cpp文件夾,並且進行編譯的方式也改成了cmake。

首先簡要介紹一下一個新的工程,怎麼支持AS2.2的新特性

第一步:在AS中下載cmake等工具

tools->android->sdkManager裏面勾選CMake、LLDB、NDK這三項,在安裝完成之後AS會自動的配好NDK的路徑,如果想手動設置可以在項目上app右鍵選擇open module setting裏面的sdk設定NDK路徑


第二步:新建工程,並勾選對C++的支持選項

下面全部next一路到底,在最後一步可以選擇C++規範,這裏我選擇默認不改變

然後就可以finish了,創建好項目之後的項目結構是這樣的:


(Android結構)

(project結構)

說明一下,cpp文件夾是由工程創建的時候勾選的支持C++來完成的,cmakeList.txt也是,如果是自己的老項目,需要手動的建,我認爲比較好的方法就是新建一個支持C++的新項目,然後對照着在老項目的project視圖裏建立cpp和cmakelist.txt,還可以直接把cmakelist裏的內容粘貼過來。

第三步:編寫(更改)cmakeList的內容

爲了要會寫或者說會改,首先看一下這個文件裏有哪些配置的內容

[cpp] view plain copy
  1. # Sets the minimum version of CMake required to build the native  
  2. # library. You should either keep the default value or only pass a  
  3. # value of 3.4.0 or lower.  
  4.   
  5. cmake_minimum_required(VERSION 3.4.1)  
  6.   
  7. # Creates and names a library, sets it as either STATIC  
  8. # or SHARED, and provides the relative paths to its source code.  
  9. # You can define multiple libraries, and CMake builds it for you.  
  10. # Gradle automatically packages shared libraries with your APK.  
  11.   
  12. add_library( # Sets the name of the library.  
  13.              native-lib  
  14.   
  15.              # Sets the library as a shared library.  
  16.              SHARED  
  17.   
  18.              # Provides a relative path to your source file(s).  
  19.              # Associated headers in the same location as their source  
  20.              # file are automatically included.  
  21.              src/main/cpp/native-lib.cpp )  
  22.   
  23. # Searches for a specified prebuilt library and stores the path as a  
  24. # variable. Because system libraries are included in the search path by  
  25. # default, you only need to specify the name of the public NDK library  
  26. # you want to add. CMake verifies that the library exists before  
  27. # completing its build.  
  28.   
  29. find_library( # Sets the name of the path variable.  
  30.               log-lib  
  31.   
  32.               # Specifies the name of the NDK library that  
  33.               # you want CMake to locate.  
  34.               log )  
  35.   
  36. # Specifies libraries CMake should link to your target library. You  
  37. # can link multiple libraries, such as libraries you define in the  
  38. # build script, prebuilt third-party libraries, or system libraries.  
  39.   
  40. target_link_libraries( # Specifies the target library.  
  41.                        native-lib  
  42.   
  43.                        # Links the target library to the log library  
  44.                        # included in the NDK.  
  45.                        ${log-lib} )  
原始的代碼是這樣的,把註釋拿掉,改成自己的說法

[cpp] view plain copy
  1. cmake_minimum_required(VERSION 3.4.1)#說明要求的cmake最低版本  
  2. add_library( #這裏三行從下往上看分別是源文件、生成庫類型、生成庫名稱  
  3.              native-lib  
  4.              SHARED  
  5.              src/main/cpp/native-lib.cpp )  
  6. find_library( #這裏是找到兩個庫  
  7.               log-lib  
  8.               log )  
  9. target_link_libraries( #這裏鏈接了我們上面生成的庫  
  10.                        native-lib  
  11.                        ${log-lib} )  

第四步:module裏的gradle編寫(改寫)

[cpp] view plain copy
  1. apply plugin: 'com.android.application'  
  2.   
  3. android {  
  4.     compileSdkVersion 25  
  5.     buildToolsVersion "25.0.1"  
  6.     defaultConfig {  
  7.         applicationId "cn.ndk.jni1"  
  8.         minSdkVersion 22  
  9.         targetSdkVersion 25  
  10.         versionCode 1  
  11.         versionName "1.0"  
  12.         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"  
  13.         externalNativeBuild {  
  14.             cmake {  
  15.                 cppFlags ""  
  16.             }  
  17.         }  
  18.     }  
  19.     buildTypes {  
  20.         release {  
  21.             minifyEnabled false  
  22.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  23.         }  
  24.     }  
  25.     externalNativeBuild {  
  26.         cmake {  
  27.             path "CMakeLists.txt"  
  28.         }  
  29.     }  
  30. }  
  31.   
  32. dependencies {  
  33.     compile fileTree(dir: 'libs', include: ['*.jar'])  
  34.     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {  
  35.         exclude group: 'com.android.support', module: 'support-annotations'  
  36.     })  
  37.     compile 'com.android.support:appcompat-v7:25.0.1'  
  38.     testCompile 'junit:junit:4.12'  
  39. }  

其中要注意到的地方我用紅框框下來了(或者截圖了下面一個忘記畫框了尷尬)

最後看一下整個調用過程,基本和沒有改變編譯方式之前是一樣的,首先是在java代碼裏面寫一個靜態塊,用來加載lib庫

[java] view plain copy
  1. // Used to load the 'native-lib' library on application startup.  
  2.  static {  
  3.      System.loadLibrary("native-lib");  
  4.  }  

然後是申明一下要調用這個庫裏的底層函數的名字同名的方法

[java] view plain copy
  1. /** 
  2.      * A native method that is implemented by the 'native-lib' native library, 
  3.      * which is packaged with this application. 
  4.      */  
  5.     public native String stringFromJNI();  

最後是在java代碼裏調用這個方法就可以了,具體的函數實現交給cpp文件裏面去寫

[java] view plain copy
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     setContentView(R.layout.activity_main);  
  5.   
  6.     // Example of a call to a native method  
  7.     TextView tv = (TextView) findViewById(R.id.sample_text);  
  8.     tv.setText(stringFromJNI());//這一行調用了這個底層函數對應的方法  
  9. }  

然後具體的這個函數會在cpp裏面實現

[cpp] view plain copy
  1. #include <jni.h>  
  2. #include <string>  
  3.   
  4. extern "C"  
  5. jstring  
  6. Java_cn_ndk_jni1_MainActivity_stringFromJNI(  
  7.         JNIEnv *env,  
  8.         jobject /* this */) {  
  9.     std::string hello = "Hello from C++";  
  10.     return env->NewStringUTF(hello.c_str());  
  11. }  

這裏可以看到方法的後半段和java的方法名字是一樣的,這個函數的名字和基本內容是可以自動生成的,這一塊留到下面講,集中說一下解決的問題和可以使用的技巧。


解決的問題和學到的技巧:

1.想到的第一個問題就是如果我要自己往裏面加cpp怎麼辦,畢竟上面都是點點點沒有自己建一個cpp啊,那自己建cpp要怎麼做呢

首先建文件這些操作我建議到project視圖裏去做,因爲這個視圖纔是真正的項目文件結構,如果出現了建了文件之後沒有編寫gradle和cmakelist沒有刷新android結構的時候可以看到文件在哪裏,心裏踏實,然後可以看到在src/main/下面有cpp文件夾,我們自己建立的cpp就放在這裏面,我這裏以一個test.cpp爲例,並且給他一個頭文件,頭文件選項就在建立cpp文件的時候可以勾選(雖然我不用頭文件主要是試一下萬一以後要用),建立後是這樣的

但是這時候如果你切回去會發現在android視圖裏還是沒有這個文件夾,不截圖了,看到這裏的可以自己試一下,這就是第二個問題了

2.爲什麼我建了cpp它不見了啊?

這個問題常見於在android視圖下就建立這個cpp文件的情況,這樣會更直觀,建完了cpp文件在cpp文件夾下面,結果什麼都沒有發生(最怕空氣突然安靜),這是因爲你在cmakelist裏面還沒有加上這個源文件呢,這時候你要這樣做:

[cpp] view plain copy
  1. cmake_minimum_required(VERSION 3.4.1)#說明要求的cmake最低版本  
  2. add_library( #這裏三行從下往上看分別是源文件、生成庫類型、生成庫名稱  
  3.              native-lib  
  4.              SHARED  
  5.              src/main/cpp/native-lib.cpp src/main/cpp/test.cpp)  
  6. find_library( #這裏是找到兩個庫  
  7.               log-lib  
  8.               log )  
  9. target_link_libraries( #這裏鏈接了我們上面生成的庫  
  10.                        native-lib  
  11.                        ${log-lib} )  
在源文件那裏添加一下這個新加進來的cpp文件,最後回到你建立好的cpp文件編輯界面,會發現它說沒有同步(sy..那個),這時候你要點擊一下讓它自己去同步

進來啦!完美,這個問題困擾我很久,是在網上找到的一個小夥伴的方法救了我,我一時找不到當時那個頁面了,等我找到了我會把原鏈接貼上來,感謝這個小天使!

特別說明一下,這是把這兩個cpp都合到一個庫裏面去了,沒有生成兩個庫,如果是生成兩個庫,我做了嘗試,就是直接把add_lib那裏複製了一遍,並且把源文件改爲新的cpp文件,把生成的庫文件名字改了一下,在link那裏把新的庫也添加上,這樣生成的項目結構是(android視圖中)會有兩個子文件夾,都是shared的庫,然後每個文件夾下面有一個cpp,分別是這兩個庫引入的cpp文件,說的羅嗦,看代碼和圖

[cpp] view plain copy
  1. add_library( # Sets the name of the library.  
  2.              native-lib  
  3.   
  4.              # Sets the library as a shared library.  
  5.              SHARED  
  6.   
  7.              # Provides a relative path to your source file(s).  
  8.              # Associated headers in the same location as their source  
  9.              # file are automatically included.  
  10.              src/main/cpp/native-lib.cpp )  
  11. add_library( # Sets the name of the library.  
  12.              mylib  
  13.   
  14.              # Sets the library as a shared library.  
  15.              SHARED  
  16.   
  17.              # Provides a relative path to your source file(s).  
  18.              # Associated headers in the same location as their source  
  19.              # file are automatically included.  
  20.              src/main/cpp/test.cpp )  

[cpp] view plain copy
  1. target_link_libraries( # Specifies the target library.  
  2.                        native-lib  
  3.                         mylib  
  4.                        # Links the target library to the log library  
  5.                        # included in the NDK.  
  6.                        ${log-lib} )  

3.解決好了添加cpp問題,再來看cpp示例的自動生成問題,我現在有一個新的方法交返回值爲String的叫做JNI的方法,怎麼讓軟件自動生成一個對應的c++函數呢,這樣做:

首先在java代碼裏寫你的native方法名字,仿照原來的那個寫就好,當然這時候肯定是標紅的,畢竟你靜態調用進來的庫裏面並沒有會這麼個函數對應啊,自然就調用不到了,這時候等..別方,等到這個native方法的前面出現一個紅色的小燈泡(總覺得小燈泡有槽要吐)這時候點一下,就會自動創建一個cpp的標準啦,對應的參數也會幫我們填上去啦,很方便對吧。(親測要把原先的那個直接複製下來,然後改名字之後更容易出現這個紅色的小燈泡,比較奇怪,可能有什麼原因)

會在cpp文件裏添加這一段

[cpp] view plain copy
  1. JNIEXPORT jstring JNICALL  
  2. Java_cn_ndk_jni1_MainActivity_jni(JNIEnv *env, jobject instance) {  
  3.   
  4.     // TODO  
  5.   
  6.   
  7.     return env->NewStringUTF(returnValue);  
  8. }  
最後連上之後兩邊的行號欄都會有一個雙向箭頭,點擊可以在兩邊切換。

我還一個更極端的情況,用來測試這種自動生成方式,首先是有兩個cpp,這兩個cpp分別用add_lib那裏生成兩個庫,然後在link裏面把兩個庫都鏈接上,再在java代碼裏用靜態塊把兩個庫都導入進來,這時候再寫底層函數對應的方法,用自動生成的方法,會發現會自動生成到某一個cpp文件裏去(沒找到規律),這時候只要把這一段複製粘貼到我們自己想要它在的cpp裏就可以了(也就是不知道自動會生成到哪裏去,但是可以手動調整),親測複製粘貼到任意的cpp裏都會自動鏈接上去的,雙向箭頭都在哦,當然啊這種比較複雜的情況是後面需要考慮的,如果再出現什麼問題,我會回來更正的。


最後的最後以一個比較蠢蠢的問題結束:

爲什麼提示我們說這段代碼無效呢,明明就是c++代碼,爲什麼還是灰色的,也不像自帶的那樣有編輯器的顏色呢,其實,這是寫的語句沒有意義造成的,只要改成這樣子就好了:

test.h

[cpp] view plain copy
  1. //  
  2. // Created by Sli.D on 2016/12/30.  
  3. //  
  4.   
  5. #ifndef JNI1_TEST_H  
  6.   
  7. #include <iostream>  
  8. #define JNI1_TEST_H  
  9.   
  10. #endif //JNI1_TEST_H  
test.cpp

當然真正JNi的的cpp是不能這樣寫的,是像前面那樣子的,不過問題原因找到了。


Android studio 2.2+下的JNi環境搭建就是這樣,歡迎一起交流學習。

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