前言
以前也講過(guò)NDK開(kāi)發(fā),但是開(kāi)始是抱著好玩的感覺(jué)去開(kāi)始的垄琐,然后呢會(huì)helloWord就覺(jué)得大大的滿足赶袄,現(xiàn)在靜下來(lái)想這NDK開(kāi)發(fā)到底是干什么呢?
NDK開(kāi)發(fā)猴娩,其實(shí)是為了項(xiàng)目需要調(diào)用底層的一些C/C++的一些東西阴幌;另外就是為了效率更加高效些但是在java與C相互調(diào)用時(shí)平白又增大了開(kāi)銷(其實(shí)效率不見(jiàn)得有所提高),然后呢勺阐,基于安全性的考慮也是為了防止代碼被反編譯我們?yōu)榱税踩鹨?jiàn),使用C語(yǔ)言來(lái)編寫這些重要的部分來(lái)增大系統(tǒng)的安全性矛双,最后呢生成so庫(kù)便于給人提供方便渊抽。
好了,我們來(lái)看一下qq的結(jié)構(gòu)议忽,我們就能理解任何有效的代碼混淆對(duì)于會(huì)smail語(yǔ)法反編譯你apk是分分鐘的事腰吟,即使你加殼也不能幸免高手的攻擊,當(dāng)然你的apk沒(méi)有什么機(jī)密和交易信息就沒(méi)有人去做這事了徙瓶。
分析qq的apk架構(gòu):
1.使用ClassyShark.jar來(lái)打開(kāi)qq.apk
2.點(diǎn)開(kāi)Archive我們來(lái)查看架構(gòu)
從上圖我們可以看出qq里面是一堆的so庫(kù)是嗎毛雇,所以呢so庫(kù)可見(jiàn)比代碼混淆安全系數(shù)高的多。
JNI與NDK的關(guān)系
- NDK:
NDK是一系列工具的集合侦镇。它提供了一系列的工具灵疮,幫助開(kāi)發(fā)者快速開(kāi)發(fā)C(或C++)的動(dòng)態(tài)庫(kù),并能自動(dòng)將so和java應(yīng)用一起打包成apk壳繁。這些工具對(duì)開(kāi)發(fā)者的幫助是巨大的震捣。它集成了交叉編譯器,并提供了相應(yīng)的mk文件隔離CPU闹炉、平臺(tái)蒿赢、ABI等差異,開(kāi)發(fā)人員只需要簡(jiǎn)單修改mk文件(指出“哪些文件需要編譯”渣触、“編譯特性要求”等)羡棵,就可以創(chuàng)建出so。它可以自動(dòng)地將so和Java應(yīng)用一起打包嗅钻,極大地減輕了開(kāi)發(fā)人員的打包工作皂冰。 - JNI:
JavaNative Interface (JNI)標(biāo)準(zhǔn)是java平臺(tái)的一部分,JNI是Java語(yǔ)言提供的Java和C/C++相互溝通的機(jī)制养篓,Java可以通過(guò)JNI調(diào)用本地的C/C++代碼秃流,本地的C/C++的代碼也可以調(diào)用java代碼。JNI 是本地編程接口柳弄,Java和C/C++互相通過(guò)的接口舶胀。Java通過(guò)C/C++使用本地的代碼的一個(gè)關(guān)鍵性原因在于C/C++代碼的高效性。
現(xiàn)在明白了吧碧注,NDK就是為我們生成了c/c++的動(dòng)態(tài)鏈接庫(kù)而已嚣伐,jni呢只不過(guò)是java和c溝通而已,兩者與android沒(méi)有半毛錢關(guān)系应闯,只因?yàn)榘沧渴莏ava程序開(kāi)發(fā)然后jni又能與c溝通纤控,所以使“Java+C”的開(kāi)發(fā)方式終于轉(zhuǎn)正挂捻。
Android是JVM架設(shè)在Linux之上的架構(gòu)碉纺。所以無(wú)論如何,在Linux OS層面,都應(yīng)該可以跑C/C++程序骨田。
Android Native C就是使用C/C++程序直接跑到Linux OS層面上的程序耿导。與其它平臺(tái)類似,只需要交叉編譯后态贤。并得到Linux OS root權(quán)限舱呻,就可以直接跑起來(lái)了。
android studio 中簡(jiǎn)單的jni開(kāi)發(fā)
Let’s GoS破O渎馈!
準(zhǔn)備工作不再需要什么cgwin來(lái)編譯ndk(太特么操蛋了),現(xiàn)在只需要你下載一下NDK的庫(kù)就ok了柿冲,然后你也可以去離線下載http://www.androiddevtools.cn最新版茬高,這里吐槽一下android studio對(duì)NDK的支持還有待提高。
效果看下今天的效果:(安卓jni獲取 apk的包名及簽名信息)
必須的步驟
1.配置你的ndk路徑(local.properties)
ndk.dir=E:\Android\sdk\android-ndk-r11b-windows-x86_64\android-ndk-r11b
2.grale配置使用ndk(gradle.properties)
android.useDeprecatedNdk=true
3.在module下的build.gradle添加ndk以及jni生成目錄
ndk{ moduleName "JNI_ANDROID" abiFilters "armeabi", "armeabi-v7a", "x86" //輸出指定三種abi體系結(jié)構(gòu)下的so庫(kù)假抄,目前可有可無(wú)怎栽。 } sourceSets.main{ jniLibs.srcDirs = ['libs'] }
準(zhǔn)備工作做好了開(kāi)始寫代碼:(jni實(shí)現(xiàn)獲取應(yīng)用的包名和簽名信息)
步驟1:先寫要實(shí)現(xiàn)本地方法的類,及加載庫(kù)(JNI_ANDROID也就是ndk 里面配的moduleName)
package com.losileeya.getapkinfo;
/**
* User: Losileeya (847457332@qq.com)
* Date: 2016-07-16
* Time: 11:09
* 類描述: * * @version :
*/
public class JNIUtils {
/**
* 獲取應(yīng)用的簽名
* @param o
* @return
*/
public static native String getSignature(Object o);
/** * 獲取應(yīng)用的包名
* @param o
* @return
*/
public static native String getPackname(Object o);
/**
* 加載so庫(kù)或jni庫(kù)
*/
static {
System.loadLibrary("JNI_ANDROID");
}}
注意我們 的加載c方法都加了native關(guān)鍵字宿饱,然后要使用jni下的c/c++文件就必須使用System.loadLibrary()熏瞄。
步驟2:使用javah命令生成.h(頭文件)
javah -jni com.losileeya.getapkinfo.JNIUtils
執(zhí)行完之后你可以在module下文件夾app\build\intermediates\classes\debug下看見(jiàn)生成的 .h頭文件為:
com_losileeya_getapkinfo_JNIUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_losileeya_getapkinfo_JNIUtils */
#ifndef _Included_com_losileeya_getapkinfo_JNIUtils
#define _Included_com_losileeya_getapkinfo_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_com_losileeya_getapkinfo_JNIUtils_getPackname(JNIEnv *, jobject, jobject);JNIEXPORT jstring JNICALL Java_com_losileeya_getapkinfo_JNIUtils_getSignature(JNIEnv *, jobject, jobject);
#ifdef __cplusplus
}
#endif
#endif
在工程的main目錄下新建一個(gè)名字為jni的目錄,然后將剛才的.h文件剪切過(guò)來(lái)谬以,當(dāng)然文件名字是可以修改的
步驟3:根據(jù).h文件生成相應(yīng)的c/cpp文件
//// Created by Administrator on 2016/7/16.//
#include <stdio.h>
#include <jni.h>
#include <stdlib.h>
#include "appinfo.h"
JNIEXPORT jstring JNICALL Java_com_losileeya_getapkinfo_JNIUtils_getPackname(JNIEnv *env, jobject clazz, jobject obj){
jclass native_class = env->GetObjectClass(obj);
jmethodID mId = env->GetMethodID(native_class, "getPackageName", "()Ljava/lang/String;");
jstring packName = static_cast<jstring>(env->CallObjectMethod(obj, mId));
return packName;
}
JNIEXPORT jstring JNICALL Java_com_losileeya_getapkinfo_JNIUtils_getSignature(JNIEnv *env, jobject clazz, jobject obj){
jclass native_class = env->GetObjectClass(obj);
jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject pm_obj = env->CallObjectMethod(obj, pm_id);
jclass pm_clazz = env->GetObjectClass(pm_obj);
// 得到 getPackageInfo 方法的
IDjmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
jstring pkg_str = Java_com_losileeya_getapkinfo_JNIUtils_getPackname(env, clazz, obj);// 獲得應(yīng)用包的信息
jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
// 獲得 PackageInfo 類
jclass pi_clazz = env->GetObjectClass(pi_obj);
// 獲得簽名數(shù)組屬性的 IDjfieldID 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;
}
注意:要使用前得先聲明强饮,方法名直接從h文件考過(guò)來(lái)就好了,studio目前還是很操蛋的为黎,對(duì)于jni的支持還是不很好胡陪。
步驟4:給項(xiàng)目添加Android.mk和Application.mk
此步驟顯然也是不必要的,如果你需要生成so庫(kù)添加一下也好碍舍,為什么不呢考過(guò)去改一下就好了柠座,如果你不寫這2文件也是沒(méi)有問(wèn)題的,因?yàn)閐ebug下也是有這些so庫(kù)的片橡。好吧妈经,勉強(qiáng)看一下這2貨:
Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)
LOCAL_MODULE := JNI_ANDROID
LOCAL_SRC_FILES =: appinfo.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_MODULES := JNI_ANDROID
APP_ABI := all
android studio下External Tools的高級(jí)配置NDK一鍵javah,ndk生成so
庫(kù)
eclipse開(kāi)發(fā)ndk的時(shí)候你可能就配置過(guò)javah,所以android studio也可以配置,是不是很興奮:Settings--->Tools---->External Tools就可以配置我們的終端命令了捧书,別急一個(gè)一個(gè)來(lái):
- javah -jni 命令的配置(一鍵生成h文件)
我們先來(lái)看參數(shù)的配置:
1.Program:$JDKPath$\bin\javah.exe 這里配置的是javah.exe的路徑(基本一致)
2.Parametes: -classpath . -jni -d $ModuleFileDir$/src/main/jni $FileClass$這里指的是定位在Module的jni文件你指定的文件執(zhí)行jni指令
3.Working:$ModuleFileDir$\src\main\java * ndk-build(一鍵生成so庫(kù))
我們同樣來(lái)看參數(shù)的配置:
1.Program:E:\Android\sdk\android-ndk-r11b-windows-x86_64\android-ndk-r11b\ndk-build.cmd 這里配置的是ndk下的ndk-build.cmd的路徑(自己去找下)
2.Working:$ModuleFileDir$\src\main\ * javap-s(此命令用于c掉java方法時(shí)方法的簽名)
我們同樣來(lái)看參數(shù)的配置:
1.Program:$JDKPath$\bin\javap.exe 這里配置的是javap.exe的路徑(基本一致)
2.Parametes: -classpath $ModuleFileDir$/build/intermediates/classes/debug -s $FileClass$ 這里指的是定位到build的debug目錄下執(zhí)行 javap -s class文件
3.Working:$ModuleFileDir$
這里介紹最常用的3個(gè)命令吹泡,對(duì)你的幫助應(yīng)該還是很大的來(lái)看一下怎么使用:
- javah -jni的使用:選中native文件--->右鍵--->External Tools--->javah -jni效果如下:
是不是自動(dòng)生成了包名.類名的.h文件。
- ndk-build的使用:選中jni文件--->右鍵--->External Tools--->ndk-build效果如下:
是不是一鍵生成了7種so庫(kù)经瓷,你還想去debug目錄下面去找嗎
-
javap-s的使用:選中native文件--->右鍵--->External Tools--->javap-s效果如下:
看見(jiàn)了每個(gè)方法下的descriptor屬性的值就是你所要的方法簽名爆哑。
3種一鍵生成的命令講完了,以后你用到了什么命令都可以這樣設(shè)置舆吮,是不是很給力揭朝。
新實(shí)驗(yàn)版Gradle插件與AS下NDK開(kāi)發(fā)
近期的 AS 與 Gradle 版本的快速更新對(duì) NDK 開(kāi)發(fā)又有了更加牛叉的體驗(yàn)队贱,因?yàn)樗耆С质褂?GDB 和 LLDB (不清楚這兩是啥的請(qǐng)自行腦部Unix編程基礎(chǔ))來(lái) GUI 化 debug 我們得 native 代碼了(以前真的好蛋疼,命令行巴拉巴拉的潭袱,淚奔爸印!)屯换。
總之現(xiàn)在的 AS 和 Gradle 已經(jīng)趨于實(shí)驗(yàn)完善 NDK 開(kāi)發(fā)了编丘,主要表現(xiàn)在如下方面:
- AS 完全支持 GUI 模式的 GDB/LLDB 調(diào)試 native 代碼(LLDB調(diào)試引擎需要gradle-experimental plugin的支持)。
- AS 可以很好的直接編寫 native 代碼彤悔。
- Java 層代碼聲明好以后 AS 可以自動(dòng)幫我們生成 JNI 接口規(guī)范代碼嘉抓。
*推出了幾乎針對(duì) NDK 的實(shí)驗(yàn)版 Gradle 插件≡我ぃ可以看見(jiàn)掌眠,現(xiàn)在 NDK 開(kāi)發(fā)已經(jīng)漸漸的變得越來(lái)越方便了,牛叉的一逼幕屹!
因?yàn)槭菍?shí)驗(yàn)版本蓝丙,所以我就試了下,不作為今后開(kāi)發(fā)的主要方向望拖,但是還是需要了解下渺尘。
區(qū)別如下:
1.情況1
//Project的build.gradle文件
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle-experimental:0.7.0-alpha1'
}}
allprojects {
repositories { jcenter() }
}
2.情況2
//Module的build.gradle文件
apply plugin: 'com.android.model.application'
model {
android {
compileSdkVersion = 23
buildToolsVersion = "23.0.2"
defaultConfig.with {
applicationId = "com.losileeya.getapkinfo"
minSdkVersion.apiLevel = 8
targetSdkVersion.apiLevel = 23
}
}
/* * native build settings */
android.ndk {
moduleName = "JNI_ANDROID"
}
android.productFlavors {
// for detailed abiFilter descriptions, refer to "Supported ABIs" @ // https://developer.android.com/ndk/guides/abis.html#sa create("arm") { ndk.abiFilters.add("armeabi") }
create("arm7") { ndk.abiFilters.add("armeabi-v7a") }
create("arm8") { ndk.abiFilters.add("arm64-v8a") }
create("x86") { ndk.abiFilters.add("x86") }
create("x86-64") { ndk.abiFilters.add("x86_64") }
create("mips") { ndk.abiFilters.add("mips") }
create("mips-64") { ndk.abiFilters.add("mips64") }
// To include all cpu architectures, leaves abiFilters empty create("all")
}
可以明顯感覺(jué)到 Project 和 Module 的 build.gradle 文件編寫閉包都有了變化。入門就講完了说敏,你也可以刪掉jni目錄鸥跟,把so庫(kù)放入jniLibs下,效果還是一模一樣的盔沫,很晚了医咨,睡覺(jué)。
總結(jié)
初步使用ndk的技巧已經(jīng)說(shuō)完了架诞,后續(xù)還會(huì)介紹jni中c調(diào)用java以及java調(diào)用c和相關(guān)一系列ndk開(kāi)發(fā)中所要注意的拟淮。
注意事項(xiàng)
1.jni調(diào)用前記得申明,比如:#include stdio.h谴忧,#include jni.h很泊,#include stdlib.h,方法被調(diào)用者寫前面或者頭文件里面>
2.c中env調(diào)方法時(shí)(*env)->但是cpp中就得這樣env->,原因是cpp中是一級(jí)指針沾谓,所以指針特別注意
demo 傳送門:GetApkInfo.rar
我的個(gè)人站點(diǎn):https://zilianliuxue.github.io/