安卓實(shí)戰(zhàn)開(kāi)發(fā)之JNI入門及高效的配置(android studio一鍵生成.h,so及方法簽名)

前言

以前也講過(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 Go?。?!

準(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容