『壹』 如何在android手機上使用hackrf
JNI方面
1)將編譯好的庫導入工程
拷貝libhackrf.h、libhackrf.so和libusb1.0.so到工程目錄,創建libusb1.0.mk和libhackrf.mk
libusb1.0.mk
通過libhackrf.mk即可將libhackrf連接到工程中。
2)初始化hackrf,注冊接收函數。
JNI介面
[cpp] view plain
JNIEXPORT jstring JNICALL java_com_pagekpang_hackrftouch_HackRFTouch_test(
JNIEnv *env, jclass cls) {
int result = HACKRF_SUCCESS;
result = hackrf_init();
if (result == HACKRF_SUCCESS) {
return env->NewStringUTF("OK!");
} else {
return env->NewStringUTF("Error!");
}
}
JNIEXPORT jstring JNICALL Java_com_pagekpang_hackrftouch_HackRFTouch_init(
JNIEnv *env, jobject thiz) {
int result = HACKRF_SUCCESS;
uint8_t board_id = BOARD_ID_INVALID;
char pbuf[2048] = { 0 };
result = hackrf_init();
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_init() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
result = hackrf_open(&device);
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_open() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
result = hackrf_board_id_read(device, &board_id);
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_board_id_read() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
sprintf(pbuf, "Board ID Number: %d (%s)\n", board_id,
hackrf_board_id_name((hackrf_board_id) board_id));
return env->NewStringUTF(pbuf);
}
JNIEXPORT jstring JNICALL Java_com_pagekpang_hackrftouch_HackRFTouch_setSampleRateManual(
JNIEnv *env, jobject thiz, jlong freq, jint divi) {
int result = HACKRF_SUCCESS;
char pbuf[2048] = { 0 };
result = hackrf_set_sample_rate_manual(device, freq, divi);
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_set_sample_rate_manual() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
return env->NewStringUTF("ok");
}
JNIEXPORT jstring JNICALL Java_com_pagekpang_hackrftouch_HackRFTouch_setVgaGain(
JNIEnv *env, jobject thiz, jint vga) {
int result = HACKRF_SUCCESS;
char pbuf[2048] = { 0 };
result = hackrf_set_vga_gain(device, vga);
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_set_vga_gain() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
return env->NewStringUTF("ok");
}
JNIEXPORT jstring JNICALL Java_com_pagekpang_hackrftouch_HackRFTouch_setLnaGain(
JNIEnv *env, jobject thiz, jint lna) {
int result = HACKRF_SUCCESS;
char pbuf[2048] = { 0 };
result = hackrf_set_lna_gain(device, lna);
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_set_lna_gain() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
return env->NewStringUTF("ok");
}
JNIEXPORT jstring JNICALL Java_com_pagekpang_hackrftouch_HackRFTouch_setFreq(
JNIEnv *env, jobject thiz, jlong freq) {
int result = HACKRF_SUCCESS;
char pbuf[2048] = { 0 };
result = hackrf_set_freq(device, freq);
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_set_freq() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
return env->NewStringUTF("ok");
}
JNIEXPORT jstring JNICALL Java_com_pagekpang_hackrftouch_HackRFTouch_setAmpEnable(
JNIEnv *env, jobject thiz, jint b) {
int result = HACKRF_SUCCESS;
char pbuf[2048] = { 0 };
result = hackrf_set_amp_enable(device, b == 0 ? false : true);
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_set_amp_enable() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
return env->NewStringUTF("ok");
}
JNIEXPORT jstring JNICALL Java_com_pagekpang_hackrftouch_HackRFTouch_startRX(
JNIEnv *env, jobject thiz) {
int result = HACKRF_SUCCESS;
char pbuf[2048] = { 0 };
result = hackrf_start_rx(device, hackrf_rx_cb, env->NewGlobalRef(thiz));
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_start_rx() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
return env->NewStringUTF("ok");
}
JNIEXPORT jstring JNICALL Java_com_pagekpang_hackrftouch_HackRFTouch_stopRX(
JNIEnv *env, jobject thiz) {
int result = HACKRF_SUCCESS;
char pbuf[2048] = { 0 };
result = hackrf_stop_rx(device);
if (result != HACKRF_SUCCESS) {
sprintf(pbuf, "hackrf_stop_rx() failed: %s (%d)\n",
hackrf_error_name((hackrf_error) result), result);
return env->NewStringUTF(pbuf);
}
return env->NewStringUTF("ok");
}
java層與JNI介面的綁定
[java] view plain
package com.pagekpang.hackrftouch;
public class HackRFTouch {
static {
System.loadLibrary("usb1.0");
System.loadLibrary("hackrf");
System.loadLibrary("HackrfTouch");
}
public static native String test();
private native String init();
private native String setSampleRateManual(long freq, int divi);
private native String setVgaGain(int g);
private native String setLnaGain(int g);
private native String setFreq(long freq);
private native String setAmpEnable(int f);
public native String startRX();
public native String stopRX();
public native float[] readRx();
private String retString = "";
private Boolean isOpen = false;
private ReadRxThread mReadRxThread = null;
public HackRFTouch() {
// TODO Auto-generated constructor stub
retString = init();
if (!retString.contains("failed")) {
isOpen = true;
mReadRxThread = new ReadRxThread(this);
}
}
class ReadRxThread extends Thread {
HackRFTouch mThisHackRFTouch = null;
public ReadRxThread(HackRFTouch t) {
// TODO Auto-generated constructor stub
mThisHackRFTouch = t;
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
while(true){
mThisHackRFTouch.cb(mThisHackRFTouch.readRx());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public Boolean isOpen() {
return isOpen;
}
public String getLastError() {
return retString;
}
public Boolean HsetSampleRateManual(long freq, int divi) {
if (!isOpen) {
return false;
}
retString = setSampleRateManual(freq, divi);
if (retString.contains("failed")) {
return false;
} else {
return true;
}
}
public Boolean HsetLnaGain(int g) {
if (!isOpen) {
return false;
}
retString = setLnaGain(g);
if (retString.contains("failed")) {
return false;
} else {
return true;
}
}
public Boolean HsetVgaGain(int g) {
if (!isOpen) {
return false;
}
retString = setVgaGain(g);
if (retString.contains("failed")) {
return false;
} else {
return true;
}
}
public Boolean HsetFreq(long freq) {
if (!isOpen) {
return false;
}
retString = setFreq(freq);
if (retString.contains("failed")) {
return false;
} else {
return true;
}
}
public Boolean HstopRX() {
if (!isOpen) {
return false;
}
retString = stopRX();
if (retString.contains("failed")) {
return false;
} else {
return true;
}
}
3)FFT運算
[cpp] view plain
if (device != NULL) {
float raw[1024];
int dalen = transfer->valid_length;
float realF, imagF, maxF;
uint8_t *pbuf = transfer->buffer;
while (dalen > 0 && runcount == 0) {
complex<double>* fdata = new complex<double> [1024];
complex<double>* fout = new complex<double> [1024];
for (int i = 0; i < 2048; i += 2) {
fdata[i / 2] = complex<double>(meanN(&pbuf[i], 100), meanN(&pbuf[i + 1], 100));
}
FFT(fdata, fout, 10);
//dft(fdata, 10, 0);
//fout = fdata;
maxF = 0.0;
for (int i = 0; i < 1024; i++) {
raw[i] = pow(pow(fout[i].real(), 2) + pow(fout[i].imag(), 2),
0.5);
if (maxF < raw[i]) {
maxF = raw[i];
}
}
for (int i = 0; i < 1024; i++) {
raw[i] = raw[i] / maxF;
}
sendBuf(raw);
//send(g_client, (char *)&raw, 4*1024, 0); //發送數據
dalen -= 2048;
pbuf += 2048;
runcount = 2;
}
runcount--;
//printf("E");
} else {
printf("O");
}
『貳』 如何在 android 系統中使用 std:stoul 和 std:stoull
為什麼您不能使用函數的原因是相當根深蒂固和遺憾的是目前無法解決。
尋找到 libs/armeabi-v7a/include/bits/c++config.h 文件在 gnu stdlibc + + 文件夾中,你會看到這個:
...
/* Define if C99 functions or macros from <wchar.h>, <math.h>, <complex.h>,
<stdio.h>, and <stdlib.h> can be used or exposed. */
/* #undef _GLIBCXX_USE_C99 */
...
在上面,在下面的代碼段從結合 bits/basic_string.h 咒語壞消息:
...
#if (defined(__GXX_EXPERIMENTAL_CXX0X__) && defined(_GLIBCXX_USE_C99) \
&& !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF))
/* The definitions of Numeric Conversions [string.conversions] */
#endif
...
因此,這些函數是在 NDK 中無法使用。
根本原因:根本原因似乎是 C99 的功能用法已在 GNU stdlibc + + 中由於事實 armeabi v7a 平台上被禁用仿生 libc 不支持復雜的數學 (Android 上的標准 C 庫是仿生)。
可能修復 (未經測試):探討CrystaX 的 Android NDK似乎對香草 Android NDK 有擴展的。
註: __GXX_EXPERIMENTAL_CXX0X__通過添加定義 -std=gnu++11 到 APP_CXXFLAGS 或 LOCAL_CXXFLAGS
詳細測試日誌:使用 NDK 版本 r8e 生成
jni/Application.mk:
APP_STL := gnustl_static
APP_CXXFLAGS += -std=gnu++11
NDK_TOOLCHAIN_VERSION := 4.7
jni/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := cxx11
LOCAL_SRC_FILES := cxx11.cpp
include $(BUILD_EXECUTABLE)
jni/cxx11.cpp:
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
#if defined(__GXX_EXPERIMENTAL_CXX0X__)
std::cout<<"__GXX_EXPERIMENTAL_CXX0X__ defined."<<std::endl;
#else
std::cout<<"__GXX_EXPERIMENTAL_CXX0X__ not defined."<<std::endl;
#endif
#if defined(_GLIBCXX_USE_C99)
std::cout<<"_GLIBCXX_USE_C99 defined."<<std::endl;
#else
std::cout<<"_GLIBCXX_USE_C99 not defined."<<std::endl;
#endif
#if defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)
std::cout<<"_GLIBCXX_HAVE_BROKEN_VSWPRINTF defined."<<std::endl;
#else
std::cout<<"_GLIBCXX_HAVE_BROKEN_VSWPRINTF not defined."<<std::endl;
#endif
#if (defined(__GXX_EXPERIMENTAL_CXX0X__) && defined(_GLIBCXX_USE_C99) \
&& !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF))
std::string s="1";
std::cout<<"ll:"<<std::stoll(s)<<std::endl<<"ul:"<<std::stoul(s)<<std::endl;
#else
std::cout<<"No support for stoll/stoul."<<std::endl;
#endif
return(0);
}
Nexus 4 (Android 4.3) 上的輸出:
u0_a51@mako:/ $ /data/local/tmp/cxx11
__GXX_EXPERIMENTAL_CXX0X__ defined.
_GLIBCXX_USE_C99 not defined.
_GLIBCXX_HAVE_BROKEN_VSWPRINTF not defined.
No support for stoll/stoul.
『叄』 在android中怎樣把utf-16的字元轉換為GBK字元用printf輸出
在java裡面應該是先轉換成newString(s,"utf-16").getBytes("gbk");這樣操作的。
不知道位元組的順序C和Java是一樣的不。原來的 JDK 中也是用char 來代表字元的,我們知道當我們想處理所有字元時1個位元組 (char) 根本不夠,所以 JDK 5 還是用回 int 來表示 code point在邏輯上一個 int 表示一個字元,而原來的 char 只能表示位元組。那麼你的 7 位元組的 unsigned int 是相當於 Java 中的什麼呢?每個 unsigned int 怎麼對應到 java 的 char[] 數組上來的?
在 Java 中內核是使用 Unicode 處理的,不存在什麼 GBK 輸出,我們已經看到字元時它就在 JVM 處理時有一個 unicode code point 了,你的 GBK 輸出是指要轉換成 GBK 位元組導出另一個系統?保存到磁碟也可以算做交換數據,不過如果只是緩存只被自己使用的話,隨便用什麼字元集保存下次再用同樣字元集讀取就可以了,哪怕保存到磁碟上是錯誤的也沒關系,只要還原過程是無損失的就可以了。那麼如果你的 printf 列印把控制台(還在內存中,不去磁碟,也不去網路另一端)就不需要考慮 byte[] 了,直接當成 Reader / Writer 這類來處理就可以了。
始終了解一點,當你不打算」交換數據「時,字元集是根本沒有任何用處的。
位元組或字元本身並沒有UTF-16和GBK的差別,主要是當我們想把它轉換成位元組時而有不同。每個字元按理說在操作系統或編程語言中會有一個unicodecodepoint與它對應(這樣的系統內核使用unicode就不存在處理不了的語言了)。因此,轉換的過程就是先讓對應到unicodecodepoint再轉換到另一個字元集編碼對應的byte[]就行了。
什麼是 unicode,看下這個圖片,那麼什麼是 UTF-8,從圖片中看到了 code point 是十六進制 0x20073 = 131187,那麼當多個字元挨在一起我們想通過網路傳送出去,如何讓對方程序知道字元的邊界在哪裡,哪幾個位元組湊在一起是一個字元,這就是字元集編碼方案了。UTF-8 只是其中一種編碼方案。
『肆』 如何在android的jni線程中實現回調
您好,很高興能幫助您
如果是C/C++回調,你只要參考linux的線程指南,在線程函數中傳入回調函數地址就行了。如果是要回調到Java層,稍微復雜點。
首先,你需要在onload的時候,找到回調函數所在的類,用全局變數保存:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
LOGE("JNI_OnLoad start");
jint version;
g_vm = vm; // 全局變數保存
JNIEnv *env;
jobject cls;
version = vm->GetEnv((void **)&env, JNI_VERSION_1_2);
if (env)
{
g_clazz = env->FindClass(CLASS_CustomSurfaceView); // 全局變數保存
}
LOGE("JNI_OnLoad finish g_clazz = 0x%x", g_clazz);
return JNI_VERSION_1_2;
}
在JNI啟動線程的時候,需要把線程掛到JVM上,不然不能訪問Java。你有了g_vm, g_clazz, 以及env,就可以做回調操作了。
// 線程函數
void *threadFunc(void *data)
{
JNIEnv *env = MNull;
int ret = g_vm->AttachCurrentThread( (JNIEnv **) &env, MNull); // 掛到JVM
if (ret < 0)
{
LOGE("fail to attach");
return;
}
// TODO: 在這里做你的回調操作
g_vm->DetachCurrentThread(); // 從JVM卸載
return;
}
你的採納是我前進的動力,
記得好評和採納,答題不易,互相幫助,
『伍』 Android studio 開發app,如何抵抗動態調試,反調試代碼怎麼寫請寫上詳細代碼。
為了保護關鍵代碼被逆向分析,一般放在應用程序初始化過程中,如init_array,或jni_onload函數里進行檢查代碼執行。
1.調試檢測
對調試器的檢測(ida,gdb,strace, ltrace等調試工具)
a.父進程檢測
b.當前運行進程檢測
例如對android_server進程檢測。針對這種檢測只需將android_server改名就可繞過
[objc] view plain
pid_t GetPidByName(const charchar *as_name) {
DIR *pdir = NULL;
struct dirent *pde = NULL;
FILEFILE *pf = NULL;
char buff[128];
pid_t pid;
char szName[128];
// 遍歷/proc目錄下所有pid目錄
pdir = opendir("/proc");
if (!pdir) {
perror("open /proc fail.\n");
return -1;
}
while ((pde = readdir(pdir))) {
if ((pde->d_name[0] < '0') || (pde->d_name[0] > '9')) {
continue;
}
sprintf(buff, "/proc/%s/status", pde->d_name);
pf = fopen(buff, "r");
if (pf) {
fgets(buff, sizeof(buff), pf);
fclose(pf);
sscanf(buff, "%*s %s", szName);
pid = atoi(pde->d_name);
if (strcmp(szName, as_name) == 0) {
closedir(pdir);
return pid;
}
}
}
closedir(pdir);
return 0;
}
c.讀取進程狀態(/proc/pid/status)
State屬性值T 表示調試狀態,TracerPid 屬性值正在調試此進程的pid,在非調試情況下State為S或R, TracerPid等於0
d.讀取 /proc/%d/wchan
下圖中第一個紅色框值為非調試狀態值,第二個紅色框值為調試狀態:
[objc] view plain
static void get_process_status(pid_t pid,const char* info,charchar *outline)
{
FILEFILE *fp;
char filename;
char line = {0};
snprintf( filename, sizeof(filename), "/proc/%d/status", pid );
fp = fopen( filename, "r" );
if ( fp != NULL )
{
while ( fgets( line, sizeof(line), fp ) )
{
if ( strstr( line, info ) )
strcpy(outline,line);
}
fclose( fp ) ;
}
return ;
}
static int getProcessStatus(int pid)
{
char readline = {0};
int result = STATUS_ELSE;
get_process_status(pid,"State",readline);
if(strstr(readline,"R"))
result = STATUS_RUNNING;
else if(strstr(readline,"S"))
result = STATUS_SLEEPING;
else if(strstr(readline,"T"))
result = STATUS_TRACING;
return result;
}
static int getTracerPid(int pid)
{
char readline = {0};
int result = INVALID_PID;
get_process_status(pid,"TracerPid",readline);
charchar *pidnum = strstr(readline,":");
result = atoi(pidnum + 1);
return result;
}
static int getWchanStatus(int pid)
{
FILEFILE *fp= NULL;
char filename;
char wchaninfo = {0};
int result = WCHAN_ELSE;
char cmd = {0};
sprintf(cmd,"cat /proc/%d/wchan",pid);
LOGANTI("cmd= %s",cmd);
FILEFILE *ptr; if((ptr=popen(cmd, "r")) != NULL)
{
if(fgets(wchaninfo, 128, ptr) != NULL)
{
LOGANTI("wchaninfo= %s",wchaninfo);
}
}
if(strncasecmp(wchaninfo,"sys_epoll\0",strlen("sys_epoll\0")) == 0)
result = WCHAN_RUNNING;
else if(strncasecmp(wchaninfo,"ptrace_stop\0",strlen("ptrace_stop\0")) == 0)
result = WCHAN_TRACING;
return result;
}
e. ptrace 自身或者fork子進程相互ptrace
[objc] view plain
ptrace me
if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
printf("DEBUGGING... Bye\n");
return 1;
}
void anti_ptrace(void)
{
pid_t child;
child = fork();
if (child)
wait(NULL);
else {
pid_t parent = getppid();
if (ptrace(PTRACE_ATTACH, parent, 0, 0) < 0)
while(1);
sleep(1);
ptrace(PTRACE_DETACH, parent, 0, 0);
exit(0);
}
}
f. 防止mp
利用Inotify機制,對/proc/pid/mem和/proc/pid/pagemap文件進行監視。inotify API提供了監視文件系統的事件機制,可用於監視個體文件,或者監控目錄。具體原理可參考:http://man7.org/linux/man- pages/man7/inotify.7.html
偽代碼:
[objc] view plain
void __fastcall anitInotify(int flag)
{
MemorPagemap = flag;
charchar *pagemap = "/proc/%d/pagemap";
charchar *mem = "/proc/%d/mem";
pagemap_addr = (charchar *)malloc(0x100u);
mem_addr = (charchar *)malloc(0x100u);
ret = sprintf(pagemap_addr, &pagemap, pid_);
ret = sprintf(mem_addr, &mem, pid_);
if ( !MemorPagemap )
{
ret = pthread_create(&th, 0, (voidvoid *(*)(voidvoid *)) inotity_func, mem_addr);
if ( ret >= 0 )
ret = pthread_detach(th);
}
if ( MemorPagemap == 1 )
{
ret = pthread_create(&newthread, 0, (voidvoid *(*)(voidvoid *)) inotity_func, pagemap_addr);
if(ret > 0)
ret = pthread_detach(th);
}
}
void __fastcall __noreturn inotity_func(const charchar *inotity_file)
{
const charchar *name; // r4@1
signed int fd; // r8@1
bool flag; // zf@3
bool ret; // nf@3
ssize_t length; // r10@3
ssize_t i; // r9@7
fd_set readfds; // @2
char event; // @1
name = inotity_file;
memset(buffer, 0, 0x400u);
fd = inotify_init();
inotify_add_watch(fd, name, 0xFFFu);
while ( 1 )
{
do
{
memset(&readfds, 0, 0x80u);
}
while ( select(fd + 1, &readfds, 0, 0, 0) <= 0 );
length = read(fd, event, 0x400u);
flag = length == 0;
ret = length < 0;
if ( length >= 0 )
{
if ( !ret && !flag )
{
i = 0;
do
{
inotity_kill((int)&event);
i += *(_DWORD *)&event + 16;
}
while ( length > i );
}
}
else
{
while ( *(_DWORD *)_errno() == 4 )
{
length = read(fd, buffer, 0x400u);
flag = length == 0;
ret = length < 0;
if ( length >= 0 )
}
}
}
}
g. 對read做hook
因為一般的內存mp都會調用到read函數,所以對read做內存hook,檢測read數據是否在自己需要保護的空間來阻止mp
h. 設置單步調試陷阱
[objc] view plain
int handler()
{
return bsd_signal(5, 0);
}
int set_SIGTRAP()
{
int result;
bsd_signal(5, (int)handler);
result = raise(5);
return result;
}
『陸』 android jni onload 為什麼重起
實現JNI中本地函數注冊可以兩種方式:
(1)採用默認的本地函數注冊流程。
(2)自己重寫JNI_OnLoad()函數。(本文介紹)(Android中採用這種)
Java端代碼:
package com.jni;
public class JavaHello {
public static native String hello();
static {
// load library: libtest.so
try {
System.loadLibrary("test");
} catch (UnsatisfiedLinkError ule) {
System.err.println("WARNING: Could not load library!");
}
}
public static void main(String[] args) {
String s = new JavaHello().hello();
System.out.println(s);
}
}
本地C語言代碼:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz)
{
printf("hello in c native code./n");
return (*env)->NewStringUTF(env, "hello world returned.");
}
#define JNIREG_CLASS "com/jni/JavaHello"//指定要注冊的類
/**
* Table of methods associated with a single class.
*/
static JNINativeMethod gMethods[] = {
{ "hello", "()Ljava/lang/String;", (void*)native_hello },//綁定
};
/*
* Register several native methods for one class.
*將此組件提供的各個本地函數(Native Function)登記到VM里,以便能加快後續呼叫本地函數的效率
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register native methods for all classes we know about.
*/
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
/*
* Set some test stuff up.
*
* Returns the JNI version on success, -1 on failure.
*該方法是在android vm調用System.loadLibrary方法時,就立即調用該方法
* 該函數做兩件事,第一:注冊所有的方法,第二:確認JNI的版本
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {//注冊
return -1;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
return result;
}
編譯及運行流程:
1 設置三個環境變數:
export JAVA_HOME:=/usr/lib/jvm/java-6-sun-1.6.0.15
export JAVA_SRC_PATH:=/home/kortide/Jackey/jni/jni_onload/com/jfo
export NATIVE_SRC_PATH:=/home/kortide/Jackey/jni/jni_onload/jni
2 編譯JavaHello.java:
javac $JAVA_SRC_PATH/JavaHello.java
3. 編譯NativeHello.c,生成共享庫
gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -c -o $NATIVE_SRC_PATH/NativeHello.o $NATIVE_SRC_PATH/NativeHello.c
gcc -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o $NATIVE_SRC_PATH/libtest.so $NATIVE_SRC_PATH/NativeHello.o
4. 運行
java com/jni/JavaHello
『柒』 如何Golang開發Android應用
環境配置好復雜,我不得不嘮叨幾句。
需要下載golang1.4rc版,下載ndk,然後編譯。 然後用go get 下載gobind這個工具, 然後,將寫好的代碼用gobind轉化下,然後使用特殊的編譯命令,將代碼編譯成.so文件,將生成的相關文件,放到android studio的項目中。然後java代碼中,利用jni調用引用的代碼。
... 好,接著往下看吧。
環境准備
一台Linux 64的機器
一個帶有AndroidStudioIDE的開發機器
因為環境配置實在復雜,所以我們引入的docker。
docker pull codeskyblue/docker-goandroid
docker run --rm -ti codeskyblue/docker-goandroid bash
cd example; echo "view example projects
docker起來之後,什麼就都配置好了,NDK啦,java啦,GO的環境變數了,等等,並且還預裝了vim,gradle,tmux,git,syncthing,svn
開始寫代碼
寫代碼之前,先約定下目錄結構
go的代碼都放在src/golib下,編譯使用make.bash編譯腳本,看下這個文件樹
.
|-- app.iml
|-- build.gradle
|-- libs/armeabi-v7a # go編譯生成的so文件
| `-- libgojni.so
|-- main.go_tmpl # 一個模板文件,先不用管它
|-- make.bash # 編譯腳本,用來生成.so和Java代碼
`-- src
|-- golib
| |-- hi
| | |-- go_hi�0�2�0�2�0�2 # 自動生成的代碼
| | | `-- go_hi.go
| | `-- hi.go # 需要編寫的代碼
| `-- main.go
`-- main
|-- AndroidManifest.xml
|-- java
| |-- go # 自動生成的代碼
| | |-- Go.java
| | |-- Seq.java
| | `-- hi
| | `-- Hi.java
| `-- me/shengxiang/gohello # 主要的邏輯代碼
| `-- MainActivity.java
`-- res
我已經寫了一個例子,先直接搞下來
編譯下,試試行不行(就算不行問題應該也不大,因為大問題都被我消滅了)
cd GoHello/app
./make.bash
../gradlew build
一切順利的話在build/outputs/apk下應該可以看到app-debug.apk這個文件。(劇透下,這個文件只有800多K)
編譯好的我放到qiniu上了,可以點擊下載看看
下面可以嘗試改改,我拋磚引玉說下
打開hi.go這個文件
hi.go的內容,比較簡單,我們寫Go代碼主要就是這部分
// Package hi provides a function for saying hello.
package hi
import "fmt"
func Hello(name string) {
fmt.Printf("Hello, %s!\n", name)
return "(Go)World"
}
文件末尾添加下面這行代碼
func Welcome(name string) string {
return fmt.Sprintf("Welcome %s to the go world", name)
}
使用./make.bash重新編譯下
打開MainActivity.java 修改下OnClickListener事件
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String message = Hi.Welcome("yourname");
Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();
}
});
編譯運行下,把生成的apk安裝到手機上試試。
原理解讀(有興趣的接著看)
首先說下gobind這個工具。
go_hi/go_hi.go這個文件時通過gobind這個工具生成的,用來配合一個簡單的程序,生成.so文件
// go_hi.go
package go_hi
import (
"golang.org/x/mobile/bind/seq"
"example/hi"
)
func proxy_Hello(out, in *seq.Buffer) {
param_name := in.ReadUTF16()
hi.Hello(param_name)
}
func init() {
seq.Register("hi", 1, proxy_Hello)
}
這個簡單的程序內容是這樣的
// main.go
package main
import (
"golang.org/x/mobile/app"
_ "golang.org/x/mobile/bind/java"
_ "example/hi/go_hi"
)
func main() {
app.Run(app.Callbacks{})
}
src/MyActivity.java文件內容是這樣的
import ...
import go.Go; // 引入Go這個包
import go.hi.Hi; // gobind生成的代碼
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Go.init(getApplicationContext()); // 初始化兩個線程
Hi.Hello("world");
}
}
其中有一句Go.init(...)這里再看go.Go這個包是什麼樣子的
public final class Go {
// init loads libgojni.so and starts the runtime.
public static void init(Context context) {
... 判斷該函數是否該執行的代碼 -- 省略 --
System.loadLibrary("gojni"); // gojni需要這句
new Thread("GoMain") {
public void run() {
Go.run(); // run()是一個native方法
}
}.start();
Go.waitForRun(); // 這個也是一個native方法
// 這部分可以理解為,啟動了一個後台線程不斷的接收結果到緩存中。
new Thread("GoReceive") {
public void run() { Seq.receive(); }
}.start();
}
private static boolean running = false;
private static native void run();
private static native void waitForRun();
}
MyActivity.java中還有段代碼是 Hi.Hello("world");,打開Hi.java路徑在src/go/hi/Hi.java,這個文件也是gobind生成的,是用來給java方便的調用.so文件
// Hi.java
// File is generated by gobind. Do not edit.
package go.hi;
import go.Seq;
public abstract class Hi {
private Hi() {} // uninstantiable
public static void Hello(String name) {
go.Seq _in = new go.Seq();
go.Seq _out = new go.Seq();
_in.writeUTF16(name);
Seq.send(DESCRIPTOR, CALL_Hello, _in, _out); // 下面接著說
}
private static final int CALL_Hello = 1;
private static final String DESCRIPTOR = "hi";
}
Seq.send這部分實際上最終調用的是一段go代碼
func Send(descriptor string, code int, req *C.uint8_t, reqlen C.size_t, res **C.uint8_t, reslen *C.size_t) {
fn := seq.Registry[descriptor][code]
in := new(seq.Buffer)
if reqlen > 0 {
in.Data = (*[maxSliceLen]byte)(unsafe.Pointer(req))[:reqlen]
}
out := new(seq.Buffer)
fn(out, in)
seqToBuf(res, reslen, out)
}
轉載僅供參考,版權屬於原作者。祝你愉快,滿意請採納哦