編輯:關於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庫可見比代碼混淆安全系數高的多。
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權限,就可以直接跑起來了。
准備工作不再需要什麼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實現獲取應用的包名和簽名信息)
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()。
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文件剪切過來,當然文件名字是可以修改的
//
// 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的支持還是不很好。
此步驟顯然也是不必要的,如果你需要生成so庫添加一下也好,為什麼不呢考過去改一下就好了,如果你不寫這2文件也是沒有問題的,因為debug下也是有這些so庫的。
好吧,勉強看一下這2貨:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JNI_ANDROID
LOCAL_SRC_FILES =: appinfo.cpp
include $(BUILD_SHARED_LIBRARY)
APP_MODULES := JNI_ANDROID
APP_ABI := all
eclipse開發ndk的時候你可能就配置過javah,所以android studio也可以配置,是不是很興奮:
Settings—>Tools—->External Tools就可以配置我們的終端命令了,別急一個一個來:
我們先來看參數的配置:
1.Program:
JDKPath \bin\javah.exe 這裡配置的是javah.exe的路徑(基本一致)2.Parametes: -classpath . -jni -d
ModuleFileDir /src/main/jniFileClass 這裡指的是定位在Module的jni文件你指定的文件執行jni指令<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCgk8cD4zLldvcmtpbmc6PG5vYnI+TW9kdWxlRmlsZURpcjwvbm9icj5cc3JjXG1haW5camF2YTwvcD4NCjwvYmxvY2txdW90ZT4NCm5kay1idWlsZCjSu7z8yfqzyXNvv+IpDQo8cD48aW1nIGFsdD0="" src="/uploadfile/Collfiles/20160718/201607181017581505.png" title="\" />我們同樣來看參數的配置:
javap-s(此命令用於c掉java方法時方法的簽名)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\我們同樣來看參數的配置:
1.Program:
JDKPath \bin\javap.exe 這裡配置的是javap.exe的路徑(基本一致)2.Parametes: -classpath
ModuleFileDir /build/intermediates/classes/debug -sFileClass 這裡指的是定位到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 完全支持 GUI 模式的 GDB/LLDB 調試 native 代碼(LLDB調試引擎需要gradle-experimental plugin的支持)。 AS 可以很好的直接編寫 native 代碼。
總之現在的 AS 和 Gradle 已經趨於實驗完善 NDK 開發了,主要表現在如下方面: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中是一級指針,所以指針特別注意
這幾天對Android中實現畫圓弧及圓弧效果中所實現的效果進行了修改,改為進度圓心進度條,效果如圖所示TasksCompletedView.java 代碼如下import
應用小掛件(也叫做窗口小掛件)在android1.5的時候被第一次引出,後來再android3.0和android3.1中得到了極大的發展,他們可以展示一些應用的常用信息
需要了解的先來張圖說明一下它們的關系你還要知道ViewGroup之間是可以嵌套的.View的繪制流程不知道大家有沒有這種疑惑, 為什麼我們在寫布局文件的時候, 一定要寫l
hello,大家好,本文主要介紹如何開始開發一個美觀、有情調、人見人愛的Android應用程序,已知我們在市面上有不少布局極其精美,在視覺上讓人愛不釋手的應用程序,如果讓