• 當前位置:首頁 > IT技術 > 移動平臺 > 正文

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

    Android NativeActivity 初探

    最近在刷題反編譯一個應用時,解壓apk這個壓縮包,發現里面根本沒有classes.dex,后來四處摸索,在Androidmanifest.xml里 的activity屬性里面發現了這個:

     android:name="android.app.NativeActivity
    

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

    1.編寫第一個NativeActivity應用

    要想了解NativeActivity與普通應用的不同,可以自己編寫一個應用來試試。

    0x01:

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

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

    image-20210916203519302

    0x02:

    接下來我們還要解決幾個問題:

    • 寫一個我們的Native的activity,需要在我們的main.cpp中去實現
    • 我們的android設備是如何知道這個activity在native層的?我們要在AndroidMainfest.xml中聲明
    • 如何將我們的main.cpp編譯成so庫,通過編寫CMakeLists的內容來實現
    • 還要告知我們CMakeLists的位置,因為它的位置不是必須的,所以我們要在app目錄下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才可以實現NativeActivity-->
        <uses-sdk android:minSdkVersion="9" />
        
          <!-- 因為我們的應用不含java代碼,所以要hascode這個屬性要設為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">
                <!-- 指定程序的入口點 -->
                <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"
    

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

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

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

     <activity android:name=".MainActivity">
    

    首先這個屬性是規定實現Activity的類的名稱,是我們Android系統四大組件 Activity 的子孫類。 該屬性值應為完全限定類名稱(例如,“com.mass.nativetest.MainActivity”)。但我們的.MainActivity之所以可行是因為如果Android:name屬性的第一個字符是"."的話,則名稱將追加到 < manifest > 元素中指定的軟件包名稱。開發者必須指定該名稱。

    這樣我們就知道我們普通程序這樣寫的理由了,然后我們的NativeActivity的android:name為什么是"android.app.NativeActivity"。我們都知道這個屬性是規定實現Activity的類的名稱,我們只要找到這個NativeActivity在哪就行,其實這個NativeActivity是位于我們的Android系統里的,他是我們Activity的直接子類,我們引用的activity其實就是Android系統自帶的,我們可以在安卓源碼上找到它: /frameworks/base/core/java/android/app/NativeActivity.java,所以這也是我們為什么要添加這個屬性來限定我們的api平臺,因為我們如果安卓版本較低的話,就會導致沒有這個NativeActivity從而報錯。

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

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

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

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

    我們可以在源碼找到以上操作對應的代碼:

    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中提取入口函數和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這個原生函數,這個native函數位于安卓源碼:

    /frameworks/base/core/jni/android_app_NativeActivity.cpp,在該文件進行動態注冊:

    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);
    	//查找函數,返回目標函數的指針
        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層線程管道的設置
            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指針,這個在寫jni應用的時候也會用到
            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);
    		//設置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為了大型軟件或者游戲設置的一種格式。
            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();
    }
    

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

    //結構體的定義
    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) {
            }
        }
    //設置變量
    NativeCode* code = static_cast<NativeCode*>(activity);
    //ANativeActivity結構體的定義    
    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;
    

    總而言之,我們應用的Activity也就是NativeActivity,在onCreate函數中會將我們定義在Androidmanifest.xml的meta-data的內容和當前環境的一些信息和設備的信息傳入到 ANativeActivity的結構體中保存,然后調用super.onCreate()函數,最終然后調用我們so庫的入口函數,如果沒有設置的話是“ANativeActivity_onCreate”。

    • 我們在CMakeLists.txt設置點東西,以便將我們編寫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
            )
    

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

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

    這兩個都沒什么好說的,注意別放錯位置就行。

    • 最后就是在我們的main.cpp函數添加自己的代碼
    #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;
        }
    }
    

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

    2.運行結果

    image-20210917170139653

    雖然什么都沒有,因為我們沒有設置布局,但不閃退就算成功。

    3.總結

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

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

    開通會員,享受整站包年服務
    国产呦精品一区二区三区网站|久久www免费人咸|精品无码人妻一区二区|久99久热只有精品国产15|中文字幕亚洲无线码