Android Studio jni - 入門篇

colawarrior發表於2018-03-12

閒來無事,在研究騰訊GT原始碼的時候看到一段記憶體佔用的程式碼,感覺平時測試的時候可能會用到,所以想移植到測試app進行使用。於是開始了這篇jni之旅

一. 環境配置

主要需要配置的就是NDK(Native Development Kit),現在Android Studio很便利,可以一鍵下載:

Android Studio -> Preferences -> Android SDK -> SDK Tools -> 選擇NDK -> 安裝

如圖:

Android Studio jni - 入門篇

  • 安裝完成以後就可以開擼了

二. jni for memory malloc

  1. 新建一個jni android專案,或者開啟一個已經有的android專案
  2. 設定jni支援

  • 開啟gradle.properties,新增

           android.useDeprecatedNdk=true

  • 開啟local.properties,新增:
        ndk.dir=NDK的路徑複製程式碼
  • 最後開啟app內build.gradle,在android/defaultConfig下面新增ndk配置
defaultConfig {
    applicationId "com.youku.playerproxy"
    minSdkVersion 16
    targetSdkVersion 19
    versionCode getSelfDefinedVersion("code")
    versionName getSelfDefinedVersion("name")
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    multiDexEnabled = true
    multiDexKeepFile file('multiDexKeep.txt')

    ndk {
        moduleName "mem_fill_tool"
        abiFilters "armeabi-v7a"
    }
}


複製程式碼
  • moduleName 表示編譯出的so檔案的名字

  3. 新建java訪問層的C介面類

package com.xxx.utils;
public class MemFillTool {

    private MemFillTool() {
    }

    private static MemFillTool instance = null;

    public static MemFillTool getInstance() {
        if (instance == null) {
            System.loadLibrary("mem_fill_tool");
            instance = new MemFillTool();
        }
        return instance;
    }

    // 填充xxxMB記憶體
    public native int fillMem(int blockNum);

    // 釋放剛才填充的記憶體
    public native int freeMem();
}複製程式碼

fillMem和freeMem就是與C層互動的jni方法

  4. 生成標頭檔案

  • 先點選make project圖示Android Studio jni - 入門篇,編譯專案,看是否有報錯
  • 然後開啟終端,執行
cd app/build/intermediates/classes/debug/
javah com.xxx.utils.MemFillTool複製程式碼
執行成功之後開啟app/build/intermediates/classes/debug/ 即可找到編譯出的標頭檔案"com_lilei_testjni_JniUtils.h",不難發現標頭檔案名是有原報名+類名組成


5.建立jni開發的資料夾

  • 點選app資料夾,New → Folder → JNI Folder, 選擇在main資料夾下即可,生成成功後main目錄下會出現一個jni的資料夾
  • 找到剛才生成到標頭檔案,複製到jni資料夾下(記得關閉剛才使用的終端,否則無法複製)
  • 標頭檔案有了,現在在jni目錄下建立一個C++檔案用於開發使用,命名與標頭檔案相同
  • 編寫C++檔案中定義函式的程式碼
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xxx_utils_MemFillTool */

#ifndef _Included_com_xxx_utils_MemFillTool
#define _Included_com_xxx_utils_MemFillTool
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xxx_utils_MemFillTool
 * Method:    fillMem
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_xxx_utils_MemFillTool_fillMem
  (JNIEnv *, jobject, jint);

/*
 * Class:     com_xxx_utils_MemFillTool
 * Method:    freeMem
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_xxx_utils_MemFillTool_freeMem
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif複製程式碼

生成的標頭檔案

#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>


#include "com_xxx_utils_MemFillTool.h"

int fill(int blockNum);
int freeMem();

JNIEXPORT jint JNICALL Java_com_xxx_utils_MemFillTool_fillMem
(JNIEnv * env, jobject obj, jint blockNum) {
   return fill((int)blockNum);
}

JNIEXPORT jint JNICALL Java_com_xxx_utils_MemFillTool_freeMem
(JNIEnv * env, jobject obj) {
   return freeMem();
}

char *p;
const int BASE_SIZE = 1024*1024; // 1M

int fill(int blockNum)
{
   int memSize = blockNum * BASE_SIZE;
   p = (char *)malloc(memSize);

    if (!p) {
        return -1;
    }

   int i;
   for (i = 0; i < memSize; i++)
   {
      p[i] = 0;
   }

   return 0;
}

int freeMem()
{
    if (p) {
       free(p);
    }
   return 0;
}複製程式碼

實現

6.java層載入so

  • 回到JniUtils,加上
public static MemFillTool getInstance() {
    if (instance == null) {
        System.loadLibrary("mem_fill_tool");   =====> 載入so
        instance = new MemFillTool();
    }
    return instance;
}複製程式碼

至此執行就完全可以了

三、生成so檔案

前文介紹如何執行C++程式,但是實際開發中大多是封裝編譯出so檔案後進行開發,就類似java裡面的jar包

1.配置NDK環境變數

  • 找到Android Studio安裝的NDK包的檔案目錄(/Users/xxx/Library/Android/sdk/ndk-bundle),新增到系統的環境變數中

2.新建mk檔案

  • 在jni目錄下新建Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := mem_fill_tool
LOCAL_SRC_FILES := com_xxx_utils_MemFillTool

include $(BUILD_SHARED_LIBRARY)
複製程式碼
  • 在jni目錄下新建Application.mk檔案
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions -std=c++0x
APP_ABI := armeabi-v7a
APP_PLATFORM := android-18
複製程式碼

3.編譯生成so

  • 開啟終端cd到jni目錄下
  • 呼叫ndk-build開始編譯so
ndk-build
複製程式碼
  • 執行無誤的話會如圖所示

    Android Studio jni - 入門篇

  • 執行成功之後即會看到main資料夾下多了libs和obj的資料夾,裡面就是生成的各種CPU的so檔案

libs和obj裡面都有so檔案,兩者的區別google給出的解釋是:
As part of the build process, the files in the libs folder have been stripped of symbols and debugging information. So you'll want to keep two copies of each of your .so files: One from the libs folder to install on the Android device, and one from the obj folder to install for GDB to get symbols from.
也就是說,libs目錄下生成的庫是剝離了符號表與除錯資訊的,而obj下的庫是帶有除錯資訊的。


至此jni的開發入門已完成


相關文章