Android Studio生成so文件的几种方式

demo链接在文末。

在Android Studio中有3种方法生成so文件。

最初的时候,我曾经使用过Visual Studio生成so文件。经历了从入门到放弃的过程。。。。

——————文中的方法二 Android Studio 3.1已经不再支持,build的时候会报错(写这篇文章的时候版本是Android Studio 2,错误信息如下)。————

Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio. Please switch to a supported build system.

Consider using CMake or ndk-build integration. For more information, go to:

https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile

To get started, you can use the sample ndk-build script the Android

plugin generated for you at:

D:\AndroidStudio\others\JNI2\app\build\intermediates\ndk\debug\Android.mk

Alternatively, you can use the experimental plugin:

https://developer.android.com/r/tools/experimental-plugin.html

To continue using the deprecated NDK compile for another 60 days, set

android.deprecatedNdkCompileLease=1526536223345 in gradle.properties

1.通过mk文件和gradle。

//这种是带mk文件的方法

让Gradle构建支持NDK

http://www.iloveandroid.net/2015/09/18/GradleNdkSupport/

首先,什么是mk文件?gradle是什么?

gradle相关资料:

gradle的wiki解释:https://zh.wikipedia.org/wiki/Gradle

注意第一句:Gradle是一个基于Apache AntApache Maven概念的项目自动化建构工具。

那么什么是自动化构建?

组建自动化(英语:Build automation,又称构建自动化自动化构建)指自动创建软件组建的一组进程,包括将计算机源代码编译二进制码、将二进制码包装软件包以及运行自动化测试

组建自动化原先是通过创建makefile来完成的,如今则主要使用两大类工具完成组建[1]

组建自动化工具(如Make、Rake、Cake、MS build、AntGradle等)。

MakeFile相关资料:

Makefile简易教程:http://www.jianshu.com/p/ff0e0e26c47a

http://read.pudn.com/downloads154/ebook/681020/%E4%B8%AD%E6%96%87Makefile%E6%95%99%E7%A8%8B.pdf

http://chuzhiyan.com/2017/03/11/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84Makefile%E6%95%99%E7%A8%8B/

可以简单看下。总而言之,一开始是使用makefile,后面就演变成使用gradle。

什么是mk文件?

首先,官方文档链接如下:

https://developer.android.com/ndk/guides/android_mk.html?hl=zh-cn

但是我没有看懂。然后找到了这篇:

浅析Android下的Android.mk文件

http://blog.csdn.net/flydream0/article/details/7164501

开头是这样写的:大家都知道在Linux下编辑经常要写一个Makefile文件, 可以把这个Makefile文件理解成一个编译配置文件,它保存着如何编译的配置信息,即指导编译器如何来编译程序,并决定编译的结果是什么。而在Android下的Android.mk文件也是类型的功能,顾名思义,从名字上就可以猜测得到,Android.mk文件是针对Android的Makefile文件.

因此可以看出android下的mk文件就是用于组件自动化的文件。

编写Android.mk文件需要按照google官方文档中规范去编写。

这里通过mk文件和gradle协同工作去完成组件自动化的工作。

Gradle Android Plugin 中文手册

https://chaosleong.gitbooks.io/gradle-for-android/content/basic_project_setup/simple_build_files.html

//-------------------这个非常详细地介绍了gradle----------------------------------

Gradle 完整指南(Android)

http://www.jianshu.com/p/9df3c3b6067a

//-------------------------------具体demo待补......-----------------

注意,在此之前,需要配置NDK环境。这里不作介绍。

所需增加文件如下:

Application.mk

Android.mk



具体步骤截图:

新建一个工程,不用勾选include C++ support。


① 首先配置gradle。文字版在下面。


gradle中配置如下:

apply plugin: 'com.android.application'

android {

    compileSdkVersion 26

    buildToolsVersion "26.0.1"

    defaultConfig {

        applicationId "com.demo.jnidemo"

          minSdkVersion 19

          targetSdkVersion 26

          versionCode 1

          versionName "1.0"

          testInstrumentationRunner   "android.support.test.runner.AndroidJUnitRunner"

     }

     buildTypes {

          release {

               minifyEnabled false

               proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'

          }

     }

     sourceSets.main {

          jni.srcDirs = []

          jniLibs.srcDirs = ['src/main/jniLibs']//设置目标的so存放路径

     }

     task ndkBuild(type:Exec,description:'Compile JNI source via NDK'){

          commandLine "C:\\Users\\lei\\AppData\\Local\\Android\\Sdk\\ndk-bundle\\ndk-build.cmd",

            //配置ndk的路径

            'NDK_PROJECT_PATH=build/intermediates/ndk',

            //ndk默认的生成so的文件

            'NDK_LIBS_OUT=src/main/jniLibs',

           //配置的我们想要生成的so文件所在的位置

            'APP_BUILD_SCRIPT=src/main/jni/Android.mk',

            //指定项目以这个mk的方式

            'NDK_APPLOCATION_MK=src/main/jni/Application.mk'

            //指定项目以这个mk的方式

}

            tasks.withType(JavaCompile){

            //使用ndkBuild

            compileTask ->compileTask.dependsOn ndkBuild

            }

}

