編輯:關於Android編程
前段時間聽朋友說在學習NDK開發,說現在的普通安卓程序猿馬上都要失業了,嚇得我趕緊去各大招聘平台搜了下,確實有不少招聘都寫著要求NDK開發經驗、JNI開發經驗、H5/JS開發經驗、cocos2d開發經驗、藍牙等底層開發經驗巴拉巴拉的,還有些招安卓的還要求同時具有IOS開發經驗是什麼鬼… 唉,安卓程序猿不好混呀,近段時間小程序也是火的不要不要的,天天處在要失業的陰影中。為了賺口猿糧不容易,還是花點時間來瞅瞅NDK開發(也叫JNI開發)吧。
Native Development Kit(NDK)是一系列工具的集合。它提供了一系列的工具,幫助開發者快速開發C/C++的動態庫,並能自動將so和java一起打包成apk。
Java Native Interface(JNI)標准是java平台的一部分,JNI是Java語言提供的Java和C/C++相互溝通的機制,Java可以通過JNI調用C/C++代碼,C/C++的代碼也可以調用java代碼。
NDK可以為我們生成了C/C++的動態鏈接庫,JNI是java和C/C++溝通的接口,兩者與android沒有半毛錢關系,只因為安卓是java程序語言開發,然後通過JNI又能與C/C++溝通,所以我們可以使用NDK+JNI來實現“Java+C”的開發方式。
NDK開發具有以下優點:
1. 項目需要調用底層的一些C/C++的一些東西(java無法直接訪問到操作系統底層(如系統硬件等)),或者已經在C/C++環境下實現了功能代碼,直接使用即可。NDK開發常用於驅動開發、無線熱點共享、數學運算、實時渲染的游戲、音視頻處理、文件壓縮、人臉識別等。
2. 為了效率更加高效些。這個可能並不能成為NDK開發的優點,因為雖然C/C++代碼是高效的,但是在java與C/C++相互調用時增大了開銷;
3. 基於安全性的考慮。防止代碼被反編譯,為了安全起見,使用C/C++語言來編寫重要的部分以增大系統的安全性,最後生成so庫(用過第三方庫的應該都不陌生)便於給人提供方便。(任何有效的代碼混淆對於會smail語法反編譯你apk是分分鐘的事,即使你加殼也不能幸免高手的攻擊)
4. 便於移植。用C/C++寫得庫可以方便在其他的嵌入式平台上再次使用。
首先我們在Android Studio下新建一個安卓項目。然後打開Project Structure界面,如下:
在SDK Location目錄下,有SDK和NDK的路徑,而這裡我們暫時還未下載配置過NDK,故我們需要點擊Download Android NDK來進行下載(Android Studio還是很強大的,相比Eclipse能省不少事)。這裡Android Studio會下載最新版本的NDK進行安裝,默認會下載保存在SDK的路徑下。我們在上圖中還能看到有一段介紹文字,說SDK以及NDK的路徑配置會保存在local.properties文件內,安裝完成後我們刷新Project,進local.properties文件查看也能看到SDK與NDK的路徑。
NDK下載配置完成之後,需要在gradle.properties文件中加上一行:
android.useDeprecatedNdk=true
接下來,我們借助強大的Android Studio的插件功能,在External Tools下配置兩個非常有用的插件。進入Settings–>Tools–>ExternalTools,點擊+號增加。
javah -jni命令,是根據java文件生成.h頭文件的,會自動根據java文件中的類名(包含包名)與方法名生成對應的C/C++裡面的方法名。下面是參數配置及其含義:
1. Program: $JDKPath$\bin\javah.exe 這裡配置的是JDK目錄下的javah.exe的路徑。
2. Parametes: -classpath . -jni -d $ModuleFileDir$/src/main/jni $FileClass$ 這裡$FileClass$指的是要執行操作的類名(即我們操作的文件),$ModuleFileDir$/src/main/jni表示生成的文件保存在這個module目錄的src/main/jni目錄下。
3. Working: $ModuleFileDir$\src\main\java module目錄下的src\main\java目錄(不是很理解)。
使用方式:選中java文件—>右鍵—>External Tools—>javah-jni,將生成jni文件夾以及文件夾下的 包名.類名的.h頭文件 (名字過長,我們可以自己重命名)。
ndk -build命令,是根據C/C++文件生成so文件的。下面是參數配置及其含義:
1. Program: F:\apk\sdk\ndk-bundle\ndk-build.cmd 這裡配置的是ndk下的ndk-build.cmd的路徑(根據實際情況填寫)。
2. Working: $ModuleFileDir$\src\main\
使用方式:選中C/C++文件—>右鍵—>ExternalTools—>ndk-build,將在main文件夾下生成libs文件夾以及多個so文件,我們可以移動至jniLibs目錄下去。
接下來我們創建一個訪問本地C/C++方法的java類。
public class JniTest { /** * 將用C++代碼實現,在android代碼中調用的方法:獲取當前app的包名 * @param o * @return */ public static native String getPackname(Object o); /** * 加載so庫或jni庫 */ static { System.loadLibrary("JNI_ANDROID_TEST"); } }
注意JNI_ANDROID_TEST這個Library名字,之後還會需要用到,要保持一致。該類提供了一個static的native方法,該方法將用來獲取app的包名。然後對該文件執行javah -jni操作,生成對應的.h頭文件。
如圖,已經根據我們的java類生成了對應的.h文件,文件名為包名_類名.h,我們可以手動改名為jnitest.h,裡面只有一個方法,返回值為String(jstring),方法名為Java_類的包名_類名_方法名(包名中的分級不是用.而是_),前面兩個參數是C++裡面必須有的(JNIEnv代表指向JVM的指針,jclass是調用該方法的java對象),第三個就是我們java類的方法裡面的參數Object。注意,這是java函數與C++函數對應的靜態注冊方法,即通過特定的規則來寫,此處方法名可以隨意起名字,然後何動態注冊關聯兩個方法(顯然,靜態注冊要簡單一些)。
然後我們新建一個C++文件,取名為jnitest.cpp,寫上需要include的文件,從.h文件中復制方法過來(方法名、參數類型、返回值等必須一致!血與淚的教訓)。
至此,.h文件和c++文件均已完成,接下來還需要在這個jni目錄下增加兩個文件,Android.mk和Application.mk。
Android.mk,注意LOCAL_MODULE的值與之前的名字相對應,LOCAL_SRC_FILES的值寫c++文件的名字,這兩個值成對設置,可設置多組。
LOCAL_PATH := $(call my-dir) // 設置當前的編譯目錄(Android.mk所在的目錄) include $(CLEAR_VARS) // 清除LOCAL_XX變量(LOCAL_PATH除外) LOCAL_MODULE := JNI_ANDROID_TEST // 指定當前編譯模塊的名稱 LOCAL_SRC_FILES =: jnitest.cpp // 編譯模塊需要的源文件 include $(BUILD_SHARED_LIBRARY) // 指定編譯出的庫類型,BUILD_SHARED_LIBRARY:動態庫;BUILD_STATIC_LIBRARY:靜態庫, BUILD_EXECUTEABLE指:可執行文件
Application.mk,APP_ABI有四種類型(默認armeabi),armeabi、armeabi-v7a、x86、mips,設置時以空格隔開,all表示所有。該文件中有個可選配置的APP_MODULES,類似於上面Android.mk文件中的LOCAL_MODULE,以空格隔開,且會覆蓋掉Android.mk文件中的LOCAL_MODULE設置(比如Android.mk文件中的寫了兩個jni庫的配置,LOCAL_MODULE := JNI1、LOCAL_MODULE := JNI2,而Application.mk中設置的APP_MODULES := JNI1,則只能生成JNI1的so文件,要生成JNI2的so文件的時候會報錯,除非寫成APP_MODULES := JNI1 JNI2,這裡我們直接省略默認使用Android.mk中的)。
APP_ABI := all
接下來我們需要對C++文件執行ndk-build操作,生成相應的so文件。
如圖,在main/libs目錄下生成了多個so文件,名字為lib+我們指定的庫名(同時還生成了obj文件夾,不知是什麼東西)。
這時候我們可以在main目錄下新建jniLibs文件夾,把生成的libs文件夾內的東西均復制過去,刪除新生成的jni、libs、obj三個文件夾。然後在Activity中測試調用,在TextView上顯示我們通過C++代碼實現的方法getPackname獲取app的包名了。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.tv_app_package_name); tv.setText("packageName: " + JniTest.getPackname(MainActivity.this)); } }
測試能正確得到包名,說明調用成功了。我們可以把JniTest類以及so文件給別人去使用,這樣別人是看不到我們的代碼實現的,能很好的保護我們的源碼。
以上內容大部分來自網絡,整理並測試過後,給出自己的一個理解以及詳細圖文操作步驟,本文僅供新手參考。本人也剛開始接觸,文中可能存在錯誤之處,請謹慎。
google可能為了安全考慮,在5.1.+後調用activitymanager.getRunningAppProcesses()方法只能返回你自己應用的進程,那如何在5.
今天練手一下,一起來畫個太極圖吧~最終效果如下:最終效果一般都是先講原理,我就反其道而行,先講實現吧。1.繼承實現初始化方法繼承View,實現基本的構造函數:public
Android Studio 1.0正式版發布啦今天是個大日子,Android Studio 1.0 終於發布了正式版, 這對於Android開發者來說簡直是喜大普奔的大
以前我們創建項目時候,一個頁面的布局是線性或者相對等,當我在AS上新建一個module時,系統默認的最外層布局不再是我們熟悉的五大布局中的一種,而是一個全新的布局:Coo