Android Studio3.0.1开发JNI流程------相比较eclipse没有了android.mk,application.mk;多了Cmake.txt

JIN——Java Native Interface的缩写

API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI标准至少要保证本地代码能工作在任何Java虚拟机环境。

具体开发流程可以查看官方原文档:https://developer.android.google.cn/ndk/index.html

官方的Android体系架构图

Android体系架构图

可以看到Android上层的Application和ApplicationFramework都是使用Java编写,底层包括系统和使用众多的Libraries都是C/C++编写的,所以上层Java要调用底层的C/C++函数库必须通过Java的JNI来实现。

Jni使用场景

当你开始着手准备一个使用JNI的项目时,请确认是否还有替代方案。应用程序使用JNI会带来一些副作用。下面给出几个方案,可以避免使用JNI的时候,达到与本地代码进行交互的效果:

1、JAVA程序和本地程序使用TCP/IP或者IPC进行交互。 
2、当用JAVA程序连接本地数据库时,使用JDBC提供的API。 
3、JAVA程序可以使用分布式对象技术,如JAVA IDL API。 
这些方案的共同点是,JAVA和C处于不同的线程,或者不同的机器上。这样,当本地程序崩溃时,不会影响到JAVA程序。

下面这些场合中,同一进程内JNI的使用无法避免:

1、程序当中用到了JAVA API不提供的特殊系统环境才会有的特征。而跨进程操作又不现实。 
2、你可能想访问一些己有的本地库,但又不想付出跨进程调用时的代价,如效率,内存,数据传递方面。 
3、JAVA程序当中的一部分代码对效率要求非常高,如算法计算,图形渲染等。

总之,只有当你必须在同一进程中调用本地代码时,再使用JNI。

Jni缺陷

一旦使用JNI,JAVA程序就丧失了JAVA平台的两个优点:

1、程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。 
2、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就降低了JAVA和C之间的耦合性。

Jni作用

JNI可以这样与本地程序进行交互:

