Android Ndk(Beginner ‘s guide)(4.0)

Java和本地线程的同步

在这部分,我们将新建一个后台线程,watcher,用来查看store。它将遍历所有输入的值,然后暂停一个指定的时间。当watcher找到了指定的值,类型,数据,他就进行相应操作。第一部分我们仅仅是新建一个watcher计数器,当他遍历的时候就计数。下一部分我们将学习怎么回调Java来作相应操作。
当然,线程是需要同步的。本地线程只有在没有用户修改数据的时候才能允许他访问和修改store。本地代码是C但是UI线程是java代码。因此我们有两种选择:
1、当UI线程调用本地方法来获取和设置值的时候使用本地互斥量mutexes。
2、使用java监视器以及通过JNI来同步本地线程
当然在专注于JNI方面的章节,我们将会选择第2中方法。下面是最终的程序结构:

行动时间——运行一个后台线程

首先在java部分添加同步操作:
1、打开前面章节创建的Store.java,创建两个新的本地函数initializeStore()和finalizeStore(),用来控制watcher的启动和停止,以及Activity开始与结束的时候来初始化和销毁store。
同步Store类的获取和设置函数,也就是在watcher线程在遍历的时候,他们不能访问和修改store的输入。
public class Store {
static {
System.loadLibrary("store");
}
public native void initializeStore();
public native void finalizeStore();
public native synchronized int getInteger(String pKey)
throws NotExistingKeyException, InvalidTypeException;
public native synchronized void setInteger(String pKey,
int pInt);
// Other getters and setters are synchronized too.
...
}
2、在Activity开始与结束的时候调用initializeStore和finalizeStore。在Store初始化的时候创建一个名为watcherCounter的整型输入。输入将会被watcher自动修改:
public class StoreActivity extends Activity {
private EditText mUIKeyEdit, mUIValueEdit;
private Spinner mUITypeSpinner;
private Button mUIGetButton, mUISetButton;
private Store mStore;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initializes components and binds buttons to handlers.
...
// Initializes the native side store.
mStore = new Store();
}
@Override
protected void onStart() {
super.onStart();
mStore.initializeStore();
mStore.setInteger("watcherCounter", 0);
}
@Override
protected void onStop() {
super.onStop();
mStore.finalizeStore();
}
...
}
3、在jni文件夹里面创建一个StoreWatcher.h的头文件包含了Store,JNI以及本地线程的头文件。watcher工作在Store里面,每隔一个规定的时候更新一次(这里是3秒)。他需要:
1)  JavaVM 能个在线程中安全共享然后JNI环境能安全获取的对象。
2)  一个java对象来同步,这里是Store,因为他有了同步函数
3)  致力于线程管理的变量
4、最后,定义两个函数在初始化之后来开启本地线程以及结束他:
#ifndef _STOREWATCHER_H_
#define _STOREWATCHER_H_
#include "Store.h"
#include <jni.h>
#include <stdint.h>
#include <pthread.h>
#define SLEEP_DURATION 5
#define STATE_OK 0
#define STATE_KO 1
typedef struct {
// Native variables.
Store* mStore;
// Cached JNI references.
JavaVM* mJavaVM;
jobject mStoreFront;
// Thread variables.
pthread_t mThread;
int32_t mState;
} StoreWatcher;
void startWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher,
Store* pStore, jobject pStoreFront);
void stopWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher);
#endif
5、创建jni/StoreWatcher.h并声明附加的私有函数:
     runWtcher() 这个代表本地线程的主循环
     processEntry()当watcher遍历输入的时候就会唤醒
     getJNIEnv() 为当前线程获取JNI环境
     deleteGlobalRef()用来删除全局引用
