乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Android Studio開發(fā)JNI示例

       QomoIT 2019-05-07

      JNI和NDK介紹

      JNI(Java Native Interface),是方便Java調(diào)用C、C++等Native代碼所封裝的一層接口,相當(dāng)于一座橋梁。通過(guò)JNI可以操作一些Java無(wú)法完成的與系統(tǒng)相關(guān)的特性,尤其在圖像和視頻處理中大量用到。

      NDK(Native Development Kit)是Google提供的一套工具,其中一個(gè)特性是提供了交叉編譯,即C或者C++不是跨平臺(tái)的,但通過(guò)NDK配置生成的動(dòng)態(tài)庫(kù)卻可以兼容各個(gè)平臺(tái)。比如C在Windows平臺(tái)編譯后生成.exe文件,那么源碼通過(guò)NDK編譯后可以生成在安卓手機(jī)上運(yùn)行的二進(jìn)制文件.so

      在AS中使用ndk-build開發(fā)JNI示例

      Android Studio2.2之前對(duì)于JNI開發(fā)的支持不是很好,開發(fā)一般使用Eclipse+插件編寫本地動(dòng)態(tài)庫(kù)。后面Google官方全面增強(qiáng)了對(duì)JNI的支持,包括內(nèi)置NDK。

      1.在AS中新建一個(gè)項(xiàng)目

      2.聲明一個(gè)native方法

      package com.mercury.jnidemo;
      
      public class JNITest {
      
          public native static String getStrFromJNI();
      
      }
      
      

      3.通過(guò)javah命令生成頭文件

      在AS的Terminal中,先進(jìn)入要調(diào)用本地代碼的類所在的目錄,也就是在項(xiàng)目中的具體路徑,比如這里是cd app\src\main\java。然后通過(guò)javah命令生成該類的頭文件,注意包名+類名.這里是javah -jni com.mercury.jnidemo.JNITest,生成頭文件com_mercury_jnidemo_JNITest.h

      實(shí)際項(xiàng)目最終可以不包含此頭文件,不熟悉C的語(yǔ)法的開發(fā)人員,借助于該頭文件可以知道JNI的相關(guān)語(yǔ)法:

      /* DO NOT EDIT THIS FILE - it is machine generated */
      #include <jni.h>
      /* Header for class com_mercury_jnidemo_JNITest */
      
      #ifndef _Included_com_mercury_jnidemo_JNITest
      #define _Included_com_mercury_jnidemo_JNITest
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
       * Class:     com_mercury_jnidemo_JNITest
       * Method:    getStrFromJNI
       * Signature: ()Ljava/lang/String;
       */
      JNIEXPORT jstring JNICALL Java_com_mercury_jnidemo_JNITest_getStrFromJNI
        (JNIEnv *, jclass);
      
      #ifdef __cplusplus
      }
      #endif
      #endif
      

      首先引入jni.h,里面包含了很多宏定義及調(diào)用本地方法的結(jié)構(gòu)體。重點(diǎn)是方法名的格式。這里的JNIEXPORT和JNICALL都是jni.h中所定義的宏。JNIEnv *表示一個(gè)指向JNI環(huán)境的指針,可通過(guò)它來(lái)訪問(wèn)JNI提供的接口方法。jclass也是jni.h中定義好的,類型是jobject,實(shí)際上是一個(gè)不確定類型的指針,這里用來(lái)接收J(rèn)ava中的this。實(shí)際編寫中一般只要遵循Java_包名_類名_方法名就好了。

      4.實(shí)現(xiàn)JNI方法

      像上面的頭文件只是定義了方法,并沒(méi)有實(shí)現(xiàn),就像一個(gè)接口一樣。這里就用C寫一個(gè)簡(jiǎn)單的無(wú)參的JNI方法。
      先創(chuàng)建一個(gè)jni目錄,我直接在src的父目錄下創(chuàng)建的,也可以在其他目錄創(chuàng)建,因?yàn)樽罱K只需要編譯好的動(dòng)態(tài)庫(kù)。在jni目錄下創(chuàng)建Android.mk和demo.c文件。

      Android.mk是一個(gè)makefile配置文件,安卓大量采用makefile進(jìn)行自動(dòng)化編譯。LOCAL_MODULE定義的名稱就是編譯好的so庫(kù)名稱,比如這里是jni-demo最終生成的動(dòng)態(tài)庫(kù)名稱就叫l(wèi)ibjni-demo.so。 LOCAL_SRC_FILES表示參與編譯的源文件名稱,這里就是demo.c

      LOCAL_PATH := $(call my-dir)
      
      include $(CLEAR_VARS)
      
      LOCAL_MODULE := jni-demo
      LOCAL_SRC_FILES := demo.c
      
      include $(BUILD_SHARED_LIBRARY)
      

      這里的demo.c實(shí)現(xiàn)了一個(gè)很簡(jiǎn)單的方法,返回String類型。

      #include<jni.h>
      
      jstring Java_com_mercury_jnidemo_JNITest_getStrFromJNI(JNIEnv *env,jobject thiz){
          return (*env)->NewStringUTF(env,"I am Str from jni libs!");
      }
      
      

      這時(shí)候NDK編譯生成的動(dòng)態(tài)庫(kù)會(huì)有四個(gè)CPU平臺(tái):arm64-v8a、armeabi-v7a、x86、x86_64。如果創(chuàng)建Application.mk就可以指定要生成的CPU平臺(tái),語(yǔ)法也很簡(jiǎn)單:

      APP_ABI := all
      

      這樣就會(huì)生成各個(gè)CPU平臺(tái)下的動(dòng)態(tài)庫(kù)。

      5.使用ndk-build編程生成.so庫(kù)

      切回到j(luò)ni目錄的父目錄下,在Terminal中運(yùn)行ndk-build指令,就可以在和jni目錄同級(jí)生成一個(gè)libs文件夾,里面存放相對(duì)應(yīng)的平臺(tái)的.so庫(kù)。同時(shí)生成的還有一個(gè)中間臨時(shí)的obj文件夾,和jni文件夾可以一起刪除。
      需要注意,使用NDK一定要先在build.gradle下要配置ndk-build的相關(guān)路徑,這樣在編寫本地代碼時(shí)才會(huì)有相關(guān)的提示功能,并且可以關(guān)聯(lián)到相關(guān)的頭文件

      externalNativeBuild {
              ndkBuild {
                  path 'jni/Android.mk'
              }
          }
      
      

      還有一點(diǎn),網(wǎng)上很多資料都在build.gradle中加入以下代碼:

      sourceSets{
              main{
                  jniLibs.srcDirs=['libs']
              }
          }
      

      這樣就指定了目標(biāo).so庫(kù)的存放位置。但在實(shí)際使用中,就算不指定,運(yùn)行時(shí)仍然可以加載正確的.so庫(kù)文件,并且如果添加該代碼后有時(shí)會(huì)報(bào)出以下錯(cuò)誤:

       Error:Execution failed for task ':usejava:transformNativeLibsWithMergeJniLibsForDebug'.
      	> More than one file was found with OS independent path 'lib/x86/libjni-calljava.so'
      	> 
      

      6.加載.so庫(kù)并調(diào)用方法

      在類初始化的時(shí)候要加載該.so庫(kù),一般會(huì)寫在靜態(tài)代碼塊里。名稱就是前面的LOCAL_MODULE。

          static {
              System.loadLibrary("jni-demo");
          }
      
      
      

      需要注意的是如果是有參的JNI方法,那么直接在參數(shù)列表里補(bǔ)充在jni.h預(yù)先typedef好的數(shù)據(jù)類型就可以了。

      JNI調(diào)用Java

      不同于JNI調(diào)用C,JNI調(diào)用Java的過(guò)程不是單獨(dú)存在的。而是編寫native方法,Java先通過(guò)JNI調(diào)用該方法,在方法內(nèi)部再去回調(diào)類中對(duì)應(yīng)的Java方法。步驟有些類似于Java中的反射。這里寫定義三個(gè)點(diǎn)擊事件,三個(gè)Native方法,三種Java的方法類型,根據(jù)相關(guān)的Log判斷是否成功。

      public class MainActivity extends AppCompatActivity {
      
          public static final String TAG = "MainActivity";
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
          }
      
          static {
              System.loadLibrary("jni-calljava");
          }
      
          public void noParamMethod() {
              Log.i(TAG, "無(wú)參的Java方法被調(diào)用了");
          }
      
          public void paramMethod(int number) {
              Log.i(TAG, "有參的Java方法被調(diào)用了" + number + "次");
          }
      
          public static void staticMethod() {
              Log.i(TAG, "靜態(tài)的Java方法被調(diào)用了");
          }
      
          public void click1(View view) {
              test1();
          }
      
          public void click2(View view) {
              test2();
          }
      
          public void click3(View view) {
              test3();
          }
      
          public native void test1();
      
          public native void test2();
      
          public native void test3();
      
      }
      

      1.調(diào)用Java無(wú)參方法

      • JNI調(diào)用本地方法,根據(jù)類名找到類,注意類名用"/"分隔。
      • 找到類后,根據(jù)方法名找到方法。該函數(shù)GetMethodID最后一個(gè)形參是該形參列表的簽名。不同于Java,C中是通過(guò)簽名標(biāo)識(shí)去找方法。
      • 獲取方法的簽名:首先定位到該類的字節(jié)碼文件所在的父目錄,一般在module\build\intermediates\classes\debug>,通過(guò)javap -s com.mercury.usejava.MainActivity獲取整個(gè)類所有的內(nèi)部類型簽名。無(wú)參方法test1()的簽名是()V
      • 通過(guò)JNIEnv對(duì)象的CallVoidMethod來(lái)完成方法的回調(diào),最后一個(gè)形參是可變參數(shù)。
      JNIEXPORT void JNICALL Java_com_mercury_usejava_MainActivity_test1
        (JNIEnv * env, jobject obj){
             //回調(diào)MainActivity中的noParamMethod
          jclass clazz = (*env)->FindClass(env, "com/mercury/usejava/MainActivity");
          if (clazz == NULL) {
              printf("find class Error");
              return;
          }
          jmethodID id = (*env)->GetMethodID(env, clazz, "noParamMethod", "()V");
          if (id == NULL) {
              printf("find method Error");
          }
          (*env)->CallVoidMethod(env, obj, id);
        }
      

      2.調(diào)用Java有參方法

      類似于無(wú)參方法,只是參數(shù)簽名和可變參數(shù)的不同

      3.調(diào)用Java靜態(tài)方法

      注意獲取方法名的方法是GetStaticMethodID,調(diào)用方法的函數(shù)名是CallStaticVoidMethod,并且由于是靜態(tài)方法,不應(yīng)該傳入jobject參數(shù),而直接是jclass.

      JNIEXPORT void JNICALL Java_com_mercury_usejava_MainActivity_test3
        (JNIEnv * env, jobject obj){
          jclass clazz = (*env)->FindClass(env, "com/mercury/usejava/MainActivity");
          if (clazz == NULL) {
              printf("find class Error");
              return;
          }
          jmethodID id = (*env)->GetStaticMethodID(env, clazz, "staticMethod", "()V");
          if (id == NULL) {
              printf("find method Error");
          }
      
          (*env)->CallStaticVoidMethod(env, clazz, id);
        }
      

      相應(yīng)日志

      使用CMake開發(fā)JNI

      CMake是一個(gè)跨平臺(tái)的安裝(編譯)工具,通過(guò)編寫CMakeLists.txt,可以生成對(duì)應(yīng)的makefile或project文件,再調(diào)用底層的編譯。AS 2.2之后工具中增加了對(duì)CMake的支持,官方也推薦用CMake+CMakeLists.txt的方式,代替ndk-build+Android.mk+Application.mk的方式去構(gòu)建JNI項(xiàng)目.

      1.創(chuàng)建使用CMake構(gòu)建的項(xiàng)目

      開始前AS要先在SDK Manager中安裝SDK Tools->CMake

      只要勾選Include C++ Support。其中會(huì)提示配置C++支持的功能.

      一般默認(rèn)就可以了,各個(gè)選項(xiàng)的具體含義:

      • C++ Standard:指定編譯庫(kù)的環(huán)境。
      • Exception Support:當(dāng)前項(xiàng)目支持C++異常處理
      • Runtime Type Information Support:除異常處理外,還支持動(dòng)態(tài)轉(zhuǎn)類型(dynamic casting) 、模塊集成、以及對(duì)象I/O

      2.工程的目錄結(jié)構(gòu)


      創(chuàng)建好的工程主Module下直接就有.externalNativeBuild,多出一個(gè)CMakeLists.txt,相當(dāng)于以前的配置文件。并且在src/main目錄下多了一個(gè)cpp文件夾,里面存放的是C++文件,相當(dāng)于以前的jni文件夾。這個(gè)是工程創(chuàng)建后AS生成的示例JNI方法,返回了一個(gè)字符串。后面開發(fā)JNI就可以按照這個(gè)目錄結(jié)構(gòu)。

      相應(yīng)的,build.gradle下也增加了一些配置。

      android {
          ...
          defaultConfig {
              ...
              externalNativeBuild {
                  cmake {
                      cppFlags "-std=c++14 -frtti -fexceptions"
                  }
              }
          }
          buildTypes {
              ...
          }
          externalNativeBuild {
              cmake {
                  path "CMakeLists.txt"
              }
          }
      }
      

      defaultConfig中的externalNativeBuild各項(xiàng)屬性和前面創(chuàng)建項(xiàng)目時(shí)的選項(xiàng)配置有關(guān),外部的externalNativeBuild則定義了CMakeLists.txt的存放路徑。
      如果只是在自己的項(xiàng)目中使用,CMake的方式在打包APK的時(shí)候會(huì)自動(dòng)將cpp文件編譯成so文件拷貝進(jìn)去。如果要提供給外部使用時(shí),Make Project,之后在libs目錄下就可以看到生成的對(duì)應(yīng)配置的相關(guān)CPU平臺(tái)的.so文件。

      CMakeLists.txt

      CMakeLists.txt可以自定義命令、查找文件、頭文件包含、設(shè)置變量,具體可見 官方文檔。項(xiàng)目默認(rèn)生成的CMakeLists.txt核心內(nèi)容如下:

      
      # 編譯本地庫(kù)時(shí)我們需要的最小的cmake版本
      cmake_minimum_required(VERSION 3.4.1)
      
      # 相當(dāng)于Android.mk
      add_library( # Sets the name of the library.設(shè)置編譯生成本地庫(kù)的名字
                   native-lib
      
                   # Sets the library as a shared library.庫(kù)的類型
                   SHARED
      
                   # Provides a relative path to your source file(s).編譯文件的路徑
                   src/main/cpp/native-lib.cpp )
      
      # 添加一些我們?cè)诰幾g我們的本地庫(kù)的時(shí)候需要依賴的一些庫(kù),這里是用來(lái)打log的庫(kù)
      find_library( # Sets the name of the path variable.
                    log-lib
      
                    # Specifies the name of the NDK library that
                    # you want CMake to locate.
                    log )
      
      # 關(guān)聯(lián)自己生成的庫(kù)和一些第三方庫(kù)或者系統(tǒng)庫(kù)
      target_link_libraries( # Specifies the target library.
                             native-lib
      
                             # Links the target library to the log library
                             # included in the NDK.
                             ${log-lib} )
      

      使用CMakeLists.txt同樣可以指定so庫(kù)的輸出路徑,但一定要在add_library之前設(shè)置,否則不會(huì)生效:

      
      set(CMAKE_LIBRARY_OUTPUT_DIRECTORY 
      	${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}) #指定路徑
      #生成的so庫(kù)在和CMakeLists.txt同級(jí)目錄下的libs文件夾下
      
      

      如果想要配置so庫(kù)的目標(biāo)CPU平臺(tái),可以在build.gradle中設(shè)置

      android {
          ...
          defaultConfig {
              ...
              ndk{
                  abiFilters "x86","armeabi","armeabi-v7a"
              }
          }
      	...
        
      }
      

      需要注意的是,如果是多次使用add_library,則會(huì)生成多個(gè)so庫(kù)。如果想將多個(gè)本地文件編譯到一個(gè)so庫(kù)中,只要最后一個(gè)參數(shù)添加多個(gè)C/C++文件的相對(duì)路徑就可以

      用C語(yǔ)言實(shí)現(xiàn)字符串加密

      Java中實(shí)現(xiàn)字符串加密的一種比較簡(jiǎn)單的方法是異或,將字符串轉(zhuǎn)換為字符數(shù)組,遍歷對(duì)其中的每個(gè)字符用密鑰(可以是字符)進(jìn)行一次異或運(yùn)算,生成新的字符串。如果用JNI和C實(shí)現(xiàn),大致步驟如下(jstring是要加密的字符串):

      1 獲取jstring的長(zhǎng)度

      2 動(dòng)態(tài)開辟一個(gè)跟data長(zhǎng)度一樣的char*

      3 將 jstring類型轉(zhuǎn)換為char數(shù)組(用char*接收)

      4 遍歷char數(shù)組,進(jìn)行異或運(yùn)算

      5 將char*轉(zhuǎn)換為jstring類型返回

      6 釋放動(dòng)態(tài)開辟的堆內(nèi)存空間

      效果圖

      我是用的是5.0的模擬器,有時(shí)會(huì)閃退,查看系統(tǒng)日志,會(huì)報(bào)出一下錯(cuò)誤:

      JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8
      

      網(wǎng)上查了一下,JNI在調(diào)用NewStringUTF方法時(shí),遇到不認(rèn)識(shí)的字符就會(huì)退出,因?yàn)樘摂M機(jī)dalvik/vm/CheckJni.cpp里面的checkUTFString會(huì)對(duì)字符類型進(jìn)行檢查。替代方案是在開始轉(zhuǎn)換前,先檢查char*中是否含有非UTF-8字符,有的話返回空字符串。完整代碼如下:

      #include<jni.h>
      #include <stdlib.h>
      
      jboolean checkUtfBytes(const char* bytes, const char** errorKind) ;
      
      jstring Java_com_mercury_cmakedemo_MainActivity_encryptStr
              (JNIEnv *env, jobject object, jstring data){
          if(data==NULL){
              return (*env)->NewStringUTF(env, "");
          }
          jsize len = (*env)->GetStringLength(env, data);
          char *buffer = (char *) malloc(len * sizeof(char));
          (*env)->GetStringUTFRegion(env, data, 0, len, buffer);
          int i=0;
          for (; i <len ; i++) {
              buffer[i] = (char) (buffer[i] ^ 2);
          }
      
          const char *errorKind = NULL;
          checkUtfBytes(buffer, &errorKind);
          free(buffer);
          if (errorKind == NULL) {
              return (*env)->NewStringUTF(env, buffer);
          } else {
              return (*env)->NewStringUTF(env, "");
          }
      }
      
      //把char*和errorKind傳入,如果errorKind不為NULL說(shuō)明含有非utf-8字符,做相應(yīng)處理
      jboolean checkUtfBytes(const char* bytes, const char** errorKind) {
          while (*bytes != '\0') {
              jboolean utf8 = *(bytes++);
              // Switch on the high four bits.
              switch (utf8 >> 4) {
                  case 0x00:
                  case 0x01:
                  case 0x02:
                  case 0x03:
                  case 0x04:
                  case 0x05:
                  case 0x06:
                  case 0x07:
                      // Bit pattern 0xxx. No need for any extra bytes.
                      break;
                  case 0x08:
                  case 0x09:
                  case 0x0a:
                  case 0x0b:
                  case 0x0f:
                      /*
                       * Bit pattern 10xx or 1111, which are illegal start bytes.
                       * Note: 1111 is valid for normal UTF-8, but not the
                       * modified UTF-8 used here.
                       */
                      *errorKind = "start";
                      return utf8;
                  case 0x0e:
                      // Bit pattern 1110, so there are two additional bytes.
                      utf8 = *(bytes++);
                      if ((utf8 & 0xc0) != 0x80) {
                          *errorKind = "continuation";
                          return utf8;
                      }
                      // Fall through to take care of the final byte.
                  case 0x0c:
                  case 0x0d:
                      // Bit pattern 110x, so there is one additional byte.
                      utf8 = *(bytes++);
                      if ((utf8 & 0xc0) != 0x80) {
                          *errorKind = "continuation";
                          return utf8;
                      }
                      break;
              }
          }
          return 0;
      }
      

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多