Android Jni開發相信多數Android開發者都有所瞭解,可是網上不少教程分爲兩種,一種是告訴你如何配置NDK環境變量,建個helloWorld的Demo,另外一種就是太過於高端,C語言一大片,雲裏霧裏,雖然不少公司開發都會有單獨的人員來寫C,可是從Android開發人員角度來講,學習C仍是頗有必要的,一切源碼終歸C。java
這篇文章你能夠學到什麼:android
1. Java語言如何調用C代碼,以C語言驗證用戶名和密碼爲例c++
2. C語言如何調用Java代碼,以C語言調用Java方法爲例web
3. 如何使用C語言,實現簡單實用的功能,以APP卸載反饋爲例面試
好了,若是你對以上內容感興趣,那就接着往下來,我要說明的是這篇文章不會告訴你如何配置NDK環境.瀏覽器
首先咱們要明白的是,爲何有些項目中要使用C,緣由很簡單,哪怕是一個計算,C的效率也要高於Java,Java作的C能夠作,Java不能夠作的C也能夠作,因此有些複雜的處理操做或者是底層相關的邏輯均可以交給C去作,像美圖秀秀,播放器等軟件都用了大量的C代碼處理業務。性能優化
1. Java調用C代碼,以驗證用戶名密碼爲例websocket
驗證用戶名密碼咱們確定要將用戶名和密碼傳給C,咱們新建一個JNI類,在類中新建一個返回整形的方法,以下所示:架構
public native int checkUser(String name, String pass);
記得使用關鍵字native,這個時候咱們就要在C中編寫相應的方法,像什麼,javah生成頭文件什麼的那種我在前言中說了,就不講解了,在studio工具中生成,鼠標點擊到方法,Alt + Enter快捷方式自動生成以下方法:app
#include <jni.h> JNIEXPORT jint JNICALL Java_jnidemo_hlq_com_jnidemo_JNI_checkUser(JNIEnv *env, jobject instance, jstring name_, jstring pass_) { const char *name = (env)->GetStringUTFChars(name_, 0); const char *pass = (env)->GetStringUTFChars(pass_, 0); // TODO (env)->ReleaseStringUTFChars( name_, name); (env)->ReleaseStringUTFChars( pass_, pass); }
在這裏要注意一下,這裏咱們建的是.cpp文件,至於.c 和 .cpp 就是一個是c一個是c++。
c++中代碼是:
const char *name = (env)->GetStringUTFChars(name_, 0);
c中對應的就是:
const char name = (env)->GetStringUTFChars(env,name_, 0);
接下來咱們要在cmake中進行配置:
add_library( # Sets the name of the library. checkuser SHARED src/main/cpp/cheruser.cpp )
checkuser 就是配置生成的so名稱爲libcheckuser.so,SHARED配置庫文件是共享, src/main/cpp/cheruser.cpp就是對應的路徑了。
target_link_libraries( # Specifies the target library. checkuser # Links the target library to the log library # included in the NDK. ${log-lib} )
checkuser保持和上面名字對應就能夠了。
這樣咱們就能夠在JNI類中,加載這個庫:
static { System.loadLibrary("checkuser"); }
在C代碼中咱們已經獲得了name和pass:
const char *name = (env)->GetStringUTFChars(name_, 0);
const char *pass = (env)->GetStringUTFChars(pass_, 0);
直接和用戶名密碼比較便可,這裏在代碼中將變量名定義爲name 密碼爲123。
const char *tureName = "name"; const char *turePass = "123";
使用strcmp函數來比較,兩個字符串相等則返回0,記得引用string.h頭文件。
#include <string.h>
if (strcmp(name,tureName) == 0 && strcasecmp(pass,turePass) == 0){ return 1; } else{ return 0; }
咱們在Activity中輸入用戶名密碼,調用C方法,若返回1則說明登錄成功,若返回0則說明用戶名密碼不正確,登陸失敗。
if (new JNI().checkUser("name", "123") == 1) { Toast.makeText(MainActivity.this, "登錄成功", Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this, "登錄shibai", Toast.LENGTH_LONG).show(); }
2. c語言調用Java方法
首先咱們在JNI類中新建一個sum方法,返回兩數之和。
public int sum(int i, int j) { Log.d("---", "我是java 我被c調用了" + (i + j)); return i + j; }
C調用Java確定要Java調用C的某個方法,在這個方法中調用java方法,因此咱們再來新建一個testHello方法。
public native String testHello();
默認生成的C方法爲:
JNIEXPORT jstring JNICALL Java_jnidemo_hlq_com_jnidemo_JNI_testHello(JNIEnv *env, jobject) { return (env)->NewStringUTF("huanglinqing"); }
咱們要調用的java方法在JNI類中,想一想java能夠經過反射來調用另外一個類的方法,那麼C其實也是經過反射的,首先咱們定義要調用方法的路徑,JNI類全路徑爲jnidemo.hlq.com.jnidemo.JNI,在C中將.替換爲/。
const char *className = "jnidemo/hlq/com/jnidemo/JNI";
方法名爲sum:
const char *sum = "sum";
經過findClass獲取class對象,而後經過AllocObject獲取類的實例。
jclass jclass1 = env->FindClass(className); jobject jobject1 = env->AllocObject(jclass1);
而後咱們獲取到要調用方法的methodId。
jmethodID jmethodID1 = env->GetMethodID(jclass1, sum,"(II)I");
第一個參數是class對象,第二個參數是函數名,第三個參數是方法簽名。
複製項目appbuildintermediatesclassesdebug文件路徑,打開cmd,進入路徑,(若是以前沒有編譯過項目記得先編譯一下,這樣才能獲取class文件),使用命令 javap -s jnidemo.hlq.com.jnidemo.JNI jnidemo.hlq.com.jnidemo.JNI是調用方法的全路徑。
運行能夠看到sum方法的簽名是(II)I。
獲取到方法的jmethodID1以後調用CallIntMethod便可調用方法。
jint value = env->CallIntMethod(jobject1, jmethodID1,1,2);
第一個參數是類的實例,第二個參數是獲取的jmethodID1,後面就是sum函數依次對應的參數。
代碼總體爲:
const char *className = "jnidemo/hlq/com/jnidemo/JNI"; const char *sum = "sum"; jclass jclass1 = env->FindClass(className); jmethodID jmethodID1 = env->GetMethodID(jclass1, sum,"(II)I"); jobject jobject1 = env->AllocObject(jclass1); jint value = env->CallIntMethod(jobject1, jmethodID1,1,2); printf("c 運行結果爲 %d",value);
咱們在activity中調用:
new JNI().testHello();
上述即爲C語言調用了java的方法。
3. 檢測APP的卸載
相信不少夥伴在面試的時候,總會被問到APP保活的問題,若是你回答不上來,面試官還會一臉鄙視的看着你,APP如何保活?
websocket心跳?三方推送?JNI fork進程?其實我以爲都是扯淡,系統版本越高Google限制的越嚴格,咱們本身作的APP除非是大廠,有白名單,不然不可能作到保活,而這個問題其實問的也沒有多大的意義。我曾經試過fork保活,殺死也是秒死。
檢測APP卸載就是,當APP被用戶卸載以後,自動打開瀏覽器網頁跳轉到一個調查問卷讓用戶去填寫爲何會卸載,這個功能PC端軟件常常能夠看到,APP用的很少,可是也是挺有意思的,可是和保活同樣這個功能很雞肋,版本稍微高一點,就完全死了,可是咱們瞭解一下仍是頗有必要的。
public native void uninstall(String packageName, int versionCode);
在c中使用:
int code = fork();
記得引入頭文件:
#include "unistd.h"
當fork的值>=0的時候 說明fork子進程和父進程成功,能夠去作判斷,固然通常都是子進程成功纔去判斷。
app安裝以後默認目錄都是:
/data/data/包名
因此咱們作一個1秒定時循環去fopen這個文件夾,當文件夾不存在的時候說明APP被卸載了。
if (code >= 0) { int flag = 1; while (flag) { sleep(1); FILE *file; try { try { file = fopen("/data/data/jnidemo.hlq.com.jnidemo", "rt"); } catch (_JNIEnv env) { LOGD("--- %s", "i一場了"); } if (file == NULL) { flag = 0; if (versionCode < 17) { execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://baidu.com", NULL); } else { execlp("am", "am", "start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", "http://baidu.com", (char *) NULL); } } else { fclose(file); LOGD("---%s", "我還在"); }
這裏咱們看到LOGD就是咱們定義的log 這樣能夠將c代碼中的日誌輸出到控制檯,定義以下:
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
當file爲null的時候咱們使用execlp命令 去操做android的一個意圖android.intent.action.VIEW,打開百度的網址。
固然,我本身在測試的時候,能夠完美運行的只有一個4.0的3G手機,其餘高版本手機也是無濟於事。
好了,JNI就是這樣了,另外偷偷告訴你,若是你想作個美圖秀秀的軟件,直接下載一個美圖秀秀,解壓,獲取裏面的so文件,和JNI方法類就能夠了,你可能會說都混淆了去哪裏找,你可能忘了,JNI方法是不能混淆的。