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的實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章