Android Studio編譯並集成SO文件

Android Studio編譯並集成SO文件

0x00 本文目標

  • 讓Java層代碼與Native層代碼交互
  • 編譯Native代碼爲SO文件
  • 將SO文件集成到最終的APK文件中

爲此你需要Android Studio和NDK套裝,百度搜索後直接到官網下載。

0x01 Java層

爲了讓Java層與JNI層交互,來個簡單的測試代碼。首先用靜態代碼塊加載了test動態鏈接庫。hello方法用於獲取從native返回的字符串,並顯示到TextView中,沒有TextView的同學自己在佈局文件中新建一個就行了。

package com.example.androidtest;

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

public class MainActivity extends AppCompatActivity {
    private TextView txt_jni;

    static {
        System.loadLibrary("test");
    }

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

        txt_jni = findViewById(R.id.txt_jni);
        txt_jni.setText(hello(123, "abc"));
    }

    private native String hello(int a, String b);
}

接下來實現JNI層代碼,在Android Studio中新建一個空白工程,File->New->Folder->JNI Folder創建一個JNI源碼文件夾,默認路徑在/src/main/jni。

博主另一篇文章演示了純Java生成SO文件:https://www.cnblogs.com/DXCyber409/p/10854415.html

在安卓中同樣有這兩種方式:Java_固定路徑方式,和JNI_OnLoad動態註冊方式。

0x02 Java_固定路徑寫法

本質就是生成一個固定前綴的函數名稱和相關參數,最後在c/cpp文件中實現它。

可以手動生成.h文件,不再演示。本文使用Android Studio提供的External Tools來實現一鍵生成.h頭文件,這將大大提高開發效率。

File->Settings->Tools->Extenal Tools,點個加號創建一個新的項。該命令實際上會執行命令行指令傳遞相關參數,結合手寫的寫法,可以改裝出其他版本:

D:\RTEws\Java\jdk1.8.0_121\bin>javah -d "E:\Workspace\NetBeans\DXCyber409\src\main\java\dxcyber409\jni" -classpath "E:\Workspace\NetBeans\DXCyber409\target\classes" -jni dxcyber409.Test$Cls

Insert Marcros是內置環境變量參考窗口,不要錯過:

最終路徑參數方案調整如下:

Name:javah
Program:$JDKPath$\bin\javah.exe
Arguments:-d "$ModuleFileDir$\src\main\jni" -classpath "$OutputPath$;$ModuleSdkPath$\platforms\android-28\android.jar;$ModuleSdkPath$\extras\android\android-support-v4.jar;$ModuleSdkPath$\extras\android\android-support-v7.jar" -jni $FileClass$
Working directory:

在Arguments中-classpath選項除了自己代碼中已編譯的classes目錄外,還加入android.jar,android-support-v4.jar,android-support-v7.jar等依賴包,不加會缺少實現類而報錯,按需解決就行。

寫法不止一種,博主在Arguments中已經把所有相關路徑都拼接完成了,因此Working directory沒啥用留空就行。

Ps.博主的Android SDK貌似不完整的樣子,在Android SDK中找不到appcompat相關依賴包。如果你也不想重新安裝Android SDK的話這裏有個小技巧,到Maven Repository上缺啥下啥,自己動手豐衣足食。下載了丟到目錄中引用路徑,位置沒有強硬要求。

添加External Tools完成後首先Build->Make module生成一下classes文件,之後對着類右鍵->External Tools->javah就可以直接調用javah生成.h文件,相當方便。

.h文件在/src/main/jni目錄中,按慣例新建一個cpp文件包含此文件並實現,完整代碼如下:

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

