Android Studio3.5 JNIDemo实现步骤详解

前言

要实现一个在安卓中调用C++模块的功能。通过查阅资料发现,网上的资料质量参差不齐,同时因为Android Studio版本的不一致,很难跟着某篇博客操作后得到博客中预期的结果,导致自己走了很多弯路。通过查阅大量资料和多次实践,终于走通安卓调用C++模块的整个流程。特此记录下整个详细操作步骤,给大家提供参考,同时对自己也是一次总结。

准备工作:

1、安装Android Studio3.5,配置Gradle。参考:Android studio下载及安装方法
2、配置NDK环境,主要包含如下几步:
1)下载ndk
2) 配置ndk环境变量
参考:android NDK——搭建Android Studio的NDK环境
3、创建安卓虚拟机。参考:android studio 创建虚拟机
4、安装配置jdk。参考:如何下载并安装JDK?我安装的是
jdk-12.0.2_windows-x64_bin。

热身运动

上述准备工作做好了,就可以创建一个安卓开发界的“Hello world”试试。
参考:使用Android Studio完成Hello World

正题:安卓调用C++模块Demo

总体要经过如下几步:
1、	新建一个空白工程
2、	配置工程ndk路径、gradle.properties文件
3、	新建一个java类,只声明,不实现
4、	新建一个jni文件夹:在这里通过javac命令生成java类的头文件;在这里新建cpp文件把java类里的函数实现;在这里新建用于编译和构建的Android.mk、Application.mk两个mk文件。
5、	将java类和实现java类的cpp源文件链接起来
6、	配置app构建文件build.gradle,设置app构建信息
7、	运用ndk-build生成so文件
8、	在MainActivity.java中调用so文件,需要时修改布局文件
9、	运行

1、新建一个工程,按照下图操作:

在这里插入图片描述
在这里插入图片描述

2、新建好工程后,进入工程界面。点击Android–》project,更改工程目录的显示方式。

在这里插入图片描述
在这里插入图片描述
更改显示方式后的文件结构如下图所示。app目录是我们的主要编码目录。

在这里插入图片描述

3、配置工程ndk路径:File–>Project structure–>SDK Location,进入如下界面,点击下拉按钮,选择NDK安装路径。

在这里插入图片描述
此时可以看到local.properties文件中已经添加ndk路径:
在这里插入图片描述
配置项目下的gradle.properties文件,在文件最后加上这句:

android.useDeprecatedNdk=true

在这里插入图片描述

4、新建jni类,依次点击app–>src–>main–>java–>com.example.jnitest,右键com.example.jnitest–>new–>java class,新建一个JNITest类

在这里插入图片描述
在JNITest类中添加如下内容:

 JNITest(){
        //static {
        System.loadLibrary("JniLib");
    }

    //定义一个方法,该方法在C中实现
    public native String getString();  //native关键字指示以原生形式实现的方法.向编译器告知实现在原生库中

最后如下图所示:
在这里插入图片描述

下面我们开始在C++中实现JNITest类中定义的getString方法。

5、右键main文件夹,新建一个jni文件夹:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6、通过javac命令生成.h头文件。网上好多资料都是用javah命令生成的.h头文件,无奈我是安装的jdk12版本,没有javah命令了。通过多番查找资料发现,javac可以实现javah的功能。

进入Terminal界面,进入main文件夹:(这里有个简便方法可以直接进入到main文件夹:鼠标左键选中main文件夹,按住不放,拖动到Terminal界面那里,即可进入到main文件夹)
在这里插入图片描述
输入javac命令后生成.h头文件,可以看到jni目录下已经有生成好的头文件了:
在这里插入图片描述
javac 命令生成.h头文件的格式为:

javac -encoding utf8 -h 目标文件夹  源文件夹

目标文件夹:生成的.h文件存放的目录
源文件夹:要生成头文件的java源文件所在目录
.\ :代表当前目录

生成的.h文件内容如下,可以看到头文件里生成了一个没有实现的函数声明,待会我们就是要在cpp文件中实现这个函数的函数体。
仔细研究会发现,这个函数的命名是有规则的:Java_是固定前缀。com_example_jnitest是包名。JNITest是类名。getString是函数名。
在这里插入图片描述

7、在jni目录下新建3个文件:JniLib.cpp 、Android.mk、Application.mk

.mk文件新建时,文件类型直接选File,如下图:
在这里插入图片描述
JniLib.cpp 文件内容如下:
直接将刚生成的.h文件中的没有实现的函数声明拷贝过来进行实现。加上extern "C"之后,编译时就不会修改这个函数的名字,这样才能在JNITest.java文件中调用getstring函数时,找到这个函数在C++中的实现。
在这里插入图片描述
Android.mk:

