JNI & NDK

       JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。

     NDK是一系列工具的集合,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

       JNI(Java Native Interface)是Java调用Native机制,是Java语言自己的特性。类似的还有微软.Net Framework上的p/invoke,可以让C#或Visual Basic.Net可以调用C/C++的API,所以说JNI和Android没有关系,在PC上开发Java的应用,如果运行在Windows平台使用JNI是是经常的,比如说读写Windows的注册表。

       而NDK(Native SDK)是Google公司推出的帮助Android开发者通过C/C++本地语言编写应用的开发包,包含了C/C++的头文件、库文件、说明文档和示例代码,我们可以理解为Windows Platform SDK一样,是纯C/C++编写的,但是Android并不支持纯C/C++编写的应用,同时NDK提供的库和函数功能很有限,仅仅处理些算法效率敏感的问题

       简单点说,用C语言生成一个库文件,在java中通过JNI机制调用这个库文件的函数。而JNI的过程比较复杂,生成.so需要大量操作,而NDK就是简化了这个过程。


关于交叉编译

编译器将中间代码连接成当前计算机可执行的二进制程序时,连接程序会根据当前计算机的CPU、操作系统的类型来转换。
根据运行的设备的不同,可以将cpu分为:
|-  arm结构:主要在移动手持、嵌入式设备上。
|-  x86结构:主要在台式机、笔记本上使用。如Intel和AMD的CPU 。
若想在使用了基于x86结构CPU的操作系统中编译出可以在基于arm结构CPU的操作系统上运行的代码,就必须使用交叉编译。
交叉编译:在一个平台下编译出在另一个平台中可以执行的二进制代码。Google提供的NDK就可以完成交叉编译的工作。

安卓体系中,很多涉及到底层硬件的方法,都是native的,只有声明,没有方法体,虚拟机加载到native时,会用JNI的机制来调用这个方法,比如音视频播放,看源码
public  void start() throws IllegalStateException {
        stayAwake(true);
        _start();
    }

    private native void _start() throws IllegalStateException;

    /**
     * Stops playback after playback has been stopped or paused.
     *
     * @throws IllegalStateException if the internal player engine has not been
     * initialized.
     */
    public void stop() throws IllegalStateException {
        stayAwake(false);
        _stop();
    }

    private native void _stop() throws IllegalStateException;

安卓体系的四层,上面的应用层和框架层都是用Java写的,第三层的核心库是Java写的,别的很多库是用C\C++写的,底层的驱动什么也是C\C++
故而JNI机制在安卓里还是很普遍的

另外,在实际应用中,可以把一些核心代码编成C\C++,弄成本地的机器码,这样就能达到保密的目的,提高安全性,应对反编译

C\C++的程序
在桌面端,CPU基本都是x86构架,生成的库文件 一般是 .libs .dll格式,而在移动端,现在主要是ARM,生成的是 .so文件

与SDK相似,NDK要导入eclipse关联之
解压后,配置环境变量即可,当然,如果你精通C\C++,那么可以再安装一个CDT,自己写C库自己调用

NDK的目录



•build/tools:linux的批处理文件
•docs:帮助文档
•platforms:编译c代码需要使用的头文件和类库
•prebuilt:预编译使用的二进制可执行文件
•sample:jni的使用例子
•source:ndk的源码
•toolchains:工具链
•ndk-build.cmd:编译打包c代码的一个指令


大概流程

1.创建一个android工程
2.JAVA代码中写声明native 方法 public native String helloFromJNI();
3. 创建jni目录,编写c代码,方法名字要对应
4.编写Android.mk文件
5.Ndk编译生成动态库
6.Java代码load 动态库.调用native代码


DEMO

1.先要建一个工程,在工程里建一个folder,里面加一个 .c的file

2.在布局里加一个button

<Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点击调用本地C方法" 
        android:onClick="click"/>

3.mainactivity里响应,同时我们在这里要调用native的C函数,这个函数返回一个字符串,用toast弹出这个字符串

  注意,Java里只是调用这个函数,函数的具体实现是在 .c里

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);	
	}
	//toast返回的字符串
	public void click(View v){
		Toast.makeText(this, getStringFromC(), Toast.LENGTH_LONG).show();
	}
	//使用本地C语言写一个函数,负责返回一个字符串
	private native String getStringFromC();
}
4.完成这个函数

 4.1关于函数名,应该是   Java+包名+类名+函数名; 点都要换成下划线

 4.2 关于返回值,这里要去头文件里看C和JAVA的对应列表

/*
 * Reference types, in C.
 */
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;

#include<jni.h>

jstring Java_com_example_myjnidemo_MainActivity_getStringFromC(JNIEnv* env,jobject thiz){
     
      char* cstr = "helloword";
      return cstr;
}

注意参数是必须的,JNI规范里的规定,都是在jni.h里定义

后者是一个指向任一类型的指针,就是指的MainActivity的引用

前者是一个结构体的指针,那么参数里传的是一个指针的指针,是一个二级指针

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;

 4.3一个*找到指针,二级*找到结构体,找到结构体后,直接点,就能调用其中的函数,不过需要上面的两个参数

这里要返回string值,调用JNI里的方法,NewStringUTF,第二个参数应该是指向需要被转换成string的字符串数组的指针,并且可以加一个const,常量化,防止指针被修改


#include<jni.h>

jstring Java_com_example_myjnidemo_MainActivity_getStringFromC(JNIEnv* env,jobject thiz){
     
      char* cstr = "helloword";
      //调用JNI里的方法,将字符数组转换成string
      jstring jstr = (*(*env)).NewStringUTF(env,cstr);
      return jstr;
}

在Java中,如果是无参,这里只需要传那两个参数即可;如果是有参,那么这里还要在后面再传参数


 4.4 Android.mk 文件,放到c原文件同文件夹下,里面要表明源文件和目标文件,用来编译

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

#该模块编译后需要生成的lib库的名称
LOCAL_MODULE  :=helloworld
#编译该模块需要用到的src源文件
LOCAL_SRC_FILES :=helloworld.c

include $(BUILD_SHARED_LIBRARY)


  4.5 编译

交叉编译,变异成 .so库,需要使用 ndk-build.cmd来编译

右击C程序源文件,properties,找到文件的文件夹路径



命令行

进入D盘

进入文件夹

cmd指令



lib库里生成 .so



4.6 java里加载类库,否则提示找不到本地方法

public class MainActivity extends Activity {
	//传进来的要和mk里的module名相同
	static{
		System.loadLibrary("helloworld");
	}
	@Override

放在static里,随着类的加载自动调用

传进来的要和module里的定义相同


OK,可以跑起来了

另外,如果此时在C文件里做了修改,要重新利用命令行编译一下~

如果实在Linux下,直接生成了 .so ,拿来就可以用,无需自己编译


还有,可以让eclipse编译,而无需自己用命令行手动编译

这里需要在preference下的安卓选项台里去设置路径

有可能出现这里没有NDK选项的情况,需要更新一下SDK,版本问题,详情请看这篇文章   解决缺少NDK选项问题,点击以打开


右击工程名,点击add native support



他会自动生成jni文件夹和里面的 cpp文件和mk文件,如果是想用C的话,改下文件名和mk里的源文件名


这时候还要添加c\c++的path and symbols

   右击工程名,preference,c\c++ general,path and symbols,add,file system -NDK里的platforms下的对应includes


OK,可以写C了,写完函数之后就可以直接跑了,无需手动编译


  

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值