AndroidStudio下NDK开发流程

前言

使用Android Studio进行NDK开发时,可在创建Android Studio项目时选择创建C++项目,创建好之后,默认会在src/main/下创建一个cpp的文件夹,C/C++相关的文件就存放在这个文件夹中;在app下面的build.gradle中有NDK的相关配置

一、NDK开发进行文件加密解密

1、实现Java层native方法

  • 创建Android项目
  • 写一个含有加密和解密按钮的view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="加密"
        android:onClick="mCrypt"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="解密"
        android:onClick="mDecrypt"/>

</LinearLayout>
  • 编写一个实现对文件加密解密的java类: Crpytor.java
package com.example.ndk.filecrypt;


public class Cryptor {

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

    /**
     * 对文件进行加密
     * @param path 需要加密的文件路径
     * @return 加密后的文件路径
     */
    public native static void crypt(String path,String cryptPath);

    /**
     * 对文件进行解密
     * @param cryptPath 加密文件的路径
     * @return 解密后的文件路径
     */
    public native static void decrypt(String cryptPath,String decryptPath);
}
  • 在View对应的java类中实现相应的点击事件
public class MainActivity extends AppCompatActivity {

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

    /**
     * 加密的点击事件
     *
     * @param view
     */
    public void mCrypt(View view) {
        File sdDir = Environment.getExternalStorageDirectory();
        String sdpath = sdDir.getAbsolutePath();

        String path = sdpath + "/ndk.jpg";
        String cryptPath = sdpath + "/ndk_crypt.jpg";
        Cryptor.crypt(path, cryptPath);
        Toast.makeText(this, "加密完成", Toast.LENGTH_SHORT).show();
    }

    /**
     * 解密的点击事件
     *
     * @param view
     */
    public void mDecrypt(View view) {
        File sdDir = Environment.getExternalStorageDirectory();
        String sdpath = sdDir.getAbsolutePath();

        String cryptPath = sdpath + "/ndk_crypt.jpg";
        String decryptPath = sdpath + "/ndk_decrypt.jpg";
        Cryptor.decrypt(cryptPath, decryptPath);
        Toast.makeText(this, "解密完成", Toast.LENGTH_SHORT).show();
    }
}

2、使用 javah 命令生成头文件

  • 执行javah命令,因为AndroidStudio使用的是UTF-8的编码,所以在执行javah命令时需要指定编码为UTF-8(默认为GBK);
javah -encoding UTF-8 com.example.ndk.filecrypt.Cryptor
  • 生成 com_example_ndk_filecrypt_Cryptor.h 文件

3、创建JNI/CPP目录,添加NDK本地支持

  • 在src/main目录下创建jni/cpp目录,将刚刚生成的.h文件复制到该目录下
  • 在项目配置中设置 Android NDK location 目录,需要提前下载 NDK 相关支持 (在Android SDK 下载中选择 SDK Tools 中的 LLDB 、CMake 、NDK 三项进行下载)
  • 在jni 目录下创建 CMakeLists.txt 文件
# cmake 版本
cmake_minimum_required(VERSION 3.4.1)

# 添加支持
add_library( # 为library设置名称
             cryptor

             # 设置该library为共享的
             SHARED

             # 提供C/C++相关文件的相对路径
             cryptor.c )


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 )

