1. 程式人生 > >Android Ndk開發進階

Android Ndk開發進階

前言

上一篇文章,我們講了Ndk的基本用法即Java呼叫C方法返回基本型別或String型別(引用型別)。接下來,我們就要講到C呼叫Java類裡面的方法包括靜態方法,然後用C來處理,Java程式碼中傳入的物件,通過C程式碼處理過後,返回該物件。好了,話不多說,新建專案。

建立工程

這裡寫圖片描述
通過上一篇blog介紹,大家都應該會建立專案了吧。不會的回去看看上一篇文章。這裡多建立了一個MyInfo類。用於在建立物件Java與C中相互傳遞。如圖:
這裡寫圖片描述
實體類裡面的欄位有基本型別,也有引用型別(String型別)。對於NDK開發來說,接收一個MyInfo實體類物件和該物件內的引用型別屬性來說,都是jobject型別。所以大家不要搞混了。
專案中H檔案與C檔案,根據命令和提示建立,建立的依據是,呼叫jni時寫的方法。如下圖:
這裡寫圖片描述


這個大家一看就明白,建立JNI方法。目前Android studio 對於Ndk開發程式設計不能夠很好的支援,所以,你會看到,即便是專案完成後成功執行,JNI方法也會標紅報錯。不過只要能執行,不管那麼多了。再就是創建出H檔案,作用類似於Java的interface(介面),C檔案“實現”H檔案,include進去。
建立h檔案,在工程目錄下我們用的是javah -jni 全類名(包名➕雷明),自動建立H檔案。這裡的全類名就是com.china.MainActivity。這樣我們就生成了。如下圖:
這裡寫圖片描述
在MainActivity中通過提示創建出C檔案,並引用(include)剛才我們生成的h檔案。如下圖:這裡寫圖片描述

接下來,就開始我們的資料操作了。

C呼叫Java

首先,我們要確定在C中要呼叫java中的什麼方法。這樣我們就在MainActivity中寫個加法運算方法add(int x,int y)。

public int add(int x, int y) {
        Toast.makeText(this, "x+y=" + (x + y), Toast.LENGTH_SHORT).show();
        return x + y;
    }

同理,在C中有對應的呼叫方法

