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部分的代码吧
  • 73ba21ec89dc:您好, 请问下. 我是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