Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

2024-04-13 1051阅读

文章目录

  • 背景
  • 新建工程
  • 拷贝/编写C/C++代码
  • 编写CMake配置文件
  • 写Java代码加载动态/静态库
  • java转换c++,c++转java
  • native层打印日志
  • Android去调用Java层的native方法
  • 对外提供.so/.a库 + jar包
  • 检查APK里面是否已经被正常包含.so/.a
  • 完成

    背景

    突然想起做了这么久的JNI开发,却没有分享过相关内容。

    今天就公司刚做完不久的一个项目来剖析,作为JNI的一个入门小实践。内容会做一些抽象,隐藏掉细节的业务部分。希望对刚学习JNI和要边学边开发的同学有所帮助。

    新建工程

    第一步就是新建一个C/C++工程:

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    一开始会有一个cpp目录,里面默认有CMakeLists.txt和一个.cpp文件:

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    拷贝/编写C/C++代码

    通常来说C代码是由别的开发小组提供,我们负责写JNI代码,让Java层去调用C/C++的代码。

    我们要做的是把C代码copy到cpp这个目录里面。

    编写CMake配置文件

    然后根据源代码去做一些CMake的配置,我这个项目是使用一个CMake文件去配置所有代码。想要更加灵活布局的话,也可以每个目录写一个Cmake文件,然后合并到根目录的Cmake中。

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    C代码的目录:

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    这个时候去编译,看能不能通过。

    不能通过的话,说明CMake写的有问题,可以参考:Cmake专栏

    写Java代码加载动态/静态库

    上面搞定之后,可以正式开始写代码了。

    写一个Java类用作加载原生库,可以是.so 或者.a。不了解的动态库和静态库的同学可以参考:Linux专栏

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    当没有实现C++方法的时候,方法名是报红的。点击:ALT+Enter可以自动生成对应的JNI方法,点击坐标的C++标记即可跳过去:

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    它会根据java的包名自动生成JNI方法名,方法名是由Java+包名+类名+方法名组合而成的。

    需要注意的是:假如java改了包名或者类名,这个JNI方法也需要修改,不然无法映射链接过来。

    java转换c++,c++转java

    下面是示例代码,供参考:

    C代码:

    int ProcessInject(const char *ipeks,  unsigned char *ipek_mkey, char *fkey_fpath, unsigned char *fk_mkey, char *datetime);
    

    JNI代码:

    extern "C"
    JNIEXPORT jint JNICALL
    Java_com_xxx_nativelib_NativeLib_processInject(JNIEnv *env, jobject thiz, jbyteArray ipeks,
    jbyteArray ipek_mkey, jbyteArray fkey_fpath,
    jbyteArray fk_mkey, jbyteArray date_time) {
        jbyte *ipeks_c = env->GetByteArrayElements(ipeks, nullptr);
        jbyte *ipek_mkey_c = env->GetByteArrayElements(ipek_mkey, nullptr);
        jbyte *fkey_fpath_c = env->GetByteArrayElements(fkey_fpath, nullptr);
        jbyte *fk_mkey_c = env->GetByteArrayElements(fk_mkey, nullptr);
        jbyte *datetime_c = env->GetByteArrayElements(date_time, nullptr);
        int result = ProcessInject((const char *) ipeks_c, (unsigned char *) ipek_mkey_c,
                               (char *) fkey_fpath_c, (unsigned char *) fk_mkey_c, (char *) datetime_c);
        // Release resources
        env->ReleaseByteArrayElements(ipeks, ipeks_c, 0);
        env->ReleaseByteArrayElements(ipek_mkey, ipek_mkey_c, 0);
        env->ReleaseByteArrayElements(fkey_fpath, fkey_fpath_c, 0);
        env->ReleaseByteArrayElements(fk_mkey, fk_mkey_c, 0);
        env->ReleaseByteArrayElements(date_time, datetime_c, 0);
    // 返回结果给 Java
        return result;
    }
    

    int类型的无需转换,直接使用即可。

    Java的byte类型需要先转为jbyte* 类型,传入C/C++代码的时候根据实际情况强制。其它类型的话,

    可以点jni.h头文件进去查看:

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    或者直接查看JNI关机文档,需要一定英文水平。

     jbyte *ipeks_c = env->GetByteArrayElements(ipeks, nullptr);
     //使用ipeks_c ,dosomething 
     ...
      // Release resources
        env->ReleaseByteArrayElements(ipeks, ipeks_c, 0);
    

    传入并使用对象类型的时候,记得用完立刻释放,不然有时候需要循环的业务逻辑会消耗掉JNI可以开辟的空间,听说最多是五百个对象。没有实际考察和测试,供参考。

    至于JNI使用的栈内存的原理我在公司部分已经做过一期技术分享。后续有时间会做相应的分享到网上,可以先关注一波这个账号。

    关于ReleaseByteArrayElements的最后一个参数,绝大部分场景使用0就OK,意思是他会把C/C++源码对这个变量所做的修改刷新回去给Java层。

    还有一个点要注意,就是Java层的byte是有符号的,可以是负数,C/C++层的unsigned char是无符号的。写业务的时候需要注意这个点,要了解清楚。

    native层打印日志

    打印日志可以直接copy这个,Google那边薅过来的:

    /*
     * Copyright (C) 2015 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     */
    #ifndef NATIVE_AUDIO_ANDROID_DEBUG_H_H
    #define NATIVE_AUDIO_ANDROID_DEBUG_H_H
    #include 
    #if 1
    #define MODULE_NAME "xxxxx"
    #define LOGV(...) \
      __android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
    #define LOGD(...) \
      __android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
    #define LOGI(...) \
      __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
    #define LOGW(...) \
      __android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__)
    #define LOGE(...) \
      __android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__)
    #define LOGF(...) \
      __android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__)
    #else
    #define LOGV(...)
    #define LOGD(...)
    #define LOGI(...)
    #define LOGW(...)
    #define LOGE(...)
    #define LOGF(...)
    #endif
    #endif  // NATIVE_AUDIO_ANDROID_DEBUG_H_H
    

    CMake文件里面记得确认已经引入log库。Google爸爸已经在NDK包里面提供,直接引入即可。

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    日志的使用示例:Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    Android去调用Java层的native方法

    上面这些搞定之后,直接在MainActivity里面new出前面创建的对象:Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    调用native函数即可。

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    对外提供.so/.a库 + jar包

    假如我们在CMake里面写了SHARED的话,编译之后,生成的是动态库,.so文件。

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    我们通过JNI代码调用的C/C++源码会被打包成一个.so库。也就是说,我们JNI写完之后提供给别人的就是这个.so库,再加上Java文件,就是这个:

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    因为JNI需要类名、包名完全一致,为了避免用户改了它们,导致调用不到JNI代码。我们把所有相关的Java代码打包为一个jar文件包。

    不叫了解怎么通过Android studio打包jar文件包的可以参考:jar生成

    那么.so在哪里呢?

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    检查APK里面是否已经被正常包含.so/.a

    再看看APK里面是否已经把动态库文件打包进去了

    Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

    完成

    好了,这就是写一个JNI工程需要掌握的基本技能。

    希望能帮助到大家。

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]