/*
 * Class:     com_china_MainActivity
 * Method
: getInt * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_china_MainActivity_getInt (JNIEnv *env, jobject jobj, jint ji, jint jj) { //TODO };

我們要使C中能夠呼叫java,那麼就需要先去呼叫c的方法。然後c中的方法再呼叫java類裡面暴露給C呼叫的方法。(有點繞啊,��)Java->C->Java. 對應的,在java中呼叫 public native int getInt(int x, int y);//。然後在C程式碼中操作使其呼叫MainActivity中的add(x,y)方法。開始吧。

JNIEXPORT jint JNICALL Java_com_china_MainActivity_getInt
        (JNIEnv *env, jobject jobj, jint ji, jint jj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
//    jclass jclazz = (*env)->FindClass(env, "com/china/MainActivity");
//2.得到方法
//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最後一個引數是方法簽名
    jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
//3.例項化該類
// jobject     (*AllocObject)(JNIEnv*, jclass);
//    jobject jobject = (*env)->AllocObject(env, jclazz);
//4.呼叫方法
//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint value = (*env)->CallIntMethod(env, jobj, jmethodIDs, ji, jj);
    return value;
}

這裡要注意的是,我們讓c呼叫java程式碼,那麼就要讓c知道是呼叫哪個類的哪個方法。哪個類呢?注意
jclass jclazz = (*env)->FindClass(env, “com/china/MainActivity”); 這句話表示,獲取到MainActivity類是“靜態的”,我們知道,Activity是有生命週期的類,普通類是沒有的。這裡理解為“靜態的”。所以還有一個方法,得到這個類的引用:jclass jclazz = (*env)->GetObjectClass(env, jobj); 傳入的jobj就是該方法傳入的jobject(類似於java中this引用之類的意思)。得到這個類後,我們還要通過操作呼叫到MainActivity中的add方法。
這裡的操作就是獲得該方法的簽名。獲得方法簽名,我們會用到javap命令。javap -s 全類名(包名加類名)。通過GetMethodID(xx,xx,xx,xx),第一二個引數就是env(C中的指標引用),獲得的java類的引用。第三個引數就是C要呼叫的方法名。第四個就是該方法簽名。然後,就是最後一步了。呼叫。(CallIntMethod)(JNIEnv, jobject, jmethodID, …);方法前幾個引數。都有了,後面還有個可變引數。就是我們呼叫java方法的傳入之。這裡,java與C中int等基本型別通用的。所以可以直接丟進去。完了。
簡單吧。接下來更難的來了。

傳遞java物件

在MAinActivity中先初始化一個MyInfo物件出來,並且賦值。

private void init() {
        mInfo = new MyInfo();
        mInfo.setAge(20);
        mInfo.setName("renk");
    }

呼叫jni方法。 Log.e(“renk”, “age=” + getAge(mInfo));當然在點選事件裡面呼叫了哦。在來看C中怎麼操作物件,並且將物件裡面的age取出來,加上100,返回給mainActivity。打印出來。
剛才,也講了。c中是不能直接操作java物件或者類的。但是,我們可以換個思路,在C中通過該物件的類得到相應的屬性。然後在C中建立一個結構體,來存傳進來的物件裡面的值。好,先把結構圖創建出來。
當然如果不知道結構體是什麼的同學,建議複習一下C知識。

typedef struct {
    _Bool gen;
    int ag;
    char str[255];
} MyCInfo;
//建立一個結構
MyCInfo myCInfo;

創建出一個結構體後,我們就會在c方法中使用它,並且對其賦值,這裡我們建立的時候,由於C沒有String型別,故使用了char[]來處理字串型別的資料。下面看具體取值、賦值、計算並返回結果的程式碼:
這裡寫圖片描述
還是剛才的操作,先拿到MainActivity的物件的引用。然後,再獲取傳入物件的的類的相關jclass,屬性id(fieldId),當然這裡面同樣有用到方法簽名,屬性簽名。屬性名稱。。不用多做解釋。主要看取值。myCInfo.gen = (env)->GetBooleanField(env, obj, jfiGen);取值是個個的來,有不同方法,這顧名思義是去boolean值得性別值。還有,GetIntField()/GetLongField()/Get…等等。要去String屬性值,就用到GetObjectField(),第一二個引數不多說,第三個引數就是該屬性的屬性Id值。這裡還要講jstring轉換為C可操作的字串。型別為char 。通過這句話myCInfo.ag += 100;我們就實現了。將傳入的年齡加上100.這邊就可直接返回了。Ok。相關Get系列方法包括靜態的見下圖:
這裡寫圖片描述

這裡還有多說一點的,就是,怎樣將C中的結構體內容賦給java物件,讓其返回呢。同理如下程式碼。

//賦值
    jobject joInfo = (*env)->AllocObject(env, jclazz);
    (*env)->SetBooleanField(env, joInfo, jfiGen, myCInfo.gen);
    (*env)->SetIntField(env, joInfo, jfiAG, myCInfo.ag);
    jstring jstrTmp = (*env)->NewStringUTF(env, myCInfo.str);
    (*env)->SetObjectField(env, joInfo, jfiNam, jstrTmp);
    (*env)->SetObjectField(env, jobj, jInfofieldid, joInfo);

很好理解了,通過上面的學習,這個很容易看懂的額。先通過類,建立其物件,然後通過Set系列方法,分別賦值給該jobject物件。然後return joInfo,就over了。不多說了。
最開始講到的還要通過C呼叫java靜態方法,這個也很簡單,就是把呼叫的方法CallStatic***Method系列方法,就完成了,其他都不變。相關方法下面給出:
這裡寫圖片描述
還有一個功能就是,怎樣讓C去改變activity中的某個屬性的值呢。哈哈,如果看了上面的東西,到這裡都應該會了哈。程式碼如下:

/*
 * Class:     com_china_MainActivity
 * Method:    changeString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring
JNICALL Java_com_china_MainActivity_changeString
        (JNIEnv *env, jobject jobj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
    jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mString", "Ljava/lang/String;");
    jstring jstr = (jstring)(*env)->GetObjectField(env, jobj, jfieldid);
    char *mString = jString2cString(env, jstr);
    char *add = " add c";
    strcat(mString, add);
    jstring encoding = (*env)->NewStringUTF(env, mString);
    (*env)->SetObjectField(env, jobj, jfieldid, encoding);
    return encoding;
}

同樣的通過傳入的jobject,獲得該jclass的引用。獲取該類的屬性Id(fieldID),然後轉換賦值或追加都可以,隨你。最後別忘啦,呼叫Set系列方法。就實現了我們想要的功能。
好了,講完了,有什麼不懂的額,線上留言哦 。最後貼一下,C中的程式碼。謝謝。

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


char *jString2cString(JNIEnv *env, jstring jstr) {
    char *mStr = "";
    jclass jclazz = (*env)->FindClass(env, "java/lang/String");
    jmethodID jmethodid = (*env)->GetMethodID(env, jclazz, "getBytes", "(Ljava/lang/String;)[B");
    jstring encode = (*env)->NewStringUTF(env, "GB2312");
    jbyteArray jarry = (jbyteArray)(*env)->CallObjectMethod(env, jstr, jmethodid, encode);
    jsize length = (*env)->GetArrayLength(env, jarry);
    jbyte *jby = (*env)->GetByteArrayElements(env, jarry, JNI_FALSE);
    if (length > 0) {
        mStr = (char *) malloc(length + 1);
        memcpy(mStr, jby, length);
        mStr[length] = 0;
    }
    (*env)->ReleaseByteArrayElements(env, jarry, jby, 0);
    return mStr;
}

typedef struct {
    _Bool gen;
    int ag;
    char str[255];
} MyCInfo;

MyCInfo myCInfo;

/*
 * Class:     com_china_MainActivity
 * Method:    getString
 * Signature: (Lcom/china/MyInfo;)Ljava/util/List;
 * public native List<String> getString(MyInfo info);
 */
