JNI應用


①交叉編譯概念java

在windows上編譯能夠在除了windows平臺以外 能夠執行的機器碼 
android是Linux windows編譯在linux上執行的機器碼 
動態連接庫 .so .dlllinux

NDK google提供的交叉編譯的工具android

②如何使用ndk指令c++

ndk-build 添加到環境變量 能夠在工程的根目錄下執行ndk-build開啓一個交叉編譯的過程shell

ndk解壓的路徑不要包含空格windows

ndk-build->在當前目錄下找jni的目錄->找Android.mk(必須的) 解析這個配置文件 知道要編譯的c源代碼在什麼位置,要生成的.so叫什麼名字->找有沒有Application.mk(可選的) 若是有 解析一下 知道要編譯生成支持哪些平臺的.so文件->編譯c的代碼數組

③jni開發流程瀏覽器

1 在java代碼中 聲明一個本地方法 使用native關鍵字 本地方法不用實現 沒有方法體 只有聲明 
2 在項目的根目錄建立一個jni目錄 建立Android.mk 建立.c的源文件 若是須要建立Application.mk安全

    #獲取當前的目錄全路徑 
     LOCAL_PATH := $(call my-dir)
    # 清除以前編譯時的配置信息 不會把call my-dir 這一步的內容清除
    include $(CLEAR_VARS)
    #編譯以後生成的模塊的名字  系統會自動加上一個lib前綴
    LOCAL_MODULE    := hello-jn
    #告訴編譯系統我要編譯的源碼的文件名字
    LOCAL_SRC_FILES := hello.c
    #生成一個動態連接庫 .so文件
    include $(BUILD_SHARED_LIBRARY)

3.寫c的代碼 須要跟native方法對應的部分函數名字不能亂寫 架構

本地函數的命名規則 返回值 Java_包名_類名_native方法的名字 
c的本地函數 有兩個必需要傳遞的參數 
JNIEnv* env jni環境 二級指針訪問結構體中的成員 
(**env).NewStringUTF() 
(*env)->NewStringUTF(); 
jobject 哪一個類調用的這個native方法 就是這個類的對象 
4.ndk-build 編譯.c的代碼 編譯的過程會建立一箇中間文件存放的目錄 obj 在這個目錄生成.so最後copy到libs->armeabi目錄下 
5.運行以前必定要加載.so文件 不加載會有問題 
System.loadlibray(「.so的名字」); .so文件的名字 把lib 去掉 去掉.so的後綴 就是要加載的模塊名字 
④jni開發常見的錯誤

兩種常見錯誤 
1本地方法沒有找到 native method not found 
名字寫錯不符合jni規範 javah生成頭文件 
沒寫System.loadlibrary 沒加載.so就執行了native方法 
2.找.so庫的時候 返回null find library returned null 
System.loadLibrary名字寫錯 
.so文件不支持當前的cpu平臺 須要經過Application.mk 指定 
APP_ABI := x86 armeabi 
⑤jni簡便開發流程 
⑥java數據傳遞給C

使用場景

java不能知足需求 圖片的處理

在傳遞過程當中要解決的問題

數據類型不匹配的問題

傳遞基本數據類型(int) 不須要特殊處理 你們都同樣 除了char有區別

字符串 c char* java String

c的字符串轉換成java NewStringUTF()

java的字符串轉換成C GetStringUTFChars(env,java的字符串,NULL); 
經過這個第三個參數能夠知道 jstring轉換成char*的時候是否建立了副本 必定會建立的不會直接修改 
jstring

java的基本數據類型的數組 傳遞給c 
數組的長度獲取 
GetArrayLength(env,jarray); 
GetXXXXXArrayElements(env,jarray,NULL); 獲取基本數據類型數組的首地址 
⑦c語言如何集成logcat

①在Android.mk當中添加一個LOCAL_LDLIBS += -llog //加載liblog.so 
②在代碼中 導入頭文件 
C代碼中增長如下內容

#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

 

C代碼中使用logcat, 例:

LOGI("info\n");
LOGD("debug\n");

 

⑧實現C回調java中的方法

 

1 .so的逆向(反彙編) 
.so只能反彙編 反彙編以後能拿到對應的彙編代碼 
經過彙編代碼能夠猜出C的源代碼(僞代碼) 
相對來講 c在防止別人作破解工做方面 比java要靠譜 比較強調安全的應用 會把加密的邏輯放到c中操做 會使用到JNI JNI 
2.美圖秀秀 
使用美圖秀秀的.so和jni文件實現相似效果(練習的時候須要注意 只能用arm架構cpu的模擬器 ) 
①反編譯 把.so文件copy到項目中 
②把JNI文件copy到項目中注意保持jni文件的文件路徑 
③編寫java代碼 調用JNI中的圖片處理方法

