概述
C調用Java方法一般是通過反射來實現的,和Java中的反射相似主要分爲三個步驟:
1,通過反射拿到字節碼對象
2,獲取方法的方法ID
3,通過反射調用Java方法
開發環境
Eclipse + ADT + AndroidSDK + NDK
注意NDK版本不要太高,我用的是NDK r10e
創建項目
創建Android項目:項目名爲CCallJava,主要是流程通過點擊button調用C函數,C中再調用Java方法顯示一個對話框;
配置NDK
Eclipse的window選項中選擇Preferences,點擊左邊Android展開子選項,點擊NDK,在右邊NDK location中輸入自己的NDK路徑,或者點擊Browser選擇找到NDK根路徑點擊確認;
自動生成jni文件夾
配置之後可以右鍵項目選在Android Tools —>Add Native Support ...輸入編譯要生成動態庫的名稱也是生成的c++文件的名稱例如:hello,點擊finish;自動生成jni目錄以及默認C++文件和Android.mk文件;這種方式新建之後不能刪除jni,不然就會報錯;
Android.mk文件內容,前兩行和最後一行一般不用修改;
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= hello #動態庫名稱,Java代碼中也是根據這個名稱加載動態庫
LOCAL_SRC_FILES:= hello.c #C文件,裏面就是我們寫的C代碼。自動生成的是hello.cpp這個改成了C文件
include $(BUILD_SHARED_LIBRARY)#生成.so動態庫
#include $(BUILD_STATIC_LIBRARY) 編譯出.a的靜態庫
#注意註釋用#開頭
關聯NDK源碼
右鍵項目—>properties—>C C++General—>Path and Symbols—>Includes—>add —>File System...選擇ndk源碼路徑,例如r10版本:android-ndk-r10e\platforms\android-19\arch-arm\usr\include (注意不同ndk版本include源碼不一樣,比如ndk r17是ndk_r17\sysroot\usr\include)
新建本地方法
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
helloC();//調用本地方法
}
//本地方法
public native void helloC();
//C中調用的方法
public void show(String msg){
Builder builder = new Builder(this); //注意導包是android.app.AlertDialog.Builder;
builder.setTitle("提示!");
builder.setMessage(msg);
builder.show();
}
}
自動生成頭文件
通過javah命令生成本地方法對應的頭文件;
cmd命令中 cd 切換到項目的src目錄下,運行javah + 本地方所在類的全路徑名;
C:\Users\Ang>cd D:\EProject\CCallJava\src
C:\Users\Ang>d:
D:\EProject\CCallJava\src>javah com.example.ccalljava.MainActivity
D:\EProject\CCallJava\src>
在src目錄下就自動生成一個com_example_ccalljava_MainActivity.h頭文件
#include <jni.h>
/* Header for class com_example_ccalljava_MainActivity */
#ifndef _Included_com_example_ccalljava_MainActivity
#define _Included_com_example_ccalljava_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ccalljava_MainActivity
* Method: helloC
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ccalljava_MainActivity_helloC
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
實現頭文件的中函數
複製頭文件中的函數到jni下的C文件中,並實現這個函數;
hello.c
#include <jni.h>
//env是個二級指針,JNIEnvb本來就是一個指針
JNIEXPORT void JNICALL Java_com_example_ccalljava_MainActivity_helloC//函數名:Java包名+類名+方法名
(JNIEnv* env, jobject obj)//這兩個參數必須要寫,JNIEvn* 是java運行環境的地址,可以理解爲Java虛擬機地址;jobject Java中調用本地方的對象
{
//jclass (*FindClass)(JNIEnv*, const char*); jni.h中定義的函數指針
jclass class = (*env)->FindClass(env,"com/example/ccalljava/MainActivity"); //是不是和MainActivity.class.forName(classNmae);類似//(*env)->FindClass表示的邏輯是調用*env結構體中的FindClass函數
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);//第三個參數是要反射的方法名;最後一個參數char*是要反射調用的方法的簽名
jmethodID methodID = (*env)->GetMethodID(env,class,"show","(Ljava/lang/String;)V");
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 因爲show返回值是void類型的所以這裏調用CallVoidMethod()函數
(*env)->CallVoidMethod(env,obj,methodID,(*env)->NewStringUTF(env,"C調用Java方法顯示的彈窗"));
//jstring (*NewStringUTF)(JNIEnv*, const char*);把C中的字符串轉化爲Java中的字符串;C中的字符串是個char*指針或者說char[];
}
注意:FindClass函數第二個參數com/example/ccalljava/MainActivity是斜槓作分割,不是點"."
獲取方法簽名
cmd 命令行中 cd 切換到項目的bin\classes 下,運行javap -s + 要簽名方法所在類的全路徑名;
C:\Users\Ang>cd D:\EProject\CCallJava\bin\classes\C:\Users\Ang>d:
D:\EProject\CCallJava\bin\classes>javap -s com.example.ccalljava.MainActivity //執行此命令
Compiled from "MainActivity.java"
public class com.example.ccalljava.MainActivity extends android.app.Activity {
public com.example.ccalljava.MainActivity();
descriptor: ()Vprotected void onCreate(android.os.Bundle);
descriptor: (Landroid/os/Bundle;)Vpublic void click(android.view.View);
descriptor: (Landroid/view/View;)Vpublic native void helloC();
descriptor: ()Vpublic void show(java.lang.String);
descriptor: (Ljava/lang/String;)V //show方法的簽名
注意:C函數調用Java方法傳入參數中有中文會報錯,因爲Eclipse創建的hello.c文件默認的時GBK編碼,而Android使用的UTF-8,碼錶不一致導致的;修改hello.c默認的碼錶爲UTF-8就OK了;
編譯生成.so動態庫
cmd 命令行 cd切換到jni上級目錄或者jni目錄下,輸入ndk-build 注意:要配置ndk-build.cmd的環境變量,才能使用ndk-bulid命令;
C:\Users\Ang>cd D:\EProject\CCallJava
C:\Users\Ang>d:
D:\EProject\CCallJava>ndk-build
Android NDK: Found platform level in ./project.properties. Setting APP_PLATFORM to android-19.
D:/SDK/ndk-r16b-windows-x86_64/build//../build/core/setup-app.mk:81: Android NDK: Application targets deprecated ABI(s): armeabi
D:/SDK/ndk-r16b-windows-x86_64/build//../build/core/setup-app.mk:82: Android NDK: Support for these ABIs will be removed in a future NDK release.
[armeabi-v7a] Install : libhello.so => libs/armeabi-v7a/libhello.so
[armeabi] Install : libhello.so => libs/armeabi/libhello.so
[x86] Install : libhello.so => libs/x86/libhello.so
注意:如果不在Application.mk文件中指定輸出CPU ABI架構的.so,會根據默認的輸出對應的ABI架構.so庫,一般是所有的ABI架構的.so庫;
指定輸出對應的ABI架構的.so庫
Application.mk
APP_ABI := armeabi-v7a armeabi x86
使用.so庫
在Java代碼加載.so動態庫
MainActivity中加載hello.so庫
static{
System.loadLibrary("hello");
}
自動編譯.so庫
不用在命令行中輸入ndk-build命令,自動完成編譯輸出.so庫
自建Builder方法
右鍵項目選擇properties—>Builders—>New...—>Program—>OK
在Edit Configuration 中 Name 輸入Builders的名字,Location輸入ndk-build.cmd的路徑,Working Directory輸入當前項目${workspace_loc}在Variables...中可以找到;
Refresh中勾選 Refresh resources upon completion.
Build Options
勾選Specify working set of relevant resources 接着點擊Specify Resources...
選擇當前項目的jni
這樣每次運行項目的時候都會重新自動生成.so庫,所以打包apk會變慢;