当前位置:首页 > 科技新闻 > 移动平台 > 正文

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/