zoukankan      html  css  js  c++  java
  • Android NDK开发(1) Java与C互相调用实例详解

    一、概述

          对于大部分应用开发者来说可能都不怎么接触到NDK,但如果涉及到硬件操作的话就不得不使用NDK了。使用NDK还有另一个原因,就是C/C++的效率比较高,因此我们可以把一些耗时的操作放在NDK中实现。

          关于java与c/c++的互相调用,网上有一大堆的文章介绍。但仔细观察可以发现,基本都是讲在java中调用一个本地方法,然后由该本地方法直接返回一个参数给java(例如,在java中定义的本地方法为private int callJNI(int i))。但在大多数时候要求的并不是由开发者在java层主动去调JNI中的函数来返回想要的数据,而是由JNI主动去调java中的函数。举个最简单的例子,Android中的Camera,图像数据由内核一直往上传到java层,然而这些数据的传递并不需要开发者每一次主动去调用来JNI中的函数来获取,而是由JNI主动传给用java中方法,这类似于Linux驱动机制中的异步通知。

     

    二、要求

          用NDK实现Java与C/C++互调,实现int,string,byte[]这三种类型的互相传递。

     

    三、实现

          下面的实现中,每次java调用JNI中的某个函数时,最后会在该函数里回调java中相应的方法而不是直接返回一个参数。可能你会觉得这不还是每次都是由开发者来主动调用吗,其实这只是为了讲解而已,在实际应用中,回调java中的方法应该由某个事件(非java层)来触发。

          新建工程MyCallback,修改main.xml文件,在里面添加3个Button,分别对应3种类型的调用和3个TextView分别显示由JNI回调java时传给java的数据。完整的main.xml文件如下:

     1 <?xml version="1.0" encoding="utf-8"?>
    2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3 android:layout_width="fill_parent"
    4 android:layout_height="fill_parent"
    5 android:orientation="vertical" >
    6
    7 <Button
    8 android:id="@+id/intbutton"
    9 android:layout_width="fill_parent"
    10 android:layout_height="wrap_content"
    11 android:text="传给JNI一个整数1"
    12 />
    13
    14 <TextView
    15 android:id="@+id/inttextview"
    16 android:layout_width="fill_parent"
    17 android:layout_height="wrap_content"
    18 android:text="接收到的整数:"
    19 />
    20
    21 <Button
    22 android:id="@+id/stringbutton"
    23 android:layout_width="fill_parent"
    24 android:layout_height="wrap_content"
    25 android:text="传给JNI一个字符A"
    26 />
    27
    28 <TextView
    29 android:id="@+id/stringtextview"
    30 android:layout_width="fill_parent"
    31 android:layout_height="wrap_content"
    32 android:text="接收到的字符:"
    33 />
    34
    35 <Button
    36 android:id="@+id/arraybutton"
    37 android:layout_width="fill_parent"
    38 android:layout_height="wrap_content"
    39 android:text="传给JNI一个数组12345"
    40 />
    41
    42 <TextView
    43 android:id="@+id/arraytextview"
    44 android:layout_width="fill_parent"
    45 android:layout_height="wrap_content"
    46 android:text="接收到的数组:"
    47 />
    48
    49
    50 </LinearLayout>

    修改MyCallbackActivity.java文件,定义了一个Handler,当JNI回调java的方法时,用来发送消息;实现3个Button的监听。如下:

      1 package com.nan.callback;
    2
    3 import android.app.Activity;
    4 import android.os.Bundle;
    5 import android.os.Handler;
    6 import android.os.Message;
    7 import android.view.View;
    8 import android.widget.Button;
    9 import android.widget.TextView;
    10
    11
    12 public class MyCallbackActivity extends Activity
    13 {
    14 private Button intButton = null;
    15 private Button stringButton = null;
    16 private Button arrayButton = null;
    17 private TextView intTextView = null;
    18 private TextView stringTextView = null;
    19 private TextView arrayTextView = null;
    20
    21 private Handler mHandler = null;
    22
    23
    24 /** Called when the activity is first created. */
    25 @Override
    26 public void onCreate(Bundle savedInstanceState)
    27 {
    28 super.onCreate(savedInstanceState);
    29 setContentView(R.layout.main);
    30
    31 intButton = (Button)this.findViewById(R.id.intbutton);
    32 //注册按钮监听
    33 intButton.setOnClickListener(new ClickListener());
    34 stringButton = (Button)this.findViewById(R.id.stringbutton);
    35 //注册按钮监听
    36 stringButton.setOnClickListener(new ClickListener());
    37 arrayButton = (Button)this.findViewById(R.id.arraybutton);
    38 //注册按钮监听
    39 arrayButton.setOnClickListener(new ClickListener());
    40
    41 intTextView = (TextView)this.findViewById(R.id.inttextview);
    42 stringTextView = (TextView)this.findViewById(R.id.stringtextview);
    43 arrayTextView = (TextView)this.findViewById(R.id.arraytextview);
    44
    45 //消息处理
    46 mHandler = new Handler()
    47 {
    48 @Override
    49 public void handleMessage(Message msg)
    50 {
    51 switch(msg.what)
    52 {
    53 //整型
    54 case 0:
    55 {
    56 intTextView.setText(msg.obj.toString());
    57 break;
    58 }
    59 //字符串
    60 case 1:
    61 {
    62 stringTextView.setText(msg.obj.toString());
    63 break;
    64 }
    65 //数组
    66 case 2:
    67 { byte[] b = (byte[])msg.obj;
    68 arrayTextView.setText(Byte.toString(b[0])+Byte.toString(b[1])+Byte.toString(b[2])+Byte.toString(b[3])+Byte.toString(b[4]));
    69 break;
    70 }
    71 }
    72
    73 }
    74
    75 };
    76
    77
    78 }
    79
    80 //按钮监听实现
    81 public class ClickListener implements View.OnClickListener
    82 {
    83
    84 @Override
    85 public void onClick(View v)
    86 {
    87 // TODO Auto-generated method stub
    88 switch(v.getId())
    89 {
    90 case R.id.intbutton:
    91 {
    92 //调用JNI中的函数
    93 callJNIInt(1);
    94 break;
    95 }
    96 case R.id.stringbutton:
    97 {
    98 //调用JNI中的函数
    99 callJNIString("你好A");
    100 break;
    101 }
    102 case R.id.arraybutton:
    103 {
    104 //调用JNI中的函数
    105 callJNIByte(new byte[]{1,2,3,4,5});
    106 break;
    107 }
    108 }
    109 }
    110
    111 }
    112
    113
    114 //被JNI调用,参数由JNI传入
    115 private void callbackInt(int i)
    116 {
    117 Message msg = new Message();
    118 //消息类型
    119 msg.what = 0;
    120 //消息内容
    121 msg.obj = i;
    122 //发送消息
    123 mHandler.sendMessage(msg);
    124 }
    125
    126 //被JNI调用,参数由JNI传入
    127 private void callbackString(String s)
    128 {
    129 Message msg = new Message();
    130 //消息类型
    131 msg.what = 1;
    132 //消息内容
    133 msg.obj = s;
    134 //发送消息
    135 mHandler.sendMessage(msg);
    136 }
    137
    138 //被JNI调用,参数由JNI传入
    139 private void callbackByte(byte[] b)
    140 {
    141 Message msg = new Message();
    142 //消息类型
    143 msg.what = 2;
    144 //消息内容
    145 msg.obj = b;
    146 //发送消息
    147 mHandler.sendMessage(msg);
    148 }
    149
    150 //本地方法,由java调用
    151 private native void callJNIInt(int i);
    152 private native void callJNIString(String s);
    153 private native void callJNIByte(byte[] b);
    154
    155 static
    156 {
    157 //加载本地库
    158 System.loadLibrary("myjni");
    159 }
    160
    161 }

    最后就是本篇随笔的“重头戏”,在工程的根目录下新建jni文件夹,在里面添加一个Android.mk文件和一个callback.c文件,Android.mk文件如下:

     1 LOCAL_PATH := $(call my-dir)
    2
    3 include $(CLEAR_VARS)
    4
    5 LOCAL_MODULE := myjni
    6 LOCAL_SRC_FILES := callback.c
    7
    8 LOCAL_LDLIBS := -llog
    9
    10 include $(BUILD_SHARED_LIBRARY)

    callback.c文件如下:

     1 #include <string.h>
    2 #include <stdio.h>
    3 #include <stdlib.h>
    4 #include <unistd.h>
    5 #include <sys/ioctl.h>
    6 #include <sys/types.h>
    7 #include <sys/stat.h>
    8 #include <fcntl.h>
    9
    10 #include <jni.h>
    11 #include <android/log.h>
    12
    13 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__))
    14 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "native-activity", __VA_ARGS__))
    15
    16
    17
    18 /**********传输整数*************
    19
    20 */
    21 JNIEXPORT void JNICALL Java_com_nan_callback_MyCallbackActivity_callJNIInt( JNIEnv* env, jobject obj , jint i)
    22 {
    23 //找到java中的类
    24 jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity");
    25 //再找类中的方法
    26 jmethodID mid = (*env)->GetMethodID(env, cls, "callbackInt", "(I)V");
    27 if (mid == NULL)
    28 {
    29 LOGI("int error");
    30 return;
    31 }
    32 //打印接收到的数据
    33 LOGI("from java int: %d",i);
    34 //回调java中的方法
    35 (*env)->CallVoidMethod(env, obj, mid ,i);
    36
    37 }
    38
    39 /********传输字符串*************
    41 */
    42 JNIEXPORT void JNICALL Java_com_nan_callback_MyCallbackActivity_callJNIString( JNIEnv* env, jobject obj , jstring s)
    43 {
    44 //找到java中的类
    45 jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity");
    46 //再找类中的方法
    47 jmethodID mid = (*env)->GetMethodID(env, cls, "callbackString", "(Ljava/lang/String;)V");
    48 if (mid == NULL)
    49 {
    50 LOGI("string error");
    51 return;
    52 }
    53 const char *ch;
    54 //获取由java传过来的字符串
    55 ch = (*env)->GetStringUTFChars(env, s, NULL);
    56 //打印
    57 LOGI("from java string: %s",ch);
    58 (*env)->ReleaseStringUTFChars(env, s, ch);
    59 //回调java中的方法
    60 (*env)->CallVoidMethod(env, obj, mid ,(*env)->NewStringUTF(env,"你好haha"));
    61
    62 }
    63
    64 /********传输数组(byte[])*************
    65 */
    66 JNIEXPORT void JNICALL Java_com_nan_callback_MyCallbackActivity_callJNIByte( JNIEnv* env, jobject obj , jbyteArray b)
    67 {
    68 //找到java中的类
    69 jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity");
    70 //再找类中的方法
    71 jmethodID mid = (*env)->GetMethodID(env, cls, "callbackByte", "([B)V");
    72 if (mid == NULL)
    73 {
    74 LOGI("byte[] error");
    75 return;
    76 }
    77
    78 //获取数组长度
    79 jsize length = (*env)->GetArrayLength(env,b);
    80 LOGI("length: %d",length);
    81 //获取接收到的数据
    82 int i;
    83 jbyte* p = (*env)->GetByteArrayElements(env,b,NULL);
    84 //打印
    85 for(i=0;i<length;i++)
    86 {
    87 LOGI("%d",p[i]);
    88 }
    89
    90 char c[5];
    91 c[0] = 1;c[1] = 2;c[2] = 3;c[3] = 4;c[4] = 5;
    92 //构造数组
    93 jbyteArray carr = (*env)->NewByteArray(env,length);
    94 (*env)->SetByteArrayRegion(env,carr,0,length,c);
    95 //回调java中的方法
    96 (*env)->CallVoidMethod(env, obj, mid ,carr);
    97 }

    利用ndk-build编译生成相应的库。代码都非常简单,思路在一开始的时候已经说明了,下面看运行结果。

    分别点击三个按钮,效果如下:

    再看看LogCat输出:

    可见两个方向(java<--->JNI)传输的数据都正确。

     

    附上完整工程代码,Android2.3的。

    https://files.cnblogs.com/lknlfy/my-CallEachOther.rar

  • 相关阅读:
    Laravel日志查看器 -- log-viewer扩展
    php对象的实现
    Grpc+MagicOnion的学习和例子(二)
    Grpc+MagicOnion的学习和例子(一)
    Connection open error . Authentication to host xxx' for user 'aa' using method 'mysql_native_password' failed with message: Access denied for user 'aaa' (using password: NO)
    .netcore的配置IConfiguration和IOptions的使用
    C#.NET CORE的AddScoped之容器注入对象和接口实现的区别
    .netcore的微服务学习(四)--网关(gateway)之Ocelot+Consul+polly学习
    .netcore的微服务学习(三)--网关(gateway)之Ocelot+Consul学习
    .netcore学习之startup的IServiceCollection的理解
  • 原文地址:https://www.cnblogs.com/lknlfy/p/2394153.html
Copyright © 2011-2022 走看看