public class MainActivity extends Activity {
    private ImageView iv_image;
    private Bitmap bitmap;
    private JNI jni;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv_image = (ImageView) findViewById(R.id.iv_image); bitmap = BitmapFactory.decodeFile("storage/sdcard/front.jpg"); iv_image.setImageBitmap(bitmap); jni = new JNI(); } public void processPic(View v){ //圖片的寬高 實際上就是 圖片橫向有多少個像素 豎直方向有多少個像素 int width = bitmap.getWidth(); int height = bitmap.getHeight(); //ARGB 8888 //alpha 透明度 8個bit 8位二進制數 //red 紅色 8位二進制數 //G green 綠色 8位二進制數 //B blue 藍色 8位二進制數 //一個像素 能夠用4byte的 內存來保存 int 4byte //建立一個數組 用來保存圖片的顏色信息 int pixels[] = new int[width*height]; //第一個參數 準備好的數組 用來保存圖片的每個像素的顏色信息 //第二個參數 寫入到pixels數組中第一元素的偏移量 也就是開始的索引 //第三個參數 傳入一個>=bitmap寬度的值 若是>bitmap的寬度 會有相似牆紙平鋪的效果 如今想獲取整張圖片因此傳入bitmap的寬度 //第四個,第五個參數 從bitmap中讀出的第一個像素的座標 //第六個第七個參數 從bitmap中每一行 讀出多少個像素 每一列讀出多少個像素 bitmap.getPixels(pixels, 0, width, 0, 0, width, height); //getPixels方法執行完 pixels數組中就保存了跟圖片相關的全部像素信息 //jni.StyleLomoB(pixels, width, height); jni.StyleJapanese(pixels, width, height); //StyleLomoB 這個方法執行完以後 pixels又發生了變化 全部的像素已是加了特效的像素信息了 //我拿着處理好的像素的數組建立一張新的圖片 這個圖片就包含特效了 Bitmap bitmap2 = Bitmap.createBitmap(pixels, width, height, bitmap.getConfig()); //bitmap2已經有特效了經過imageview展現出來 iv_image.setImageBitmap(bitmap2); //c的函數 能夠處理圖片 須要你傳遞 圖片int數組的首地址 傳遞圖片的寬度 高度 //int* p = GetIntArrayElments(env,jarry,NULL); //becomeBeautiful(int* ,intwith, int height) //becomeB(p,width,height); } }

 

3.鍋爐壓力 
如何跟硬件溝通 
須要調用硬件驅動(c編寫的) 返回一個結果 能夠在界面上展現 
經過jni的調用 c的函數也是執行在主線程中 一樣不能執行耗時的操做

4.C++JNI開發

#include <jni.h> //直接到系統指定的include目錄去找

#include "com_ngyb_cppjni_MainActivity.h" //用雙引號 來include頭文件 會先到當前目錄下找.h 當前目錄沒有 去系統指定的include目錄找

JNIEXPORT jstring JNICALL Java_com_ngyb_cppjni_MainActivity_sayHelloInCPP

  (JNIEnv * env, jobject obj){

    //在c++當中 JNIEnv再也不是結構體的一級指針 而是結構體的別名 實際上就是結構體

    //因此 env 是結構體一級指針 經過env訪問函數的時候 直接env->

    // C++的結構體 能夠有函數實現  c的結構體只能有函數指針

    //在c++中 JNIEnv 其實是結構體 _JNIEnv的別名  _JNIEnv 包含了一堆函數

    //實際上就是調用了 結構體JNINativeInterface的同名函數指針

    //因此在寫c++的jni相關代碼時 調用到_JNIEnv結構體裏的函數跟c的時候 名字都同樣

    //有區別的是 第一個參數 env不用傳了 _JNIEnv結構體 在調用同名函數指針的時候把第一個參數 已經傳進去了

​

    //c++的函數要先聲明再使用 可使用javah生成的頭文件做爲函數聲明

    return env->NewStringUTF("hello from cpp!!!");

}

 

5.fork 分叉進程 
5.1 fork函數

能夠在當前的進程中複製出一個如出一轍的進程 這個子進程跟父進程有相同的資源 
fork函數返回值三種狀況 
返回值>0 說明 分叉成功 而且這個代碼執行在主線程中 返回的是分叉出的子進程的進程編號 
返回值==0 分叉成功 這個代碼是執行在子進程中 子進程再調用fork只會返回0 不會繼續複製新的進程 
返回值<0 說明覆制失敗 
5.2 am命令

am命令 :在adb shell裏能夠經過am命令進行一些操做 如啓動activity Service 啓動瀏覽器等等am命令的源碼在Am.java中, 在adb shell裏執行am命令實際上就是啓動一個線程執Am.java的main方法,am命令後面帶的參數都會看成運行時的參數傳遞到main函數中 
am命令能夠用start子命令,而且帶指定的參數 
常見參數: -a: action -d data -t 表示傳入的類型 -n 指定的組件名字 
舉例: 在adb shell中經過am命令打開網頁 
am start –user 0 -a android.intent.action.VIEW -d http://www.baidu.com 
經過am命令打開activity 
am start –user 0 -n com.ngyb.cppjni/com.ngyb.cppjni.MainActivity 
5.3 execlp

JNIEXPORT void JNICALL Java_com_ngyb_cfork_MainActivity_cfork
  (JNIEnv * env, jobject obj){
        int pid = fork();
        //若是成功複製了(分叉了)一個進程 返回值會>=0
        //若是返回值<0說明分叉失敗
        if(pid>0){
            LOGE("pid=%d",pid);
        }else if(pid == 0){
            //複製出的進程 來做爲守護進程 我能夠不斷檢測父進程的編號 若是變成1了說明親爹不在了(應用進程別殺死了)
            //① 進程死了 應用還在
            //② 進程死了 應用也卸了
            LOGE("pid==0");
           int ppid;
            FILE* file = NULL;
            //執行在子進程中 這個進程是由java進程複製出來的(分叉出來);
            while(1){
                LOGE("subprocess is running");
                sleep(3);
                //獲取當前進程的父進程編號
                ppid = getppid();
                //若是父進程編號變爲init進程了
                if(ppid==1){
                    //應用進程別殺死了
                    file = fopen("/data/data/com.ngyb.cfork","r");
                    if(file==NULL){
                        //應用被卸載了 彈出瀏覽器 顯示調查問卷
                        execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);
                    }else{
                        //應用還在 從新開啓進程
                        execlp("am", "am", "start", "--user","0", "-n" , "com.ngyb.cfork/com.ngyb.cfork.MainActivity",(char *) NULL);
                    }
                }
            }
        }else{
            LOGE("failed");
        }
}
相關文章
相關標籤/搜索