一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Android Studio開發(fā)JNI示例

 QomoIT 2019-05-07

JNI和NDK介紹

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

NDK(Native Development Kit)是Google提供的一套工具,其中一個(gè)特性是提供了交叉編譯,即C或者C++不是跨平臺(tái)的,但通過NDK配置生成的動(dòng)態(tài)庫(kù)卻可以兼容各個(gè)平臺(tái)。比如C在Windows平臺(tái)編譯后生成.exe文件,那么源碼通過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.通過javah命令生成頭文件

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

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

/* 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)境的指針,可通過它來訪問JNI提供的接口方法。jclass也是jni.h中定義好的,類型是jobject,實(shí)際上是一個(gè)不確定類型的指針,這里用來接收J(rèn)ava中的this。實(shí)際編寫中一般只要遵循Java_包名_類名_方法名就好了。

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

像上面的頭文件只是定義了方法,并沒有實(shí)現(xiàn),就像一個(gè)接口一樣。這里就用C寫一個(gè)簡(jiǎn)單的無參的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),語法也很簡(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的過程不是單獨(dú)存在的。而是編寫native方法,Java先通過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, "無參的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無參方法

  • JNI調(diào)用本地方法,根據(jù)類名找到類,注意類名用"/"分隔。
  • 找到類后,根據(jù)方法名找到方法。該函數(shù)GetMethodID最后一個(gè)形參是該形參列表的簽名。不同于Java,C中是通過簽名標(biāo)識(shí)去找方法。
  • 獲取方法的簽名:首先定位到該類的字節(jié)碼文件所在的父目錄,一般在module\build\intermediates\classes\debug>,通過javap -s com.mercury.usejava.MainActivity獲取整個(gè)類所有的內(nèi)部類型簽名。無參方法test1()的簽名是()V。
  • 通過JNIEnv對(duì)象的CallVoidMethod來完成方法的回調(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有參方法

類似于無參方法,只是參數(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)的安裝(編譯)工具,通過編寫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ù),這里是用來打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語言實(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說明含有非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)論公約

    類似文章 更多

    欧美午夜一级特黄大片| 欧美美女视频在线免费看| 男人操女人下面国产剧情| 国产视频福利一区二区| 91午夜少妇极品福利| 草草夜色精品国产噜噜竹菊| 国产精品乱子伦一区二区三区| 久久热在线视频免费观看| 扒开腿狂躁女人爽出白浆av| 在线亚洲成人中文字幕高清| 色狠狠一区二区三区香蕉蜜桃| 黄片在线免费观看全集| 精品国产亚洲一区二区三区| 国产成人在线一区二区三区| 日韩午夜老司机免费视频| 尹人大香蕉中文在线播放| 久久精品少妇内射毛片| 欧美一区日韩二区亚洲三区| 亚洲一区二区三区三区| 日韩免费午夜福利视频| 女生更色还是男生更色| 亚洲一区二区三区三区| 日韩性生活片免费观看| 国产精品伦一区二区三区四季| 老熟妇乱视频一区二区| 欧美韩国日本精品在线| 尹人大香蕉一级片免费看| 狠狠做深爱婷婷久久综合| 好吊视频一区二区在线| 亚洲欧美日韩国产自拍| 国产传媒精品视频一区| 亚洲精品一区二区三区日韩| 日本中文字幕在线精品| 国产又粗又猛又爽色噜噜| 国产成人精品一区二区在线看| 成年女人下边潮喷毛片免费| 亚洲欧美日韩在线看片 | 日韩熟妇人妻一区二区三区| 日本女优一色一伦一区二区三区| 日韩精品一区二区三区四区| 在线九月婷婷丁香伊人|