• Android Activity 生命周期函数 Native 化


    偶然发现 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;
      }
  • 相关阅读:
    在IDEA通过Maven构建Scala项目
    6.Pair RDD操作
    5.RDD的Action操作和持久化persist()
    29.Spark SQL发展史
    AirFlow初始化的时候遇到 Global variable explicit_defaults_for_timestamp needs to be on (1) for mysql
    4.RDD操作之Transform
    3.RDD详解和创建RDD方式
    28.Spark中action的介绍
    2.Spark 2.x 集群部署和测试
    Repeater分页
  • 原文地址:https://www.cnblogs.com/lesten/p/16286387.html
Copyright © 2020-2023  润新知