當(dāng)前位置:首頁 > IT技術(shù) > 移動(dòng)平臺(tái) > 正文

Android NativeActivity 初探
2021-09-17 19:51:31

Android NativeActivity 初探

最近在刷題反編譯一個(gè)應(yīng)用時(shí),解壓apk這個(gè)壓縮包,發(fā)現(xiàn)里面根本沒有classes.dex,后來四處摸索,在Androidmanifest.xml里 的activity屬性里面發(fā)現(xiàn)了這個(gè):

 android:name="android.app.NativeActivity

看見這個(gè)native單詞,就差不多猜到我們的activity應(yīng)該是藏在了so庫里面了,然后就開始上網(wǎng)到處搜索這個(gè)沒有任何java的nativeActivity的資料。

1.編寫第一個(gè)NativeActivity應(yīng)用

要想了解NativeActivity與普通應(yīng)用的不同,可以自己編寫一個(gè)應(yīng)用來試試。

0x01:

首先在Android studio新建一個(gè)項(xiàng)目-->選擇no activity-->名字隨便起一個(gè)-->然后就到了主界面:
image-20210916114840651

因?yàn)槲覀冞@個(gè)項(xiàng)目不需要寫java代碼,所以我們可以把這個(gè)java目錄刪除了。然后再新建一個(gè)cpp目錄來存放我們的native層的代碼,然后再在隨便一個(gè)目錄下新建一個(gè)文件,我的實(shí)在app目錄下新建,取名為CMakeList.txt,位置在哪無所謂,只要在我們項(xiàng)目的build.gradle下指明位置即可,如圖:

image-20210916203519302

0x02:

接下來我們還要解決幾個(gè)問題:

  • 寫一個(gè)我們的Native的activity,需要在我們的main.cpp中去實(shí)現(xiàn)
  • 我們的android設(shè)備是如何知道這個(gè)activity在native層的?我們要在AndroidMainfest.xml中聲明
  • 如何將我們的main.cpp編譯成so庫,通過編寫CMakeLists的內(nèi)容來實(shí)現(xiàn)
  • 還要告知我們CMakeLists的位置,因?yàn)樗奈恢貌皇潜仨毜模晕覀円赼pp目錄下build.gradle聲明
0x03:

首先是AndroidMainfest.xml的編寫,

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mass.nativetest">
    <!-- 只有在這版本之上的api才可以實(shí)現(xiàn)NativeActivity-->
    <uses-sdk android:minSdkVersion="9" />
    
      <!-- 因?yàn)槲覀兊膽?yīng)用不含java代碼,所以要hascode這個(gè)屬性要設(shè)為false. -->
    <application
         android:hasCode="false"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.NativeTest">
        <!-- 告訴我們Activity的信息-->
        <activity android:name="android.app.NativeActivity">
            <!-- 指定程序的入口點(diǎn) -->
            <meta-data android:name="android.app.lib_name"
                android:value="TestLib">
            </meta-data>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
  • 我們首先添加了這兩行代碼:
 <uses-sdk android:minSdkVersion="9" />
  android:hasCode="false"

雖然在大部分情況下我們沒加這兩行代碼,可能生成安裝也不會(huì)報(bào)錯(cuò),但官方還是說要加上,所以最好還是加上為好。

  • 然后是在activity 里面定義了
<activity android:name="android.app.NativeActivity">

我們在生成普通的程序往往是下面這樣:

 <activity android:name=".MainActivity">

首先這個(gè)屬性是規(guī)定實(shí)現(xiàn)Activity的類的名稱,是我們Android系統(tǒng)四大組件 Activity 的子孫類。 該屬性值應(yīng)為完全限定類名稱(例如,“com.mass.nativetest.MainActivity”)。但我們的.MainActivity之所以可行是因?yàn)槿绻鸄ndroid:name屬性的第一個(gè)字符是"."的話,則名稱將追加到 < manifest > 元素中指定的軟件包名稱。開發(fā)者必須指定該名稱。

