介绍
so库介绍:Android开发中经常会见到jinLibs文件夹下的so库文件,就算用第三方的sdk也会经常看到so库,so库是一个用c/c++语言些的函数库。Android中可以用过使用jni的方式来调取so库。在某些方面so函数库可能会更高效更安全。
JNI介绍:Java Native Interface,它是Java平台的一个特性(并不是Android系统特有的)。其实主要是定义了一些JNI函数,让开发者可以通过调用这些函数实现Java代码调用C/C++的代码,C/C++的代码也可以调用Java的代码,JNI也有些自己的语法和函数。
NDK介绍:NDK是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。以下是google官方对于NDK的介绍
The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input. The NDK may not be appropriate for most novice Android programmers who need to use only Java code and framework APIs to develop their apps. However, the NDK can be useful for cases in which you need to do one or more of the following:
Squeeze extra performance out of a device to achieve low latency or run computationally intensive applications, such as games or physics simulations.
Reuse your own or other developers’ C or C++ libraries.
一 · 在AndroidStudio中配置NDK环境
1.下载
也可以直接在官方网站下载zip不过需要科学上网哦!
官网地址:https://developer.android.com/ndk/downloads/index.html
local.properties文件中出现ndk.dir则配置成功。
2.配置*gradle.properties*文件,添加android.useDeprecatedNdk=true
这样NDK环境就搭建ok了。
二 · Java、C/C++代码编写以及so的生成与使用
1.创建对应的java类
package com.xuanguofeng.t2_ndk;
public class JniUtil {
static {
//jniutil这个参数对应着c的文件名,以及后面的配置名以及so的库名称
System.loadLibrary("jniutil");
}
//c/c++中要对应实现的方法,必须声明native
public native String getKey(String key);
}
2.对项目进行编译
编译后对项目会在文件夹下出现class文件
2.生成.h文件
打开Android Studio的Terminal也可以在对应目录的命令窗口中,切换到项目的app/src/main目录下,执行命令:
javah -d jni -classpath 编译后的class文件的绝对路径
.h文件会在app目录下的jni文件夹中,如果在不同的目录下执行命令会在不同的目录中生成jni文件夹
3.编写.c/.cpp文件
.c/.cpp文件的文件名要与之前在java类中定义的System.loadLibrary(“jniutil”);中的“jniutil”一致。
说明:.c文件对应的c语言,.cpp对应的是c++语言。
#include"com_xuanguofeng_t2_ndk_JniUtil.h"
#include<stdio.h>
#include<stdlib.h>
char *RELEASE_SIGN = "330818b310f300d0660355040a0c0ce7acace4b880e8bda6e7bd9131b7f5d1bd5e607ecdc1d9a0fef8eec91b621d6071a10af23135bd3d7115865f3e5859d8f7d44b78479adeb071f48d91eb162aced5510cddc106734d152c75db1622cfdb935d7213817589d7c4a33f829c9d74ff0dd008caa9f705e30550be64fe22887373644bbb63134ec1aff680171214643cb8d1c7e5...";
char *APK_SIGN_ERROR = "签名不一致";
char *a = "a";
#... 这里可以定义更多的干扰字符
char *b = "b";
char *c = "c";
char *d = "d";
char *e = "e";
char *f = "f";
char *g = "g";
char *x = "x";
char *y = "y";
char *z = "z";
char *i1 = "1";
char *i2 = "2";
char *i3 = "3";
char *i4 = "4";
char *i5 = "5";
char *i6 = "6";
char *i7 = "7";
char *i8 = "8";
char *i9 = "9";
char *i0 = "0";
JNIEXPORT jstring JNICALL Java_com_xuanguofeng_t2_1ndk_JniUtil_getKey (JNIEnv *env, jobject obj, jstring appkey)
{
char *rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", (Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(appkey, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char *) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
if (strcmp(RELEASE_SIGN, rtn) == 0)
{
char ack[6] = "";
strcat(ack, i1);
strcat(ack, i2);
strcat(ack, i3);
strcat(ack, i4);
strcat(ack, i5);
strcat(ack, i6);
return env->NewStringUTF(ack);
} else
{
return env->NewStringUTF(APK_SIGN_ERROR);
}
}
代码说明:
RELEASE_SIGN是定一个了一个key的变量,这个key固定在so里面 是通过keystore文件生成的后面会讲解方法。原因是如果直接单纯的返回密钥,拿到so一样不是那么安全,只有通过对应的签名文件验证通过后才会返回具体的密钥。这里返回的ack变量则是要返回给java的密钥。
说明:由于本次不怎么懂c所以只是通过查询资料简单的对密钥进行打乱拼接。读者可自行优化c算法。如有建议或者纠正欢迎留言。
4.在app目录下的build.gradle文件中添加如下代码
ndk {
moduleName "jniutil"
abiFilters 'armeabi', 'x86', 'armeabi-v7a'
}
注意:此处的moduleName还是与之前配置的 System.loadLibrary(“jniutil”); 中的jniutil一直。
5.在activity调用库中的方法。
package com.xuanguofeng.t2_ndk;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView= (TextView) findViewById(R.id.text);
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
Signature sign = signs[0];
textView.setText(new JniUtil().getKey(sign.toCharsString()));
} catch (Exception e) {
e.printStackTrace();
}
}}
代码说明:
sign.toCharsString()这个方法就是获取keystore中的值来和so库中的进行比较。开发时候debug的和正式发布的keystore值是不一样的所以要注意更换。
这时候点击运行项目如果签名一致就会打印密钥,如果不一致就会打印签名错误。
6.so的使用
在main文件夹下创建jniLibs有的项目会放在libs中,但是需要在gradle中配置目录,其实是一样的,复制我们之前生成的so文件。复制对应的JniUtil类注意,包名要与之前创建的so时的包名一致。之前的jni文件下的.h和c/c++源文件以及gradle里面配置的ndk信息也都不需要了。
当然 关于ndk,jni,so的相关知识可能还有很多推荐几篇文章给大家参考。
jni讲解:http://www.jianshu.com/p/aba734d5b5cd
NDK下载:https://developer.android.com/ndk/downloads/index.html
google官方NDK介绍(需要科学上网…你懂的):https://developer.android.google.cn/ndk/guides/index.html