1、你可以使用JNI来实现“本地方法”(native methods),并在JAVA程序中调用它们。 
2、JNI支持一个“调用接口”(invocation interface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。

简单了解以上内容后,我们开启正题,在Android开发中,我们该怎么使用jni,或者说是在Android Studio3.0中,我们该怎么使用jni呢?

首先配置系统的环境变量

在Android Studio中配置jni需要在SDK Tools中下载支持JNI开发的配置,如下图: 
下载支持JNI开发的配置

下载完成后需要在系统属性的环境变量的path中进行配置ndk-bundle路径<自行百度不多介绍>。 
在命令行输入ndk-build,出现以下截图提示证明配置成功 
配置成功

接下来在Android studio3.0中正式开发JNI

Android studio已经支持创建C/C++的开发,并使用CMake的模式构建NDK开发。

一,创建一个支持C/C++的Android项目

创建一个支持C/C++的Android项目
创建过程一直Next下去,直到最后一步 
创建过程的最后一步

这里需要特殊说明:

  • C++ Standard:使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置;
  • Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake;
  • Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

点击Finish等待项目创建完成。

支持C/C++项目的整体结构

项目的整体结构

多出了cpp目录以及External Build Files目录的内容

native-lib.cpp文件内容

native-lib.cpp文件内容 
简单可以看到,首先定义hello变量,之后return回该字符。

CMakeLists.txt文件内容

CMakeLists.txt文件内容 
查看官方原文档:https://d.android.com/studio/projects/add-native-code.html 
和以前唯一不同的就是多出了cpp目录以及External Build Files两个目录,那么这两个都有什么用呢?一起来看看。

cpp 目录 
存放所有 native code 的地方,包括源码,头文件,预编译项目等。对于新项目,Android Studio 创建了一个 C++ 模板文件:native-lib.cpp,并且将该文件放到了你的 app 模块的 src/main/cpp/ 目录下。这份模板代码提供了一个简答的 C++ 函数:stringFromJNI(),该函数返回一个字符串:”Hello from C++”

External Build Files 目录 
存放 CMake 或 ndk-build 构建脚本的地方。有点类似于 build.gradle 文件告诉 Gradle 如何编译你的APP 一样,CMake 和 ndk-build 也需要一个脚本来告知如何编译你的 native library。对于一个新的项目,Android Studio 创建了一个 CMake脚本:CMakeLists.txt,并且将其放到了你的 module 的根目录下

查看MainActivity.java程序代码

package fj.clover.ndktest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

首先static静态块去加载so库,本地编写native方法,调用该方法输出内容。

查看CMakeLists.txt,简单的翻译下

# 有关使用CMake在Android Studio的更多信息,请阅读文档:https://d.android.com/studio/projects/add-native-code.html

# 设置CMake的最低版本构建本机所需库
cmake_minimum_required(VERSION 3.4.1)

# 创建并命名库,将其设置为静态的
# 或共享,并提供其源代码的相对路径。
# 你可以定义多个library库,并使用CMake来构建。
# Gradle会自动将包共享库关联到你的apk程序。

add_library( # 设置库的名称
             native-lib
             # 将库设置为共享库。
             SHARED
             # 为源文件提供一个相对路径。
             src/main/cpp/native-lib.cpp )
# 搜索指定预先构建的库和存储路径变量。因为CMake包括系统库搜索路径中默认情况下,只需要指定想添加公共NDK库的名称,在CMake验证库之前存在完成构建
find_library( # 设置path变量的名称
              log-lib
              # 在CMake定位前指定的NDK库名称
              log )
# 指定库CMake应该链接到目标库中,可以链接多个库,比如定义库,构建脚本,预先构建的第三方库或者系统库
target_link_libraries( # 指定目标库
                       native-lib
                       # 目标库到日志库的链接 包含在NDK
                       ${log-lib} )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

分析完系统自动构建的程序后,按照之前的简单分析,这个案例输出的结果应该为:Hello from C++ ,来运行看看结果: 
这里写图片描述

编译到运行示例 APP 的流程过程

  • Gradle 调用外部构建脚本,也就是 CMakeLists.txt;
  • CMake 会根据构建脚本的指令去编译一个 C++ 源文件,也就是 native-lib.cpp,并将编译后的产物扔进共享对象库中,并将其命名为 libnative-lib.so,然后 Gradle 将其打包到 APK 中;
  • 在运行期间,APP 的 MainActivity 会调用 System.loadLibrary() 方法,加载 native library。而这个库的原生函数,stringFromJNI(),就可以为 APP 所用了;
  • MainActivity.onCreate() 方法会调用 stringFromJNI(),然后返回 “Hello from C++”,并更新 TextView 的显示;

注意:Instant Run 并不兼容使用了 native code 的项目。Android Studio 会自动禁止 Instant Run 功能。

如果你想验证一下 Gradle 是否将 native library 打包进了 APK,你可以使用 APK Analyzer:

  • 选择 Build > Analyze APK。
  • 从 app/build/outputs/apk/ 路径中选择 APK,并点击 OK。

如下图,在 APK Analyzer 窗口中,选择 lib,你就可以看见 libnative-lib.so 
这里写图片描述

在原项目基础上增加自己的代码

//在MainActivity.java中增加一个native方法
public native String getHelloJni();
  • 1
  • 2

你会发现getHelloJni( )方法是红色的。不要急,按住Alt+Enter回车后,系统会自动为你在之前.cpp文件中创建一个getHelloJni( )的C++代码,是不是很智能……

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring

JNICALL
Java_fj_clover_ndktest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_fj_clover_ndktest_MainActivity_getHelloJni(JNIEnv *env, jobject instance) {

    // TODO


    return env->NewStringUTF(returnValue);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

现在我们来简单的实现C++中getHelloJni方法

JNIEXPORT jstring JNICALL
Java_fj_clover_ndktest_MainActivity_getHelloJni(JNIEnv *env, jobject instance) {
    return env->NewStringUTF("Hello Jni,欢迎来到我的世界...");
}
  • 1
  • 2
  • 3
  • 4

现在在布局文件中添加一个TextView控件,我们来改变这个控件上显示的内容

<TextView
        android:textColor="#FF00FF"
        android:id="@+id/sample_text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在MainActivity.java中实现控件改变

TextView tv1 = (TextView) findViewById(R.id.sample_text1);
tv1.setText(getHelloJni());
  • 1
  • 2

运行结果: 
这里写图片描述

一般情况下,Gradle 会将你的本地库构建成 .so 文件,然后将其打包到你的 APK 中。如果你想 Gradle 构建并打包某个特定的 ABI 。你可以在你的 module 层级的 build.gradle 文件中使用 ndk.abiFilters 标签来指定他们:

 defaultConfig {
        ......
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
        ndk {
            abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                    'arm64-v8a'
        }
    }

猜你喜欢

转载自blog.csdn.net/u014644594/article/details/80482970
今日推荐