程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2023-06(1)

Android so库生成教程

发布于2021-05-29 19:37     阅读(842)     评论(0)     点赞(24)     收藏(5)


一. 准备工作

ndk下载:https://developer.android.google.cn/ndk/downloads/

  1. 首先配置终端的ndk命令:

    • 启动终端Terminal

    • 输入cd~,进入当前用户的home目录

    • 如果没有.bash_profile文件,先输入touch .bash_profile进行创建

    • 输入open -e .bash_profile 编辑.bash_profile文件,加入下面语句

      • export PATH=${PATH}:“这里填你的ndk安装路径”

    • 保存并关闭.bash_profile文件

    • 输入source .bash_profile,更新刚刚配置的环境变量

    • 命令行输入ndk-build,验证是否配置成功,出现下图表示配置成功

     

  2. 另外,Android Studio中也不要忘记设置(一般会默认设置成Android Sdk中的ndk路径,但是自己如果下载了最新的ndk,可以替换),打开File --> Project Structure:

     

 

二. JNI文件编写

  1. 创建一个含有native方法的java文件:

    1. package com.hyf.kaviewer.jni;
    2. /**
    3. * Created by heyf on 2019/2/28
    4. */
    5. public class JniTest {
    6.    public static native void getSecret();
    7. }
  2. 通过javah命令生成.h头文件

    首先需要进入到当前项目当前module的java路径下:

    cd /Users/admin/Demo-Project/KAViewer/app/src/main/java

    然后输入:

    javah -d ../jni com.hyf.kaviewer.jni.JniTest

    生成的.h文件如下所示(jni路径也是自动生成)

    .h文件的内容为:

    红框中的方法与java文件中所写的方法相对应,我们马上也会在.cpp文件中实现它。

  3. 创建.cpp文件

    复制一份.h文件并将后缀改为.cpp

    接下来编辑.cpp文件的内容:

     具体代码如下:

    1. #include <android/log.h>
    2. #include <string.h>
    3. #include "com_hyf_kaviewer_jni_JniTest.h"
    4. #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "security", __VA_ARGS__))
    5. #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "security", __VA_ARGS__))
    6. static const char *SIGN = "ABCDEFGHIJKLMN";
    7. static const char *SECRET = "1111111111111111111";
    8. static int verifySign(JNIEnv *env);
    9. jstring Java_com_hyf_kaviewer_jni_JniTest_getSecret(JNIEnv *env, jclass type){
    10.      return env->NewStringUTF(SECRET);
    11. }
    12. jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    13.    JNIEnv *env = NULL;
    14.    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
    15.        return JNI_ERR;
    16.   }
    17.    if (verifySign(env) == JNI_OK) {
    18.        return JNI_VERSION_1_4;
    19.   }
    20.    LOGE("签名不一致!");
    21.    return JNI_ERR;
    22. }
    23. static jobject getApplication(JNIEnv *env) {
    24.    jobject application = NULL;
    25.    jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
    26.    if (activity_thread_clz != NULL) {
    27.        jmethodID currentApplication = env->GetStaticMethodID(
    28.                activity_thread_clz, "currentApplication", "()Landroid/app/Application;");
    29.        if (currentApplication != NULL) {
    30.            application = env->CallStaticObjectMethod(activity_thread_clz, currentApplication);
    31.       } else {
    32.            LOGE("Cannot find method: currentApplication() in ActivityThread.");
    33.       }
    34.        env->DeleteLocalRef(activity_thread_clz);
    35.   } else {
    36.        LOGE("Cannot find class: android.app.ActivityThread");
    37.   }
    38.    return application;
    39. }
    40. static int verifySign(JNIEnv *env) {
    41.    // Application object
    42.    jobject application = getApplication(env);
    43.    if (application == NULL) {
    44.        return JNI_ERR;
    45.   }
    46.    // Context(ContextWrapper) class
    47.    jclass context_clz = env->GetObjectClass(application);
    48.    // getPackageManager()
    49.    jmethodID getPackageManager = env->GetMethodID(context_clz, "getPackageManager",
    50.                                                   "()Landroid/content/pm/PackageManager;");
    51.    // android.content.pm.PackageManager object
    52.    jobject package_manager = env->CallObjectMethod(application, getPackageManager);
    53.    // PackageManager class
    54.    jclass package_manager_clz = env->GetObjectClass(package_manager);
    55.    // getPackageInfo()
    56.    jmethodID getPackageInfo = env->GetMethodID(package_manager_clz, "getPackageInfo",
    57.                                                "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    58.    // context.getPackageName()
    59.    jmethodID getPackageName = env->GetMethodID(context_clz, "getPackageName",
    60.                                                "()Ljava/lang/String;");
    61.    // call getPackageName() and cast from jobject to jstring
    62.    jstring package_name = (jstring) (env->CallObjectMethod(application, getPackageName));
    63.    // PackageInfo object
    64.    jobject package_info = env->CallObjectMethod(package_manager, getPackageInfo, package_name, 64);
    65.    // class PackageInfo
    66.    jclass package_info_clz = env->GetObjectClass(package_info);
    67.    // field signatures
    68.    jfieldID signatures_field = env->GetFieldID(package_info_clz, "signatures",
    69.                                                "[Landroid/content/pm/Signature;");
    70.    jobject signatures = env->GetObjectField(package_info, signatures_field);
    71.    jobjectArray signatures_array = (jobjectArray) signatures;
    72.    jobject signature0 = env->GetObjectArrayElement(signatures_array, 0);
    73.    jclass signature_clz = env->GetObjectClass(signature0);
    74.    jmethodID toCharsString = env->GetMethodID(signature_clz, "toCharsString",
    75.                                               "()Ljava/lang/String;");
    76.    // call toCharsString()
    77.    jstring signature_str = (jstring) (env->CallObjectMethod(signature0, toCharsString));
    78.    // release
    79.    env->DeleteLocalRef(application);
    80.    env->DeleteLocalRef(context_clz);
    81.    env->DeleteLocalRef(package_manager);
    82.    env->DeleteLocalRef(package_manager_clz);
    83.    env->DeleteLocalRef(package_name);
    84.    env->DeleteLocalRef(package_info);
    85.    env->DeleteLocalRef(package_info_clz);
    86.    env->DeleteLocalRef(signatures);
    87.    env->DeleteLocalRef(signature0);
    88.    env->DeleteLocalRef(signature_clz);
    89.    const char *sign = env->GetStringUTFChars(signature_str, NULL);
    90.    if (sign == NULL) {
    91.        LOGE("分配内存失败");
    92.        return JNI_ERR;
    93.   }
    94.    LOGI("应用中读取到的签名为:%s", sign);
    95.    LOGI("native中预置的签名为:%s", SIGN);
    96.    int result = strcmp(sign, SIGN);
    97.    // 使用之后要释放这段内存
    98.    env->ReleaseStringUTFChars(signature_str, sign);
    99.    env->DeleteLocalRef(signature_str);
    100.    if (result == 0) { // 签名一致
    101.        return JNI_OK;
    102.   }
    103.    return JNI_ERR;
    104. }

    为什么要在so文件中验证签名?是为了让我们的so库只能被我们想要的应用去调用,因此在so库中会验证预置的签名与当前调用so库的应用的签名是否一致,防止特定so库的滥用,进一步保证安全。

  4. 获取应用签名

    1. /**
    2.     * 展示了如何用Java代码获取签名
    3.     */
    4.    private String getSign() {
    5.        try {
    6.            // 下面几行代码展示如何任意获取Context对象,在jni中也可以使用这种方式
    7. //           Class<?> activityThreadClz = Class.forName("android.app.ActivityThread");
    8. //           Method currentApplication = activityThreadClz.getMethod("currentApplication");
    9. //           Application application = (Application) currentApplication.invoke(null);
    10. //           PackageManager pm = application.getPackageManager();
    11. //           PackageInfo pi = pm.getPackageInfo(application.getPackageName(), PackageManager.GET_SIGNATURES);
    12.            PackageManager pm = getPackageManager();
    13.            PackageInfo pi = pm.getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
    14.            Signature[] signatures = pi.signatures;
    15.            Signature signature0 = signatures[0];
    16.            String str = signature0.toCharsString();
    17.            Log.i(TAG, "getSign: " + signature0.toCharsString());
    18.            return signature0.toCharsString();
    19.       } catch (Exception e) {
    20.            e.printStackTrace();
    21.            return "";
    22.       }
    23.   }

    并在build.gradle中加入:

    1. signingConfigs {
    2.       debug {
    3.           storeFile file("../xx.keystore")
    4.           storePassword "xxxxxx"
    5.           keyAlias "xx"
    6.           keyPassword "xxxxxx"
    7.       }
    8.   }

    可以用上述代码获取签名(可以将方法放入启动Activity中,运行后在log中获取签名)

  5. 在jni目录下创建Android.mk文件

    写入以下配置:

    1. LOCAL_PATH := $(call my-dir)
    2. include $(CLEAR_VARS)
    3. LOCAL_LDLIBS := -lm -llog //这一句是为了支持.cpp文件中的Log
    4. LOCAL_MODULE := security
    5. LOCAL_SRC_FILES := com_hyf_kaviewer_jni_JniTest.cpp
    6. include $(BUILD_SHARED_LIBRARY)

    需要注意的是:

    • LOCAL_MODULE := security ,security是将要生成的so库的名字,可以按需要改成自己想要的

    • LOCAL_SRC_FILES := com_hyf_kaviewer_jni_JniTest.c ,此处需要替换成自己的c文件

  6. 在jni目录下创建Application.mk文件

    写入以下配置:

    APP_ABI := all

    该配置表示会生成所有主流ABI类型的so库

    至此,所需的文件已创建完毕:

     

  7. 在当前Module下的build.gradle下增加配置

    在 defaultConfig{} 下增加以下配置:

    1. ndk {
    2.   moduleName "security"
    3. }
  8. 最后生成so库

    • 在终端中,cd到jni目录下

    • 输入ndk-build

    • 成功后打印如下所示

    • 生成so库的路径如下图

     

  9. PS:在使用so库的时候,方法调用处的路径和.h中的路径名一定要一致(即包名和文件名),否则会报错,因为编译器是根据这个路径去调用so库的代码

 

原文链接:https://blog.csdn.net/VanEasley/article/details/117338962



所属网站分类: 技术文章 > 博客

作者:小泽圈儿郎

链接:http://www.javaheidong.com/blog/article/207102/eca31e23991e84a70675/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

24 0
收藏该文
已收藏

评论内容:(最多支持255个字符)