偶然发现 Android 生命周期函数 也可以通过 JNI 来调用。

在Android 2.3中Google开始逐渐的放宽NDK功能,新增的NativeActivity类允许Android开发者使用C/C++在NDK环境中处理 Activity的生命周期。

这可以更好的用来隐藏代码实际逻辑,以及在 apk 加固等方面 当下有相关的方案。所以试试手,这里通过 JNI 来写 Activity 的 onCreate 方法。

主要流程:

1、Android Studio 生成 Acivity 类, 编写 native 接口
2、javah -classpath xxx  生成 c++ 头文件(也可以自己手动写,根据 JNI 规则来)
3、编写 JNI 接口 实现代码,分为 静态注册 或者 动态注册两种方式
4、编写 NDK 模块生成脚本 Android.mk  和 Application.mk
5、ndk-build
6、添加 java代码 System.loadlibrary("xxx")  然后  Run
 

1、Android Studio 创建空工程就不说了,创建完  原有onCreate 改成 native 声明, 大概这样。

package com.example.myapplication;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    public MainActivity() {
    }

    public native void onCreate(Bundle var1);
}

2、默认设置好了 JDK 环境变量。  CMD 输入 

javah -verbose -classpath D:\AndroidSDK\platforms\android-28\android.jar:. -jni com.example.myapplication.MainActivity

注意 android.jar 后面的 冒号: 和  .

 生成的 头文件  com_example_myapplication_MainActivity.h

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

#ifndef _Included_com_example_myapplication_MainActivity
#define _Included_com_example_myapplication_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_myapplication_MainActivity
 * Method:    onCreate
 * Signature: (Landroid/os/Bundle;)V
 */
JNIEXPORT void JNICALL Java_com_example_myapplication_MainActivity_onCreate
  (JNIEnv *, jobject, jobject);

#ifdef __cplusplus
}
#endif
#endif

 

3、编写 JNI 代码,同时将 编写的 cpp 方法 注册到 java 类中的 native 方法声明进行绑定,也就是 将cpp 方法注册到 java 虚拟机环境中,这里有一个 动态注册和 静态注册的区别。

 动态 静态 是针对于 cpp 方法 与 java class 的绑定时机来区分的。 静态绑定 是在 编译后的链接过程就进行了绑定。 而动态注册 是在 链接过程绑定 一个映射关系数组。在load so的阶段才会 实际的通过 映射关系 将 java class 方法与 cpp 方法关联起来!

那么 动态注册 相对静态注册有什么优势呢? 比如 so JNI  代码的混淆,隐藏 关键方法接口,比如脚本资源等的解密接口 等

静态注册   接口 需要通过符号导出 到 java 类,java 又非常容易被解密  所以很容易暴露。  ida 一拖就看到了。

好,下面看代码,先看 静态注册 ,也就是直接实现 

void Java_com_example_myapplication_MainActivity_onCreate(JNIEnv *env, jobject thiz, jobject saved_instance_state) {
    LOGE("MainActivity  OnCreate be called!");
    //super.onCreate(savedInstanceState);
    jclass MainActivity = env->GetObjectClass(thiz);
    jclass AppCompatActivity = env->GetSuperclass(MainActivity);
    jmethodID onCreate = env->GetMethodID(AppCompatActivity, "onCreate", "(Landroid/os/Bundle;)V");
    env->CallNonvirtualVoidMethod(thiz, AppCompatActivity, onCreate, saved_instance_state); //调用父类方法
}

也就是直接实现从  JNI 导出的接口。build 出 so 后 在ida 中可以很明显的看到 Java_com_example_myapplication_MainActivity_onCreate 接口。

再来看动态注册 :

先将  com_example_myapplication_MainActivity.h 文件中的方法 声明 里的 函数名称 改成 

JNIEXPORT void JNICALL test(JNIEnv *, jobject, jobject);

然后 jni 代码:

#include <jni.h>
#include <string.h>
#include <cassert>
#include <android/log.h>

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "Tag", __VA_ARGS__)

#include "..\jni\com_example_myapplication_MainActivity.h"


void test(JNIEnv *env, jobject thiz, jobject saved_instance_state) {
    LOGD("MainActivity  Oncreate be called!!!!!");

    //super.onCreate(savedInstanceState);
    jclass MainActivity = env->GetObjectClass(thiz);
    jclass AppCompatActivity = env->GetSuperclass(MainActivity);
    jmethodID onCreate = env->GetMethodID(AppCompatActivity, "onCreate", "(Landroid/os/Bundle;)V");
    env->CallNonvirtualVoidMethod(thiz, AppCompatActivity, onCreate, saved_instance_state); //调用父类方法
}

