編輯:關於Android編程
JNI:Java Native Interface(Java 本地接口),它是為了方便Java調用C、C++等本地代碼所封裝的一層接口。
NDK:Native Development Kit(本地開發工具包),通過NDK可以在Android中更加方便的通過JNI來訪問本地代碼。
打開AS的SDK Manager,安裝NDK插件:
整個NDK比較大,解壓縮完2個G,自動安裝到配置的sdk目錄下:
安裝完畢後,點開structure,配置NDK的路徑:
配置NDK的環境變量:
驗證是否配置成功:
在命令行輸入ndk-build,如果顯示以上內容,表示成功。
項目名稱:JNITest
包名:com.dgk.jnitest
實現功能:界面有兩個按鈕,點擊Get從本地方法中獲取一個字符串,並toast出來;點擊Set向本地方法傳遞一個字符串,打印到Logcat。
2.1 寫Android界面和基本邏輯,並聲明兩個本地方法。
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private static final String tag = "【MainActivity】"; private Button btn_get; private Button btn_set; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_get = (Button) findViewById(R.id.btn_get); btn_set = (Button) findViewById(R.id.btn_set); btn_get.setOnClickListener(this); btn_set.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_get: Log.i(tag, "點擊Get按鈕"); Toast.makeText(this, getStringFromJNI(), Toast.LENGTH_SHORT).show(); break; case R.id.btn_set: Log.i(tag, "點擊Set按鈕"); setStringToJNI("Hello C! 我是一只來自Java世界的Cat,喵~~~"); break; } } /** * 聲明get方法 * - 作用是從本地方法返回一個String * @return 返回一個字符串 */ public native String getStringFromJNI(); /** * 聲明set方法 * - 作用是向本地方法傳遞一個String */ public native void setStringToJNI(String str); /** * 加載本地代碼庫 * - 在應用啟動的時候加載名為"libjni-test.so"的代碼庫,該庫在安裝Apk的時候就已經 * 被包管理器拆包放到了/data/data/包名/lib/目錄下了。 */ static { System.loadLibrary("jni-test"); } }
activity_main.xml
2.2 寫C代碼
選中main右鍵選擇New->Folder->JNI Folder,會在main路徑下創建一個jni的文件夾,用於存放本地源代碼,src\main\jni這個是AS默認的jni源代碼存放路徑,也可以自己手動創建。
創建Hello.c
#include#include #include #include #define LOG_TAG "【C_LOG】" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) JNIEXPORT jstring JNICALL Java_com_dgk_jnitest_MainActivity_getStringFromJNI (JNIEnv *env, jobject thiz) { LOGI("調用 C getStringFromJNI() 方法\n"); char* str = "Hello Java! 我是一只來自C世界的Dog,汪!!!"; return (*env)->NewStringUTF(env, str); } JNIEXPORT void JNICALL Java_com_dgk_jnitest_MainActivity_setStringToJNI (JNIEnv* env, jobject thiz, jstring str){ LOGI("調用 C setStringFromJNI() 方法\n"); char* string = (char*)(*env)->GetStringUTFChars(env, str, NULL); LOGI("%s\n", string); (*env)->ReleaseStringUTFChars(env, str, string); }
C代碼的我的理解,可以先大致看一下:
#include#include #include #include /* 在C語言中標准輸出的方法是printf,但是打印出來的內容在logcat看不到,需要使用 __android_log_print()方法打印log,才能在logcat看到,由於該方法名比較長,我們在 這裡需要定義宏,使得在C語言中能夠向Android一樣打印log。 注意:該方法還需要在gradle中聲明ldLibs "log",詳見build.gradle */ #define LOG_TAG "【C_LOG】" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) /* 返回類型 Java_包名_類名_方法名 - 返回類型:jstring,即java格式的String - 參數: 可以在jni.h頭文件中查找到各個自定義變量的原類型。 必帶的兩個參數: - JNIEnv:是結構體JNINativeInterface的一級指針,即JNINativeInterface*, 結構體JNINativeInterface:接口函數指針表,該表就是用來Java和C語言之間進行交互的, 包含著Java變量和C變量之間的對應關系,可以用於變量之間的轉換。 JNIEnv* env:是JNIEnv的一級指針, 是結構體JNINativeInterface的二級指針,即JNINativeInterface** - jobject thiz:誰調用了這個本地函數,那麼這個thiz就是指的哪個對象,本項目中是MainActicity */ JNIEXPORT jstring JNICALL Java_com_dgk_jnitest_MainActivity_getStringFromJNI (JNIEnv *env, jobject thiz) { LOGI("調用 C getStringFromJNI() 方法\n"); /* char* 相當與C語言中的字符串 char* 指的是字符串str的指針,指向的是該str的內存區域, 但是在C語言中操作字符串可以直接使用一級指針,來獲取字符串的各個元素。 */ char* str = "Hello Java! 我是一只來自C世界的Dog,汪!!!"; /* 通過在jni.h的結構體JNINativeInterface中查找jstring,可以找到將C語言的字符串轉換成 Java字符串的代碼: const struct JNINativeInterface* functions; jstring NewStringUTF(const char* bytes){ return functions->NewStringUTF(this, bytes); } 即:JNINativeInterface*->NewStringUTF(*env, str) 即:(env*)->NewStringUTF(*env, str) 將str轉換並保存在結構體中,然後使用間接引用運算符->來獲得這個jstring成員。 在這裡,結構體是JNINativeInterface,他的一級指針是JNIEnv,即*env(因為env又是JNIEnv的一級指針)。 所以(*env)->NewStringUTF(env, str)就相當於JNINativeInterface.jstring,表示該結構體內的 jstring格式的變量。 */ return (*env)->NewStringUTF(env, str); } JNIEXPORT void JNICALL Java_com_dgk_jnitest_MainActivity_setStringToJNI (JNIEnv* env, jobject thiz, jstring str){ LOGI("調用 C setStringFromJNI() 方法\n"); // 將收到的jstring轉換成UTF-8格式的C字符串 char* string = (char*)(*env)->GetStringUTFChars(env, str, NULL); LOGI("%s\n", string); // 顯示釋放轉換成UTf-8的string空間,如果不顯示調用,JVM會一直保存該對象,不回收,容易導致內存溢出 (*env)->ReleaseStringUTFChars(env, str, string); }
2.3 配置gradle
配置完gradle後如果直接同步,會提示:
如果點擊第一行,會轉到google開發網站,顯示一個實驗性的gradle,可以用來集成NDK,在本篇不予介紹。點擊第二行,或者直接在gradle.properties中貼上代碼,表示使用當前過時的插件:
android.useDeprecatedNdk=true
直接Build->Make Project,會自動生成.so庫,保存路徑為\app\build\intermediates\ndk\debug\lib
將生成的lib包下的需要的.so庫copy到main/jniLibs目錄中即可:
該文件夾為AS默認的jni庫的目錄,可以在gradle中修改。
運行手機:魅族pro5 Android 5.1
點擊GET:
點擊SET:
到現在為止,已經完成了最簡單的JNI開發流程。
在這裡我們的C代碼的方法名和參數是自己寫的,而實際上完全可以讓jdk來幫我們生成,流程是:
編譯java代碼生成.class文件—>使用javah命令生成C語言的頭文件—>將.h文件中的JNI方法聲明復制到C文件中,並完善方法的方法體。這樣的話,可以加速我們的開發流程,同時避免寫錯方法名,但是在這個過程中由於對自己的開發環境還有命令了解不夠,經常會出問題,所以小編在簡潔流程中並未寫出。
如果對C語言不了解,就需要自動生成頭文件了,在這裡給出小編自己的流程,一行代碼搞定:
在Make Project之後,在Terminal中輸入命令(當前目錄為應用的根目錄)
javah -d . -cp android.jar的地址;編譯生成的.class目錄 全類名
格式:
-d . 表示生成文件存放在當前目錄
-cp class文件的加載根路徑,在這裡需要加載兩個類,一個是android的基礎類庫,要不然jdk本身識別不了android的東西,比如Activity\Log等;第二個是MainActivity編譯生成的路徑 + 全類名,注意中間有個空格,而且不能寫全地址,不識別。
運行完該命令後會默認在當前目錄(即程序的根目錄)下生成一個文件:com_dgk_jnitest_MainActivity.h,該文件就是JNI的頭文件,裡面定義好了JNI的本地方法聲明,可以直接復制來用。
對於這一步,網上的命令七花八門,感覺好多復制過來直接用然後就發表了,很多坑,令人很頭疼,搞了一天,煩躁!其實主要的問題就是,Android Studio對JNI的兼容性剛開始不是很好,後來又更新的版本比較多,再加上如果對gradle不熟悉的話,就會造成寫起來很難受的感覺。
還好,當真正的把整個流程給串起來以後就會感覺還是很清晰的,不過還是希望成熟的插件能夠將整個流程串起來~
首先新建了一個項目用來演示集成ShareSDK 下載好了ShareSDK之後,解壓sharesd
前言:那些年我們用過的顯示性能指標 相對其他 Android 性能指標(如內存、CPU、功耗等)而言,顯示性能(包括但不僅限於我們常說的“流暢度”)的概念本來就相對復雜
本文根據自己的實踐總結而來,參考前人博客之余,也自己總結和開發了一些功能,在這裡給自己備份也分享給大家。不同之處在於:自動打開並搜索藍牙、修改藍牙名字、完整接收藍牙傳輸
一、服務器端實現 (1)創建動態服務器項目 個部分代碼如下: package com.lc.dao; import java.sql.Connection; imp