這樣我們就知道我們普通程序這樣寫的理由了,然后我們的NativeActivity的android:name為什么是"android.app.NativeActivity"。我們都知道這個(gè)屬性是規(guī)定實(shí)現(xiàn)Activity的類的名稱,我們只要找到這個(gè)NativeActivity在哪就行,其實(shí)這個(gè)NativeActivity是位于我們的Android系統(tǒng)里的,他是我們Activity的直接子類,我們引用的activity其實(shí)就是Android系統(tǒng)自帶的,我們可以在安卓源碼上找到它: /frameworks/base/core/java/android/app/NativeActivity.java,所以這也是我們?yōu)槭裁匆砑舆@個(gè)屬性來限定我們的api平臺(tái),因?yàn)槲覀內(nèi)绻沧堪姹据^低的話,就會(huì)導(dǎo)致沒有這個(gè)NativeActivity從而報(bào)錯(cuò)。

  • 我們還在我們的activity中添加了meta-data元素:
            <meta-data android:name="android.app.lib_name"
                android:value="TestLib">
            </meta-data>

告訴NativeActivity我們so庫的名字,如果未指定,則會(huì)默認(rèn)使用“main”作s為我們的so庫,到時(shí)候NativieActivity就會(huì)按照這個(gè)名字查找目標(biāo)庫文件。除此之外,我們還可以添加一個(gè)

            <meta-data android:name="android.app.func_name"
                android:value="xxx">
            </meta-data>

它的作用是告知NativityActivity我們的程序的入口點(diǎn),就是本機(jī)代碼中此本機(jī)活動(dòng)的主入口點(diǎn)的名稱,如果未指定,就默認(rèn)使用“ANativeActivity_onCreate”作為我們程序的入口函數(shù)。比如說我只設(shè)置了第一個(gè)指定so的名字為TestLib,然后沒有指定入口函數(shù)的名字,所以NativeActivity就會(huì)將我們的入口定在libTestLib.so的ANativeActivity_onCreate函數(shù),到時(shí)候NativeActivity就會(huì)執(zhí)行這個(gè)函數(shù)來執(zhí)行我們自己編寫的代碼

我們可以在源碼找到以上操作對(duì)應(yīng)的代碼:

public class NativeActivity extends Activity implements SurfaceHolder.Callback2,InputQueue.Callback, OnGlobalLayoutListener {
    public static final String META_DATA_LIB_NAME = "android.app.lib_name";
    public static final String META_DATA_FUNC_NAME = "android.app.func_name"
}
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        String libname = "main";
        String funcname = "ANativeActivity_onCreate";
?```````
//從我們AndroidManifest.xml的meta-data中提取入口函數(shù)和so庫,如果沒有就使用"ANativeActivity_onCreate"和"main"
        try {
            ai = getPackageManager().getActivityInfo(
                    getIntent().getComponent(), PackageManager.GET_META_DATA);
            if (ai.metaData != null) {
                String ln = ai.metaData.getString(META_DATA_LIB_NAME);
                if (ln != null) libname = ln;
                ln = ai.metaData.getString(META_DATA_FUNC_NAME);
                if (ln != null) funcname = ln;
            }
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException("Error getting activity info", e);
        }
      	//獲取so庫的路徑
        BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader();
        String path = classLoader.findLibrary(libname);
		if (path == null) {
            throw new IllegalArgumentException("Unable to find native library " + libname +" using classloader: " + classLoader.toString()); }
		//通過loadNativeCode加載我們的原生代碼
        byte[] nativeSavedState = savedInstanceState != null ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;
        mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(),
                getAbsolutePath(getFilesDir()), getAbsolutePath(getObbDir()),
                getAbsolutePath(getExternalFilesDir(null)),
                Build.VERSION.SDK_INT, getAssets(), nativeSavedState,
                classLoader, classLoader.getLdLibraryPath());
                if (mNativeHandle == 0) {
            		throw new UnsatisfiedLinkError(
                    "Unable to load native library "" + path + "": " + getDlError());
		        }
        super.onCreate(savedInstanceState);
    }
    private native long loadNativeCode(String path, String funcname, MessageQueue queue,
            String internalDataPath, String obbPath, String externalDataPath, int 					sdkVersion, AssetManager assetMgr, byte[] savedState, ClassLoader 						classLoader, String libraryPath);

NativeActivity將我們的一些配置信息,傳給了loadNativityCode這個(gè)原生函數(shù),這個(gè)native函數(shù)位于安卓源碼:

/frameworks/base/core/jni/android_app_NativeActivity.cpp,在該文件進(jìn)行動(dòng)態(tài)注冊:

static const JNINativeMethod g_methods[] = {
    { "loadNativeCode","	(Ljava/lang/String;Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;[BLjava/lang/ClassLoader;Ljava/lang/String;)J",(void*)loadNativeCode_native },
    ```
	}
