Android中創建so庫存儲密鑰,NDK環境搭建與jni介紹

介紹

  • 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.下載

配置NDK環境
也可以直接在官方網站下載zip不過需要科學上網哦!
官網地址:https://developer.android.com/ndk/downloads/index.html

local.properties文件
local.properties文件中出現ndk.dir則配置成功。

2.配置*gradle.properties*文件,添加android.useDeprecatedNdk=true

gradle.properties文件

這樣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文件

Paste_Image.png

2.生成.h文件

打開Android Studio的Terminal也可以在對應目錄的命令窗口中,切換到項目的app/src/main目錄下,執行命令:

javah -d jni -classpath 編譯後的class文件的絕對路徑

生成對應的.h文件

.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文件中添加如下代碼

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值是不一樣的所以要注意更換。

這時候點擊運行項目如果簽名一致就會打印密鑰,如果不一致就會打印簽名錯誤。

生成so文件

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章