static JNINativeMethod methods[] = {
        {"onCreate", "(Landroid/os/Bundle;)V", (void*)test}
};

static int registerNatives(JNIEnv *env) {
    const char *className = "com/example/myapplication/MainActivity";
    jclass clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }

    int methodsNum = sizeof(methods) / sizeof(methods[0]);
    if (env->RegisterNatives(clazz, methods, methodsNum) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }

    assert(env != NULL);
    if (!registerNatives(env)) {
        return -1;
    }
    //返回jni 的版本
    return JNI_VERSION_1_6;
}

 动态注册就是 在  JNI_OnLoad 方法中, 通过 JNIEnv 的 RegisterNatives 方法 进行  cpp 方法与 native 接口进行映射。

生成so 后,拖入 ida 中 将看到的  是 test 方法。

 

4、 编写 ndk 编译脚本,生成 so 的 方式 mk 或者 makefile 都行。

Android.mk 文件是用来配置 jni编译的 源文件输入、编译参数、编译输出产物 等等。

LOCAL_PATH := $(call my-dir) #指定当前位置
include $(CLEAR_VARS)    #清理编译符号 避免影响当前模块编译
LOCAL_MODULE    :=TestJni #指定模块名称
LOCAL_SRC_FILES :=../cpp/TestOncreate1.cpp #指定参与编译源文件

LOCAL_LDLIBS :=  -L$(SYSROOT)/usr/lib -llog   #打开log 开关


NDK_APP_DST_DIR := ../../../libs/$(TARGET_ARCH_ABI)  #指定模块生成 位置
include $(BUILD_SHARED_LIBRARY)  #指定模块类型

Application.mk 文件是用来 指定平台有关的配置信息,比如编译的平台版本、平台架构、使用的标准库 等等

APP_PLATFORM := android-28   #指定 Android 平台版本
NDK_TOOLCHAIN_VERSION=4.8     # 指定 ndk 工具链版本
APP_ABI :=all    # 指定生成 架构   mips   i386  x86  armv7  arm64 等等

 

5、 CMD 下 执行  ndk-build  默认 ndk 环境已经配置!

 

android Profiler生命周期显示 android生命周期函数_android

 so 动态库已经成功生成

 

6、 activity 中 添加  

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

同时 build.gradle 中添加 sourceSets.main

plugins {
    id 'com.android.application'
}
android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"
        archivesBaseName = "testJni"
    }

    sourceSets.main {
        jniLibs.srcDir 'libs'
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}

 直接 Run 就可以看到 输出了。

android Profiler生命周期显示 android生命周期函数_android_02

 

在过程中遇到过一个问题,  给出的报错是:  

Failed to register non-native method xxxxx  as native。

查看 RegisterNatives 源码后发现  导致 jni 绑定的时候 去找 native 的 onCreate 没有找到。

是我手误 在 Activity  native 方法声明 中  将 onCreate 写成了 oncreate。

这里贴出  RegisterNatives 源码:

static jint RegisterNatives(JNIEnv* env,
                              jclass java_class,
                              const JNINativeMethod* methods,
                              jint method_count) {
    if (UNLIKELY(method_count < 0)) {
      JavaVmExtFromEnv(env)->JniAbortF("RegisterNatives", "negative method count: %d",
                                       method_count);
      return JNI_ERR;  // Not reached except in unit tests.
    }
    CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", java_class, JNI_ERR);
    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
    ScopedObjectAccess soa(env);
    StackHandleScope<1> hs(soa.Self());
    Handle<mirror::Class> c = hs.NewHandle(soa.Decode<mirror::Class>(java_class));
    if (UNLIKELY(method_count == 0)) {
      LOG(WARNING) << "JNI RegisterNativeMethods: attempt to register 0 native methods for "
          << c->PrettyDescriptor();
      return JNI_OK;
    }
    CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", methods, JNI_ERR);
 
    // 1. 迭代每一个待注册的方法
    // 回顾一下之前的定义 
// JNINativeMethod method[] = {{"getString", "()Ljava/lang/String;", (void *) native_getString}};
    // 
typedef struct {
     //    const char* name; // java method name 
    //     const char* signature; // java method signature(args and return type)
   //      void*       fnPtr; //native method addr
  //    } JNINativeMethod;
 
    for (jint i = 0; i < method_count; ++i) {
      const char* name = methods[i].name;
      const char* sig = methods[i].signature;
      const void* fnPtr = methods[i].fnPtr;
      if (UNLIKELY(name == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "method name", i);
        return JNI_ERR;
      } else if (UNLIKELY(sig == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "method signature", i);
        return JNI_ERR;
      } else if (UNLIKELY(fnPtr == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "native function", i);
        return JNI_ERR;
      }
      bool is_fast = false;
      // Notes about fast JNI calls:
      //
      // On a normal JNI call, the calling thread usually transitions
      // from the kRunnable state to the kNative state. But if the
      // called native function needs to access any Java object, it
      // will have to transition back to the kRunnable state.
      //
      // There is a cost to this double transition. For a JNI call
      // that should be quick, this cost may dominate the call cost.
      //
      // On a fast JNI call, the calling thread avoids this double
      // transition by not transitioning from kRunnable to kNative and
      // stays in the kRunnable state.
      //
      // There are risks to using a fast JNI call because it can delay
      // a response to a thread suspension request which is typically
      // used for a GC root scanning, etc. If a fast JNI call takes a
      // long time, it could cause longer thread suspension latency
      // and GC pauses.
      //
      // Thus, fast JNI should be used with care. It should be used
      // for a JNI call that takes a short amount of time (eg. no
      // long-running loop) and does not block (eg. no locks, I/O,
      // etc.)
      //
      // A '!' prefix in the signature in the JNINativeMethod
      // indicates that it's a fast JNI call and the runtime omits the
      // thread state transition from kRunnable to kNative at the
      // entry.
      if (*sig == '!') { // 这个已经被弃用了
        is_fast = true;
        ++sig;
      }
 
      // Note: the right order is to try to find the method locally
      // first, either as a direct or a virtual method. Then move to
      // the parent.
      ArtMethod* m = nullptr;
      bool warn_on_going_to_parent = down_cast<JNIEnvExt*>(env)->GetVm()->IsCheckJniEnabled();
      for (ObjPtr<mirror::Class> current_class = c.Get();
           current_class != nullptr;
           current_class = current_class->GetSuperClass()) {
        // Search first only comparing methods which are native.
        m = FindMethod<true>(current_class, name, sig); // 找到是native方法且和name,sig相同的java方法
        if (m != nullptr) {
          break;
        }
 
        // Search again comparing to all methods, to find non-native methods that match.
        m = FindMethod<false>(current_class, name, sig); // 找到不是native方法且和name,sig相同的java方法
        if (m != nullptr) {
          break;
        }
 
        if (warn_on_going_to_parent) {
          LOG(WARNING) << "CheckJNI: method to register "" << name << "" not in the given class. "
                       << "This is slow, consider changing your RegisterNatives calls.";
          warn_on_going_to_parent = false;
        }
      }
 
      if (m == nullptr) {
        c->DumpClass(LOG_STREAM(ERROR), mirror::Class::kDumpClassFullDetail);
        LOG(ERROR)
            << "Failed to register native method "
            << c->PrettyDescriptor() << "." << name << sig << " in "
            << c->GetDexCache()->GetLocation()->ToModifiedUtf8();
        ThrowNoSuchMethodError(soa, c.Get(), name, sig, "static or non-static");
        return JNI_ERR;
      } else if (!m->IsNative()) {
        LOG(ERROR)
            << "Failed to register non-native method "
            << c->PrettyDescriptor() << "." << name << sig
            << " as native";
        ThrowNoSuchMethodError(soa, c.Get(), name, sig, "native");
        return JNI_ERR;
      }
 
      VLOG(jni) << "[Registering JNI native method " << m->PrettyMethod() << "]";
 
      if (UNLIKELY(is_fast)) {
        // There are a few reasons to switch:
        // 1) We don't support !bang JNI anymore, it will turn to a hard error later.
        // 2) @FastNative is actually faster. At least 1.5x faster than !bang JNI.
        //    and switching is super easy, remove ! in C code, add annotation in .java code.
        // 3) Good chance of hitting DCHECK failures in ScopedFastNativeObjectAccess
        //    since that checks for presence of @FastNative and not for ! in the descriptor.
        LOG(WARNING) << "!bang JNI is deprecated. Switch to @FastNative for " << m->PrettyMethod();
        is_fast = false;
        // TODO: make this a hard register error in the future.
      }
 
      const void* final_function_ptr = class_linker->RegisterNative(soa.Self(), m, fnPtr);// --------
      UNUSED(final_function_ptr);
    }
    return JNI_OK;
  }