①交叉編譯概念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"); } }