Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 安卓實戰開發之JNI入門及高效的配置(android studio一鍵生成.h,so及方法簽名)

安卓實戰開發之JNI入門及高效的配置(android studio一鍵生成.h,so及方法簽名)

編輯:關於Android編程

前言

以前也講過NDK開發,但是開始是抱著好玩的感覺去開始的,然後呢會helloWord就覺得大大的滿足,現在靜下來想這NDK開發到底是干什麼呢?

NDK開發,其實是為了項目需要調用底層的一些C/C++的一些東西;另外就是為了效率更加高效些但是在java與C相互調用時平白又增大了開銷(其實效率不見得有所提高),然後呢,基於安全性的考慮也是為了防止代碼被反編譯我們為了安全起見,使用C語言來編寫這些重要的部分來增大系統的安全性,最後呢生成so庫便於給人提供方便。

好了,我們來看一下qq的結構,我們就能理解任何有效的代碼混淆對於會smail語法反編譯你apk是分分鐘的事,即使你加殼也不能幸免高手的攻擊,當然你的apk沒有什麼機密和交易信息就沒有人去做這事了。

分析qq的apk架構:

1.使用ClassyShark.jar來打開qq.apk
\

2.點開Archive我們來查看架構

從上圖我們可以看出qq裡面是一堆的so庫是嗎,所以呢so庫可見比代碼混淆安全系數高的多。

JNI與NDK的關系

NDK:

NDK是一系列工具的集合。它提供了一系列的工具,幫助開發者快速開發C(或C++)的動態庫,並能自動將so和java應用一起打包成apk。這些工具對開發者的幫助是巨大的。它集成了交叉編譯器,並提供了相應的mk文件隔離CPU、平台、ABI等差異,開發人員只需要簡單修改mk文件(指出“哪些文件需要編譯”、“編譯特性要求”等),就可以創建出so。它可以自動地將so和Java應用一起打包,極大地減輕了開發人員的打包工作。

JNI:

JavaNative Interface (JNI)標准是java平台的一部分,JNI是Java語言提供的Java和C/C++相互溝通的機制,Java可以通過JNI調用本地的C/C++代碼,本地的C/C++的代碼也可以調用java代碼。JNI 是本地編程接口,Java和C/C++互相通過的接口。Java通過C/C++使用本地的代碼的一個關鍵性原因在於C/C++代碼的高效性。

現在明白了吧,NDK就是為我們生成了c/c++的動態鏈接庫而已,jni呢只不過是java和c溝通而已,兩者與android沒有半毛錢關系,只因為安卓是java程序開發然後jni又能與c溝通,所以使“Java+C”的開發方式終於轉正。

Android是JVM架設在Linux之上的架構。所以無論如何,在Linux OS層面,都應該可以跑C/C++程序。

Android Native C就是使用C/C++程序直接跑到Linux OS層面上的程序。與其它平台類似,只需要交叉編譯後。並得到Linux OS root權限,就可以直接跑起來了。

android studio 中簡單的jni開發

Let’s Go!!!

准備工作不再需要什麼cgwin來編譯ndk(太特麼操蛋了),現在只需要你下載一下NDK的庫就ok了,然後你也可以去離線下載http://www.androiddevtools.cn最新版,這裡吐槽一下android studio對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體系結構下的so庫,目前可有可無。
}
sourceSets.main{
jniLibs.srcDirs = [‘libs’]
}

准備工作做好了開始寫代碼:(jni實現獲取應用的包名和簽名信息)

步驟1:先寫要實現本地方法的類,及加載庫(JNI_ANDROID也就是ndk 裡面配的moduleName)

package com.losileeya.getapkinfo;

/**
 * User: Losileeya ([email protected])
 * Date: 2016-07-16
 * Time: 11:09
 * 類描述:
 *
 * @version :
 */
public class JNIUtils {
    /**
     * 獲取應用的簽名
     * @param o
     * @return
     */
    public static native String getSignature(Object o);

    /**
     * 獲取應用的包名
     * @param o
     * @return
     */
    public static native String getPackname(Object o);

    /**
     * 加載so庫或jni庫
     */
    static {
        System.loadLibrary("JNI_ANDROID");
    }
}

注意我們 的加載c方法都加了native關鍵字,然後要使用jni下的c/c++文件就必須使用System.loadLibrary()。

步驟2:使用javah命令生成.h(頭文件)

javah -jni com.losileeya.getapkinfo.JNIUtils

執行完之後你可以在module下文件夾app\build\intermediates\classes\debug下看見生成的 .h頭文件為:

com_losileeya_getapkinfo_JNIUtils.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* 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目錄下新建一個名字為jni的目錄,然後將剛才的.h文件剪切過來,當然文件名字是可以修改的

步驟3:根據.h文件生成相應的c/cpp文件

