讓JNI告訴你,你的應用爲何被卸載

/   前言   /

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方法是不能混淆的。

更多資料分享歡迎Android工程師朋友們加入安卓開發技術進階互助:856328774免費提供安卓開發架構的資料(包括Fultter、高級UI、性能優化、架構師課程、 NDK、Kotlin、混合式開發(ReactNative+Weex)和一線互聯網公司關於Android面試的題目彙總。
相關文章
相關標籤/搜索