天天看點

Android NDK 開發總結 - breakpointer

Android NDK 開發總結

一.安裝配置環境

1.安裝Android Studio,下載下傳路徑https://developer.android.com/studio/index.html?hl=zh-cn。我下載下傳的是Windows 64位內建Android SDK版本https://dl.google.com/dl/android/studio/install/2.3.2.0/android-studio-bundle-162.3934792-windows.exe?hl=zh-cn,不用單獨安裝Android SDK。因為公司支援通路Google,是以下載下傳很友善。

2.安裝完畢,下載下傳CMake,LLDB和NDK工具

Android NDK 開發總結 - breakpointer

3.如果要使用Git版本控制,需要單獨下載下傳安裝包,官網位址https://git-scm.com/,安裝好後在Android Studio裡設定路徑

Android NDK 開發總結 - breakpointer

然後可以指定下載下傳路徑擷取代碼,比如kotlin的代碼倉庫

Android NDK 開發總結 - breakpointer

二.分析建立NDK工程

首先建立一個新工程,選擇Include C++Support

Android NDK 開發總結 - breakpointer

打開工程,裡面有一個C++輸出字元串的例子

路徑在D:\Project\MyApplication\app\src\main\cpp\native-lib.cpp,代碼如下

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

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

調用的檔案是MainActivity.java,路徑是D:\Project\MyApplication\app\src\main\java\com\example\myapplication\MainActivity.java,内容如下

package com.example.myapplication;

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();
}      

按Shift+F9,調試運作,選擇真機

Android NDK 開發總結 - breakpointer

在手機端輸出如下

Android NDK 開發總結 - breakpointer

預設對這個C++代碼是用CMake編譯的,CMakeLists.txt的路徑在D:\Project\MyApplication\app\CMakeLists.txt,内容精簡如下:

cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
             native-lib
             # Sets the library as a shared library.
             SHARED
             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )
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 )
target_link_libraries( # Specifies the target library.
                       native-lib
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )      

其中,#代表行注釋。大概意思是設定要編譯的庫名稱是native-lib,源代碼路徑是src/main/cpp/native-lib.cpp

在子產品的gradle檔案(注意不是項目的gradle檔案,前者的路徑是D:\Project\MyApplication\app\build.gradle,後者路徑是D:\Project\MyApplication\build.gradle)裡預設配置使用cmake檔案編譯

Android NDK 開發總結 - breakpointer

三.按照CMake+gradle的方式編寫NDK程式

接下來我嘗試自己模仿用NDK寫一個C++的例子

編寫一個包含native方法的java類,如TestNative.java,路徑在D:\Project\MyApplication\app\src\main\java\com\example\myapplication\TestNative.java

代碼如下:

package com.example.myapplication;

public class TestNative {
    static {
        System.loadLibrary("NativeLib");
    }
    public static native String GetStr();
}      

其中,NativeLib指定的是這個類要加載的庫的名稱,稍後我會生成這個庫,方法一定要用native修飾,而且為了不定義對象,把這個方法聲明為static類型,這樣可以直接用TestNative.GetStr()這樣的方式通路。

接下來需要生成對應的庫,首先要編寫C++檔案,有兩種方式,一種是使用Javah,生成頭檔案,對應頭檔案件編寫源代碼;另一種是根據jni生成函數的格式手動寫一個cpp即可,不用頭檔案,先在實作第一種:

輸入CMD使用控制台或者在Android Studio下方的Terminal終端,先cd找到目前TestNative.java檔案的位置

D:\Project\MyApplication>cd D:\Project\MyApplication\app\src\main\java\com\example\myapplication

D:\Project\MyApplication\app\src\main\java\com\example\myapplication>      

然後輸入javac TestNative.java編譯生成class檔案

D:\Project\MyApplication\app\src\main\java\com\example\myapplication>javac TestNative.java

D:\Project\MyApplication\app\src\main\java\com\example\myapplication>

      

在D:\Project\MyApplication\app\src\main\java\com\example\myapplication下會生成一個TestNative.class檔案

