Android Studio 3.0 更新了很多新特性,其中对C++开发者也越来越友好。目前Android Studio默认构建工具是CMake(当然也是支持ndk-build),我们将使用Cmake来开始编写我们的Helleworld。

准备

更新我们的sdk-tool中的LLDB、CMake和NDK三个选项。

创建项目

可以先创建一个helloWorld项目来看下它和普通的Android项目的区别。

这样一步步的next创建了一个简单的Helleworld的用CMake作为构建工具的JNI程序。

来看下它和普通的Android程序的区别吧。

这三处使我们和一般应用程序中所没有的,所以我们在普通程序中添加这三处也是可以添加自己的c/c++代码的。

  1. cpp文件夹使我们的c/c++代码代码的目录,这和src是我们的源代码目录类似。

  2. CMakeLists.txt是和Gradle交互的一个桥梁,里面的内容类似于我们之前写的make文件。

  3. 在主APP下要写两处externalNativeBuild,一处是可以根据处理的来打某一个平台的so文件,而另一处是为了将CMakeLists.txt和Gradle构建关联。

运行项目,通过Analyze APK是可以直接看到打出来的APK是有这个so的。

配置某个平台(如x86)的so

如上图上的标记3,在第一处添加cpu对应的架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
android {
defaultConfig {

......
externalNativeBuild {
cmake {
cppFlags ""
}
ndk {
abiFilters 'x86'
}
// ndk {
// abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a','armeabi'
// }
}
}
buildTypes {
......
}
......
}

编写一个简单的JNI程序

之前在网易工作时,发现网易系的有些应用是通过so文件来保证数据校验,如加密一个字符串,同时会传一个Context,这样可以通过context来校验是不是正版应用,校验应用的安全信息。我们就来实现一个通过Context来获取包名。

  1. native方法
    我们是通过java代码调用native方法,所以先声明一个native方法:

    1
    2
    3
    4
    5
    6
    7
    8
    package me.cyning.helloworld;

    import android.content.Context;

    public class NativeUtils {

    public static native String getPackageName(Context context);
    }
  2. native代码
    在我们的native-lib.cpp来实现具体的功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <jni.h>
    #include <string>

    extern "C" JNIEXPORT jstring
    JNICALL
    Java_me_cyning_helloworld_NativeUtils_getPackageName(
    JNIEnv *env, jclass clazz, jobject instance) {
    jclass nativeClass = env->GetObjectClass(instance);
    jmethodID jmethodID1 = env->GetMethodID(nativeClass, "getPackageName", "()Ljava/lang/String;");
    jstring packageName = static_cast<jstring>(env->CallObjectMethod(instance, jmethodID1));
    return packageName;
    }

让我们来解释下:

  • 函数签名:Java-(包名+类型+函数)(参数)

  • jni编程的类型和java类型的区别

  • JNI 函数访问 Java 对象的变量

结合上面的具体代码,很容易理解,先拿到Context对应的class类,通过这个类得到getPackageName方法的id,通过CallObjectMethod这个Context实例的getPackageName方法,看着很像反射的用法。

参考