20.Eclipse下Ndk开发(pthread开启线程调用Java方法)

本项目最终的目的是在pthread线程中,调用Java一个工具类得到多个uuid,然后调用类中另一个方法弹出toast,实现在c中获取安卓上下文对象Context

编译native方法,生成头文件的一系列过程不再赘述,直接上代码,都在注释中

PosixUtils:

package com.example.ndk_pthread;

public class PosixUtils {
    
    static{
        System.loadLibrary("ndk_pthread");
    }
    /**
     * pthread开启子线程前的一些初始化操作,比如获取本类的jclass对象,生成需要的
     * 全局引用等等,在子线程中无法获取到类的jclass对象,就是这行代码,获取class必须要在主线程中
     * jclass uuidutils_class_tmp = (*env)->FindClass(env,"com/example/ndk_pthread/UUIDUtils");
     */
    public native void init();
    /**
     * 执行一些善后的操作,比如init方法中生成的全局引用的销毁等等
     */
    public native void destroy();
    /**
     * 子线程操作
     */
    public native void pthread(); 
}

UUIDUtils:

package com.example.ndk_pthread;

import java.util.UUID;

import android.content.Context;
import android.widget.Toast;

public class UUIDUtils {

    public static String get(){
        return UUID.randomUUID().toString();
    }
    
    public static void showToast(Context context){
        Toast.makeText(context, "c端弹出了toast", 0).show();
    }
    
}

编译生成的头文件com_example_ndk_pthread_UUIDUtils.h:

package com.example.ndk_pthread;

import java.util.UUID;

import android.content.Context;
import android.widget.Toast;

public class UUIDUtils {

    public static String get(){
        return UUID.randomUUID().toString();
    }
    
    public static void showToast(Context context){
        Toast.makeText(context, "c端弹出了toast", 0).show();
    }
    
}

MainActivity:

package com.example.ndk_pthread;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class MainActivity extends Activity {

    private PosixUtils p;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        p = new PosixUtils();
        p.init();
    }

    public void start(View btn){
        p.pthread();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //页面销毁时同时销毁c端的一些东西
        p.destroy();
    }

}

ndk_pthread.c

#include "com_example_ndk_pthread_PosixUtils.h"
#include <stdio.h>
#include <pthread.h>
#include <android/log.h>
#include <unistd.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"renzhenming",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"renzhenming",FORMAT,##__VA_ARGS__);

JavaVM *javaVM;
jobject uuidutils_jcls;
jmethodID get_mid;
jmethodID toastId;
jobject jcontext;
//动态库加载时会被调用执行,不需要我们手动调用
//兼容Android SDK 2.2之后,2.2没有这个函数
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
    LOGI("%s","JNI_OnLoad");
    javaVM = vm;
    return JNI_VERSION_1_4;
}

/**
 * 目标:调用UUIDUtils类中的方法得到一个uuid
 */