LOCAL_PATH := $(call my-dir) #不用修改

include $(CLEAR_VARS) #不用修改

LOCAL_MODULE := JniLib #动态库名称
LOCAL_SRC_FILES =: JniLib.cpp #Cpp文件,里面就是我们写的Cpp代码
include $(BUILD_SHARED_LIBRARY) #生成.so动态库

Application.mk:

APP_MODULES := JniLib
APP_ABI := all

8、将java文件链接到C++文件:

右键java文件夹–》Link C++…
在这里插入图片描述
点击下拉菜单,选择ndk-build,在Project Path 中找到Android.mk文件所在地址,点击OK
在这里插入图片描述
此时可以看到.h文件的图标变了,cpp文件中getstring函数前面也多了个J图标:
在这里插入图片描述
java文件夹getstring函数那里也多了个C++图标。两个函数链接到一起了。
在这里插入图片描述

9、 修改app下的build.gradle文件:

加上ndk节点、sourceSets节点、task ndkBuild节点:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.example.jnitest"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        ndk{
            moduleName "JniLib"
            abiFilters  "armeabi-v7a", "x86" //输出指定的三种abi体系下的so库"armeabi",只加载armabi架构(目录下)的so库,如果是别的架构,就会找不到
        }
        sourceSets{  //不配的话都会有一个默认值  可以指定哪些源文件(或文件夹下的源文件)要被编译,哪些源文件要被排除
            main{
                jni.srcDirs = []  //禁用as自动生成mk
                //jniLibs.srcDirs=["src/main/libs" ] //so包就去src/main/libs目录下找
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    task ndkBuild(type:Exec,description:'Compile JNI source via NDK'){
        commandLine "C\\:\\Users\\lmkhd\\AppData\\Local\\Android\\Sdk\\ndk\\21.0.6113669\\ndk-build.cmd",//配置ndk的路径
                'NDK_PROJECT_PATH=build/intermediates/ndk',//ndk默认的生成so的文件
                'NDK_LIBS_OUT=src/main/libs',//配置的我们想要生成的so文件所在的位置
                'APP_BUILD_SCRIPT=src/main/jni/Android.mk',//指定项目以这个mk的方式
                'NDK_APPLOCATION_MK=src/main/jni/Application.mk'//指定项目以这个mk的方式
    }
    externalNativeBuild {
        ndkBuild {
            path file('src/main/jni/Android.mk')
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

10、执行ndk-build生成so文件:

先配置ndk-build工具:
program里面选择ndk路径,我的是:C:\Users\lmkhd\AppData\Local\Android\Sdk\ndk\21.0.6113669\ndk-build.cmd
working dictionary是指定生成的so文件的存放路径,我的是:$ProjectFileDir$\app\src\main
在这里插入图片描述
可以点击Insert Macro获取项目的路径$ProjectFileDir$,然后后面加上\app\src\main
在这里插入图片描述
配置好ndk-build工具后,选中JNITest类右键->External Tools->ndk-build,生成so文件:
在这里插入图片描述
如图,main文件夹下多了一个libs文件:
在这里插入图片描述

11、在MainActivity.java中调用so文件getString()函数:

MainActivity.java:

package com.example.jnitest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    Button button;
    TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        tv = findViewById(R.id.tv);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                tv.setText("结果:"+ new JNITest().getString());   #按钮点击时调用
                //tv.setText("结果:");
            }
        });
    }
}

此时会报错说button和tv无法解析。需要在布局文件activity_main.xml中进行修改:
在这里插入图片描述
先切换到design视图下,选中Button拖动到布局中,添加一个Button控件:
在这里插入图片描述
切换到Text视图,对activity_main.xml代码进行相关修改,最终activity_main.xml
内容如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"  #自己添加
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"    #自己添加
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用JNI"  #修改
        tools:layout_editor_absoluteX="25dp"
        tools:layout_editor_absoluteY="37dp"
        tools:ignore="MissingConstraints"/>    #自己添加

</androidx.constraintlayout.widget.ConstraintLayout>

12、运行:

在这里插入图片描述

结果如下所示:
在这里插入图片描述
点击“调用JNI”按钮后:
在这里插入图片描述
总的来说,此Demo主要思路是:在java类中只声明函数,不实现,然后在C++中实现函数。再将java类和其C++方式实现的函数体变成一个so包。最后在MainActivity.java中进行调用。

Demo链接

参考:
Android Studio 3.0 JNI的实现

猜你喜欢

转载自blog.csdn.net/u011208984/article/details/104246431
今日推荐