android基礎鞏固之ndk
摘要:
環境配置
為了工程不出現問題最好是新建專案的時候選擇c++/c專案支援 (android studio建立專案)
app/library module gradle 配置
apply plugin: 'com.android.library'
an...
環境配置
為了工程不出現問題最好是新建專案的時候選擇c++/c專案支援 (android studio建立專案)
app/library module gradle 配置
apply plugin: 'com.android.library' android { defaultConfig { externalNativeBuild { cmake { cppFlags "" } } ndk { ldLibs "log"//實現__android_log_print abiFilters'x86', 'armeabi-v7a' } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" } } }
src/main/cpp/CMakeList.txt 專案建立的時候會自動生成,如果沒有從其它地方複製 , main/cpp目錄不存在就自己建立
cmake_minimum_required(VERSION 3.4.1) add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). native-lib.cpp ) 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 ) target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} )
native-lib.cpp 選擇cpp是因為了解這塊,各選語言###
#include <jni.h> #include <stdlib.h> #include <string> #include <iostream> #include <android/log.h> using namespace std; #defineLOG_TAG"zzg-ndk" #defineLOGI(...)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGD(...)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) extern "C" JNIEXPORT jboolean JNICALL Java_com_example_zyframework_SDKApi_isExpired(JNIEnv *env, jobject jobj, jlong time) { LOGE("native isExpret time = %lld ", time); return (jboolean) b; }
使用示例
1.資料傳遞
- java傳遞資料給c(包含物件)
- c傳遞資料給java(包含物件)
java傳遞資料給c,c返回資料給java(包含物件)
java程式碼
static { System.loadLibrary("native-lib"); } private Stringtag = "zzg-ndk"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = findViewById(R.id.sample_text); tv.setText(stringFromJNI()); SimpleDateFormat simpleDateFormat = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss"); String dateStr = simpleDateFormat.format(new Date()); sayHello(dateStr); int addV = add(12, 5); Log.e(tag, "c add result = " + addV); float f = getFloat(3.1415926f); Log.e(tag, "c getFloat result = " + f); double d = getDouble(35.0); Log.e(tag, "c getDouble result = " + d); boolean b = getBoolean(true); Log.e(tag, "c getBoolean result = " + b); String s = getString("開啟電視"); Log.e(tag, "c getString result = " + s); long l = getLong(3500l); Log.e(tag, "c getDouble result = " + l); String[] n_arr = getStringArray(new String[]{"上海", "天津", "濟南"}); for (String s_t : n_arr) { Log.e(tag, s_t); } /** * 在c層進行物件的轉換 */ ResultInfo resultInfo = new ResultInfo("2018-12-22", 39); Log.e(tag, "resultInfo = " + resultInfo); WeatherInfo weatherInfo = convertInfo(resultInfo); if (weatherInfo != null) { Log.e(tag, "weatherInfo = " + weatherInfo); } } public native String stringFromJNI(); private native void sayHello(String str); private native int add(int a, int b); private native float getFloat(float f); private native double getDouble(double d); private native boolean getBoolean(boolean b); private native String getString(String str); private native long getLong(long l); private native String[] getStringArray(String[] sa); /** * 物件轉換 */ private native WeatherInfo convertInfo(ResultInfo info);
cpp程式碼
#include <jni.h> #include <stdlib.h> #include <string> #include <iostream> #include <android/log.h> using namespace std; #defineLOG_TAG"zzg-ndk" #defineLOGI(...)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGD(...)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) extern "C" JNIEXPORT jstring JNICALL Java_com_example_hncpp_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; jstring sr = env->NewStringUTF(hello.c_str()); const char *locstr = env->GetStringUTFChars(sr, 0); LOGE("stringFromJNI locstr = %s", locstr); env->ReleaseStringUTFChars(sr, locstr); return sr; } extern "C" JNIEXPORT void JNICALL Java_com_example_hncpp_MainActivity_sayHello(JNIEnv *env, jobject obj, jstring str_) { const char *str = env->GetStringUTFChars(str_, 0); env->ReleaseStringUTFChars(str_, str); LOGE("sayHello str = %s", str); } extern "C" JNIEXPORT jint JNICALL Java_com_example_hncpp_MainActivity_add(JNIEnv *env, jobject obj, jint a, jint b) { int i = a; int j = b; LOGE("add 接收到的java 資料:a =%d , b =%d", a, b); LOGE("add 轉換之後c的資料:i = %d , j = %d", i, j); return i + j; } extern "C" JNIEXPORT jfloat JNICALL Java_com_example_hncpp_MainActivity_getFloat(JNIEnv *env, jobject obj, jfloat f) { float f_l = f; LOGE("getFloat 接收到的java 資料:f = %3f ", f); LOGE("getFloat 轉換之後c的資料:f_l = %3f", f_l); f_l = f_l * 0.2f; LOGE("getFloat 計算之後的資料:f_l = %3f", f_l); return (jfloat) f_l; } extern "C" JNIEXPORT jdouble JNICALL Java_com_example_hncpp_MainActivity_getDouble(JNIEnv *env, jobject obj, jdouble d) { double d_1 = d; LOGE("getDouble 接收到的java 資料:d = %1f ", d); LOGE("getDouble 轉換之後c的資料:d_l = %1f", d_1); d_1 = 3 * d_1; LOGE("getFloat 計算之後的資料:d_1 = %1f", d_1); return (jdouble) d_1; } extern "C" JNIEXPORT jboolean JNICALL Java_com_example_hncpp_MainActivity_getBoolean(JNIEnv *env, jobject obj, jboolean b) { unsigned char b_1 = b; LOGE("getDouble 接收到的java 資料:d = %lu", b_1); if (b_1) { LOGE("true"); } else { LOGE("false"); } return !b_1; } // extern "C" JNIEXPORT jstring JNICALL Java_com_example_hncpp_MainActivity_getString(JNIEnv *env, jobject obj, jstring str_) { const char *str = env->GetStringUTFChars(str_, 0); //LOGE("getDouble 接收到的java 資料:str = %s", str); //std::string hello = "收到指令,準備進行操作, 操作成功"; //env->ReleaseStringUTFChars(str_, str); //return env->NewStringUTF(hello.c_str()); //字串拼接 //string str = "Dream a Dream"; //str.insert(str.length()," i have"); //str.insert(0, " i have "); //字串拼接 string tmp_s_1 = "收到指令 ", tmp_s_2 = " , 準備進行操作, 操作成功"; tmp_s_1.insert(tmp_s_1.length(), str); tmp_s_1.insert(tmp_s_1.length(), tmp_s_2); env->ReleaseStringUTFChars(str_, str); return env->NewStringUTF(tmp_s_1.c_str()); } extern "C" JNIEXPORT jlong JNICALL Java_com_example_hncpp_MainActivity_getLong(JNIEnv *env, jobject obj, jlong l) { long l_1 = l; LOGE("getDouble 接收到的java 資料:l = %ld ", l); LOGE("getDouble 轉換之後c的資料:l_1 = %ld", l_1); l_1 = 1.5f * l_1; LOGE("getFloat 計算之後的資料:l_1 = %ld", l_1); return (jlong) l_1; } extern "C" JNIEXPORT jobjectArray JNICALL Java_com_example_hncpp_MainActivity_getStringArray(JNIEnv *env, jobject obj, jobjectArray sa) { int len = (*env).GetArrayLength(sa); int i = 0; // 獲取資料型別 jclass objCls = env->FindClass("java/lang/String"); // 生成新的陣列 jobjectArray jarr = (*env).NewObjectArray(len, objCls, 0); for (i = 0; i < len; i++) { jobject jobj = (*env).GetObjectArrayElement(sa, i); jstring str = static_cast<jstring>(jobj); const char *szStr = (*env).GetStringUTFChars(str, 0); LOGE("arr[%d] = %s ", i, szStr); env->ReleaseStringUTFChars(str, szStr); //拼接資料 string t_new = "new_"; t_new.insert(t_new.length(), szStr); env->SetObjectArrayElement(jarr, i, env->NewStringUTF(t_new.c_str())); } return jarr; } //物件轉換 extern "C" JNIEXPORT jobject JNICALL Java_com_example_hncpp_MainActivity_convertInfo(JNIEnv *env, jobject jobj, jobject rInfo) { // 獲取傳入的物件的值 jclass r_info_jcls = (*env).GetObjectClass(rInfo); // 這裡通過反射的方式取欄位的值 jfieldID r_info_date_jfid = (*env).GetFieldID(r_info_jcls, "date", "Ljava/lang/String;"); // 取date值 jobject r_info_date_jobj = (*env).GetObjectField(rInfo, r_info_date_jfid); // 這裡是java字串 jstring r_info_date_jstr = static_cast<jstring>(r_info_date_jobj); const char *r_info_date_cstr = (*env).GetStringUTFChars(r_info_date_jstr, 0); // 釋放資源 (*env).ReleaseStringUTFChars(r_info_date_jstr, r_info_date_cstr); LOGE("ndk ->ndk -> date = %s", r_info_date_cstr); // 這裡通過呼叫方法取到tmp值 jmethodID jmid_rinfo_get_tmp = (*env).GetMethodID(r_info_jcls, "getTmp", "()I"); jint val_rinfo_tmp = (*env).CallIntMethod(rInfo, jmid_rinfo_get_tmp); LOGE("ndk ->ndk -> val_rinfo_tmp = %d", val_rinfo_tmp); // 在c層中構建WeatherInfo物件,設定資料,可以通過呼叫方法也可以通過設定成員變數來實現 jclass w_info_jcls = (*env).FindClass("com/example/hncpp/WeatherInfo"); // 找到構造方法id,無參構造 jmethodID jmid_const_w_info = (*env).GetMethodID(w_info_jcls, "<init>", "()V"); // 構建java物件 jobject jobj_w_info = (*env).NewObject(w_info_jcls, jmid_const_w_info); // 設定屬性可以通過呼叫GetFieldID->SetFileID來設定,也可以通過GetMethodId->CallMethodId,總之,哪個方便用哪個 // 我們這裡用簡單的操作 jmethodID jmid_winfo_setid = (*env).GetMethodID(w_info_jcls, "set_id", "(I)V"); (*env).CallVoidMethod(jobj_w_info, jmid_winfo_setid, 35); jmethodID jmid_winfo_setdate = (*env).GetMethodID(w_info_jcls, "setDate", "(Ljava/lang/String;)V"); (*env).CallVoidMethod(jobj_w_info, jmid_winfo_setdate, r_info_date_jstr); jmethodID jmid_winfo_settmp = (*env).GetMethodID(w_info_jcls, "setTmp", "(I)V"); (*env).CallVoidMethod(jobj_w_info, jmid_winfo_settmp, val_rinfo_tmp); return jobj_w_info; }
2.c呼叫java成員變數\方法\建立java層物件
Utils類程式碼
public class Utils { public static String TEST_LOG_TAG = "zzg_log"; private static String TEST_LOG_TAG2 = "zzg_log_2"; private static int build_num = 35; public String app_old_version = "v0.0.3"; public long person_id = 430644198806230032l; private String app_version = "v1.3.0"; private int test_num = 50; private long phone_num = 13244105539l; private Utils() { Log.e(TEST_LOG_TAG, "私有構造觸發了"); } public Utils(int t) { Log.e(TEST_LOG_TAG, "公有構造觸發了,param = " + t); } public static void logE(String str) { Log.e(TEST_LOG_TAG, str); } public static void logE(String logTag, String str) { Log.e(logTag, str); } private static long getAppRunTimer() { return 3500l; } private boolean isAppRunning() { return true; } public native void test(); }
java方法
cpp程式碼
公有靜態成員變數呼叫
//公開的靜態方法和成員變數 jclass jcls = env->GetObjectClass(jobj); //呼叫java的靜態成員變數 jfieldID jfid = (*env).GetStaticFieldID(jcls, "TEST_LOG_TAG", "Ljava/lang/String;"); jstring jlog_tag = static_cast<jstring>((*env).GetStaticObjectField(jcls, jfid)); const char *jlogtag_str = (*env).GetStringUTFChars(jlog_tag, 0); (*env).ReleaseStringUTFChars(jlog_tag, jlogtag_str); LOGE("獲取到的屬性 log_tag value = %s", jlogtag_str);
呼叫私有java的靜態成員變數
jfieldID log2_jfid = (*env).GetStaticFieldID(jcls, "TEST_LOG_TAG2", "Ljava/lang/String;"); jstring jlog2_tag = static_cast<jstring>((*env).GetStaticObjectField(jcls, log2_jfid)); const char *jlog2tag_str = (*env).GetStringUTFChars(jlog2_tag, 0); (*env).ReleaseStringUTFChars(jlog2_tag, jlog2tag_str); LOGE("獲取到的屬性 log_tag jlog2_tag = %s", jlog2tag_str);
呼叫java的公有靜態方法
jmethodID loge_m = (*env).GetStaticMethodID(jcls, "logE", "(Ljava/lang/String;)V"); jstring log_c = (*env).NewStringUTF("從c呼叫的方法,請重新整理介面"); (*env).CallStaticVoidMethod(jcls, loge_m,log_c);
呼叫java的公有非靜態成員變數
jfieldID jfid = (*env).GetFieldID(jcls, "app_old_version", "Ljava/lang/String;"); jstring jlog_tag = static_cast<jstring>((*env).GetObjectField(jobj, jfid)); const char *jlogtag_str = (*env).GetStringUTFChars(jlog_tag, 0); (*env).ReleaseStringUTFChars(jlog_tag, jlogtag_str); LOGE("獲取到的屬性 log_tag value = %s", jlogtag_str);
呼叫私有非靜態的成員變數
//GetFieldID預設獲取的就是私有的 jfieldID jfid = (*env).GetFieldID(jcls, "test_num", "I"); jint test_n = (*env).GetIntField(jobj, jfid); LOGE("獲取到的屬性 test_num value = %d", test_n);
呼叫私有非靜態的成員String變數
jfieldID app_vers_fid = (*env).GetFieldID(jcls, "app_version", "Ljava/lang/String;"); // 這裡容易傳錯GetObjectField(jobj, app_vers_fid)可千萬要注意 jobject app_vers_obj = (*env).GetObjectField(jobj, app_vers_fid); jstring app_ver_str = static_cast<jstring>(app_vers_obj); if (app_ver_str != NULL) { const char *app_ver_cstr = (*env).GetStringUTFChars(app_ver_str, 0); (*env).ReleaseStringUTFChars(app_ver_str, app_ver_cstr); LOGE("獲取到的屬性 log_tag app_version = %s", app_ver_cstr); }
呼叫公私有非靜態的成員long變數
person_id公有
jfieldID person_id_fid = (*env).GetFieldID(jcls, "person_id", "J"); // 這裡容易傳錯GetObjectField(jobj, app_vers_fid)可千萬要注意 jlong person_id = (*env).GetLongField(jobj, person_id_fid); LOGE("獲取到的屬性 log_tag person_id = %lld", person_id);
phone_num私有
jfieldID phone_num_fid = (*env).GetFieldID(jcls, "phone_num", "J"); // 這裡容易傳錯GetObjectField(jobj, app_vers_fid)可千萬要注意 jlong phone_num = (*env).GetLongField(jobj, phone_num_fid); LOGE("獲取到的屬性 log_tag phone_num = %lld", phone_num);
3. static native 使用示例
3. static native 靜態的native方法
c程式碼中呼叫java中的方法示例
java程式碼
public class SDKUtils { public void setTag(String tag) { Log.e("zzg-ndk", "setTag tag = " + tag); } public static native void updateTag(String key); }
cpp程式碼
extern "C" JNIEXPORT void JNICALL Java_com_example_sdkjiagu_SDKUtils_updateTag(JNIEnv *env, jclass jcls, jstring key_) { const char *key = env->GetStringUTFChars(key_, 0); LOGE("native updateTag key = %s ", key); env->ReleaseStringUTFChars(key_, key); // 得到jobject不會走構造方法 jobject jobj = (*env).AllocObject(jcls); jmethodID _mid = (*env).GetMethodID(jcls, "setTag", "(Ljava/lang/String;)V"); string t_new = "2019-01-18 23:59:59"; (*env).CallVoidMethod(jobj,_mid,env->NewStringUTF(t_new.c_str())); }
輔助資料
ndk編譯之後so的路徑
\HelloNdkCpp\ZYFramework\build\intermediates\cmake\debug\obj\x86
方法的方法簽名的檢視
as 3.1.2
app/build/intermediates/classes/debug/com/example/hncpp
as 3.3.0
cmd -> javap -s Utils
public class com.example.hncpp.Utils { public java.lang.String app_old_version; descriptor: Ljava/lang/String; public static java.lang.String TEST_LOG_TAG; descriptor: Ljava/lang/String; public com.example.hncpp.Utils(int); descriptor: (I)V public static void logE(java.lang.String); descriptor: (Ljava/lang/String;)V public static void logE(java.lang.String, java.lang.String); descriptor: (Ljava/lang/String;Ljava/lang/String;)V public native void test(); descriptor: ()V static {}; descriptor: ()V }