#ifndef _Included_com_example_androidtest_MainActivity
#define _Included_com_example_androidtest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
#undef com_example_androidtest_MainActivity_BIND_ABOVE_CLIENT
#define com_example_androidtest_MainActivity_BIND_ABOVE_CLIENT 8L
#undef com_example_androidtest_MainActivity_BIND_ADJUST_WITH_ACTIVITY
#define com_example_androidtest_MainActivity_BIND_ADJUST_WITH_ACTIVITY 128L
#undef com_example_androidtest_MainActivity_BIND_ALLOW_OOM_MANAGEMENT
#define com_example_androidtest_MainActivity_BIND_ALLOW_OOM_MANAGEMENT 16L
#undef com_example_androidtest_MainActivity_BIND_AUTO_CREATE
#define com_example_androidtest_MainActivity_BIND_AUTO_CREATE 1L
#undef com_example_androidtest_MainActivity_BIND_DEBUG_UNBIND
#define com_example_androidtest_MainActivity_BIND_DEBUG_UNBIND 2L
#undef com_example_androidtest_MainActivity_BIND_EXTERNAL_SERVICE
#define com_example_androidtest_MainActivity_BIND_EXTERNAL_SERVICE -2147483648L
#undef com_example_androidtest_MainActivity_BIND_IMPORTANT
#define com_example_androidtest_MainActivity_BIND_IMPORTANT 64L
#undef com_example_androidtest_MainActivity_BIND_NOT_FOREGROUND
#define com_example_androidtest_MainActivity_BIND_NOT_FOREGROUND 4L
#undef com_example_androidtest_MainActivity_BIND_WAIVE_PRIORITY
#define com_example_androidtest_MainActivity_BIND_WAIVE_PRIORITY 32L
#undef com_example_androidtest_MainActivity_CONTEXT_IGNORE_SECURITY
#define com_example_androidtest_MainActivity_CONTEXT_IGNORE_SECURITY 2L
#undef com_example_androidtest_MainActivity_CONTEXT_INCLUDE_CODE
#define com_example_androidtest_MainActivity_CONTEXT_INCLUDE_CODE 1L
#undef com_example_androidtest_MainActivity_CONTEXT_RESTRICTED
#define com_example_androidtest_MainActivity_CONTEXT_RESTRICTED 4L
#undef com_example_androidtest_MainActivity_MODE_APPEND
#define com_example_androidtest_MainActivity_MODE_APPEND 32768L
#undef com_example_androidtest_MainActivity_MODE_ENABLE_WRITE_AHEAD_LOGGING
#define com_example_androidtest_MainActivity_MODE_ENABLE_WRITE_AHEAD_LOGGING 8L
#undef com_example_androidtest_MainActivity_MODE_MULTI_PROCESS
#define com_example_androidtest_MainActivity_MODE_MULTI_PROCESS 4L
#undef com_example_androidtest_MainActivity_MODE_NO_LOCALIZED_COLLATORS
#define com_example_androidtest_MainActivity_MODE_NO_LOCALIZED_COLLATORS 16L
#undef com_example_androidtest_MainActivity_MODE_PRIVATE
#define com_example_androidtest_MainActivity_MODE_PRIVATE 0L
#undef com_example_androidtest_MainActivity_MODE_WORLD_READABLE
#define com_example_androidtest_MainActivity_MODE_WORLD_READABLE 1L
#undef com_example_androidtest_MainActivity_MODE_WORLD_WRITEABLE
#define com_example_androidtest_MainActivity_MODE_WORLD_WRITEABLE 2L
#undef com_example_androidtest_MainActivity_RECEIVER_VISIBLE_TO_INSTANT_APPS
#define com_example_androidtest_MainActivity_RECEIVER_VISIBLE_TO_INSTANT_APPS 1L
#undef com_example_androidtest_MainActivity_DEFAULT_KEYS_DIALER
#define com_example_androidtest_MainActivity_DEFAULT_KEYS_DIALER 1L
#undef com_example_androidtest_MainActivity_DEFAULT_KEYS_DISABLE
#define com_example_androidtest_MainActivity_DEFAULT_KEYS_DISABLE 0L
#undef com_example_androidtest_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL
#define com_example_androidtest_MainActivity_DEFAULT_KEYS_SEARCH_GLOBAL 4L
#undef com_example_androidtest_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL
#define com_example_androidtest_MainActivity_DEFAULT_KEYS_SEARCH_LOCAL 3L
#undef com_example_androidtest_MainActivity_DEFAULT_KEYS_SHORTCUT
#define com_example_androidtest_MainActivity_DEFAULT_KEYS_SHORTCUT 2L
#undef com_example_androidtest_MainActivity_RESULT_CANCELED
#define com_example_androidtest_MainActivity_RESULT_CANCELED 0L
#undef com_example_androidtest_MainActivity_RESULT_FIRST_USER
#define com_example_androidtest_MainActivity_RESULT_FIRST_USER 1L
#undef com_example_androidtest_MainActivity_RESULT_OK
#define com_example_androidtest_MainActivity_RESULT_OK -1L
#undef com_example_androidtest_MainActivity_HONEYCOMB
#define com_example_androidtest_MainActivity_HONEYCOMB 11L
#undef com_example_androidtest_MainActivity_MSG_REALLY_STOPPED
#define com_example_androidtest_MainActivity_MSG_REALLY_STOPPED 1L
#undef com_example_androidtest_MainActivity_MSG_RESUME_PENDING
#define com_example_androidtest_MainActivity_MSG_RESUME_PENDING 2L
/*
 * Class:     com_example_androidtest_MainActivity
 * Method:    hello
 * Signature: (ILjava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_androidtest_MainActivity_hello
  (JNIEnv *, jobject, jint, jstring);

#ifdef __cplusplus
}
#endif
#endif

博主在編寫C層的時候Android Studio沒有出現代碼自動補全,解決辦法File > Invalidate Caches / Restart... > Click at Invalidate and Restart

/**
 * The implemention of com_example_androidtest_MainActivity.h
 * Created by DXCyber409 on 2019/6/9.
 */

