3-ndk學(xué)習(xí)之jni基礎(chǔ)篇(1)

首先需要搞清楚幾個(gè)概念,是先有jni,還是ndk?
很多人都會(huì)覺(jué)得先有ndk,然后才有的jni,其實(shí)不是的。JNI是JAVA提供的,即Java Native Interface,它是為了實(shí)現(xiàn)java和底層代碼(例如c,c++)之間的交互而生的一套api。NDK是安卓的一套工具集(JNI,gcc,g++ ..... ),為了方便編譯而集成的。
因此可以聯(lián)系android系統(tǒng)架構(gòu)的概念,


u=1052085805,427367910&fm=26&gp=0.jpg

從上到下:上層應(yīng)用層,如聯(lián)系人,通話(huà),短信,我們開(kāi)發(fā)的apk應(yīng)用等;
系統(tǒng)應(yīng)用層,如四大組件,系統(tǒng)通知,manager等,這些都是java寫(xiě)的;
類(lèi)庫(kù)層, 大部分是c,c++寫(xiě)的,主要是jvm和一些三方庫(kù)比如sqlite等;
linux內(nèi)核層,主要是一些藍(lán)牙,wifi,camara驅(qū)動(dòng)等;
所以網(wǎng)上會(huì)有很多人都覺(jué)得,JNI是很偉大的,如果沒(méi)有JNI,這個(gè)系統(tǒng)架構(gòu)不復(fù)存在。

jni項(xiàng)目介紹

首先在as中新建一個(gè)c++項(xiàng)目,會(huì)自動(dòng)生成CMakeList.txt native-lib.cpp ,代碼如下,默認(rèn)生成的那些注釋我都刪掉了,下面注釋是我自己理解的:

CMakeList.txt

cmake_minimum_required(VERSION 3.4.1)
add_library(
        native-lib
        SHARED    # 動(dòng)態(tài)庫(kù)  Linux .so   Windows .dll
        native-lib.cpp)
find_library(
        log-lib
        log)
target_link_libraries(
        native-lib
        ${log-lib})

native-lib.cpp

#include <jni.h>
#include <string>

extern "C"  // 支持C語(yǔ)言的代碼
JNIEXPORT   // Linux 和 Windows jni.h 內(nèi)部定義全部都不一樣,此宏代表我要暴露出去的標(biāo)準(zhǔn)形式定義
            // 例如:在Windows中,對(duì)外暴露的標(biāo)準(zhǔn)就規(guī)定了,所以函數(shù)必須是Windows系統(tǒng)規(guī)則定義的
jstring
JNICALL     // Linux 和 Windows jni.h 內(nèi)部定義全部都不一樣,此宏代表 當(dāng)前函數(shù) 壓棧規(guī)則(行參規(guī)則)
            // 例如:Windows中:代表函數(shù)壓棧 從右 到 左邊
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

我們自己寫(xiě)一個(gè)jni方法

在MainActivity中,新建一個(gè)最簡(jiǎn)單的native方法,alt+enter自動(dòng)生成還是很簡(jiǎn)單的,

public native void test01();

其中在native-lib.cpp中生成如下:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test01(JNIEnv *env, jobject instance) {
      //任何jni方法都會(huì)有這兩個(gè)參數(shù)
      //其中第一個(gè)參數(shù) env,Java虛擬機(jī)自動(dòng)攜帶過(guò)來(lái)的,就是為了讓我們可以使用JNI的API
      //其中第二個(gè)參數(shù)instance,誰(shuí)調(diào)用就是指的誰(shuí),這里是java中的 MainActivity 這個(gè)實(shí)例
}

c,jni,java3個(gè)領(lǐng)域中數(shù)據(jù)類(lèi)型的轉(zhuǎn)換關(guān)系

    // C領(lǐng)域中              JNI領(lǐng)域中          Java領(lǐng)域中
    // int                         jint                        int
    // const char *          jstring                  String
    //                              jclass                   class
    //                              jmethodID            Method

接下來(lái)就開(kāi)始進(jìn)行jni的基礎(chǔ)學(xué)習(xí)了,在MainActivity中新建幾個(gè)test方法,大家可以直接擼代碼,注釋步驟寫(xiě)的非常清楚:

jni基本方法調(diào)用

package com.example.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int[] ints = {1,2,3,4,5,6};
        String[] strings = {"斯塔克", "史蒂夫"};
        //第一個(gè)test1方法
        test1(42,"a warm heart",ints,strings);
    }

    //第一個(gè)test方法
    public native void test1(int number, String text, int[] intArray, String[] array);
}

#include <jni.h>
#include <string>
#include <android/log.h>

// 日志打印
#define TAG "ftd"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)


extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test1(JNIEnv *env, jobject instance, jint number,
                                                  jstring text_, jintArray intArray_,
                                                  jobjectArray array) {
    //1,打印第一個(gè)參數(shù),int和jint可以隨意切換,所以可以這樣直接賦值
    int my_number = number;
    LOGD("my_number: %d\n", my_number);



    //2,打印第二個(gè)參數(shù)
    const char * my_text = env->GetStringUTFChars(text_,NULL);
    //GetStringUTFChars方法中的第二個(gè)參數(shù)意思是,第一重意思:是否在內(nèi)部完成Copy操作,NULL==0 false,  第二重意思:要給他一個(gè)值,讓內(nèi)部可以轉(zhuǎn)起來(lái),這個(gè)值,隨意
    LOGD("my_text: %s\n", my_text);
    //這里一定要記得回收GetStringUTFChars
    env->ReleaseStringUTFChars(text_,my_text);



    //3,打印第三個(gè)參數(shù),即int數(shù)組
    jint * my_int_array = env->GetIntArrayElements(intArray_,NULL);
    //由于這里遍歷的時(shí)候不知道數(shù)組的大小,所有還需要獲取一下數(shù)組的大小,同樣也是jni的api
    jsize  my_int_array_length = env->GetArrayLength(intArray_);
    for (int i = 0; i < my_int_array_length; i++) {
        int result = *(my_int_array+i);//這里使用了指針遍歷
        LOGD("遍歷IntArray里面的值:%d\n",result);
    }
    //還是要記著回收
    env->ReleaseIntArrayElements(intArray_,my_int_array,0);//0代表刷新



    //4,打印第四個(gè)參數(shù),即String數(shù)組,引用類(lèi)數(shù)據(jù)類(lèi)型比較復(fù)雜
    jsize my_string_array_length = env->GetArrayLength(array);
    for (int i = 0; i < my_string_array_length; i++) {
        //引用類(lèi)數(shù)據(jù)類(lèi)型都先用jobject接收
        jobject jobject1 = env->GetObjectArrayElement(array,i);
        jstring jstring1 = static_cast<jstring>(jobject1);
        const char * itemStr = env->GetStringUTFChars(jstring1,NULL);
        LOGD("遍歷String Array 里面的值:%s\n", itemStr);
        // 回收
        env->ReleaseStringUTFChars(jstring1, itemStr);
    }
}

運(yùn)行結(jié)果如下


image.png

jni調(diào)用對(duì)象,是反射機(jī)制

新建Student實(shí)體類(lèi)

public class Student {
    private final static String TAG = Student.class.getSimpleName();

    public String name;
    public int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        Log.d(TAG, "Java setName: name:" + name);
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        Log.d(TAG, "Java setName: age:" + age);
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
    public static void myStaticMethod() {
        Log.d(TAG, "myStaticMethod: ");
    }
}

MainActivity中調(diào)用

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int[] ints = {1,2,3,4,5,6};
        String[] strings = {"斯塔克", "史蒂夫"};
        //第一個(gè)test1方法
        test1(42,"a warm heart",ints,strings);


        Student student = new Student();
        student.age = 27;
        student.name = "托尼";
        //第二個(gè)test2方法
        test2(student);
    }

    //第一個(gè)test方法
    public native void test1(int number, String text, int[] intArray, String[] array);

    //第二個(gè)test方法
    public native void test2(Student student);

native-lib中:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_test2(JNIEnv *env, jobject instance, jobject student) {

    // 1.獲取字節(jié)碼(這里要注意是全包名,但是c++不認(rèn)識(shí).   所有要把.改成/)
    const char * student_clss_str = "com/example/myapplication/Student";
    jclass student_class = env->FindClass(student_clss_str);

    // 2.拿到方法對(duì)象
    const char * sig = "(Ljava/lang/String;)V";
    jmethodID  setName = env->GetMethodID(student_class, "setName", sig);

    //簽名原理:
    //(1).首先先來(lái)一個(gè)()
    //(2).然后括號(hào)后面跟方法的返回值  void--V
    //(3).然后括號(hào)里面是方法的參數(shù),String -- java/lang/String;
    //(4).然后在括號(hào)后的第一位加L


    sig = "(I)V";//基本數(shù)據(jù)類(lèi)型簡(jiǎn)單,直接一個(gè)I就可以
    jmethodID setAge = env->GetMethodID(student_class, "setAge", sig);

    sig = "()V";//沒(méi)有參數(shù),直接就不寫(xiě)
    jmethodID myStaticMethod = env->GetStaticMethodID(student_class, "myStaticMethod", sig);

    // 3.調(diào)用對(duì)象
    const char * str = "09";
    jstring str2 = env->NewStringUTF(str);
    env->CallVoidMethod(student, setName, str2);
    env->CallVoidMethod(student, setAge, 888);
    env->CallStaticVoidMethod(student_class, myStaticMethod);

    env->DeleteLocalRef(student_class); // 回收
    env->DeleteLocalRef(student); // 回收
}

運(yùn)行結(jié)果為


image.png

這里要說(shuō)些簽名,按照原理寫(xiě)可以,也可以直接在命令行里面敲,首先需要把Student編譯成class文件(也就是運(yùn)行一下),在app-build-intermediates-javac-debug-compileDebugJavaWithJavac-classes里,打開(kāi)命令行窗口,輸入javap -s com.example.myapplication.Student,就可以獲得Student下所有方法的簽名了。


image.png

謝謝觀(guān)看,有不對(duì)或者不詳細(xì)的地方希望大家在評(píng)論區(qū)提出寶貴的意見(jiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容