• JNI通过线程c回调java层的函数


    1、参看博客:http://www.jianshu.com/p/e576c7e1c403

    Android JNI 篇 - JNI回调的三种方法(精华篇)

    2、参看博客: 

    JNI层线程回调Java函数关键点及示例

    http://blog.csdn.net/fu_shuwu/article/details/41121741

     3 http://blog.csdn.net/u010402982/article/details/48199487

    核心的关键点:

    三、本地线程中调用java对象

    问题1:

    JNIEnv是一个线程相关的变量

    JNIEnv 对于每个 thread 而言是唯一的

    JNIEnv *env指针不可以为多个线程共用

    解决办法:

    但是java虚拟机的JavaVM指针是整个jvm公用的,我们可以通过JavaVM来得到当前线程的JNIEnv指针.

    可以使用javaAttachThread保证取得当前线程的Jni环境变量

    static JavaVM *gs_jvm=NULL;

    gs_jvm->AttachCurrentThread((void **)&env, NULL);//附加当前线程到一个Java虚拟机

    jclass cls = env->GetObjectClass(gs_object);

    jfieldID fieldPtr = env->GetFieldID(cls,"value","I");

    问题2:

    不能直接保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它。

    解决办法:

    用env->NewGlobalRef创建一个全局变量,将传入的obj(局部变量)保存到全局变量中,其他线程可以使用这个全局变量来操纵这个java对象

    注意:若不是一个 jobject,则不需要这么做。如:

    jclass 是由 jobject public 继承而来的子类,所以它当然是一个 jobject,需要创建一个 global reference 以便日后使用。

    而 jmethodID/jfieldID 与 jobject 没有继承关系,它不是一个 jobject,只是个整数,所以不存在被释放与否的问题,可保存后直接使用。

    总结:创建两个全局的变量一个是JavaVM 虚拟机环境jvm

    另外一个全局变量是jobj对象

    然后创建一个线程,使用全局的jvm获得与该线程一一对应的env,通过env和全局的jobj对象,创建java层的对象,调用java层的方法,最近将线程环境关闭。

    我们来看下程序的框架:

    我们来看下程序的代码:

    package im.weiyuan.com.jni;
    
    public class Sdk {
    
        static {
            System.loadLibrary("hello");
        }
    
    
        public Sdk() {
        }
    
        //单例
        private static class SdkHodler {
            static Sdk instance = new Sdk();
        }
    
        public static Sdk getInstance() {
            return SdkHodler.instance;
        }
        //回调到各个线程
        public interface OnSubProgressListener {
    
            public int onProgressChange(long total, long already);
        };
    //c层回调上来的方法
        public int onProgressCallBack(long total, long already) {
            //自行执行回调后的操作
            System.out.println("total:"+total);
            System.out.println("already:"+already);
            return 1;
        }
    
        //调到C层的方法
        public native void nativeDownload();
    
    }

    然后

    (一)   第二步:make project一下,目的就是编译成对应的class文件。然后根据生成的class文件,利用javah生成对应的 .h头文件。

    (一)   第三步:

    Cmd终端进入到你新建的android工程的src/main目录下:我的目录是:

    F:JNIappsrcmain

    执行命令:

    Javah -d jni -classpath D:android_sdk_ndksdkplatformsandroid-21android.jar;....uildintermediatesclassesdebug im.weiyuan.com.jni.Sdk

    其中: D:android_sdk_ndksdk是你sdk的路径 

     im.weiyuan.com.jni.Sdk是你对应的

    就是你声明的native函数所在的包名加上类名。

    就会发现在main目录下多了一个jni文件夹,里面有生成好的头文件:

    在这个头文件中就自动帮助我们生成了函数的声明

    第五步:

    在jni目录下新建一个 .c文件。来实现头文件里面声明的方法。我的叫im_weiyuan_com_jni_Sdk.c

    我们来看下程序的代码:

    //
    // Created by wei.yuan on 2017/6/13.
    //
    #include <jni.h>
    #include <string.h>
    #include <pthread.h>
    #include "im_weiyuan_com_jni_Sdk.h"
    #include "im_weiyuan_com_jni_Sdk_OnSubProgressListener.h"
    #include "im_weiyuan_com_jni_Sdk_SdkHodler.h"
    JavaVM *g_VM;
    jobject g_obj;
    #include <jni.h>
    #include <string.h>
    #include <android/log.h>
    #include <time.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <dlfcn.h>
    #include <assert.h>
    #include <androidlog.h>
    #include <errno.h>
    #include <pthread.h>
    
    
    
    static  void* native_thread_exec(void* arg){
    
        JNIEnv *env;
        int mNeedDetach = -1;
        //获取当前native线程是否有没有被附加到jvm环境中
        int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env,JNI_VERSION_1_6);
        if (getEnvStat == JNI_EDETACHED) {
            //如果没有, 主动附加到jvm环境中,获取到env
            if ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {
                return;
            }
            mNeedDetach = JNI_TRUE;
        }
    
        //通过全局变量g_obj 获取到要回调的类
        jclass javaClass = (*env)->GetObjectClass(env, g_obj);
    
        if (javaClass == 0) {
           // LOGI("Unable to find class");
            (*g_VM)->DetachCurrentThread(g_VM);
            return;
        }
    
        //获取要回调的方法ID
        jmethodID javaCallbackId = (*env)->GetMethodID(env, javaClass,
                                                       "onProgressCallBack", "(JJ)I");
        if (javaCallbackId == NULL) {
            //LOGI("Unable to find method:onProgressCallBack");
            return;
        }
        //执行回调
        (*env)->CallIntMethod(env, g_obj, javaCallbackId,100,100);
    
        //释放当前线程
        if(mNeedDetach) {
            (*g_VM)->DetachCurrentThread(g_VM);
        }
    

    //释放你的全局引用的接口,生命周期自己把控
    (*env)->DeleteGlobalRef(env, g_obj);
    g_obj = NULL;
        env = NULL;

    } JNIEXPORT
    void JNICALL Java_im_weiyuan_com_jni_Sdk_nativeDownload (JNIEnv * env , jobject thiz){ //JavaVM是虚拟机在JNI中的表示,等下再其他线程回调java层需要用到 (*env)->GetJavaVM(env, &g_VM); // 生成一个全局引用保留下来,以便回调 g_obj = (*env)->NewGlobalRef(env, thiz); // 此处使用c语言开启一个线程,进行回调,这时候java层就不会阻塞,只是在等待回调 pthread_t thread_id; if(( pthread_create(&thread_id,NULL, native_thread_exec,NULL))!=0){ return ; } return; }

    其中:

    JNIEXPORT void JNICALL Java_im_weiyuan_com_jni_Sdk_nativeDownload
    (JNIEnv * env , jobject thiz)就是在im_weiyuan_com_jni_Sdk.h头文件中系统自动生成的

    (一)   第五步:配置ndk的路径

    在 local.properties 文件中设置ndk的路径:

    sdk.dir=D:\android_sdk_ndk\sdk
    ndk.dir=D:\android_sdk_ndk\android-ndk-r10e

    
    

    (一)   在gradle.propertes中添加

    android.useDeprecatedNdk=true

    http://stackoverflow.com/questions/31979965/after-updating-android-studio-to-version-1-3-0-i-am-getting-ndk-integration-is

     

    在app目录下的 build.gradle中设置库文件名(生成的so文件名):

    找到 defaultConfig 这项,在里面添加如下内容:

            ndk{

                moduleName "hello"  //设置库(so)文件名称

                abiFilters "armeabi", "armeabi-v7a", "x86" 

            }

    这里    hello必须和 

     static {

            System.loadLibrary("hello");

    }中的名字是对应的,abiFilters是指定生成那种平台下的so库,对应于eclipse中的Aplication.mk文件中的内容。编译,并运行。界面上就会显示从native方法传过来的值。

    在c代码中
      "onProgressCallBack", "(JJ)I"
    这里调用上层java函数的时候,使用到了函数的签名:
    如何得到函数的签名了:

    如何查看函数的签名:

    如果当前的工程存放的目录在F盘下的JNI目录:

    进入到工程的F:JNIappuildintermediatesclassesdebug 目录下

    执行命令:

    Javap  -s  im.weiyuan.com.jni.Sdk

    其中im.weiyuan.com.jni是包名,Sdk是类名

    F:JNIappuildintermediatesclassesdebug> javap -s im.weiyuan.com.jni.Sdk

    Compiled from "Sdk.java"

    public class im.weiyuan.com.jni.Sdk {

      public im.weiyuan.com.jni.Sdk();

        descriptor: ()V

      public static im.weiyuan.com.jni.Sdk getInstance();

        descriptor: ()Lim/weiyuan/com/jni/Sdk;

      public int onProgressCallBack(long, long);

        descriptor: (JJ)I

      public native void nativeDownload();

        descriptor: ()V

      static {};

        descriptor: ()V

    }

    在activity中我们可以调用native函数的代码:

    package im.weiyuan.com.jni;
    
    import android.app.Activity;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //调用native的方法
            Sdk.getInstance().nativeDownload();
        }
    }

    我们来看下程序的运行结果:

    06-13 15:38:40.070 14935-15032/? I/System.out: total:100

    android studio 的代码地址:

     http://download.csdn.net/detail/jksfkdjksdfjkjk/9869331

    生成的so的目录如下所示:

  • 相关阅读:
    围炉夜话(14)
    围炉夜话(13)
    ATmega128中应用的hex与bootloader的hex合并的实现
    配置IAR for AVR 既可以仿真也可以生成Hex文件
    atmega128 bootloader程序在IAR-AVR下 linker文件的配置及原因
    一步步学习SPD2010--第十四章节--在Web页面使用控件(8)--关键点
    一步步学习SPD2010--第十四章节--在Web页面使用控件(7)--使用SP服务器控件
    一步步学习SPD2010--第十四章节--在Web页面使用控件(6)--测试ASP.NET表单
    一步步学习SPD2010--第十四章节--在Web页面使用控件(4)--使用ASP.NET验证控件
    一步步学习SPD2010--第十四章节--在Web页面使用控件(3)--验证用户数据输入
  • 原文地址:https://www.cnblogs.com/kebibuluan/p/7001705.html
Copyright © 2020-2023  润新知