static jlong loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName,jobject messageQueue, jstring internalDataDir, jstring obbDir,jstring externalDataDir, jint sdkVersion, jobject jAssetMgr,jbyteArray savedState, jobject classLoader, jstring libraryPath) {
    if (kLogTrace) {        
        ALOGD("loadNativeCode_native");
    }
    const char* pathStr = env->GetStringUTFChars(path, NULL);
    std::unique_ptr<NativeCode> code;
    bool needNativeBridge = false;
    //打開so庫,返回句柄
    void* handle = OpenNativeLibrary(env, sdkVersion, pathStr, classLoader, libraryPath);
    if (handle == NULL) {
        if (NativeBridgeIsSupported(pathStr)) {
            handle = NativeBridgeLoadLibrary(pathStr, RTLD_LAZY);
            needNativeBridge = true;
        }
    }
    env->ReleaseStringUTFChars(path, pathStr);
	//查找函數(shù),返回目標(biāo)函數(shù)的指針
    if (handle != NULL) {
        void* funcPtr = NULL;
        const char* funcStr = env->GetStringUTFChars(funcName, NULL);
        if (needNativeBridge) {
            funcPtr = NativeBridgeGetTrampoline(handle, funcStr, NULL, 0);
        } else {
            funcPtr = dlsym(handle, funcStr);
        }

        code.reset(new NativeCode(handle, (ANativeActivity_createFunc*)funcPtr));
        env->ReleaseStringUTFChars(funcName, funcStr);

        if (code->createActivityFunc == NULL) {
            ALOGW("ANativeActivity_onCreate not found");
            return 0;
        }

        code->messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueue);
        if (code->messageQueue == NULL) {
            ALOGW("Unable to retrieve native MessageQueue");
            return 0;
        }
		//native層線程管道的設(shè)置
        int msgpipe[2];
        if (pipe(msgpipe)) {
            ALOGW("could not create pipe: %s", strerror(errno));
            return 0;
        }
        code->mainWorkRead = msgpipe[0];
        code->mainWorkWrite = msgpipe[1];
        int result = fcntl(code->mainWorkRead, F_SETFL, O_NONBLOCK);
        SLOGW_IF(result != 0, "Could not make main work read pipe "
                "non-blocking: %s", strerror(errno));
        result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK);
        SLOGW_IF(result != 0, "Could not make main work write pipe "
                "non-blocking: %s", strerror(errno));
        code->messageQueue->getLooper()->addFd(
        code->mainWorkRead, 0, ALOOPER_EVENT_INPUT, mainWorkCallback, code.get());
		//得到我們的VM指針,這個(gè)在寫jni應(yīng)用的時(shí)候也會(huì)用到
        code->ANativeActivity::callbacks = &code->callbacks;
        if (env->GetJavaVM(&code->vm) < 0) {
            ALOGW("NativeActivity GetJavaVM failed");
            return 0;
        }
        code->env = env;
        code->clazz = env->NewGlobalRef(clazz);
		//設(shè)置internalData的路徑
        const char* dirStr = env->GetStringUTFChars(internalDataDir, NULL);
        code->internalDataPathObj = dirStr;
        code->internalDataPath = code->internalDataPathObj.string();
        env->ReleaseStringUTFChars(internalDataDir, dirStr);

        if (externalDataDir != NULL) {
            dirStr = env->GetStringUTFChars(externalDataDir, NULL);
            code->externalDataPathObj = dirStr;
            env->ReleaseStringUTFChars(externalDataDir, dirStr);
        }
        code->externalDataPath = code->externalDataPathObj.string();

        code->sdkVersion = sdkVersion;

        code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
		//獲取我們obb文件的路徑,obb(paque Binary Blob)Android為了大型軟件或者游戲設(shè)置的一種格式。
        if (obbDir != NULL) {
            dirStr = env->GetStringUTFChars(obbDir, NULL);
            code->obbPathObj = dirStr;
            env->ReleaseStringUTFChars(obbDir, dirStr);
        }
        code->obbPath = code->obbPathObj.string();
		//讀取傳過來的State
        jbyte* rawSavedState = NULL;
        jsize rawSavedSize = 0;
        if (savedState != NULL) {
            rawSavedState = env->GetByteArrayElements(savedState, NULL);
            rawSavedSize = env->GetArrayLength(savedState);
        }

        code->createActivityFunc(code.get(), rawSavedState, rawSavedSize);

        if (rawSavedState != NULL) {
            env->ReleaseByteArrayElements(savedState, rawSavedState, 0);
        }
    }

    return (jlong)code.release();
}