//
// Created by Administrator on 2016/7/16.
//
#include 
#include 
#include 
#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(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 方法的 ID
jmethodID 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);
// 獲得應用包的信息
jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
// 獲得 PackageInfo 類
jclass pi_clazz = env->GetObjectClass(pi_obj);
// 獲得簽名數組屬性的 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(env->CallObjectMethod(signature_obj, string_id));
char *c_msg = (char*)env->GetStringUTFChars(str,0);

return str;
}

注意:要使用前得先聲明,方法名直接從h文件考過來就好了,studio目前還是很操蛋的,對於jni的支持還是不很好。

步驟4:給項目添加Android.mk和Application.mk

此步驟顯然也是不必要的,如果你需要生成so庫添加一下也好,為什麼不呢考過去改一下就好了,如果你不寫這2文件也是沒有問題的,因為debug下也是有這些so庫的。
好吧,勉強看一下這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的高級配置NDK一鍵javah,ndk生成so庫

eclipse開發ndk的時候你可能就配置過javah,所以android studio也可以配置,是不是很興奮:
Settings—>Tools—->External Tools就可以配置我們的終端命令了,別急一個一個來:

javah -jni 命令的配置(一鍵生成h文件)

\

我們先來看參數的配置:

1.Program:JDKPath\bin\javah.exe 這裡配置的是javah.exe的路徑(基本一致)

2.Parametes: -classpath . -jni -d ModuleFileDir/src/main/jni FileClass這裡指的是定位在Module的jni文件你指定的文件執行jni指令<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCgk8cD4zLldvcmtpbmc6PG5vYnI+TW9kdWxlRmlsZURpcjwvbm9icj5cc3JjXG1haW5camF2YTwvcD4NCjwvYmxvY2txdW90ZT4NCm5kay1idWlsZCjSu7z8yfqzyXNvv+IpDQo8cD48aW1nIGFsdD0="" src="/uploadfile/Collfiles/20160718/201607181017581505.png" title="\" />

我們同樣來看參數的配置:

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方法時方法的簽名)

我們同樣來看參數的配置:

1.Program:JDKPath\bin\javap.exe 這裡配置的是javap.exe的路徑(基本一致)

2.Parametes: -classpath ModuleFileDir/build/intermediates/classes/debug -s FileClass 這裡指的是定位到build的debug目錄下執行 javap -s class文件

3.Working:ModuleFileDir

這裡介紹最常用的3個命令,對你的幫助應該還是很大的來看一下怎麼使用:

javah -jni的使用:選中native文件—>右鍵—>External Tools—>javah -jni
效果如下:


是不是自動生成了包名.類名的.h文件。

ndk-build的使用:選中jni文件—>右鍵—>External Tools—>ndk-build
效果如下:


是不是一鍵生成了7種so庫,你還想去debug目錄下面去找嗎

javap-s的使用:選中native文件—>右鍵—>External Tools—>javap-s
效果如下:

看見了每個方法下的descriptor屬性的值就是你所要的方法簽名。

3種一鍵生成的命令講完了,以後你用到了什麼命令都可以這樣設置,是不是很給力。

新實驗版Gradle插件與AS下NDK開發

近期的 AS 與 Gradle 版本的快速更新對 NDK 開發又有了更加牛叉的體驗,因為它完全支持使用 GDB 和 LLDB (不清楚這兩是啥的請自行腦部Unix編程基礎)來 GUI 化 debug 我們得 native 代碼了(以前真的好蛋疼,命令行巴拉巴拉的,淚奔啊!)。
總之現在的 AS 和 Gradle 已經趨於實驗完善 NDK 開發了,主要表現在如下方面:

AS 完全支持 GUI 模式的 GDB/LLDB 調試 native 代碼(LLDB調試引擎需要gradle-experimental plugin的支持)。 AS 可以很好的直接編寫 native 代碼。

Java 層代碼聲明好以後 AS 可以自動幫我們生成 JNI 接口規范代碼。
*推出了幾乎針對 NDK 的實驗版 Gradle 插件。
可以看見,現在 NDK 開發已經漸漸的變得越來越方便了,牛叉的一逼!
因為是實驗版本,所以我就試了下,不作為今後開發的主要方向,但是還是需要了解下。區別如下:

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"
        /*
         * Other ndk flags configurable here are
         * cppFlags.add("-fno-rtti")
         * cppFlags.add("-fno-exceptions")
         * ldLibs.addAll(["android", "log"])
         * stl       = "system"
         */
    }

    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")
    }

可以明顯感覺到 Project 和 Module 的 build.gradle 文件編寫閉包都有了變化。
入門就講完了,你也可以刪掉jni目錄,把so庫放入jniLibs下,效果還是一模一樣的,很晚了,睡覺。

總結

初步使用ndk的技巧已經說完了,後續還會介紹jni中c調用java以及java調用c和相關一系列ndk開發中所要注意的。

注意事項

1.jni調用前記得申明,比如:#include stdio.h,#include jni.h,#include stdlib.h,方法被調用者寫前面或者頭文件裡面

2.c中env調方法時(*env)->但是cpp中就得這樣env->,原因是cpp中是一級指針,所以指針特別注意

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved