Android Studio 2.2 之 NDK开发

作者: 木兮家先生 | 来源:发表于2016-09-24 03:36 被阅读7493次

    前言

    期待了几个月的Android Studio 2.2 版本稳定版昨天终于发布,迫不及待的更新尝试。这次更新内容颇多。我主要关注NDK开发,所以期待这一版本带来的c/c++支持的增强。本文介绍一下这两天折腾新版AS开发和调试NDK的一些经验

    Paste_Image.png

    一、NDK的支持

    Android Studio 2.2 的NDK开发支持 Cmake和ndk-build两种方式。相比与以前的gradle去配置ndk编译目录什么的简直是方便多了。对于老的通过Android.mk文件编译的NDK项目,直接一条配置整个项目就可以被AS支持了。

    1.Cmake方式使用AS开发调试NDK

    新版的Android Studio支持使用Cmake构建c/c++工程。相比与上一版要通过gradle来配置c/c++工程简单了多,而且也便于老项目的迁移了,下面先来一个简单的例子

    1).新建一个空的工程

    默认建一个空的工程,只包含一个MainActivity

    2).创建jni目录和cpp文件

    在左侧Project栏选择app,右键-->New-->Folder-->JNI Folder ,app目录下会多出一个cpp(貌似上一版还是jni目录)的目录。

    Paste_Image.png

    在cpp目录右键New-->C/C++ Source File ,新建两个文件jni_lib.cpp/h

    Paste_Image.png

    3).配置jni工程

    进入文件管理器,在jni目录下创建CmakeLists.txt文件

    # Sets the minimum version of CMake required to build the native
    # library. You should either keep the default value or only pass a
    # value of 3.4.0 or lower.
    
    cmake_minimum_required(VERSION 3.4.1)
    
    # Creates and names a library, sets it as either STATIC
    # or SHARED, and provides the relative paths to its source code.
    # You can define multiple libraries, and CMake builds it for you.
    # Gradle automatically packages shared libraries with your APK.
    
    add_library( # Sets the name of the library.
                 native-lib
                 # 这个是jni编译生产的so库的名字
                 # Sets the library as a shared library.
                 SHARED
    
                 # Provides a relative path to your source file(s).
                 # Associated headers in the same location as their source
                 # file are automatically included.
                 # 要编译的c/c++文件列表 文件路径想对于cmake文件路径
                 ./jni_lib.cpp )
    
    # Searches for a specified prebuilt library and stores the path as a
    # variable. Because system libraries are included in the search path by
    # default, you only need to specify the name of the public NDK library
    # you want to add. CMake verifies that the library exists before
    # completing its build.
    
    find_library( # Sets the name of the path variable.
                  log-lib
                  # 依赖的系统so库
                  # Specifies the name of the NDK library that
                  # you want CMake to locate.
                  log )
    
    # Specifies libraries CMake should link to your target library. You
    # can link multiple libraries, such as libraries you define in the
    # build script, prebuilt third-party libraries, or system libraries.
    
    target_link_libraries( # Specifies the target library.
                           native-lib
    
                           # Links the target library to the log library
                           # included in the NDK.
                           ${log-lib} )
    

    Project栏app右键,Link C++ Project With Gradle 选择CMakeLists.txt文件。或者也可以直接在gradle中加入如下配置即可

    externalNativeBuild{    
          cmake{
            path file("src/main/jni/CMakeLists.txt") 
       }
    }
    

    选择Gralde同步之后就完成了jni工程的创建

    4).写第一个jni函数

    我们可以完成我们的第一个jni函数
    jni_lib.h

    #ifndef DEMOCMAKE_JNI_LIB_H
    #define DEMOCMAKE_JNI_LIB_H
    #include "jni.h"
    jstring GetStrFromJNI(JNIEnv* env,jobject callObj);
    #endif //DEMOCMAKE_JNI_LIB_H
    

    jni_lib.cpp

    #include "jni_lib.h"
    jstring GetStrFromJNI(JNIEnv *env, jobject callObj)
     {    
          return env->NewStringUTF("String From Jni With c++");
    }
    

    光有这些是不够的,java虚拟机是无法直接找到GetStrFromJNI这个函数的,需要通过调用JNI_OnLoad函数,实现JNI函数和java native声明的对接。关于jni的知识可以多Google一下学习。

    so库的加载和native函数的声明
    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        static {
            /*
            加载动态库,动态库加载的时候 JNI_OnLoad函数会被调用
            
            在JNI——OnLoad函数中,Java虚拟机通过函数表的形式将JNI函数和java类中native函数对应起来
             */
            System.loadLibrary("native-lib");
        }
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView textView= (TextView) findViewById(R.id.text);
            textView.setText(GetStrFromJNI());
        }
        
        /*
        Jni 函数的声明
        当调用到此函数时,java虚拟机会通过JNI_OnLoad里注册的函数表找到对应的函数去执行
         */
        private native String GetStrFromJNI();
    }
    

    JNI_OnLoad的实现
    jni_lib.cpp

    //
    // Created by kang on 9/23/16.
    //
    
    #include "jni_lib.h"
    
    #define JNI_AN_MainActivity     "com/kang/demondk/MainActivity"
    
    #define METHOD_NUM 1
    JNINativeMethod g_nativeMethod[METHOD_NUM]={
            {"GetStrFromJNI","()Ljava/lang/String;",(void*)GetStrFromJNI}
    };
    
    /*
     * 被虚拟机自动调用
     */
    jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        JNIEnv *env;
        if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
            return JNI_ERR;
    
        jclass jClass = env->FindClass(JNI_AN_MainActivity);
        env->RegisterNatives(jClass,g_nativeMethod,METHOD_NUM);
        env->DeleteLocalRef(jClass);
        return JNI_VERSION_1_6;
    }
    
    void JNI_OnUnload(JavaVM* vm, void* reserved) {
        JNIEnv *env;
        int nJNIVersionOK = vm->GetEnv((void **)&env, JNI_VERSION_1_6) ;
        jclass jClass = env->FindClass(JNI_AN_MainActivity);
        env->UnregisterNatives(jClass);
        env->DeleteLocalRef(jClass);
    }
    
    
    
    jstring GetStrFromJNI(JNIEnv *env, jobject callObj) {
        return env->NewStringUTF("String From Jni With c++");
    }
    
    
    

    2.ndk-build方式使用AS开发调试NDK

    ndk-build方式与cmake方式类似,只需要将cmake文件改写为Android.mk和Appliction.mk文件。在CMakeLists.txt加载的位置将CMakeLists.txt替换为Android.mk即可

    Android.mk

    
    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    APP_ABI := all
    
    LOCAL_MODULE    := native-lib
    LOCAL_CPPFLAGS  := -O0 -D_UNICODE -DUNICODE -DUSE_DUMP -Wno-error=format-security
    LOCAL_CPP_EXTENSION := .cpp
    LOCAL_LDLIBS    := -lm -llog -lz
    LOCAL_SHORT_COMMANDS := true
    INC_DIRS = -I$(LOCAL_PATH)/jni
    LOCAL_CPPFLAGS += $(INC_DIRS)
    
    LOCAL_SRC_FILES := \
        jni_lib.cpp \   \
    
    
    LOCAL_SHARED_LIBRARIES += libandroid_runtime
        
    
    include $(BUILD_SHARED_LIBRARY)
    
    

    Appliction.mk

    APP_ABI := all
    NDK_TOOLCHAIN_VERSION := clang
    APP_SHORT_COMMANDS      := true
    APP_STL := stlport_static
    APP_CPPFLAGS := -std=gnu++11 -D__STDC_LIMIT_MACROS
    
    

    在gradle的配置中cmake的配置替换为

    //    externalNativeBuild{
    //        cmake{
    //            path file("src/main/jni/CMakeLists.txt")
    //        }
    //    }
        externalNativeBuild{
            ndkBuild{
                path file("src/main/jni/Android.mk")
            }
        }
    

    4.一些简单的配置

    产见

    二、体验改进和存在的问题

    1.编译

    配置好CmakeLists.txt或者Android.mk之后,编译的时候,Android Stuido 会自动进行NDK的编译。我测试了mac和ubuntu都没有问题,在windows下似乎有些问题。发现,当Android.mk项目中有依赖的静态库的时候,编译处理有些问题,无法通过编译。windows党自行测试。

    2.调试

    我升级新版Androd Studio的直接原因就是,终于可以方便的调试底层代码了。但是经过两天的使用还是发现有些问题的。

    • 调试带有NDK项目的工程的时候,Android Studio会同时启动两个调试器,一个针对NDK的lldb调试器,和Java调试器。
    • 默认NDK的调试是开的,因为打开NDK调试,启动调试的时候还是挺慢的,不需要的时候,可以将工程设置里的Debuger从Auto 改为Java
    • 另为需要注意的问题是,如果NDK代码为了项目的Model中,必须在如下位置Symbol Directories,增加NDK所在项目的根路径,否则LLDB调试器会报找不到符号文件错误,是无法进行调试的
    Paste_Image.png
    • 在Ubuntu测试的时候还发现一个见鬼的问题,有时候配置好一个项目,程序死活不会停在c/c++的断点出。刚开始感觉很莫名其妙,配置两个一模一样的工程,一个可以调试NDK,一个不可以。最后经过反复测试,才发现一个问题:当NDK代码位于Model中的时候,这个项目的APP的目录深度一定不能大于NDK所在Model的路径深度!

    后话

    Android Studio 2.2的发布,尽管还有许多问题,在这两天的使用中还是发现有很多不稳定的地方。在编辑和调试大的项目时还是经常容易出现异常。但是毕竟对于Androd Studio 进行NDK开发来说还是很大的进步。本人才疏学浅,近一年一直学习和使用NDK,发现网上资料甚少,写次博文全当交流学习。欢迎有共同爱好和需求的朋友交流讨论,共同学习

    本文Sample代码

    相关文章

      网友评论

      • xunmengyoufeng: 楼主有么有碰到过
        The currently selected Native debugger doesn't support breakpoints of type 'Java Line Breakpoints'. As a result, these breakpoints will not be hit.
        The debugger selection can be modified in the run configuration dialog.
        新建工程没问题,老工程调不了,不晓得啥原因。
        木兮家先生: @xunmengyoufeng android.mk里声明的ndk版本太旧了吧
      • shixinBook:楼主你好导入你的工程出现在这个错误Error while executing 'D:\Android\android-sdk-windows\ndk-bundle\ndk-build.cmd' with arguments {NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=D:\QQ\fileUnzip\DemoJNI-master\app\src\main\jni\Android.mk NDK_APPLICATION_MK=D:\QQ\fileUnzip\DemoJNI-master\app\src\main\jni\Application.mk APP_ABI=armeabi NDK_ALL_ABIS=armeabi NDEBUG=1 APP_PLATFORM=android-21 NDK_OUT=D:\QQ\fileUnzip\DemoJNI-master\app\.externalNativeBuild\ndkBuild\debug\obj NDK_LIBS_OUT=D:\QQ\fileUnzip\DemoJNI-master\app\build\intermediates\ndkBuild\debug\lib APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -n},是不是版本的问题。
        木兮家先生: @shixinBook 新版ndk有不少改变。这个cmake文件是基于ndk12版本写的。需要修改些东西。我抽空看看
        be28d17f43b7:CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
        Please set them or make sure they are set and tested correctly in the CMake files:
        log-lib
        linked by target "native-lib" in directory /Users/dongye/mogujie_android/TestIn/app

        -- Configuring incomplete, errors occurred!
        See also "/Users/dongye/mogujie_android/TestIn/app/CMakeFiles/CMakeOutput.log".


        请问一下这是什么问题呢?
        木兮家先生: @shixinBook 你先尝试ndk-build命令是否可以编译成功
      • 25d01aa717a1:楼主 我们项目 socket是用c++写的 我怎么把c++代码导入到工程里
        25d01aa717a1:@木兮家先生 😂 是把所有用到的都写jni
        木兮家先生: @我自己的生活 得先完成jni部分的代码吧
      • xiaohuh421:您好, 请问下. 我是AS2.2.3, 提示jni.h找不到, 但是编译运行都正常, 是哪里没配置对吗?
        不是说2.2已经完美支持NDK了吗
      • Y_KANG:楼主,请教一个问题,如何----引用----其他编译好的so文件 或者 c/c++库
        木兮家先生: @Y_KANG so必须是包含jni代码的,且用ndk编译的。一般第三方提供SDK的都会包含so和jar。这两者是一一对应的。so放在armabi目录,jar放在libs目录即可
      • studentliubo:为什么Cmake方式要创建.cpp/.h文件?博主,有推荐的相关学习的文档吗?
        木兮家先生: @Neo12306 百度一下,有很多
      • jabinwong:官网讲到可以继续关联mk文件,但是还是不能断点调试
        木兮家先生: @jabinwong 要设置符号目录,调试器选择自动
      • 2d78bbb21282:博主,想问一下哈,这句话是什么意思?

        当NDK代码位于Model中的时候,这个项目的APP的目录深度一定不能大于NDK所在Model的路径深度!


        ps。2.2出来后,打算放弃adt了,没想到断点有时候断到 有时候又不行。。。。
        木兮家先生: @隔夜酸菜鱼 不一定,在别的目录也可以,注意目录深度就行
        2d78bbb21282:@木兮家先生 一定要把model放在/root/svn/app下?像这样 /root/svn/app/model?
        木兮家先生: @隔夜酸菜鱼 例如app根目录在/root/svn/app下,包含ndk的model根目录在/root/model下。这是个无法启动调试。这应该是个bug
      • 0c28ba0c531d:正在做一个驱动程序的项目,前些日子把android studio更新到了最新的2.2版本,项目中用到了<android/bitmap.h>,按照以前的方法在android.mk中添加LOCAL_LDLIBS += -ljnigraphics即可,可是现在使用cmakelist,里面有个target_link_libraries,但是我在里面添加这个库还是解决不了问题,应该是添加的方法不对,求助!!!感激不尽
        木兮家先生:@MPhone 你的添加方法应该是没错的,为什么不生效我就可不清楚了,我平常用Android.mk比较多。可以参考这个试试http://tools.android.com/tech-docs/external-c-builds
        0c28ba0c531d: @木兮家先生 我的问题是怎么添加那个库
        木兮家先生: @MPhone 先确定是否有语法错误。直接在命令行窗口执行cmake编译看是否有效
      • 1ef625b0f437:在2.2里面,CMake 如何设置单独编译出.so?
        还有就是设置编译之后,第二次编译android工程时,不理会cmake源码,只读取.so 构建 新apk?在win平台
        木兮家先生:@hbally 编译完成后会在ndk项目所在目录生成一个linux的隐藏文件夹,so就在里面。只要不改动as识别到的cpp代码就不需要重新编译,改动了也会增量编译
      • 木兮姑娘:好棒呀
      • 爱孔孟:嘿嘿

      本文标题:Android Studio 2.2 之 NDK开发

      本文链接:https://www.haomeiwen.com/subject/laraettx.html