JNIEXPORT void

JNICALL Java_com_china_MainActivity_getString
        (JNIEnv *env, jobject jobj, jobject obj) {
    MyCInfo jinfo ;
    jclass jclazz = (*env)->FindClass(env,"com/china/MyInfo");
    jfieldID jinfoGender = (*env)->GetFieldID(env,jclazz,"gender","Z");
    jfieldID jinfoage = (*env)->GetFieldID(env,jclazz,"age","I");
    jfieldID jinfoname = (*env)->GetFieldID(env,jclazz,"name","Ljava/lang/String;");
    jinfo.gen = (*env)->GetBooleanField(env,obj,jinfoGender);
    jinfo.ag = (*env)->GetIntField(env,obj,jinfoage);
    jstring jstr = (jstring)(*env)->GetObjectField(env, obj, jinfoname);
    char *str = (*env)->GetStringUTFChars(env, jstr, 0);
    strcpy(jinfo.str, str);
    (*env)->ReleaseStringUTFChars(env, jstr, str);
    str ="";
    jclass jclazz2 = (*env)->GetObjectClass(env, jobj);
    jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz2, "getInfoName", "(Ljava/lang/String;)V");
    (*env)->CallVoidMethod(env,jobj,jmethodIDs,jstr);
};

/*
 * Class:     com_china_MainActivity
 * Method:    getAge
 * Signature: (Lcom/china/MyInfo;)I
 * public native int getAge(MyInfo info);
 */
JNIEXPORT jint

JNICALL Java_com_china_MainActivity_getAge
        (JNIEnv *env, jobject jobj, jobject obj) {
    jclass jclaz = (*env)->GetObjectClass(env, jobj);

    jclass jclazz = (*env)->FindClass(env, "com/china/MyInfo");
    jfieldID jfiGen = (*env)->GetFieldID(env, jclazz, "gender", "Z");
    jfieldID jfiAG = (*env)->GetFieldID(env, jclazz, "age", "I");
    jfieldID jfiNam = (*env)->GetFieldID(env, jclazz, "name", "Ljava/lang/String;");
    jfieldID jInfofieldid = (*env)->GetFieldID(env, jclaz, "mInfo", "Lcom/china/MyInfo;");
    //取值
    myCInfo.gen = (*env)->GetBooleanField(env, obj, jfiGen);
//  myCInfo.ag = (*env)->GetIntField(env,obj,jfiAG);
    jstring jstr = (jstring)(*env)->GetObjectField(env, obj, jfiNam);
    char *str = (*env)->GetStringUTFChars(env, jstr, 0);
    strcpy(myCInfo.str, str);
    (*env)->ReleaseStringUTFChars(env, jstr, str);
    str = "";
    myCInfo.ag += 100;

    //賦值
    jobject joInfo = (*env)->AllocObject(env, jclazz);
    (*env)->SetBooleanField(env, joInfo, jfiGen, myCInfo.gen);
    (*env)->SetIntField(env, joInfo, jfiAG, myCInfo.ag);
    jstring jstrTmp = (*env)->NewStringUTF(env, myCInfo.str);
    (*env)->SetObjectField(env, joInfo, jfiNam, jstrTmp);
    (*env)->SetObjectField(env, jobj, jInfofieldid, joInfo);

    return myCInfo.ag;
};