dependencies {

          compile fileTree(dir: 'libs', include: ['*.jar'])

.......................................................................

}

//这里还应该加上一个clear部分的代码,暂时未找到。


② 生成.h头文件

首先,新建一个类JNI。


public class JNI {

    static{

        System.loadLibrary("jni_demo");

    }

    public native int getInt();//随便定义一个函数。

}


——————————android studio 3中生成.h头文件分割线——————————

android studio 3中这种生成.h头文件的方式不行了,我没有找到生成的class文件的位置,最简单的就是自己写.h文件或者使用External Tools生成。

使用External Tools一劳永逸。

使用External Tools步骤如下所示:

1.点击File-Settings

2.点击Tools,选择External Tools,然后点击add,弹出Create Tool。





具体配置如下:

Name:javah

Description:javah

Program:$JDKPath$\bin\javah.exe

Parameters:-classpath . -jni -d $ModuleFileDir$\src\main\jni $FileClass$

Working directory:$ModuleFileDir$\src\main\Java




-classpath classes 指明类所在的位置

-d 产生的.h文件放到指定目录下;


配置完后选中相应的java文件,右键,点击External Tools->javah


然后main/jni目录下就会生成.h文件了。


参考链接:

Android Studio 3.0 JNI的实现

https://blog.csdn.net/ziyoutiankoong/article/details/79696279



——————————android studio 3中生成.h头文件分割线——————————


android studio 3中下面这张方式生成.h失效了,因为路径改了。

—————————失效分割线—————————

然后rebuild project。

查看这个目录下是否生成.class文件,没有生成请重新来过。


再打开Terminal输入指令

cd app/build/intermediates/classes/debug




再输入

javah -jni com.demo.jnidemo.JNI


然后就会生成.h文件。这个文件后面有用。

自动生成的代码如下:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include  <jni.h>

/* Header for class com_demo_jnidemo_JNI */

#ifndef _Included_com_demo_jnidemo_JNI

#define _Included_com_demo_jnidemo_JNI

#ifdef __cplusplus

extern"C"{

#endif

/*

* Class:    com_demo_jnidemo_JNI

* Method:    getInt

* Signature: ()I

*/

JNIEXPORT jint JNICALL Java_com_demo_jnidemo_JNI_getInt

(JNIEnv *, jobject);

#ifdef __cplusplus

}

#endif

#endif

—————————失效分割线—————————


③新建mk文件。

在project模式下,main目录中新建文件夹-jni。

然后,在jni目录下新建2个mk文件。

Application.mk

Android.mk





Application.mk中这句表示生成所以平台的so文件。

APP_ABI:=all


然后新建Android.mk文件。


Android.mk文件需要根据具体文件去编写,暂时不管。

将之前生成的.h头文件复制到jni目录下。


然后在jni目录下新建一个jni.c文件。


编写.c文件

#include <stdio.h>

#include <com_demo_jnidemo_JNI.h>


Java_com_demo_jnidemo_JNI_getInt

(JNIEnv *env, jobject jobj){

    return 3;

}


然后就可以编写Android.mk文件了。

一个最简单Android.mk文件。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := jni_demo

LOCAL_SRC_FILES := jni.c

include $(BUILD_SHARED_LIBRARY)

然后就可以rebuild project,生成so文件了。


有时会遇到这种错误,rebuild project无法clean,解决方法,关掉android studio,找到文件手动删除。找到目录D:\AndroidStudio\JNIDemo\app\build  ,删除build文件夹,然后重新打开。打开后,重新rebuild project。

Error:Execution failed for task ':app:clean'.

> Unable to delete directory: D:\AndroidStudio\JNIDemo\app\build\intermediates\classes\debug



成功的话,会生成jniLibs文件夹,此时由于已经在gradle中配置了该路径,可以直接使用了。

在MainActivity中使用jni类,打印log,查看日志。


JNI jni =new JNI();

Log.d(TAG,"jni getInt is : "+ jni.getInt());


log显示return 3.

到这里为止是只有一个c文件的情况,实际使用中,存在多个.c文件。但是由于在生成so文件的时候src设定所以需要修改。实际使用几个.c文件就添加几个.c文件到Android.mk文件中。

LOCAL_SRC_FILES := xxx.c  yyy.c


C语言是魔鬼,C++我差点挂科的好吗!



首先,新建一个test_get_int.h文件



添加一句:int test(void);  //这是函数声明。