#include "StoreWatcher.h"
#include <unistd.h>
void deleteGlobalRef(JNIEnv* pEnv, jobject* pRef);
JNIEnv* getJNIEnv(JavaVM* pJavaVM);
void* runWatcher(void* pArgs);
void processEntry(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry);
...
6、在jni/StoreWatcher.c里面实现startWatcher()。通过UI线程激活,设置StoreWatcher的结构以及开启watcher线程。
7、因为UI线程可能会在watcher线程检查输入的时刻访问store数据,所以我们需要使用一个对象来同步。我们将使用Store自身来实现获取和设置值的同步:
...
void startWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher,
Store* pStore, jobject pStoreFront) {
// Erases the StoreWatcher structure.
memset(pWatcher, 0, sizeof(StoreWatcher));
pWatcher->mState = STATE_OK;
pWatcher->mStore = pStore;
// Caches the VM.
if ((*pEnv)->GetJavaVM(pEnv, &pWatcher->mJavaVM) != JNI_OK) {
goto ERROR;
}
// Caches objects.
pWatcher->mStoreFront = (*pEnv)->NewGlobalRef 
(pEnv, pStoreFront);
if (pWatcher->mStoreFront == NULL) goto ERROR;
// Initializes and launches the native thread. For simplicity
// purpose, error results are not checked (but we should...).
pthread_attr_t lAttributes;
int lError = pthread_attr_init(&lAttributes);
if (lError) goto ERROR;
lError = pthread_create(&pWatcher->mThread, &lAttributes,
runWatcher, pWatcher);
if (lError) goto ERROR;
return;
ERROR:
stopWatcher(pEnv, pWatcher);
return;
}
...
8、在StoreWatcher.c里面实现帮助函数getJNIEnv(),该函数在线程启动的时候调用。watcher线程是原生线程,也就是说:
没有JNI环境的连接,即JNI不是线程默认激活的。
没有被Java源代码支持,就是你查看调用栈,永远找不到Java方法
9、为了获取JNIEnv原生线程通过AttachCurrentThread()与VM绑定。JIN环境对于当前线程来说是很特别的,并且不能被其他线程共享。事实上,VM新建了一个新的线程对象并把他添加到一个主线程组里面,像其他Java线程:
...
JNIEnv* getJNIEnv(JavaVM* pJavaVM) {
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
JNIEnv* lEnv;
if ((*pJavaVM)->AttachCurrentThread(pJavaVM, &lEnv,
&lJavaVMAttachArgs) != JNI_OK) {
lEnv = NULL;
}
return lEnv;
}
...
10、最重要的函数是runWatcher(),主循环线程。在这我们并不是在UI线程了,而是在watcher线程里面。因此我们需要把它与VM绑定才能获得工作的JNI环境。
11、这个线程只在指定时间内工作并且暂停。当他唤醒的时候,线程就开始在临界区间独自的遍历每一个输入值以保证访问数据的安全性。
12、临界区是由JNI监视器划分的与Java中的关键字synchronized有相同的功能。显然,MonitorEnter()和MonitorExit()必须锁住与解锁mStoreFront以实现获取和设置值的同步。这些操作保证了当第一个
...
void* runWatcher(void* pArgs) {
StoreWatcher* lWatcher = (StoreWatcher*) pArgs;
Store* lStore = lWatcher->mStore;
JavaVM* lJavaVM = lWatcher->mJavaVM;
JNIEnv* lEnv = getJNIEnv(lJavaVM);
if (lEnv == NULL) goto ERROR;
int32_t lRunning = 1;
while (lRunning) {
sleep(SLEEP_DURATION);
StoreEntry* lEntry = lWatcher->mStore->mEntries;
int32_t lScanning = 1;
while (lScanning) {
// Critical section begining, one thread at a time.
// Entries cannot be added or modified.
(*lEnv)->MonitorEnter(lEnv, lWatcher->mStoreFront);
lRunning = (lWatcher->mState == STATE_OK);
StoreEntry* lEntryEnd = lWatcher->mStore->mEntries
+ lWatcher->mStore->mLength;
lScanning = (lEntry < lEntryEnd);
if (lRunning && lScanning) {
processEntry(lEnv, lWatcher, lEntry);
}
// Critical section end.
(*lEnv)->MonitorExit(lEnv, lWatcher->mStoreFront);
// Goes to next element.
++lEntry;
}
}
ERROR:
(*lJavaVM)->DetachCurrentThread(lJavaVM);
pthread_exit(NULL);
}
...
14、在StoreWatcher里面,添加processEntry()函数,他用来检测watcherCounter的输入以及对其值做增量操作。因此watcherCOunter记录了从一开始watcher线程重复了多少次循环:
...
void processEntry(JNIEnv* pEnv, StoreWatcher* pWatcher,
StoreEntry* pEntry) {
if ((pEntry->mType == StoreType_Integer)
&& (strcmp(pEntry->mKey, "watcherCounter") == 0) {
++pEntry->mValue.mInteger;
}
}
...
15、在jni/StoreWatcher里面实现stopWatcher(),用来结束watcher线程以及释放所有的全局引用。为了帮助释放空间要实现deeleteGlobalRef()他能使得代码更加的简洁。注意mState是一个线程间共享的变量,只能在临界区访问他:
...
void deleteGlobalRef(JNIEnv* pEnv, jobject* pRef) {
if (*pRef != NULL) {
(*pEnv)->DeleteGlobalRef(pEnv, *pRef);
*pRef = NULL;
}
}
void stopWatcher(JNIEnv* pEnv, StoreWatcher* pWatcher) {
if (pWatcher->mState == STATE_OK) {
// Waits for the watcher thread to stop.
(*pEnv)->MonitorEnter(pEnv, pWatcher->mStoreFront);
pWatcher->mState = STATE_KO;
(*pEnv)->MonitorExit(pEnv, pWatcher->mStoreFront);
pthread_join(pWatcher->mThread, NULL);
deleteGlobalRef(pEnv, &pWatcher->mStoreFront);
}
}
16、使用javaah生成JNI头文件。
17、打开jni/com_packtpub_Store.c,声明一个静态的Store变量,并且定义initializeStore()来创建和运行watcher线程以及finalizeStore()来停止和释放数据:
#include "com_packtpub_Store.h"
#include "Store.h"
#include "StoreWatcher.h"
#include <stdint.h>
#include <string.h>
static Store mStore;
static StoreWatcher mStoreWatcher;
JNIEXPORT void JNICALL Java_com_packtpub_Store_initializeStore
(JNIEnv* pEnv, jobject pThis) {
mStore.mLength = 0;
startWatcher(pEnv, &mStoreWatcher, &mStore, pThis);
}
JNIEXPORT void JNICALL Java_com_packtpub_Store_finalizeStore
(JNIEnv* pEnv, jobject pThis) {
stopWatcher(pEnv, &mStoreWatcher);
StoreEntry* lEntry = mStore.mEntries;
StoreEntry* lEntryEnd = lEntry + mStore.mLength;
while (lEntry < lEntryEnd) {
free(lEntry->mKey);
releaseEntryValue(pEnv, lEntry);
++lEntry;
}
mStore.mLength = 0;
}
...
18、不要忘了把StoreWatcher.c添加到Android.mk文件里面
19、编译运行

刚刚完成了什么?

我们创建了一个后台运行的本地线程,并设法把它与Dalvik VM联系了起来,允许我们获取JNI环境。然后我们同步了Java和原生线程来处理并发的问题。
在原生代码部分,同步是在JNI监视器下实现的相当于synchronized关键字。因为Java线程是基于POSIX一种原始内部接口,他同样可以完全通过使用互斥量实现同步。
pthread_mutex_t lMutex;
pthread_cond_t lCond;
// Initializes synchronization variables
pthread_mutex_init(&lMutex, NULL);
pthread_cond_init(&lCond, NULL);
// Enters critical section.
pthread_mutex_lock(&lMutex);
// Waits for a condition
While (needToWait)
pthread_cond_wait(&lCond, &lMutex);
// Does something...
// Wakes-up other threads.
pthread_cond_broadcast(&lCond);
// Leaves critical section.
pthread_mutex_unlock(&lMutex);







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Preface Chapter 1: Setting Up your Environment Getting started with Android development Setting up Windows Time for action – preparing Windows for Android development Installing Android development kits on Windows Time for action – installing Android SDK and NDK on Windows Setting up Mac OS X Time for action – preparing Mac OS X for Android development Installing Android development kits on Mac OS X Time for action – installing Android SDK and NDK on Mac OS X Setting up Linux Time for action – preparing Ubuntu Linux for Android development Installing Android development kits on Linux Time for action – installing Android SDK and NDK on Ubuntu Setting up the Eclipse development environment Time for action – installing Eclipse Emulating Android Time for action – creating an Android virtual device Developing with an Android device on Windows and Mac OS X Time for action – setting up your Android device on Windows and Mac OS X Developing with an Android device on Linux Time for action – setting up your Android device on Ubuntu Troubleshooting a development device Summary Chapter 2: Creating, Compiling, and Deploying Native Projects Chapter 3: Interfacing Java and C/C++ with JNI Chapter 4: Calling Java Back from Native Code Chapter 5: Writing a Fully-native Application Chapter 6: Rendering Graphics with OpenGL ES Chapter 7: Playing Sound with OpenSL ES Chapter 8: Handling Input Devices and Sensors Chapter 9: Porting Existing Libraries to Android Chapter 10: Towards Professional Gaming Chapter 11: Debugging and Troubleshooting

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值