/*
 * Class:     com_china_MainActivity
 * Method:    changeString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring

JNICALL Java_com_china_MainActivity_changeString
        (JNIEnv *env, jobject jobj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
    jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mString", "Ljava/lang/String;");
    jstring jstr = (jstring)(*env)->GetObjectField(env, jobj, jfieldid);
    char *mString = jString2cString(env, jstr);
    char *add = " add c";
    strcat(mString, add);
    jstring encoding = (*env)->NewStringUTF(env, mString);
    (*env)->SetObjectField(env, jobj, jfieldid, encoding);
    return encoding;
};

/*
 * Class:     com_china_MainActivity
 * Method:    changeNumber
 * Signature: ()I
 */
JNIEXPORT jint

JNICALL Java_com_china_MainActivity_changeNumber
        (JNIEnv *env, jobject jobj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
    jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mNumber", "I");
    jint aa = (*env)->GetIntField(env, jobj, jfieldid);
    aa += 100;
    (*env)->SetIntField(env, jobj, jfieldid, aa);
    return aa;
};

/*
 * Class:     com_china_MainActivity
 * Method:    testGetString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 * public native String testGetString(String test);
 */
JNIEXPORT jstring

JNICALL Java_com_china_MainActivity_testGetString
        (JNIEnv *env, jobject jobj, jstring jstr) {

};

/*
 * Class:     com_china_MainActivity
 * Method:    getInt
 * Signature: (II)I
 */
JNIEXPORT jint

JNICALL Java_com_china_MainActivity_getInt
        (JNIEnv *env, jobject jobj, jint ji, jint jj) {
    jclass jclazz = (*env)->GetObjectClass(env, jobj);
//    jclass jclazz = (*env)->FindClass(env, "com/china/MainActivity");
//2.得到方法
//jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最後一個引數是方法簽名
    jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
//3.例項化該類
// jobject     (*AllocObject)(JNIEnv*, jclass);
//    jobject jobject = (*env)->AllocObject(env, jclazz);
//4.呼叫方法
//jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint value = (*env)->CallIntMethod(env, jobj, jmethodIDs, ji, jj);
    return value;
}

相關推薦

Android Ndk開發

前言 上一篇文章,我們講了Ndk的基本用法即Java呼叫C方法返回基本型別或String型別(引用型別)。接下來,我們就要講到C呼叫Java類裡面的方法包括靜態方法,然後用C來處理,Java程式碼中傳入的物件,通過C程式碼處理過後,返回該物件。好了,話不多說,

Android開發——使用Dagger2

前言 關於Dagger2的學習,首先看的官方文件,確實看不懂。然後搜尋網路上的介紹博文,不乏一些講得比較好的,如這個。但終究不夠透徹,還是得回頭研究官方文件。本文不僅僅是翻譯,而是記錄了自己對官方文件的理解。 提供依賴的兩種方式 使用@Inject註解構造器 class

Android開發——測試

導言 每次使用Android Studio建立一個新的工程,都會看到類似如下的目錄結構: 我們編寫的Java程式碼全部放在最上面的包中,下面兩個使用紅線圈中的包總是沒有用過,僅僅知道他們是用於放置測試程式碼的。標註為androidTest的包放置UI相關的測試,標註為test

HenCoder Android 開發: 自定義 View 1-1 繪製基礎

自定義繪製概述 二話不說,我反手就是一個視訊:(視訊掛了,先直接點到優酷去看吧:優酷連結) 首先總結一下視訊中的關鍵點: 自定義繪製的方式是重寫繪製方法,其中最常用的是 onDraw() 繪製的關鍵是 Canvas 的使用 Canvas 的繪製類方法: drawXX

NDKAndroid中的操作