該函數(shù)將我們傳入的參數(shù)進(jìn)行了處理,并設(shè)置相對(duì)應(yīng)的值給我們的code這個(gè)結(jié)構(gòu)體變量,然后返回code.release()的結(jié)果,release()是一個(gè)釋放捕捉的函數(shù),就是將我們code的這個(gè)結(jié)構(gòu)體所占用的設(shè)備的控制權(quán)釋放,(我也不知道該code結(jié)構(gòu)體所占用的設(shè)備是什么,只是去找release這個(gè)函數(shù)的解釋說的,反正不會(huì)是釋放內(nèi)存,哪有剛存東西就釋放掉的,至于這個(gè)返回值有什么用,我也搞不懂,網(wǎng)上查也找不到。但根據(jù)NativeActivty.java的利用這返回值的名稱為 mNativeHandle ,所以返回值是一個(gè)句柄)該結(jié)構(gòu)體繼承自ANativeActivity,該結(jié)構(gòu)體定義位于:/frameworks/native/include/android/native_activity.h這個(gè)頭文件中,是一個(gè)比較簡單的結(jié)構(gòu)體

//結(jié)構(gòu)體的定義
struct NativeCode : public ANativeActivity {
    NativeCode(void* _dlhandle, ANativeActivity_createFunc* _createFunc) {
        memset((ANativeActivity*)this, 0, sizeof(ANativeActivity));
        memset(&callbacks, 0, sizeof(callbacks));
        dlhandle = _dlhandle;
        createActivityFunc = _createFunc;
        nativeWindow = NULL;
        mainWorkRead = mainWorkWrite = -1;
    }

    ~NativeCode() {
        if (callbacks.onDestroy != NULL) {
            callbacks.onDestroy(this);
        }
        if (env != NULL && clazz != NULL) {
            env->DeleteGlobalRef(clazz);
        }
        if (messageQueue != NULL && mainWorkRead >= 0) {
            messageQueue->getLooper()->removeFd(mainWorkRead);
        }
        setSurface(NULL);
        if (mainWorkRead >= 0) close(mainWorkRead);
        if (mainWorkWrite >= 0) close(mainWorkWrite);
        if (dlhandle != NULL) {
        }
    }
//設(shè)置變量
NativeCode* code = static_cast<NativeCode*>(activity);
//ANativeActivity結(jié)構(gòu)體的定義    
typedef struct ANativeActivity {
    struct ANativeActivityCallbacks* callbacks;    JavaVM* vm;    JNIEnv* env;
	jobject clazz;    const char* internalDataPath;    const char* externalDataPath;         int32_t sdkVersion;    void* instance;    AAssetManager* assetManager;
    const char* obbPath;
} ANativeActivity;

總而言之,我們應(yīng)用的Activity也就是NativeActivity,在onCreate函數(shù)中會(huì)將我們定義在Androidmanifest.xml的meta-data的內(nèi)容和當(dāng)前環(huán)境的一些信息和設(shè)備的信息傳入到 ANativeActivity的結(jié)構(gòu)體中保存,然后調(diào)用super.onCreate()函數(shù),最終然后調(diào)用我們so庫的入口函數(shù),如果沒有設(shè)置的話是“ANativeActivity_onCreate”。

