編輯:關於Android編程
1.在android studio中新建一個測試項目,並進行配置
如果已經安裝了ndk可以在項目的根目錄右鍵open Module Settings中看到你配置的ndk路徑
vc+88rWlo6y9q9St09Cy5bz+vLQ8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
classpath'com.android.tools.build:gradle:2.2.0-rc2'
換成
classpath'com.android.tools.build:gradle-experimental:0.7.0'
3.配置module(模塊)的build.gradle
1)與正常的配置區別1
正常的配置使用的是以下這個插件
apply plugin: 'com.android.application'
而NDK則需要使用以下插件
apply plugin: 'com.android.model.application'
2)與正常配置區別2
需要在最外層添加一個model標簽來包裹android標簽
而依賴的標簽需要在model標簽外層
效果如下:
3)與正常配置區別3
觀察android標簽會發現裡面的變量類型和變量名不再像正常以”空格”作為分隔符,而是以”=”作為分割符因此需要將android標簽下的
compileSdkVersion 24
buildToolsVersion "24.0.2"
改成
compileSdkVersion=24
buildToolsVersion="24.0.2"
同理defaultConfig標簽下的分割符也需要修改成 “=”同時還要注意到
minSdkVersion變為了minSdkVersion.apiLevel
targetSdkVersion變為了targetSdkVersion.apiLevel
即變成了
defaultConfig {
applicationId="com.wbl.ndktest"
minSdkVersion.apiLevel=15
targetSdkVersion.apiLevel=24
versionCode=1
versionName="1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildType標簽由原來的
buildTypes {
release {
minifyEnabled false
proguardFiles.getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
變為
buildTypes {
release {
minifyEnabled false
proguardFiles.add(file('proguard-rules.txt'));
}
debug {
ndk.debuggable = true //有這個才會支持調試native 代碼,這個放到release裡一樣能用
}
}
4)與正常配置的區別4
在android標簽下添加了一個ndk的標簽所有關於ndk的配置都可以在此完成,我這裡只配置了三個屬性:
ndk{
moduleName='wbl-jni' //動態庫的名稱
toolchain= 'clang' //編譯器,據說這個比gcc要快,沒有這個寫native代碼時沒有自動補全的功能
CFlags.addAll(['-Wall']) //對應gcc中的編譯選項 CFLAGS,方括號內是一個數組,可以有多個值
}
然後同步一下gradle編輯完成即可
native方法的使用
1.在項目的src/mian/下新建文件夾jni並在該文件夾下新建一個.c文件
//添加頭文件
#include
#include
jstring
//本地函數定義需要遵循一定規則可以到
//該鏈接去查看相應規則,這裡不作介紹了
Java_com_wbl_ndktest_MainActivity_stringFromJNI( JNIEnv* env,
jobject thiz )
{
//預編譯處理
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#if defined(__ARM_NEON__)
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a/NEON (hard-float)"
#else
#define ABI "armeabi-v7a/NEON"
#endif
#else
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a (hard-float)"
#else
#define ABI "armeabi-v7a"
#endif
#endif
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#define ABI "x86"
#elif defined(__x86_64__)
#define ABI "x86_64"
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#else
#define ABI "unknown"
#endif
return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI ".");
}
之後在java層創建一個TextView在setText中調用這個函數stringFromJNI,該函數會告訴你當前使用的是什麼平台。
java層的代碼如下:
使用本地方法實現計時
簡介:之前使用一個簡單的例子來描述NDK的使用,接下來通過計時的例子來加深對ndk的使用,這裡可能會涉及到java層在native層的回調和native層在java層的回調。
java層的實現
java層實現較為簡單
1.首先定義三個整形變量 hour,minute,second並進行賦值
int hour = 0;
int minute = 0;
int second = 0;
2.
3.在onResume()函數中調用本地函數startTicks()開始計時
@Override
public void onResume() {
super.onResume();
hour = minute = second = 0;
startTicks();
}
4.在onPause()函數中調用本地函數StopTocks()停止計時
@Override
public void onPause () {
super.onPause();
StopTicks();
}
5.在updateTimer()函數用於處理每秒中的UI更新。
@Keep
private void updateTimer() {
++second;
if(second >= 60) {
++minute;
second -= 60;
if(minute >= 60) {
++hour;
minute -= 60;
}
}
runOnUiThread(new Runnable() {
@Override
public void run() {
String ticks = "" + MainActivity.this.hour + ":" +
MainActivity.this.minute + ":" +
MainActivity.this.second;
MainActivity.this.tickView.setText(ticks);
}
});
}
6.加載本地庫
static {
System.loadLibrary("wbl-jni");
}
7.聲明本地函數
public native void startTicks();
public native void StopTicks();
在java層使用這些函數時你會發現有一個函數似乎被架空了,它沒有被任何人調用,但卻一直被執行。那就是
updateTimer()這個函數
它為什麼會被執行呢?,可以從下面的native層找到答案
native層的實現
當進程初始化時,會產生一個JavaVM的結構體,這個結構體在一個進程中只存在一個
當java層通過System.loadLibrary加載完JNI動態庫後,接著會調用一個JNI_OnLoad函數,在這裡可以完成初始化的工作
1.調用UpdateTicks實現每秒的計時
/*
*在java層的UI線程中被調用
* java層通過MainActivity::updateTimer() 在UI線程中展示計時
* java層通過JniHandler::updateStatus(String msg)獲取更新的信息
*/
void* UpdateTicks(void* context) {
//TickContext *pctx = (TickContext*) context;
//得到tick_context結構體
struct tick_context *pctx=(struct tick_context*)context;
JavaVM *javaVM = pctx->javaVM;
JNIEnv *env;
jint res = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
return NULL;
}
}
//獲取JniHandler類中updateStatus()函數
jmethodID statusId = (*env)->GetMethodID(env, pctx->jniHelperClz, "updateStatus","(Ljava/lang/String;)V");
//通過該方法發生消息到updateStatus函數
sendJavaMsg(env, pctx->jniHelperObj, statusId,
"TickerThread status: initializing...");
// 得到 mainActivity updateTimer 函數
jmethodID timerId = (*env)->GetMethodID(env, pctx->mainActivityClz,
"updateTimer", "()V");
//timeval結構體定義了兩個變量,一個用來表示秒數,一個用來表示 微秒數
struct timeval beginTime, curTime, usedTime, leftTime;
const struct timeval kOneSecond = {
(__kernel_time_t)1, //秒數
(__kernel_suseconds_t) 0 //微秒數
};
//通過該方法發生消息到updateStatus函數
sendJavaMsg(env, pctx->jniHelperObj, statusId,
"TickerThread status: start ticking ...");
while(1) {
//獲得當前精確時間
//其參數1是保存獲取時間結果的結構體,參數2用於保存時區結果
//
gettimeofday(&beginTime, NULL);
//加上線程同步鎖
pthread_mutex_lock(&pctx->lock);
//用於判斷是否停止計時
int done = pctx->done;
if (pctx->done) {
pctx->done = 0;
}
pthread_mutex_unlock(&pctx->lock);
if (done) {
break;
}
//timerId是java層的方法updateTimer
(*env)->CallVoidMethod(env, pctx->mainActivityObj, timerId);
gettimeofday(&curTime, NULL);
timersub(&curTime, &beginTime, &usedTime);
timersub(&kOneSecond, &usedTime, &leftTime);
struct timespec sleepTime;
sleepTime.tv_sec = leftTime.tv_sec;
sleepTime.tv_nsec = leftTime.tv_usec * 1000;
if (sleepTime.tv_sec <= 1) {
nanosleep(&sleepTime, NULL);
} else {
sendJavaMsg(env, pctx->jniHelperObj, statusId,
"TickerThread error: processing too long!");
}
}
sendJavaMsg(env, pctx->jniHelperObj, statusId,
"TickerThread status: ticking stopped");
(*javaVM)->DetachCurrentThread(javaVM);
return context;
}
2.startTicks()函數的實現
JNIEXPORT void JNICALL
Java_com_example_hello_1jnicallback_MainActivity_startTicks(JNIEnv *env, jobject instance) {
pthread_t threadInfo_;
pthread_attr_t threadAttr_;
pthread_attr_init(&threadAttr_);
pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED);
pthread_mutex_init(&g_ctx.lock, NULL);
jclass clz = (*env)->GetObjectClass(env, instance);
g_ctx.mainActivityClz = (*env)->NewGlobalRef(env, clz);
g_ctx.mainActivityObj = (*env)->NewGlobalRef(env, instance);
int result = pthread_create( &threadInfo_, &threadAttr_, UpdateTicks, &g_ctx);
assert(result == 0);
(void)result;
}
3.StopTicks()函數的實現
4.首先創建結構體tick_context
struct tick_context{ //另外一種建立結構體的方式
JavaVM *javaVM; //可以從中獲取線程的 JNIEnv* 結構體
jclass jniHelperClz; //java層class類型的變量
jobject jniHelperObj; //java層自定義變量
jclass mainActivityClz;
jobject mainActivityObj;
pthread_mutex_t lock; //線程同步鎖
int done;
} g_ctx;
//
5.在JNI_OnLoad()函數中初始化結構體,該結構體用於調用來自java層的函數
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
//將結構體所在內存的每個字節的內容設置為0;
memset(&g_ctx, 0, sizeof(g_ctx));
//為結構體的javaVm賦值
g_ctx.javaVM = vm;
//拿到該線程的JNIEnv*結構體存入env中
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
//找到java層的JniHandler類的引用
//前面是包名com/wbl/ndktest/
//JniHandler是對應的java類
jclass clz = (*env)->FindClass(env, "com/wbl/ndktest/JniHandler");
//實例化這個類
g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);
//取得該類的init()方法,jmethodId代表java類的成員方法,
//
jmethodID jniHelperCtor = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "", "()V");
//調用該類的init()方法
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz,jniHelperCtor);
g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);
queryRuntimeInfo(env, g_ctx.jniHelperObj);
g_ctx.done = 0;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
----------
queryRuntimeinfo()函數的實現
void queryRuntimeInfo(JNIEnv *env, jobject instance) {
//調用靜態函數,通過GetStaticMethodID調用java層的getBuildVersion方法
jmethodID versionFunc = (*env)->GetStaticMethodID(
env, g_ctx.jniHelperClz,
"getBuildVersion", "()Ljava/lang/String;");
//通過string字段來接收版本號,獲取當前的versionCode
jstring buildVersion = (*env)->CallStaticObjectMethod(env, g_ctx.jniHelperClz, versionFunc);
//將string類型的變量轉換為char類型的變量 ,編碼為UTF-8
const char *version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
//釋放buildversion和version指針所占內存
(*env)->ReleaseStringUTFChars(env, buildVersion, version);
// we are called from JNI_OnLoad, so got to release it to avoid
// 內存洩漏
(*env)->DeleteLocalRef(env, buildVersion);
// 調用一個非靜態函數
//我們需要使用一個JniHandler的實例來調用
jmethodID memFunc = (*env)->GetMethodID(env, g_ctx.jniHelperClz,
"getRuntimeMemorySize", "()J");
//調用該方法返回空閒內存的大小
jlong result = (*env)->CallLongMethod(env, instance, memFunc);
(void)result; // 忽略編譯器的警告
}
上一篇博客我們講到了ViewRoot中與UI相關的三個重要步驟:performMeasure(測量)、performLayout(布局)和performDraw(繪制),
Android 目前支持下面幾個版本的OpenGL ES API : OpenGL ES 1.0 和 1.1 :Android 1.0和更高的版本支持這個API規范。 O
上一篇,初步開發了這個應用,功能都有了(見http://www.jb51.net/article/96992.htm 點擊打開鏈接)。但是遺留了兩個問題:1、還是無法卸載
gradle lintgradle中有lint任務,可以直接執行lint靜態代碼檢查,但是前提是你的build.gradle設置了lintOptions選項