久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

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++不是跨平臺的,,但通過NDK配置生成的動態(tài)庫卻可以兼容各個(gè)平臺。比如C在Windows平臺編譯后生成.exe文件,,那么源碼通過NDK編譯后可以生成在安卓手機(jī)上運(yùn)行的二進(jìn)制文件.so

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

Android Studio2.2之前對于JNI開發(fā)的支持不是很好,,開發(fā)一般使用Eclipse+插件編寫本地動態(tài)庫。后面Google官方全面增強(qiáng)了對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è)簡單的無參的JNI方法,。
先創(chuàng)建一個(gè)jni目錄,我直接在src的父目錄下創(chuàng)建的,,也可以在其他目錄創(chuàng)建,,因?yàn)樽罱K只需要編譯好的動態(tài)庫。在jni目錄下創(chuàng)建Android.mk和demo.c文件,。

Android.mk是一個(gè)makefile配置文件,,安卓大量采用makefile進(jìn)行自動化編譯。LOCAL_MODULE定義的名稱就是編譯好的so庫名稱,比如這里是jni-demo,,最終生成的動態(tài)庫名稱就叫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è)很簡單的方法,,返回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編譯生成的動態(tài)庫會有四個(gè)CPU平臺:arm64-v8a、armeabi-v7a,、x86,、x86_64。如果創(chuàng)建Application.mk就可以指定要生成的CPU平臺,,語法也很簡單:

APP_ABI := all

這樣就會生成各個(gè)CPU平臺下的動態(tài)庫,。

5.使用ndk-build編程生成.so庫

切回到j(luò)ni目錄的父目錄下,在Terminal中運(yùn)行ndk-build指令,,就可以在和jni目錄同級生成一個(gè)libs文件夾,,里面存放相對應(yīng)的平臺的.so庫。同時(shí)生成的還有一個(gè)中間臨時(shí)的obj文件夾,,和jni文件夾可以一起刪除,。
需要注意,使用NDK一定要先在build.gradle下要配置ndk-build的相關(guān)路徑,,這樣在編寫本地代碼時(shí)才會有相關(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庫的存放位置,。但在實(shí)際使用中,,就算不指定,運(yùn)行時(shí)仍然可以加載正確的.so庫文件,,并且如果添加該代碼后有時(shí)會報(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庫并調(diào)用方法

在類初始化的時(shí)候要加載該.so庫,,一般會寫在靜態(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)類中對應(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)識去找方法。
  • 獲取方法的簽名:首先定位到該類的字節(jié)碼文件所在的父目錄,,一般在module\build\intermediates\classes\debug>,通過javap -s com.mercury.usejava.MainActivity獲取整個(gè)類所有的內(nèi)部類型簽名,。無參方法test1()的簽名是()V
  • 通過JNIEnv對象的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è)跨平臺的安裝(編譯)工具,,通過編寫CMakeLists.txt,,可以生成對應(yīng)的makefile或project文件,再調(diào)用底層的編譯,。AS 2.2之后工具中增加了對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,。其中會提示配置C++支持的功能.

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

  • C++ Standard:指定編譯庫的環(huán)境。
  • Exception Support:當(dāng)前項(xiàng)目支持C++異常處理
  • Runtime Type Information Support:除異常處理外,,還支持動態(tài)轉(zhuǎn)類型(dynamic casting) ,、模塊集成、以及對象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í)候會自動將cpp文件編譯成so文件拷貝進(jìn)去,。如果要提供給外部使用時(shí),Make Project,,之后在libs目錄下就可以看到生成的對應(yīng)配置的相關(guān)CPU平臺的.so文件,。

CMakeLists.txt

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


# 編譯本地庫時(shí)我們需要的最小的cmake版本
cmake_minimum_required(VERSION 3.4.1)

# 相當(dāng)于Android.mk
add_library( # Sets the name of the library.設(shè)置編譯生成本地庫的名字
             native-lib

             # Sets the library as a shared library.庫的類型
             SHARED

             # Provides a relative path to your source file(s).編譯文件的路徑
             src/main/cpp/native-lib.cpp )

# 添加一些我們在編譯我們的本地庫的時(shí)候需要依賴的一些庫,,這里是用來打log的庫
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)自己生成的庫和一些第三方庫或者系統(tǒng)庫
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庫的輸出路徑,但一定要在add_library之前設(shè)置,否則不會生效:


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

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

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

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

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

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

1 獲取jstring的長度

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

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

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

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

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

效果圖

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

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

網(wǎng)上查了一下,JNI在調(diào)用NewStringUTF方法時(shí),,遇到不認(rèn)識的字符就會退出,,因?yàn)樘摂M機(jī)dalvik/vm/CheckJni.cpp里面的checkUTFString會對字符類型進(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è)人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多