  • 我們在CMakeLists.txt設(shè)置點(diǎn)東西,以便將我們編寫main.cpp編譯成so庫:
cmake_minimum_required(VERSION 3.4.1)
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
add_library(
        TestLib
        SHARED
        src/main/cpp/main.cpp
        )
add_library(
        glue
        STATIC
        ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
)
target_link_libraries(
        TestLib
        log
        android
        glue
        )

第一個(gè)是為了限定cmake的最低版本,這個(gè)沒什么好說的,第二個(gè)就是include我們ndk目錄下的一個(gè)頭文件,因?yàn)槲覀兊膍ain函數(shù)需要用到native_app_glue.c,而native_app_glue.c和native_app_glue.h位于我們的ndk目錄,所以要將其include進(jìn)來。第三個(gè)和第四個(gè)就是將他們編譯成庫,第一個(gè)參數(shù)是庫的名字,第二個(gè)是參數(shù)是庫的類型,第三個(gè)就是要編譯文件的路徑。然后最后一個(gè)就是,添加依賴,比如說第一個(gè)庫里面需要用到第二個(gè)庫的函數(shù),就需要對(duì)他們進(jìn)行鏈接才可以,這個(gè)函數(shù)的參數(shù)順序不能打亂。

  • 然后我們在我們項(xiàng)目的構(gòu)建腳本build.gradle告知我們CMakeList.txt的位置
//在Android下的defaultConfig下添加
        externalNativeBuild{
            cmake{
                cppFlags "-std=c++11"
            }
        }
//在Android下添加
 externalNativeBuild{
        cmake{
            path "CMakeLists.txt"
        }
    }

這兩個(gè)都沒什么好說的,注意別放錯(cuò)位置就行。

  • 最后就是在我們的main.cpp函數(shù)添加自己的代碼
#include <android_native_app_glue.h>
#include <android/log.h>

#define TAG "NativeTest"
#define ALOGD(__VA_ARGS__) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
void cmd(android_app* app,int32_t state);
void android_main(android_app* app)
{
    app_dummy();
    ALOGD("android_main() started");
    android_poll_source* src;
    int state;
    app->onAppCmd = cmd;
    while(1)
    {
        while((state = ALooper_pollAll(0, nullptr,nullptr,(void**)&src)) >= 0)
        {
            if(src)
            {
                src->process(app,src);
            }
            if(app->destroyRequested)
            {
                ALOGD("android_main() exited");
                return;
            }
        }
    }
}
void cmd(android_app* app,int32_t state)
{
    switch(state)
    {
        case APP_CMD_TERM_WINDOW:
            ALOGD("window terminated");
            break;
        case APP_CMD_INIT_WINDOW:
            ALOGD("window initialized");
            break;
        case APP_CMD_GAINED_FOCUS:
            ALOGD("gained focus");
            break;
        case APP_CMD_LOST_FOCUS:
            ALOGD("lost focus");
            break;
        case APP_CMD_SAVE_STATE:
            ALOGD("saved state");
            break;
        default:
            break;
    }
}

直接把這些拷貝過去就行,關(guān)于main.cpp詳細(xì)的編寫,由于設(shè)計(jì)了一些循環(huán)和繪圖類的(畢竟我們沒有setContentView),會(huì)比較復(fù)雜,等下篇博客再寫。然后這樣我們的第一個(gè)程序就編寫完成了,我們來看看運(yùn)行結(jié)果:

2.運(yùn)行結(jié)果

image-20210917170139653

雖然什么都沒有,因?yàn)槲覀儧]有設(shè)置布局,但不閃退就算成功。

3.總結(jié)

在編寫的過程中,我們可以發(fā)現(xiàn)雖然我們沒有編寫任何java代碼,但我們的程序還是使用了java代碼,比如那個(gè)NativeActivity.java的使用,我們native層代碼的使用也還是通過jni來實(shí)現(xiàn)。在這個(gè)編寫過程中為了理清順序,實(shí)現(xiàn)原理在Android源碼上跑來跑去還是挺舒服,雖然很浪費(fèi)時(shí)間。

本文摘自 :https://www.cnblogs.com/

開通會(huì)員,享受整站包年服務(wù)立即開通 >