#ifndef JNIDEMO_TEST_GET_INT_H

#define JNIDEMO_TEST_GET_INT_H

int test(void);

#endif //JNIDEMO_TEST_GET_INT_H




然后,修改jni.c文件。

修改了这几个地方。


具体代码如下:

#include <stdio.h>

#include <com_demo_jnidemo_JNI.h>


//新增处

#include <.h>


Java_com_demo_jnidemo_JNI_getInt

(JNIEnv *env, jobject jobj){

//  return 3;

//修改处

return test();

}

//test中的自定义函数

int test(){

return 1;

}

然后rebuild project。

然后run运行,返回1。



2.直接通过gradle。

通过gradle和jni,无需mk文件。

①配置gradle。

首先在gradle.properties里面加上这句。

android.useDeprecatedNdk=true


配置gradle。

ndk {

moduleName = "jni_test"

abiFilters "armeabi","armeabi-v7a","x86"

ldLibs "log"

}




②新建JNI类。

public class JNI {

static{

System.loadLibrary("jni_test");

}

public native int getInt();

}


project模式下,在main目录下,新建jni文件夹。


然后在jni目录下,新建jni_test.c文件。注意,这个.c文件的名字就是之前在gradle中配置的那个名字,也是JNI类中load的那个名字。

.c文件内容如下。

#include <stdio.h>

#include <jni.h>

jint Java_com_demo_jni2_JNI_getInt(JNIEnv* env, jobject jobj){

return 3;

}


android中调用c的关键在于桥接函数。

这个函数是有命名规则的。

拆分下面的字符:

j  

int  (返回值类型)

Java

_(以下划线连接)

com.demo.jni2(包名)

JNI(类名)

getInt(函数名)

JNIEnv* env, jobject jobj(这个貌似都是统一的参数)

jint Java_com_demo_jni2_JNI_getInt(JNIEnv* env, jobject jobj)




在main中调用。

最后,成功运行。



3.通过cmake和gradle //这个是比较新的方法。配置比较简单。

新建工程,不用勾选include C++ support。

勾选include C++ support。好像是给你看一个例子。

一路next下去,然后finish。

然后,在main目录下新建cpp文件夹。

新建.c文件或者.cpp文件。

jni_demo.c或者jni_demo.cpp。

jni_demo.c具体代码如下:

#include <jni.h>

JNIEXPORT jint JNICALL

Java_com_demo_cmaketestdemo_JNI_getInt

(JNIEnv *env, jobject jobj){

    return 3;

}


新建.cpp文件也可以。

jni_demo.cpp具体代码如下。

#include <jni.h>

extern"C"

JNIEXPORT jint JNICALL

Java_com_demo_cmaketestdemo_JNI_getInt

(JNIEnv *env, jobject jobj){

return 3;

}



将CMakeLists.txt文件复制到app目录下或者新建一个CMakeLists.txt。

D:\AndroidStudio\CMakeTestDemo\app\CMakeLists.txt

关于CMakeLists.txt的具体解释,可参考文章结尾处的参考链接。

这里以jni_demo.c为例,生成so,如果你需要以jni_demo.cpp生成so,需要修改这里。将

jni_demo.c改成jni_demo.cpp即可。





# For more information about using CMake with Android Studio, read the

# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

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 them for you.

# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.

jni_demo

# Sets the library as a shared library.

SHARED

# Provides a relative path to your source file(s).

src/main/cpp/jni_demo.c )

# Searches for a specified prebuilt library and stores the path as a

# variable. Because CMake includes system libraries 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

# 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 this

# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.

jni_demo

# Links the target library to the log library

# included in the NDK.

${log-lib} )

最后配置gradle文件。

apply plugin: 'com.android.application'

android {

    compileSdkVersion 26

    buildToolsVersion "26.0.1"

    defaultConfig {

        applicationId "com.demo.cmaketestdemo"

          minSdkVersion 19

          targetSdkVersion 26

          versionCode 1

          versionName "1.0"

          testInstrumentationRunner   "android.support.test.runner.AndroidJUnitRunner"

           externalNativeBuild {

                       cmake {

                                              cppFlags ""

                                    }

            }

         }

         buildTypes {

                  release {

                           minifyEnabled false

                           proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'

                    }

            }

           externalNativeBuild {

                    cmake {

                               path "CMakeLists.txt"

                    }

          }

}

dependencies {

compile fileTree(dir: 'libs', include: ['*.jar'])

.......................................................................

}




更新gradle之后,rebuild project,然后这个目录下就会生成so文件。



依旧是简单地验证,在main中添加下列代码。



JNI jni =new JNI();

Log.d(TAG,"jni getInt is : "+ jni.getInt());



最后结果:


下面以cmake工程为例,记录如何在别的project中使用so文件。