target_link_libraries( # Specifies the target library.与上面add_library的名称相同
                       cryptor

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
  • 在 app目录下的build.gradle 文件中配置jni 目录,以及NDK支持
android {

    defaultConfig {
        // NDK的配置
        ndk{
             moduleName "cryptor"
            abiFilters  "armeabi-v7a", "x86"
        }
    }

    sourceSets {
        //配置jni目录
        main {
            jni.srcDirs = []
        }
    }

    externalNativeBuild {
        //配置cmake文件路径
        cmake {
            path file('src/main/jni/CMakeLists.txt')
        }
    }

}

4、实现头文件中定义的函数

#include "com_example_ndk_filecrypt_Cryptor.h"
#include <string.h>

char password[] = "qazwsxedc";
//加密
char *crypt(char normal_path[], char crypt_path[]) {
    //打开文件
    FILE *normal_fp = fopen(normal_path, "rb");
    FILE *crypt_fp = fopen(crypt_path, "wb");
    //一次读取一个字符
    int ch;
    int i = 0;
    int pwd_len = strlen(password);
    while ((ch = fgetc(normal_fp)) != EOF) {//End of FILE
        //加密
        fputc(ch ^ password[i % pwd_len], crypt_fp);
        i++;
    }
    fclose(normal_fp);
    fclose(crypt_fp);
}

//解密
char *decrypt(char crypt_path[], char decrypt_path[]) {
    //打开文件
    FILE *crypt_fp = fopen(crypt_path, "rb");
    FILE *decrypt_fp = fopen(decrypt_path, "wb");
    //一次读取一个字符
    int ch;
    int i = 0;
    int pwd_len = strlen(password);
    while ((ch = fgetc(crypt_fp)) != EOF) {//End of FILE
        //加密
        fputc(ch ^ password[i % pwd_len], decrypt_fp);
        i++;
    }
    fclose(crypt_fp);
    fclose(decrypt_fp);
}



/**
 * 加密
 * @param env
 * @param cls
 * @param jstr
 * @return
 */
JNIEXPORT void JNICALL Java_com_example_ndk_filecrypt_Cryptor_crypt
        (JNIEnv *env, jclass cls, jstring normal_path_str, jstring crypt_path_str) {
    char *path = (*env)->GetStringUTFChars(env, normal_path_str, NULL);
    char *crypt_path = (*env)->GetStringUTFChars(env, crypt_path_str, NULL);
    crypt(path, crypt_path);
    //对变量的内存进行释放
    (*env)->ReleaseStringChars(env, normal_path_str, path);
    (*env)->ReleaseStringChars(env, crypt_path_str, crypt_path);
}


/**
 * 解密
 * @param env
 * @param cls
 * @param jstr
 * @return
 */
JNIEXPORT void JNICALL Java_com_example_ndk_filecrypt_Cryptor_decrypt
        (JNIEnv *env, jclass cls, jstring crypt_path_str, jstring decrypt_path_str) {
    char *crypt_path = (*env)->GetStringUTFChars(env, crypt_path_str, NULL);
    char *decrypt_path = (*env)->GetStringUTFChars(env, decrypt_path_str, NULL);
    decrypt(crypt_path, decrypt_path);
    //对变量的内存进行释放
    (*env)->ReleaseStringChars(env, crypt_path_str, crypt_path);
    (*env)->ReleaseStringChars(env, decrypt_path_str, decrypt_path);
}


5、编译生成.so动态库

  • 点击Build -> Make Module app
  • 可以在app -> build -> intermediates -> cmake -> debug -> obj 下看到对应的armeabi-v7a 和 x86 的so库

6、加载.so动态库,运行程序

  • 在src/main 目录下创建 jniLibs 文件夹
  • 将上面生成的 armeabi-v7a 和 x86 的so库 复制到该 jniLibs 文件夹内
  • 运行程序

二、NDK开发进行文件拆分与合并

1、实现Java层native方法

  • 创建Android项目
  • 写一个含有加密和解密按钮的view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拆分"
        android:onClick="mDiff"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="合并"
        android:onClick="mPatch"/>

</LinearLayout>
  • 编写一个实现对文件拆分合并的java类: FilePatchUtils.java
package com.example.ndk.filepatch
public class FilePatchUtils {
    static {
        System.loadLibrary("filepatch-lib");
    }

    /**
     * 对文件进行拆分
     * @param path  文件路径
     * @param count 拆分成多少个
     */
    public native static void diff(String path,String pathPattern,int count);

    /**
     * 对文件进行合并
     * @param pathPattern 需合并文件路径(%d)
     * @param count 将多少个文件合并
     * @param patchPath 合并后文件路径
     */
    public native static void patch(String pathPattern,int count,String patchPath);
}
  • 在View对应的java类中实现相应的点击事件

    public class FilePatchActivity extends AppCompatActivity {
    
      private String sdpath;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_file_patch);
          File sdDir = Environment.getExternalStorageDirectory();
          sdpath = sdDir.getAbsolutePath();
      }
    
       /**
       * 文件拆分
       *
       * @param view
       */
      public void mDiff(View view) {
          String path = sdpath + File.separatorChar + "ndk.jpg";
          String pathPattern = sdpath + File.separatorChar + "ndk_%d.jpg";
          FilePatchUtils.diff(path, pathPattern, 3);
          Toast.makeText(this, "拆分完成", Toast.LENGTH_SHORT).show();
      }
    
      /**
       * 文件合并
       *
       * @param view
       */
      public void mPatch(View view) {
          String patchPath = sdpath + File.separatorChar + "ndk_patch.jpg";
          String pathPattern = sdpath + File.separatorChar + "ndk_%d.jpg";
          FilePatchUtils.patch(pathPattern, 3, patchPath);
          Toast.makeText(this, "合并完成", Toast.LENGTH_SHORT).show();
      }
    }
    
### 2、使用 javah 命令生成头文件
- 执行javah命令,因为AndroidStudio使用的是UTF-8的编码,所以在执行javah命令时需要指定编码为UTF-8(默认为GBK);

javah -encoding UTF-8 com.example.ndk.filepatch.FilePatchUtils

- 生成 com_example_ndk_filepatch_FilePatchUtils.h 文件

### 3、创建JNI目录,添加NDK本地支持
- 在src/main目录下创建jni目录,将刚刚生成的.h文件复制到该目录下
- 在项目配置中设置 Android NDK location 目录,需要提前下载 NDK 相关支持 (在Android SDK 下载中选择 SDK Tools 中的 LLDB 、CMake 、NDK 三项进行下载)
- 在jni 目录下创建 CMakeLists.txt 文件;同上;
- 在 app目录下的build.gradle 文件中配置jni 目录,以及NDK支持;同上;

### 4、实现头文件中定义的函数

#include “com_example_ndk_filepatch_FilePatchUtils.h”