一、建立一個專案名字叫做JNIDemo,在專案中建立一個類名字叫做JNIDemo,我們編寫如下程式碼: public class JNIDemo { //建立一個方法名字叫做sayHello public native void sa

Android開發之NIO非阻塞包(二)

有關Android NIO我們主要分為三大類,ByteBuffer、FileChannel和SocketChannel。由於篇幅原因今天Android123只對前兩個做說明。NIO和傳統的I/O比較大的區別在於傳輸方式非阻塞,一種基於事件驅動的模式,將會使方法執行完後立即返回

Android開發之NIO非阻塞包(三)

有關Android NIO的精髓主要用於高負載的Socket網路傳輸,相對於傳統I/O模型的Socket傳輸方式的優勢,我們已經在 Android開發進階之NIO非阻塞包(一) 中講到了,這裡不再贅述,一起來看看Android NIO有關Socket操作提供的類吧: 一、S

Android開發:Activity和程序的回收和狀態恢復

不管是安卓的官方文件還是原始碼註釋,處處可見“從 Activity A 跳到 Activity B,當系統記憶體不足時 A 可能會被回收……”,而且沒有明確說明 A 和 B 是否屬於同一個 app 或程序。 但是,在官方給的 Activity 生命週期圖中

Android開發-:Json字串轉換為java物件的各種實現方法[json_lib框架、Gson、org.json]

JSON (JavaScript Object Notation) 是一種輕量級的資料交換格式。易於人閱讀和編寫。同時也易於機器解析和生成。它基於JavaScript Programming Language, Standard ECMA-262 3rd Edition

Android開發-:實現微信自動搶紅包的程式

簡單實現了微信自動搶紅包的服務,原理就是根據關鍵字找到相應的View, 然後自動點選。主要是用到AccessibilityService這個輔助服務,基本可以滿足自動搶紅包的功能,但是有些邏輯需要優化,比如,拆完一個紅包後,必須手動點選返回鍵,才能進行下一次自動搶紅包。

Android開發——自定義View的使用及其原理探索

  在Android開發中,系統提供給我們的UI控制元件是有限的,當我們需要使用一些特殊的控制元件的時候,只靠系統提供的控制元件,可能無法達到我們想要的效果,這時,就需要我們自定義一些控制元件,來完成我們想要的效果了。下面,我就來講講自定義控制元件的那些事。   首先,我來講講Android的控制元件架構。

Android開發 -- 通用介面卡 CommonAdapter

      在Android開發中,我們經常會用到ListView 這個元件,為了將ListView 的內容展示出來,我們會去實現一個Adapter來適配,將Layout中的佈局以列表的形式展現到元件中。     比如,像 GGTalk 安卓版的查

Android NDK 開發總結

設置 .text nbsp def runt 編寫 abi 文件的 targe 一.安裝配置環境 1.安裝Android Studio,下載路徑https://developer.android.com/studio/index.html?hl=zh-cn。我下載的是Win

android NDK開發中,用Cygwin調試本地代碼時報錯“Another debug session running,Use --force to kill it”原因及解決的方法

能夠 att cati kill 時報 andro 使用 deb gdb調試 在使用ndk-gdb調試的時候。運行$NDK/ndk-gdb --verbose報錯“Another debug session running,Use --force to kil

移動端web開發

page 提升 最終 好玩的 ini 進階 body user hang posted @ 2014-11-24 20:09 vajoy 閱讀(4708) 評論(12) 編輯 收藏 三個月前曾寫過一篇跨終端響應式頁面設計入門的博客,上了博客園頭條也得到了不少關註

Java開發之路

為我 變量 kafka 自然 ges 想想 javaweb 目前 沒有 背景一 我接觸Java開發已經一年多了了。 從剛開始生澀地接觸語法,惹得編譯器報各種語法錯誤;到接觸OOD的思想,封裝、繼承、多態,懵懵懂懂地聽說著它們;再到學習Web開發,想著這些也許並不重要,實際用

《iOS開發》書籍目錄

archive 憑證 修改 工具 core serial 第二部分 破解 uilabel 第一部分:iOS開發工具 第二部分:iOS開發實踐 第10章 理解內存管理 10.1 引用計數 10.1.1 什麽是引用計數,原理是什麽 10.1.2 我們為什麽需要引用計數 10

Python學習----第七模塊筆記(Web開發之Django數據庫操作)

long 機制 idt 4.5 gen git 表之間 protoc 小數 4、Django ORM 4.1、連接數據庫 創建Django工程後運行該工程,會在工程根目錄下創建db.sqlite3文件,為Django自帶的sqlite3數據庫(Django自帶的功能也需要數

模塊七:web開發筆記

多行匹配1、JS 正則 test - 判斷字符串是否符合規定的正則 rep = /\d+/; rep.test("asdfoiklfasdf89asdfasdf") # true rep = /^\d+$/; rep.test("asdfoiklfasdf89as

python3開發-Django框架的詳解

本質 負責 log 生命周期 target 了解 定制 基本 del 一、MVC框架和MTV框架 MVC,全名是Model View Controller,是軟件工程中的一種軟件架構模式,把軟件系統分為三個基本部分: 模型(Model)、視圖(View)和控制器(Con