Java(JNI)Android使用JNI开发

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35427437/article/details/82323038

交叉编译

  • 在一个平台上去编译另一个平台上可以执行的本地代码

  • cpu平台 arm x86 mips

  • 操作系统平台 windows linux mac os

  • 原理 模拟不同平台的特性去编译代码

  • jni开发工具

  • ndk native develop kit

  • ndk目录

    • docs 帮助文档

    • platforms 好多平台版本文件夹 选择时选择项目支持的最小版本号对应的文件夹

    • 每一个版本号的文件夹中放了 不同cpu架构的资源文件

    • include文件夹 jni开发中常用的 .h头文件

    • lib 文件夹 google打包好的 提供给开发者使用的 .so文件

    • samples google官方提供的样例工程 可以参考进行开发

    • android-ndk-r9d\build\tools linux系统下的批处理文件 在交叉编译时会自动调用

    • ndk-build 交叉编译的命令

  • cdt eclipse的插件 高亮C代码 C的代码提示

步骤:

  • jni开发的步骤

  • ①写java代码 声明本地方法 用到native关键字 本地方法不用去实现

       

  • ②项目根目录下创建jni文件夹

     

  • ③在jni文件夹下创建.c文件

    • 本地函数命名规则: Java包名类名_本地方法名

    • 第二个参数jobject 调用本地函数的java对象就是这个jobject

    • (*env)->调用结构体中的函数指针

    • env 就是结构体JniNativeInterface这个结构体的二级指针

    • JniNativeInterface这个结构体定义了大量的函数指针

    • JNIENV* env JNIEnv 是JniNativeInterface这个结构体的一级指针

  • ④ 导入<jni.h>

  • ⑤ 创建Android.mk makefile 告诉编译器.c的源文件在什么地方,要生成的编译对象的名字是什么 LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE := hello #指定了生成的动态链接库的名字 LOCAL_SRC_FILES := hello.c #指定了C的源文件叫什么名字

    include $(BUILD_SHARED_LIBRARY)

  • ⑥ 调用ndk-build编译c代码生成动态链接库.so文件 文件的位置 lib->armeabi->.so

  • ⑦ 在java代码中加载动态链接库 System.loadlibrary("动态链接库的名字"); Android.mkLOCAL_MODULE所指定的名字

jni开发中的常见错误

  • java.lang.UnsatisfiedLinkError: Native method not found: 本地方法没有找到

    • 本地函数名写错

    • 忘记加载.so文件 没有调用System.loadlibrary

  • findLibrary returned null

    • System.loadLibrary("libhello"); 加载动态链接库时 动态链接库名字写错

    • 平台类型错误 把只支持arm平台的.so文件部署到了 x86cpu的设备上

      • 在jni目录下创建 Application.mk 在里面指定

      • APP_ABI := armeabiAPP_PLATFORM := android-14

  • javah

    • jdk 1.7 项目 src目录下运行javah
    • jdk 1.6 项目 bin目录下 classes文件夹

    • javah native方法声明的java类的全类名

快速实现C语言方法名步骤:

    第一步:

  第二步:

第三步 :

jni简便开发流程

  • ① 写java代码 native 声明本地方法

  • ② 添加本地支持 右键单击项目->andorid tools->add native surport

    • 如果发现 finish不能点击需要给工作空间配置ndk目录的位置

    • window->preferences->左侧选择android->ndk 把ndk解压的目录指定进来

  • ③ 如果写的是.c的文件 先修改一下生成的.cpp文件的扩展名 不要忘了 相应修改Android.mk文件中LOCAL_SRC_FILES的值

  • ④ javah生成头文件 在生成的头文件中拷贝c的函数名到.c的文件

  • ⑤ 解决CDT插件报错的问题

  • 右键单击项目选择 properties 选测 c/c++ general->paths and symbols->include选项卡下->点击add..->file system 选择ndk目录下 platforms文件夹 对应平台下(项目支持的最小版本)usr 目录下 arch-arm -> include 确定后 会解决代码提示和报错的问题

  • ⑥编写C函数 如果需要单独编译一下c代码就在c/c++视图中找到小锤子

  • 如果想直接运行到模拟器上 就不用锤子了

  • ⑦ java代码中不要忘了 system.loadlibrary();

C代码中向logcat输出内容

Android.mk文件增加以下内容
LOCAL_LDLIBS += -llog
C代码中增加以下内容
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  • define C的宏定义 起别名 #define LOG_TAG "System.out" 给"System.out"起别名LOG_TAG

  • define LOGI(...) android_log_print(ANDROID_LOG_INFO, LOG_TAG, VA_ARGS__)

  • 给 __android_log_print函数起别名 写死了前两个参数 第一个参数 优先级 第二个参数TAG

  • VA_ARGS 可变参数的固定写法

  • LOGI(...)在调用的时候 用法跟printf()一样

C代码回调java方法

  • ① 找到字节码对象

    • //jclass (FindClass)(JNIEnv, const char*);

    • //第二个参数 要回调的java方法所在的类的路径 "com/itheima/callbackjava/JNI"

  • ② 通过字节码对象找到方法对象

    • //jmethodID (GetMethodID)(JNIEnv, jclass, const char, const char);

    • 第二个参数 字节码对象 第三个参数 要反射调用的java方法名 第四个参数 要反射调用的java方法签名

    • javap -s 要获取方法签名的类的全类名 项目/bin/classes 运行javap

  • ③ 通过字节码创建 java对象(可选) 如果本地方法和要回调的java方法在同一个类里可以直接用 jni传过来的java对象 调用创建的Method

    • jobject obj =(*env)->AllocObject(env,claz);

    • 当回调的方法跟本地方法不在一个类里 需要通过刚创建的字节码对象手动创建一个java对象

    • 再通过这个对象来回调java的方法

    • 需要注意的是 如果创建的是一个activity对象 回调的方法还包含上下文 这个方法行不通!!!回报空指针异常

  • ④ 反射调用java方法

    • //void (CallVoidMethod)(JNIEnv, jobject, jmethodID, ...);

    • 第二个参数 调用java方法的对象 第三个参数 要调用的jmethodID对象 可选的参数 调用方法时接收的参数

c++ 开发JNI

  • C的预处理命令

  • 开头的就是c/c++的预处理命令

  • 在编译之前 先会走预编译阶段 预编译阶段的作用就是 把 include进来的头文件 copy到源文件中

  • define这些宏定义 用真实的值替换一下

  • if #else #endif 该删除的删除掉

  • c++开发jni代码时 env不再是结构体Jninativeinterface的二级指针

  • _JNIEnv JNIEnv _JNIEnv 是C++的结构体 C++结构体跟C区别 C++的结构体可以定义函数

  • env 是JNIEnv的一级指针 也就是结构体_JNIEnv的一级指针 env-> 来调用 结构体里的函数

  • _JNIEnv的函数 实际上调用的就是结构体JNINativeInterface的同名函数指针

  • 在调用时第一个参数 env已经传进去了

  • C++的函数要先声明再使用 可以把javah生成的头文件include进来作为函数的声明

  • include的方法 <> "" ""

  • 如果用"" 来导入头文件 系统会先到 源代码所在的文件夹去找头文件 如果找不到再到系统指定的incude文件夹下找

  • //用<> 直接到系统指定的include目录下去找

am 命令

  • am命令 :在adb shell里可以通过am命令进行一些操作 如启动activity Service 启动浏览器等等

  • am命令的源码在Am.java中, 在adb shell里执行am命令实际上就是启动一个线程执Am.java的main方法,am命令后面带的参数都会当作运行时的参数传递到main函数中

  • am命令可以用start子命令,并且带指定的参数

  • 常见参数: -a: action -d data -t 表示传入的类型 -n 指定的组件名字

  • 举例: 在adb shell中通过am命令打开网页

  • am start --user 0 -a android.intent.action.VIEW -d http://www.baidu.com

  • 通过am命令打开activity

  • am start --user 0 -n com.itheima.fork/com.itheima.fork.MainActivity

  • (系统sdk版本>16 需要加上--user 0 , <16不需要加)

  • execlp c语言中执行系统命令的函数

  • execlp() 会从PATH环境变量所指的目录中查找符合参数file的文件找到后就执行该文件, 第二个参数开始就是执行这个文件的 args[0],args[1] 最后一个参数用(char*)NULL结束

  • android开发中 execlp函数对应android的path路径为system/bin/目录

  • 调用格式

    execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);

    execlp("am", "am", "start", "--user","0", "-n" , "com.itheima.cforktest/com.itheima.cforktest.MainActivity",(char *) NULL);

猜你喜欢

转载自blog.csdn.net/qq_35427437/article/details/82323038
今日推荐