偶然发现 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 环境已经配置!
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 就可以看到 输出了。
在过程中遇到过一个问题, 给出的报错是:
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;
}