#include <stdio.h>
#include <string.h>
#include "com_example_androidtest_MainActivity.h"

JNIEXPORT jstring JNICALL Java_com_example_androidtest_MainActivity_hello(JNIEnv *env, jobject obj, jint p1, jstring p2)
{
    uint8_t success;
    char tmp[256] = {0};
    // 將int參數轉換爲字符串
    sprintf(tmp, "%d", p1);
    // 將jstring轉換爲const char*
    const char* str_utf8 = env->GetStringUTFChars(p2, &success);
    if (success) {
        strcat(tmp, str_utf8);
        env->ReleaseStringUTFChars(p2, str_utf8);
    }
    // 將char*轉換爲jstring
    return env->NewStringUTF(tmp);
}

Java_固定前綴寫法這部分就全部實現完了,編譯等步驟看後續的章節。

0x03 JNI動態註冊寫法

JNI_OnLoad是安卓SO文件執行時,在較早時機會調用的一個初始化函數。此時通過JNIEnv環境指針調用RegisterNatives函數綁定Java層方法和native層的函數,可以在native函數被執行之前進行提前註冊,避免報錯的同時也省去了Java_固定路徑的長串寫法。

那麼就不需要javah,也不用配置External Tools命令行了,直接新建一個cpp文件開搞。

/**
 * Main module of libtest.so
 * Created by DXCyber409 on 2019/6/9.
 */

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

JNIEXPORT jstring JNICALL func_test(JNIEnv *env, jobject obj, jint p1, jstring p2)
{
    uint8_t success;
    char tmp[256] = {0};
    // 將int參數轉換爲字符串
    sprintf(tmp, "%d", p1);
    // 將jstring轉換爲const char*
    const char* str_utf8 = env->GetStringUTFChars(p2, &success);
    if (success) {
        strcat(tmp, str_utf8);
        env->ReleaseStringUTFChars(p2, str_utf8);
    }
    // 將char*轉換爲jstring
    return env->NewStringUTF(tmp);
}

JNINativeMethod gMethods[] = {
        {"hello", "(ILjava/lang/String;)Ljava/lang/String;", (void*)func_test},
};

static const char* const className = "com/example/androidtest/MainActivity";

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reversed)
{
    jclass myClass;
    JNIEnv* env = NULL;
    jint result = -1;

    // 從JavaVM中獲取JNIEnv
    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
        printf("get env error.");
        return -1;
    }

    // 獲取映射的java類
    myClass = env->FindClass(className);
    if (myClass == NULL) {
        printf("cannot get class:%s\n", className);
        return -1;
    }

    // 通過RegisterNatives方法動態註冊
    if (env->RegisterNatives(myClass, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) {
        printf("cannot get method:%s\n", gMethods[0].name);
        return -1;
    }

    return JNI_VERSION_1_4;
}

JNI的函數功能以及寫法不是本篇文章的講解目標,這裏不做過多說明。

0x04 SO文件編譯

有了源碼就需要編譯,此時ndk-build指令就派上用場。期間需要一個Makefile文件Android.mk,該文件指示了完成編譯所必需的特性。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := test
LOCAL_SRC_FILES := com_example_androidtest_MainActivity.cpp

include $(BUILD_SHARED_LIBRARY)

在上面的Android.mk中博主指定了編譯模塊的名稱爲test,所使用的源碼爲com_example_androidtest_MainActivity.cpp,編譯爲共享庫(SO)等。這一步Java_固定路徑寫法和JNI動態註冊寫法都通用。

具體含義可以參考:https://blog.csdn.net/zhandoushi1982/article/details/5316669

在命令行切換到jni目錄ndk-build,默認會生成所有架構的so文件。在jni/../libs文件有編譯好的成品。

0x04 集成SO文件

爲了在APK文件中的lib文件夾出現SO文件,還需要最後一步SO文件集成步驟。

博主使用的是Android Studio 3.4.1,這個版本會在執行ndk-build成功後直接將jni/../libs目錄下的所有SO集成到APK中,做其他配置反而會報錯。

於是博主就沒事做了。如果你的版本不是這個,不會自動集成SO文件的話,有兩個集成方案,具體參考:https://blog.csdn.net/lizhanqihd/article/details/79885606

0x05 編譯APK測試結果

Build->Build Bundle/APK->Build APK

生成APK文件後直接安裝到手機上測試吧,結果良好。

 

參考源

https://www.jianshu.com/p/faa3eebbd401

https://stackoverflow.com/questions/18370599/android-studio-auto-complete-and-other-features-not-working

posted @ 2019-06-09 01:30 DXCyber409 閱讀(...) 評論(...) 編輯 收藏

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