然後cd到最上層包的路徑上,這點非常重要,是我反複嘗試帶包名的類執行java或javah的方法

執行javah,參數是帶包名的類

D:\Project\MyApplication\app\src\main\java\com\example\myapplication>cd D:\Project\MyApplication\app\src\main\java

D:\Project\MyApplication\app\src\main\java>javah com.example.myapplication.TestNative

D:\Project\MyApplication\app\src\main\java>      

會在D:\Project\MyApplication\app\src\main\java路徑下生成com_example_myapplication_TestNative.h檔案,内容如下

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_myapplication_TestNative */

#ifndef _Included_com_example_myapplication_TestNative
#define _Included_com_example_myapplication_TestNative
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_myapplication_TestNative
 * Method:    GetStr
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_myapplication_TestNative_GetStr
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif      

按照這個聲明編寫一個cpp,如如TestNative.cpp,路徑是在D:\Project\MyApplication\app\src\main\cpp\TestNative.cpp,包含該頭檔案,實作即可

#include <string>
#include "../java/com_example_myapplication_TestNative.h"

extern "C" {
JNIEXPORT jstring JNICALL Java_com_example_myapplication_TestNative_GetStr
        (JNIEnv * env, jclass) {
    std::string str = "My First Native Test";
    return env->NewStringUTF(str.c_str());
}
}

      

第二種就是根據這樣的聲明特征,直接寫一個TestNative.cpp,注意函數命名方式一定要和用Javah生成的頭檔案的方法一緻,仔細觀察記住就行了。這樣不用使用javac和javah手動生成class檔案和頭檔案,但要包含jni.h頭檔案,代碼如下

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

extern "C" {
JNIEXPORT jstring JNICALL Java_com_example_myapplication_TestNative_GetStr
        (JNIEnv * env, jclass) {
    std::string str = "My First Native Test";
    return env->NewStringUTF(str.c_str());
}
}

      

然後修改CMakeLists.txt檔案,如下

cmake_minimum_required(VERSION 3.4.1)
add_library( NativeLib
             SHARED
             src/main/cpp/TestNative.cpp )
find_library( log-lib
              log )
target_link_libraries( NativeLib
                       ${log-lib} )      

修改調用JNI的檔案,如下

package com.example.myapplication;

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

public class MainActivity extends AppCompatActivity {

    @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(TestNative.GetStr());
    }
}      

運作程式,在手機輸出

Android NDK 開發總結 - breakpointer

用CMake方式,會在路徑D:\Project\MyApplication\app\build\intermediates\cmake\debug\obj下生成各種平台版本的so檔案,如armeabi中的libNativeLib.so,注意生成庫的名稱是在設定庫名稱前加了lib

三.按照ndk-build+gradle的方式編寫NDK程式

按照ndk-build的方式編譯,需要生成.mk檔案

在之前建立工程的基礎上

1.首先,要在D:\Project\MyApplication\app\src\main下建立jni檔案夾,在裡面建立Android.mk檔案,内容如下

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE = NativeLib

LOCAL_SRC_FILES := ../cpp/TestNative.cpp

LOCAL_LDLIBS := -landroid -llog -latomic

include $(BUILD_SHARED_LIBRARY)      

大體意思是指定目标庫的名稱,源代碼的路徑,可以對比CMakeLists.txt。

2.接在,在Android.mk的同級目錄下,建立Application.mk檔案,内容如下

APP_STL := c++_static      

即說明使用C++的版本,具體可以參考https://developer.android.com/ndk/guides/cpp-support.html#runtimes

注意在使用C++時要建立Application.mk檔案,寫上C++版本,直接寫在Android.mk不行,否則會提示STL的庫找不到

3.修改gradle的編譯方式為ndkBuild,内容如下

用ndk-build的方式,會在路徑D:\Project\MyApplication\app\build\intermediates\ndkBuild\debug\obj\local\下生成各種平台版本的so檔案

四.設定指定平台版本的庫

1.清理掉D:\Project\MyApplication\app\build下的檔案夾

2.在子產品的build.gradle檔案中添加如下:

就隻會生成libNativeLib.so了。