美文网首页android技术
NDK JNI开发 之 Android Studio中动态注册(

NDK JNI开发 之 Android Studio中动态注册(

作者: Sharkchilli | 来源:发表于2020-06-08 17:59 被阅读0次

前言

上一章我们对JNI静态注册进行了讨论,现在我们就基于Android Studio和C++来讨论一下JNI的动态注册。因为要熟悉不同平台的开发环境所以JNI的这一系列我都换了不同的环境。其实我们依然可以使用上一章手动的方法编写动态注册的so,但是Android Studio提供了CMake,方便了我们的开发流程

环境

Android Studio中需安装CMake

image.png

创建项目

image.png

这里不同的Android Studio版本中会有所出入,不过你在创建的时候找找应该都能找到

image.png
image.png

完成

image.png

IDE已经帮我们写了一个简单的JIN例子,我们只要照着他修改即可
这里我们关注三个文件

  • MainActivity.java 这个是我们Android的主界面,我也将native写进了这里
  • CMakeLists.txt 这个是CMake的配置文件(它取代了我们以前Application.mk和Android.mk)
  • native-lib.cpp 我们so的源码文件(这里我使用c++编写)

MainActivity.java

package com.example.jnitest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

native-lib.cpp

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnitest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

CMakeLists.txt

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.
# 指定CMark构建本地库时所需的最小版本
cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

# 创建并命名库  native-lib 动态库的名称,“SHARED ”表示加载的是动态库。 native-lib.cpp源码路径
# set(DIR ${CMAKE_SOURCE_DIR}/libs) ${CMAKE_SOURCE_DIR}表示的是CMakeLists.txt所在的目录 其实应该有一段路径设置的 这里cmake应该默认 这里我们不深究
add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
# 引用android系统的系统库,如log库,是打印日志用的库 我们也可以自己实现
find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
# 与第三库链接生成目标native-lib库
target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

MainActivity.java与native-lib.cpp的内容不再赘述(前两章都有说明),CMakeLists.txt注释中简单进行了说明,其实英文注释已经说得很清楚了。

运行

image.png

编写我们自己的c++文件

我们只需要在原来的基础上拷贝修改一下即可


image.png

修改三个文件如下:

MainActivity.java

package com.example.ancpp;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
        System.loadLibrary("Mylib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(dynamicJavaMethod2());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    public native void dynamicJavaMethod1(int i);
    public native String dynamicJavaMethod2();
}

CMakeLists.txt

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.
# 指定CMark构建本地库时所需的最小版本
cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

# 创建并命名库  native-lib 动态库的名称,“SHARED ”表示加载的是动态库。 native-lib.cpp源码路径
# set(DIR ${CMAKE_SOURCE_DIR}/libs) ${CMAKE_SOURCE_DIR}表示的是CMakeLists.txt所在的目录 其实应该有一段路径设置的 这里cmake应该默认 这里我们不深究
add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)

add_library( # Sets the name of the library.
        Mylib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        Mylib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
# 引用android系统的系统库,如log库,是打印日志用的库 我们也可以自己实现
find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
# 与第三库链接生成目标native-lib库
target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

target_link_libraries( # Specifies the target library.
        Mylib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

Mylib.cpp(重点是Mylib.cpp的编写,因为IDE给的例子是静态注册,我们这是动态注册)

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

JavaVM *_vm;

//动态注册的第一个方法
void dynamicJNIMethod1(JNIEnv *env, jobject jobj, jint ji) {
    __android_log_print(ANDROID_LOG_ERROR, "JNI", "第一个动态注册方法传递的参数:%d", ji);
}

//动态注册的第二个方法
jstring dynamicJNIMethod2(JNIEnv *env, jobject jobj) {
    jstring returnStr = env->NewStringUTF("第二个动态注册方法返回的字符串");
    return returnStr;
}


//需要动态注册的方法组
static JNINativeMethod gMethods[] = {
        {"dynamicJavaMethod1", "(I)V",(void *) dynamicJNIMethod1},
        //()Ljava/lang/String; 函数签名不要用.
        {"dynamicJavaMethod2", "()Ljava/lang/String;", (void *) dynamicJNIMethod2}
};
//需要动态注册的类全名
static const char *mClassName = "com/example/ancpp/MainActivity";

//此函数通过调用RegisterNatives方法来注册我们的函数

static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *getMethods,
                                 int methodsNum) {
    jclass clazz;
    //找到声明native方法的类
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }

    jint result = 0;
    result = env->RegisterNatives(clazz, gMethods, methodsNum);
    //注册函数 参数:java类 所要注册的函数数组 注册函数的个数
    if ( result< 0) {

        __android_log_print(ANDROID_LOG_ERROR, "JNI", "RegisterNatives:%d", result);
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

static int registerNatives(JNIEnv *env) {
    const char *className = "com/example/ancpp/MainActivity";
    return registerNativeMethods(env, className, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));
}

//动态注册时都会执行到这个方法中
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;//定义JNI Env
    jint result = -1;
    /*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
     */
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return result;
    }
    /*开始注册
     * 传入参数是JNI env
     * 以registerNatives(env)为例说明
     */

    if (!registerNatives(env)) {
        return -1;
    }
    _vm = vm;

    result = JNI_VERSION_1_6;
    return result;
}

运行

image.png

代码注释已经写得很清楚了,我们只讨论一下细节和要注意的地方

  • 方法签名
    我们要注册类的native方法必须在JNINativeMethod数组中指定方法签名,我们如何得到这个签名呢?你可以手写,单java提供了获取方法签名的工具
    进入到你项目的classes文件夹
1、javap -s packagename.classname
2、javap -s -p packagename.classname
-s表示打印签名信息
-p表示打印所有函数和成员的签名信息,默认只打印public的签名信息。

例如:javap -s -p com.example.ancpp.MainActivity 得到以下信息(descriptor后面就是签名信息):

public class com.example.ancpp.MainActivity extends androidx.appcompat.app.AppCompatActivity {
  public com.example.ancpp.MainActivity();
    descriptor: ()V

  protected void onCreate(android.os.Bundle);
    descriptor: (Landroid/os/Bundle;)V

  public native java.lang.String stringFromJNI();
    descriptor: ()Ljava/lang/String;

  public native void dynamicJavaMethod1(int);
    descriptor: (I)V

  public native java.lang.String dynamicJavaMethod2();
    descriptor: ()Ljava/lang/String;

  static {};
    descriptor: ()V
  • JNIEnv 与 JavaVM

JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局仅仅有一个;
JNIEnv : JavaVM 在线程中的代表, 每一个线程都有一个, JNI 中可能有非常多个 JNIEnv;

详细介绍可以看一下这篇文章:https://www.cnblogs.com/mfmdaoyou/p/7252031.html

总结

在java代码中无论是静态注册还是动态注册其实都没什么变化,主要体现在c和c++代码中。

  • 静态注册
    需要遵守jni的命名规则,并且将写的函数导出,这样jni就能一一对应c函数和java方法的关系
  • 动态注册
    动态注册在java层loadLibrary的时候回去执行so中的JNI_OnLoad函数,所以这个函数必须导出,在这里面我们使用env->RegisterNatives的函数去依照JNINativeMethod数组给的信息去指定的类中建立方法调用对应关系。

在逆向分析so的时候使用动态注册可以有更好的隐秘性,因为我们只导出JNI_OnLoad,很多时候脱壳的操作就会在这里。

相关文章

网友评论

    本文标题:NDK JNI开发 之 Android Studio中动态注册(

    本文链接:https://www.haomeiwen.com/subject/khnrtktx.html