在别的project使用so我们还需要jar文件,因此,先修改project的gradle使其生成jar。


apply plugin: 'com.android.library'

    android {

        compileSdkVersion 26

        buildToolsVersion "26.0.1"

        defaultConfig {

           //    applicationId "com.demo.cmaketestdemo"

           minSdkVersion 19

            targetSdkVersion 26

           versionCode 1

           versionName "1.0"

          testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

         externalNativeBuild {

             cmake {

                 cppFlags ""

                 }

           }

       }

    buildTypes {

          release {

              minifyEnabled false

             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

             }

         }

          externalNativeBuild {

              cmake {

                  path "CMakeLists.txt"

              }

          }

}


dependencies {

compile fileTree(dir: 'libs', include: ['*.jar'])

androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {

exclude group: 'com.android.support', module: 'support-annotations'

})

compile 'com.android.support:appcompat-v7:26.+'

compile 'com.android.support.constraint:constraint-layout:1.0.2'

testCompile 'junit:junit:4.12'

}


task makeJar(type: Copy) {

//删除存在的

delete 'build/libs/jni.jar'

//设置拷贝的文件

from('build/intermediates/bundles/default/')

//打进jar包后的文件目录

into('build/libs/')

//将classes.jar放入build/libs/目录下

//include ,exclude参数来设置过滤

//(我们只关心classes.jar这个文件)

include('classes.jar')

//重命名

rename ('classes.jar', 'jni.jar')

}

makeJar.dependsOn(build)

修改后sync gradle一下。然后打开右边的gradle命令栏。

找到other中的makeJar命令。鼠标左键双击。

这个界面就弹出来了。


命令执行完在这个目录下就生成了jni.jar文件啦。


————————update_20180518————————

Terminal中执行失败,后来我找到了解决办法,参考我这篇文章。AndroidStudio中Terminal运行gradle(或gradlew)命令失败解决记录  https://www.jianshu.com/p/9a655815e9b0

——————————————————————————

也可以在命令行中输入这个命令,但是我这台不行,会报错,同样的步骤我在别的电脑上可以。尝试了各种设置依然不行,只能使用上面的方法。



* Try:

Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

> Starting Daemon

D:\AndroidStudio\CMakeTestDemo>

D:\AndroidStudio\CMakeTestDemo>gradlew makeJar

Starting a Gradle Daemon, 2 incompatible Daemons could not be reused, use --status for details

FAILURE: Build failed with an exception.

* What went wrong:

Unable to start the daemon process.

This problem might be caused by incorrect configuration of the daemon.

For example, an unrecognized jvm option is used.

Please refer to the user guide chapter on the daemon at https://docs.gradle.org/3.3/userguide/gradle_daemon.html

Please read the following process output to find out more:

-----------------------

Error occurred during initialization of VM

java/lang/NoClassDefFoundError: java/lang/Object

* Try:

Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.


好了,下面开始介绍如何在新的project中使用。

①在src目录下新建jniLibs文件夹,并将之前生成的so文件拷进去。


配置gradle

sourceSets {

    main {

    jniLibs.srcDirs = ['src/jniLibs'];

    }

}


applyplugin:'com.android.application'


android {

          compileSdkVersion 26

           buildToolsVersion "26.0.1"

          defaultConfig {

               .......

           }

         buildTypes {

              release {

                  .....

                   }

            }

          sourceSets {

                   main {

                           jniLibs.srcDirs = ['src/jniLibs'];

                  }

           }

}


dependencies {

.......

}



②将jni.jar赋值到libs目录下(libs是默认存在的)。


然后添加depend。



点击ok,然后就可以了。

最后在mainActivity中使用。


package com.demo.myapplication;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import com.demo.cmaketestdemo.JNI;

public class MainActivity extends AppCompatActivity {

           private static final String TAG = "MainActivity";

             @Override

            protected void onCreate(Bundle savedInstanceState) {

                    super.onCreate(savedInstanceState);

                   setContentView(R.layout.activity_main);

                   JNI jni =new JNI();

                   Log.d(TAG,"jni getInt is : "+ jni.getInt());

              }

}


// 20180518更新

demo链接:

https://github.com/VIVILL/android_so_demo

我上传了第一个和第三个方法的代码(这里面是不带.gradle文件夹的,打开后会联网自动配置,因此保持连接外网状态)。

本机软件环境配置:

软件版本:AndroidStudio3.1

gradle版本:4.4

plugin版本:3.1.2



参考链接:

Android Studio NDK CMake 指定so输出路径以及生成多个so的案例与总结

http://blog.csdn.net/b2259909/article/details/58591898

Android Studio 2.2 NDK CMake方式入门

http://www.jianshu.com/p/18724f29d30e

使用Android Studio和CMake进行NDK开发 - 基础

http://www.jianshu.com/p/86aac765eecb

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容