void thread_fun(void* arg){
    //得到这个类UUIDUtils,需要用到JNIEnv,但是这是一个子线程,每个线程都有独立的JNIEnv,所以我们需要获取到
    //这个线程的JNIEnv,通过JavaVM关联当前线程,获取当前线程的JNIEnv,(*javaVM)->AttachCurrentThread(javaVM,&env,NULL);
    //那么就需要先获取到JavaVM
    //如何获取JavaVM?
    //1.在JNI_OnLoad函数中获取
    //2.(*env)->GetJavaVM(env,&javaVM);

    //的到JavaVM后,会得到env并赋值给变量
    JNIEnv* env = NULL;
    (*javaVM)->AttachCurrentThread(javaVM,&env,NULL);

    char* no = (char*)arg;
    int i;
    for (i = 0; i < 5; ++i) {
        LOGI("thread %s, i:%d",no,i);
        jobject uuid = (*env)->CallStaticObjectMethod(env,uuidutils_jcls,get_mid);
        char* uuid_char = (*env)->GetStringUTFChars(env,uuid,NULL);
        LOGI("%s",uuid_char);
        if(i == 4){
            goto end;
        }
        (*env)->ReleaseStringUTFChars(env,uuid,uuid_char);
        sleep(1);
    }
end:
    //如果直接将这两行写在循环之后是有问题的,当i=4的时候,线程直接退出,导致没有执行DetachCurrentThread,报异常
    (*javaVM)->DetachCurrentThread(javaVM);
    pthread_exit((void*)0);
}
/**
 * 当在JNI调用Android自带的类时,经常需要传入Context参数,那怎么在JNI层获取Context呢?
 * 我们知道Application和Activity是Context的子类,由于每个Activity对应的Context是不一样的,
 * 所以一般情况下我们使用Application的Context,它在整个程序中只有一个实例。所以现在问题就变成了
 * 怎么在JNI中获取Application呢?
 * Android APP在启动时会创建一个Activity Thread作为主线程,只要程序存活,这个线程就一直存在,
 * 所以我们可以考虑从Activity Thread中获取Application,查看Activity Thread的源码发现,
 * 它提供了一个方法可以获取Application,如下:
 *
 * public Application getApplication() {
 *    return mInitialApplication;
 * }
 *
 * 也就是说我们只需要获取到Activity Thread的对象即可,Activity Thread提供了一个静态方法用于获取其实例,如下:
 *
 * public static ActivityThread currentActivityThread() {
 *    return sCurrentActivityThread;
 * }
 *
 * 至此获取Context的步骤已经很清晰了
 */
JNIEXPORT void JNICALL Java_com_example_ndk_1pthread_PosixUtils_init
(JNIEnv *env, jobject jobj){
    /**
     * 打印uuid
     */
    //获取class必须要在主线程中
    jclass uuidutils_class_tmp = (*env)->FindClass(env,"com/example/ndk_pthread/UUIDUtils");
    //创建全局引用
    uuidutils_jcls = (*env)->NewGlobalRef(env,uuidutils_class_tmp);
    //获取jmethodId也可以在子线程中
    get_mid = (*env)->GetStaticMethodID(env,uuidutils_jcls,"get","()Ljava/lang/String;");
    /**
     * show toast
     */
    //获取jmethodId也可以在子线程中
    toastId = (*env)->GetStaticMethodID(env,uuidutils_jcls,"showToast","(Landroid/content/Context;)V");

    //获取Activity Thread的实例对象
    jclass activityThread = (*env)->FindClass(env,"android/app/ActivityThread");
    jmethodID currentActivityThread = (*env)->GetStaticMethodID(env,activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;");
    jobject at = (*env)->CallStaticObjectMethod(env,activityThread, currentActivityThread);
    //获取Application,也就是全局的Context
    jmethodID getApplication = (*env)->GetMethodID(env,activityThread, "getApplication", "()Landroid/app/Application;");
    jobject context = (*env)->CallObjectMethod(env,at, getApplication);

    jcontext = (*env)->NewGlobalRef(env,context);
}


JNIEXPORT void JNICALL Java_com_example_ndk_1pthread_PosixUtils_destroy
(JNIEnv *env, jobject jobj){
    //释放全局引用
    (*env)->DeleteGlobalRef(env,uuidutils_jcls);
}

JNIEXPORT void JNICALL Java_com_example_ndk_1pthread_PosixUtils_pthread
  (JNIEnv *env, jobject jobj){
    LOGI("%s","begin");
    //获取JavaVM的第一种方式,本项目中我们采用在JNI_OnLoad中获取的方式
    //(*env)->GetJavaVM(env,&javaVM);
    pthread_t tid;
    pthread_create(&tid,NULL,thread_fun,(void*)"NO1");

    //调用Java函数show toast
    (*env)->CallStaticVoidMethod(env,uuidutils_jcls,toastId,jcontext);
}

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := ndk_pthread
LOCAL_SRC_FILES := ndk_pthread.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容