#include <android/log.h>

#define LOGI(FORMAT, …) android_log_print(ANDROID_LOG_INFO,”FILE_PATCH”,FORMAT,VA_ARGS__);

#define LOGE(FORMAT, …) android_log_print(ANDROID_LOG_ERROR,”FILE_PATCH”,##FORMAT,VA_ARGS__);

/**

  • 获取文件大小
  • @param path 文件路径
  • @return 文件大小
  • /
    long get_file_size(char *path) {
    FILE *fp = fopen(path, “r”);
    if (fp == NULL) {
      return 0;
    }
    fseek(fp, 0, SEEK_END);
    return ftell(fp);
    }

/*

  • Class: com_example_ndk_filepatch_FilePatchUtils

  • Method: diff

  • Signature: (Ljava/lang/String;Ljava/lang/String;I)V

  • /
    JNIEXPORT void JNICALL Java_com_example_ndk_filepatch_FilePatchUtils_diff

      (JNIEnv *env, jclass cls, jstring file_path_str, jstring pattern_str, jint file_count) {

    const char path = (env)->GetStringUTFChars(env, file_path_str, NULL);
    const char pattern = (env)->GetStringUTFChars(env, pattern_str, NULL);

    //得到分割之后的文件的路径列表
    char **filePaths = malloc(sizeof(char *) * file_count);
    //读取path对应路径,循环写入子文件
    int i = 0;
    for (; i < file_count; i++) {

      //给元素开辟空间
      filePaths[i] = malloc(sizeof(char) * 100);
      //给元素赋值
      sprintf(filePaths[i], pattern, (i + 1));
      LOGI("patch path:%s", filePaths[i]);

    }
    //分割文件
    FILE *fp = fopen(path, “rb”);
    int fileSize = get_file_size(path);
    if (fileSize % file_count == 0) {

      //能整除
      int part = fileSize / file_count;
      //逐一写入分割子文件中
      int i = 0;
      for (; i < file_count; i++) {
          FILE *fwp = fopen(filePaths[i], "wb");
          int j = 0;
          for (; j < part; j++) {
              fputc(fgetc(fp), fwp);
          }
          fclose(fwp);
      }
      fclose(fp);

    } else {

      //不能整除
      int part = fileSize / (file_count - 1);
      //逐一写入分割子文件中
      int i = 0;
      for (; i < file_count - 1; i++) {
          FILE *fwp = fopen(filePaths[i], "wb");
          int j = 0;
          for (; j < part; j++) {
              fputc(fgetc(fp), fwp);
          }
          fclose(fwp);
      }
      part = fileSize % (file_count - 1);
      if (part > 0) {
          FILE *fwp = fopen(filePaths[file_count - 1], "wb");
          int j = 0;
          for (; j < part; j++) {
              fputc(fgetc(fp), fwp);
          }
          fclose(fwp);
      }
      fclose(fp);

    }
    //释放
    i = 0;
    for (; i < file_count; i++) {

      free(filePaths[i]);

    }
    free(filePaths);
    //对变量的内存进行释放
    (env)->ReleaseStringChars(env, file_path_str, path);
    (
    env)->ReleaseStringChars(env, pattern_str, pattern);
    }

/*

  • Class: com_example_ndk_filepatch_FilePatchUtils
  • Method: patch
  • Signature: (Ljava/lang/String;Ljava/lang/String;I)V
  • /
    JNIEXPORT void JNICALL Java_com_example_ndk_filepatch_FilePatchUtils_patch
      (JNIEnv *env, jclass cls, jstring pattern_str, jint count, jstring patch_path_str) {
    char pattern = (env)->GetStringUTFChars(env, pattern_str, NULL);
    char patch_path = (env)->GetStringUTFChars(env, patch_path_str, NULL);
    FILE *fwp = fopen(patch_path, “wb”);
    int i = 0;
    for (; i < count; i++) {
      //单个文件逐一写入fwp
      char *path = malloc(sizeof(char) * 100);
      sprintf(path, pattern, (i + 1));
      LOGI("patch path:%s", path);
      FILE *frp = fopen(path, "rb");
      //判断文件是否为NULL
      if (frp != NULL) {
          //获取单个文件大小
          int file_size = get_file_size(path);
          int j = 0;
          for (; j < file_size; ++j) {
              fputc(fgetc(frp), fwp);
          }
          fclose(frp);
      }
      //释放分配的内存空间
      free(path);
    }
    fclose(fwp);
    //对变量的内存进行释放
    (env)->ReleaseStringChars(env, pattern_str, pattern);
    (
    env)->ReleaseStringChars(env, patch_path_str, patch_path);
    }

### 5、编译生成.so动态库
- 点击Build -> Make Module app 
- 可以在app -> build -> intermediates -> cmake -> debug -> obj 下看到对应的armeabi-v7a 和 x86 的so库

### 6、加载.so动态库,运行程序
- 在src/main 目录下创建 jniLibs 文件夹
- 将上面生成的 armeabi-v7a 和 x86 的so库 复制到该 jniLibs 文件夹内
- 运行程序