需求:項目中某些關(guān)鍵字段放在java文件中不安全,需要將其放在so文件中,使用java native方法調(diào)用so中的方法,由于so是不能被反編譯的,所以會相對安全。但如果有人知道了so中的方法名,他會模擬對應(yīng)的包環(huán)境,調(diào)用里面的方法,同樣也可以獲取字段信息,所以so中要對apk的簽名進行驗證,如果是指定的簽名,才返回字段值。而簽名文件一般不會泄露給他人。
AS版本3.4.2,
步驟:
1. 生成so文件,并在java代碼中調(diào)用so文件中的方法。
在原來項目上進行編譯的話會比較慢,而且build.gradle文件會修改多次,很是麻煩。所以,新建個Native C++項目,利用這個項目生成so文件。創(chuàng)建步驟好像和之前不太一樣。沒有了support c++這個選項。如下:
New Project,選擇Native C++

指定項目名、包名。就起名為Native吧。


一切默認,最后點擊finish創(chuàng)建項目。
創(chuàng)建成功后,注意看這個東西:

這個cpp中的方法名,是由"Java_包名類名方法名"組成的,也就是說想把native方法放在哪里,這個cpp中的方法名就要與之對應(yīng),不能搞錯了。如:想要把native方法放在原來項目的aaa/bbb/ccc/utils/JNIToos.java中,那就得這樣寫了

對應(yīng)的要修改cpp中的方法名

關(guān)于CMakeLists.txt只需要關(guān)注兩點:

native-lib是要和JNITools.java中的這個對應(yīng)

native-lib.cpp就是相對于native-lib.cpp的路徑。

需要在app/build.gradle中加上

以適配不同的cpu架構(gòu)。
在Activity中調(diào)用JNITools.stringFromJNI()方法,設(shè)置給TextView
運行結(jié)果:

運行成功后,下面需要在cpp中實現(xiàn)具體邏輯了。
獲取當(dāng)前apk簽名,如果和原有項目的apk簽名一致,則返回字段信息。
獲取原有項目的簽名(必須由簽名文件打包后再調(diào)用該方法)
private String getSign() {
String sign = null;
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
Signature[] signatures = packageInfo.signatures;
sign = signatures[0].toCharsString();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return sign;
}
getSign()方法會生成一個長長長長長的由0-9和a-f組成的字符串。在so中會將獲取到當(dāng)前apk的簽名后和這個字符串作對比,如果相等,則apk是由我們自己的簽名文件簽過名的,這時可以返回所需信息,否則返回error。
下面是cpp中獲取簽名的方法。
注意多了一個參數(shù)jclass jclazz,且,java native方法需要傳入context。
#include <jni.h>
#include <string>
const char* APP_ID = "aaaaa";
const char* SECRET_KEY = "bbbbbbbbbbbbbbbbbbbbbbb";
//應(yīng)用簽名
const char* RELEASE_SIGN = "30a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e0630a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e0630a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e06";
extern "C" JNIEXPORT jstring JNICALL
Java_aaa_bbb_ccc_utils_JNITools_stringFromJNI
(JNIEnv *env, jclass jclazz, jobject contextObject){
jclass native_class = env->GetObjectClass(contextObject);
jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject pm_obj = env->CallObjectMethod(contextObject, pm_id);
jclass pm_clazz = env->GetObjectClass(pm_obj);
// 得到 getPackageInfo 方法的 ID
jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
jclass native_classs = env->GetObjectClass(contextObject);
jmethodID mId = env->GetMethodID(native_classs, "getPackageName", "()Ljava/lang/String;");
jstring pkg_str = static_cast<jstring>(env->CallObjectMethod(contextObject, mId));
// 獲得應(yīng)用包的信息
jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
// 獲得 PackageInfo 類
jclass pi_clazz = env->GetObjectClass(pi_obj);
// 獲得簽名數(shù)組屬性的 ID
jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures", "[Landroid/content/pm/Signature;");
jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
jobjectArray signaturesArray = (jobjectArray)signatures_obj;
jsize size = env->GetArrayLength(signaturesArray);
jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
jclass signature_clazz = env->GetObjectClass(signature_obj);
jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString", "()Ljava/lang/String;");
jstring str = static_cast<jstring>(env->CallObjectMethod(signature_obj, string_id));
char *c_msg = (char*)env->GetStringUTFChars(str,0);
//return str;
if(strcmp(c_msg,RELEASE_SIGN)==0)//簽名一致 返回合法的 api key,否則返回錯誤
{
return (env)->NewStringUTF(APP_ID);
}else
{
return (env)->NewStringUTF("error");
}
}
public static native String stringFromJNI(Object contextObject);
運行:

由于新建的Native項目沒有使用原項目的簽名文件簽過名,故,返回error。
到此為止已經(jīng)在Native項目中實現(xiàn)了驗證apk簽名并獲取關(guān)鍵字段功能.
AS編譯時會自動把生成的so打包到apk中。So文件生成的路徑是: Native\app\build\intermediates\cmake\。但在我的AS中app\build\intermediates目錄打不開,

不知道是不是AS升級后故意這樣的。在對應(yīng)的windows目錄下則可以看到。

接下來將JNITools.java和生成的so文件拷貝到原有項目中去。
我們新建個項目MyApplication,假裝MyApplication就是我們原有的項目。

在app/build.gradle中設(shè)置jniLibs的路徑


由于MyApplication沒有使用原有項目的簽名文件簽名,故返回了error,cpp中的方法是得到了正常調(diào)用的。
當(dāng)然也可以將so文件放在src/main/jniLibs中,如下圖

更改jniLibs的配置

下面問題又來了
由于so中的方法對應(yīng)java中的固定的包名+類名+方法名,故如果將native方法放到項目中,一旦項目包結(jié)構(gòu)發(fā)生改變,則還需要保留原有的native方法所在的包。
考慮到上面原因,解決方法有兩種:
1.可以另外新建一個module,將so和native方法放到該module中。在主module(app module)中調(diào)用新建module中的native方法。
2.將so文件和native方法打包成一個aar,并引入到app項目中來。
新建module


- 將app中的jniLibs移動到j(luò)nilib下的main目錄下,
- 將aaa/bbb/ccc/utils/JNITools.java復(fù)制到j(luò)nilib中,
-
同樣的在jnilib中的build.gradle中的配置
image.png - 刪除app的build.gradle中的該配置。在dependencies下添加對jnilib的依賴
- 重新build項目,運行。
到此已經(jīng)將該功能從app中移動到了新的module中。
打包生成aar文件,
選擇Gradle,雙擊build,

然后在D:\AndroidStudioProjects\MyApplication\jnilib\build\outputs\aar目錄下生成了兩個aar文件。

這時aar就可以放在項目中使用了,步驟如下:
aar放在libs中,build.gradle中加入

,將aar當(dāng)作一個倉庫使用
至此,從cpp文件到so到module再到aar流程就走完了.
