简介
项目决定移植1款C++开源项目到Android平台,开始对JNI深入研究。
JNI是甚么?
JNI(Java Native Interface)意为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互,简单的说,1种在Java虚拟机控制下履行代码的标准机制。
NDK是甚么?
Android NDK(Native Development Kit )是1套工具集合,允许你用像C/C++语言那样实现利用程序的1部份。
为何要用NDK?
1、安全性,java是半解释型语言,很容易被反汇编后拿到源代码文件,我们可以在重要的交互功能使用C语言代替。
2、效力,C语言比起java来讲效力要高出很多。
JNI和NDK的区分?
从工具上说,NDK其实多了1个把.so和.apk打包的工具,而JNI开发并没有打包,只是把.so文件放到文件系统的特定位置。
从编译库说,NDK开发C/C++只能能使用NDK自带的有限的头文件,而使用JNI则可使用文件系统中带的头文件。
从编写方式说,它们1样。
详解
1、JNI 元素
1、JNI组织结构
JNI函数表的组成绩像C++的虚函数表,虚拟机可以运行多张函数表。
JNI接口指针仅在当前线程中起作用,指针不能从1个线程进入另外一个线程,但可以在不同的线程中调用本地方法。
2、原始数据
Jobject 对象 援用类型
Java类型 | 本地类型(JNI) | 描写 |
boolean(布尔型) | jboolean | 无符号8个比特 |
byte(字节型) | jbyte | 有符号8个比特 |
char(字符型) | jchar | 无符号16个比特 |
short(短整型) | jshort | 有符号16个比特 |
int(整型) | jint | 有符号32个比特 |
long(长整型) | jlong | 有符号64个比特 |
float(浮点型) | jfloat | 32个比特 |
double(双精度浮点型) | jdouble | 64个比特 |
void(空型) | void | N/A |
函数操作
函数 | Java 数组类型 | 本地类型 | 说明 |
GetBooleanArrayElements | jbooleanArray | jboolean | ReleaseBooleanArrayElements 释放 |
GetByteArrayElements | jbyteArray | jbyte | ReleaseByteArrayElements 释放 |
GetCharArrayElements | jcharArray | jchar | ReleaseShortArrayElements 释放 |
GetShortArrayElements | jshortArray | jshort | ReleaseBooleanArrayElements 释放 |
GetIntArrayElements | jintArray | jint | ReleaseIntArrayElements 释放 |
GetLongArrayElements | jlongArray | jlong | ReleaseLongArrayElements 释放 |
GetFloatArrayElements | jfloatArray | jfloat | ReleaseFloatArrayElements 释放 |
GetDoubleArrayElements | jdoubleArray | jdouble | ReleaseDoubleArrayElements 释放 |
GetObjectArrayElement | 自定义对象 | object | |
SetObjectArrayElement | 自定义对象 | object | |
GetArrayLength | | | 获得数组大小 |
New<Type>Array | | | 创建1个指定长度的原始数据类型的数组 |
GetPrimitiveArrayCritical | | | 得到指向原始数据类型内容的指针,该方法可能使垃圾回收不能履行,该方法可能返回数组的拷贝,因此必须释放此资源。 |
ReleasePrimitiveArrayCritical | | | 释放指向原始数据类型内容的指针,该方法可能使垃圾回收不能履行,该方法可能返回数组的拷贝,因此必须释放此资源。 |
NewStringUTF | | | jstring类型的方法转换 |
GetStringUTFChars | | | jstring类型的方法转换 |
DefineClass | | | 从原始类数据的缓冲区中加载类 |
FindClass | | | 该函数用于加载本地定义的类。它将搜索由CLASSPATH 环境变量为具有指定名称的类所指定的目录和 zip文件 |
GetObjectClass | | | 通过对象获得这个类。该函数比较简单,唯1注意的是对象不能为NULL,否则获得的class肯定返回也为NULL |
GetSuperclass | | | 获得父类或说超类 。 如果 clazz 代表类class而非类 object,则该函数返回由 clazz 所指定的类的超类。 如果 clazz指定类 object 或代表某个接口,则该函数返回NULL |
IsAssignableFrom | | | 肯定 clazz1 的对象是不是可安全地强迫转换为clazz2 |
Throw | | | 抛出 java.lang.Throwable 对象 |
ThrowNew | | | 利用指定类的消息(由 message 指定)构造异常对象并抛出该异常 |
ExceptionOccurred | | | 肯定是不是某个异常正被抛出。在平台相干代码调用 ExceptionClear() 或 Java 代码处理该异常前,异常将始终保持抛出状态 |
ExceptionDescribe | | | 将异常及堆栈的回溯输出到系统毛病报告信道(例如 stderr)。该例程可便利调试操作 |
ExceptionClear | | | 清除当前抛出的任何异常。如果当前无异常,则此例程不产生任何效果 |
FatalError | | | 抛出致命毛病并且不希望虚拟机进行修复。该函数无返回值 |
NewGlobalRef | | | 创建 obj 参数所援用对象的新全局援用。obj 参数既可以是全局援用,也能够是局部援用。全局援用通过调用DeleteGlobalRef() 来显式撤销。 |
DeleteGlobalRef | | | 删除 globalRef 所指向的全局援用 |
DeleteLocalRef | | | 删除 localRef所指向的局部援用 |
AllocObject | | | 分配新 Java 对象而不调用该对象的任何构造函数。返回该对象的援用。clazz 参数务必不要援用数组类。 |
getObjectClass | | | 返回对象的类 |
IsSameObject | | | 测试两个援用是不是援用同1 Java 对象 |
NewString | | | 利用 Unicode 字符数组构造新的 java.lang.String 对象 |
GetStringLength | | | 返回 Java 字符串的长度(Unicode 字符数) |
GetStringChars | | | 返回指向字符串的 Unicode 字符数组的指针。该指针在调用 ReleaseStringchars() 前1直有效 |
ReleaseStringChars | | | 通知虚拟机平台相干代码无需再访问 chars。参数chars 是1个指针,可通过 GetStringChars() 从 string 取得 |
NewStringUTF | | | 利用 UTF⑻ 字符数组构造新 java.lang.String 对象 |
GetStringUTFLength | | | 以字节为单位返回字符串的 UTF⑻ 长度 |
GetStringUTFChars | | | 返回指向字符串的 UTF⑻ 字符数组的指针。该数组在被ReleaseStringUTFChars() 释放前将1直有效 |
ReleaseStringUTFChars | | | 通知虚拟机平台相干代码无需再访问 utf。utf 参数是1个指针,可利用 GetStringUTFChars() 取得 |
NewObjectArray | | | 构造新的数组,它将保存类 elementClass 中的对象。所有元素初始值均设为 initialElement |
Set<PrimitiveType>ArrayRegion | | | 将基本类型数组的某1区域从缓冲区中复制回来的1组函数 |
GetFieldID | | | 返回类的实例(非静态)域的属性 ID。该域由其名称及签名指定。访问器函数的 Get<type>Field 及 Set<type>Field系列使用域 ID 检索对象域。GetFieldID() 不能用于获得数组的长度域。应使用GetArrayLength()。 |
Get<type>Field | | | 该访问器例程系列返回对象的实例(非静态)域的值。要访问的域由通过调用GetFieldID() 而得到的域 ID 指定。 |
Set<type>Field | | | 该访问器例程系列设置对象的实例(非静态)属性的值。要访问的属性由通过调用 SetFieldID() 而得到的属性 ID指定。 |
GetStaticFieldID
GetStatic<type>Field
SetStatic<type>Field | | | 同上,只不过是静态属性。 |
GetMethodID | | | 返回类或接口实例(非静态)方法的方法 ID。方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。 GetMethodID() 可以使未初始化的类初始化。要取得构造函数的方法 ID,应将<init> 作为方法名,同时将void (V) 作为返回类型。 |
CallVoidMethod | | | |
CallObjectMethod | | | |
CallBooleanMethod | | | |
CallByteMethod | | | |
CallCharMethod | | | |
CallShortMethod | | | |
CallIntMethod | | | |
CallLongMethod | | | |
CallFloatMethod | | | |
CallDoubleMethod | | | |
GetStaticMethodID | | | 调用静态方法 |
Call<type>Method | | | |
RegisterNatives | | | 向 clazz 参数指定的类注册本地方法。methods 参数将指定 JNINativeMethod 结构的数组,其中包括本地方法的名称、签名和函数指针。nMethods 参数将指定数组中的本地方法数。 |
UnregisterNatives | | | 取消注册类的本地方法。类将返回到链接或注册了本地方法函数前的状态。该函数不应在常规平台相干代码中使用。相反,它可以为某些程序提供1种重新加载和重新链接本地库的途径。 |
| | | |
域描写符
域 | Java 语言 |
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
| |
援用类型则为 L + 该类型类描写符 + 。
数组,其为 : [ + 其类型的域描写符 + 。
多维数组则是 n个[ +该类型的域描写符 , N代表的是几维数组。
String类型的域描写符为 Ljava/lang/String;
[ + 其类型的域描写符 + ;
int[ ] 其描写符为[I
float[ ] 其描写符为[F
String[ ] 其描写符为[Ljava/lang/String;
Object[ ]类型的域描写符为[Ljava/lang/Object;
int [ ][ ] 其描写符为[[I
float[ ][ ] 其描写符为[[F
将参数类型的域描写符依照申明顺序放入1对括号中后跟返回值类型的域描写符,规则以下: (参数的域描写符的叠加)返回类型描写符。对,没有返回值的,用V(表示void型)表示。
举例以下:
Java层方法 JNI函数签名
String test ( ) Ljava/lang/String;
int f (int i, Object object) (ILjava/lang/Object;)I
void set (byte[ ] bytes) ([B)V
JNIEnv与JavaVM
JNIEnv 概念 : 是1个线程相干的结构体, 该结构体代表了 Java 在本线程的运行环境 ;
JNIEnv 与 JavaVM : 注意辨别这两个概念;
-- JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有1个;
-- JNIEnv : JavaVM 在线程中的代表, 每一个线程都有1个, JNI 中可能有很多个 JNIEnv;
JNIEnv 作用 :
-- 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可使用 JNIEnv 调用 Java 中的代码;
-- 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;
JNIEnv 体系结构
线程相干 : JNIEnv 是线程相干的, 即 在 每一个线程中 都有1个 JNIEnv 指针, 每一个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv;
JNIEnv 不能跨线程 :
-- 当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在 线程之间进行传递, 在同1个线程中, 屡次调用 JNI层方法, 传入的 JNIEnv 是相同的;
-- 本地方法匹配多JNIEnv : 在 Java 层定义的本地方法, 可以在不同的线程调用, 因此 可以接受不同的 JNIEnv;
JNIEnv 结构 : 由上面的代码可以得出, JNIEnv 是1个指针, 指向1个线程相干的结构, 线程相干结构指向 JNI 函数指针 数组, 这个数组中寄存了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数;
注意:JNIEnv只在当前线程中有效。本地方法不能将JNIEnv从1个线程传递到另外一个线程中。相同的 Java 线程中对本地方法屡次调用时,传递给该本地方法的JNIEnv是相同的。但是,1个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。
UTF⑻编码
JNI使用改进的UTF⑻字符串来表示不同的字符类型。Java使用UTF⑴6编码。UTF⑻编码主要使用于C语言,由于它的编码用u000表示为0xc0,而不是通常的0×00。非空ASCII字符改进后的字符串编码中可以用1个字节表示。
毛病
JNI不会检查NullPointerException、IllegalArgumentException这样的毛病,缘由是:致使性能降落。
在绝大多数C的库函数中,很难避免毛病产生。
JNI允许用户使用Java异常处理。大部份JNI方法会返回毛病代码但本身其实不会报出异常。因此,很有必要在代码本身进行处理,将异常抛给Java。在JNI内部,首先会检查调用函数返回的毛病代码,以后会调用ExpectOccurred()返回1个毛病对象。
jthrowable ExceptionOccurred(JNIEnv *env);
例如:1些操作数组的JNI函数不会报错,因此可以调用ArrayIndexOutofBoundsException或ArrayStoreExpection方法报告异常。
3、JNI函数实战
1、*.so的入口函数
JNI_OnLoad()与JNI_OnUnload()
当Android的VM(Virtual Machine)履行到System.loadLibrary()函数时,首先会去履行C组件里的JNI_OnLoad()函数。它的用处有2:
(1)告知VM此C组件使用那1个JNI版本。如果你的*.so档没有提供JNI_OnLoad()函数,VM会默许该*.so档是使用最老的JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
(2)由于VM履行到System.loadLibrary()函数时,就会立即先呼唤JNI_OnLoad(),所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的早期值之设定(Initialization) 。
2、返回值
jstring str = env->newStringUTF("HelloJNI"); //直接使用该JNI构造1个jstring对象返回
return str ;
jobjectArray ret = 0;
jsize len = 5;
jstring str;
string value("hello");
ret = (jobjectArray)(env->NewObjectArray(len, env->FindClass("java/lang/String"), 0));
for(int i = 0; i < len; i++)
{
str = env->NewStringUTF(value..c_str());
env->SetObjectArrayElement(ret, i, str);
}
return ret; 返回数组
jclass m_cls = env->FindClass("com/ldq/ScanResult");
jmethodID m_mid = env->GetMethodID(m_cls,"<init>","()V");
jfieldID m_fid_1 = env->GetFieldID(m_cls,"ssid","Ljava/lang/String;");
jfieldID m_fid_2 = env->GetFieldID(m_cls,"mac","Ljava/lang/String;");
jfieldID m_fid_3 = env->GetFieldID(m_cls,"level","I");
jobject m_obj = env->NewObject(m_cls,m_mid);
env->SetObjectField(m_obj,m_fid_1,env->NewStringUTF("AP1"));
env->SetObjectField(m_obj,m_fid_2,env->NewStringUTF("00⑴1⑵2⑶3⑷4⑸5"));
env->SetIntField(m_obj,m_fid_3,⑸0);
return m_obj; 返回自定义对象
jclass list_cls = env->FindClass("Ljava/util/ArrayList;");//取得ArrayList类援用
if(listcls == NULL)
{
cout << "listcls is null
" ;
}
jmethodID list_costruct = env->GetMethodID(list_cls , "<init>","()V"); //取得得构造函数Id
jobject list_obj = env->NewObject(list_cls , list_costruct); //创建1个Arraylist集合对象
//或得Arraylist类中的 add()方法ID,其方法原型为: boolean add(Object object) ;
jmethodID list_add = env->GetMethodID(list_cls,"add","(Ljava/lang/Object;)Z");
jclass stu_cls = env->FindClass("Lcom/feixun/jni/Student;");//取得Student类援用
//取得该类型的构造函数 函数名为 <init> 返回类型必须为 void 即 V
jmethodID stu_costruct = env->GetMethodID(stu_cls , "<init>", "(ILjava/lang/String;)V");
for(int i = 0 ; i < 3 ; i++)
{
jstring str = env->NewStringUTF("Native");
//通过调用该对象的构造函数来new 1个 Student实例
jobject stu_obj = env->NewObject(stucls , stu_costruct , 10,str); //构造1个对象
env->CallBooleanMethod(list_obj , list_add , stu_obj); //履行Arraylist类实例的add方法,添加1个stu对象
}
return list_obj ; 返回对象集合
3、操作Java层的类
//取得jfieldID 和 该字段的初始值
jfieldID nameFieldId ;
jclass cls = env->GetObjectClass(obj); //取得Java层该对象实例的类援用,即HelloJNI类援用
nameFieldId = env->GetFieldID(cls , "name" , "Ljava/lang/String;"); //取得属性句柄
if(nameFieldId == NULL)
{
cout << " 没有得到name 的句柄Id
;" ;
}
jstring javaNameStr = (jstring)env->GetObjectField(obj ,nameFieldId); // 取得该属性的值
const char * c_javaName = env->GetStringUTFChars(javaNameStr , NULL); //转换为 char *类型
string str_name = c_javaName ;
cout << "the name from java is " << str_name << endl ; //输出显示
env->ReleaseStringUTFChars(javaNameStr , c_javaName); //释放局部援用
//构造1个jString对象
char * c_ptr_name = "I come from Native" ;
jstring cName = env->NewStringUTF(c_ptr_name); //构造1个jstring对象
env->SetObjectField(obj , nameFieldId , cName); // 设置该字段的值
4、回调Java层方法
jstring str = NULL;
jclass clz = env->FindClass("cc/androidos/jni/JniTest");
//获得clz的构造函数并生成1个对象
jmethodID ctor = env->GetMethodID(clz, "<init>", "()V");
jobject obj = env->NewObject(clz, ctor);
// 如果是数组类型,则在类型前加[,如整形数组int[] intArray,则对应类型为[I,整形数组String[] strArray对应为[Ljava/lang/String;
jmethodID mid = env->GetMethodID(clz, "sayHelloFromJava", "(Ljava/lang/String;II[I)I");
if (mid)
{
LOGI("mid is get");
jstring str1 = env->NewStringUTF("I am Native");
jint index1 = 10;
jint index2 = 12;
//env->CallVoidMethod(obj, mid, str1, index1, index2);
// 数组类型转换 testIntArray能不能不申请内存空间
jintArray testIntArray = env->NewIntArray(10);
jint *test = new jint[10];
for(int i = 0; i < 10; ++i)
{
*(test+i) = i + 100;
}
env->SetIntArrayRegion(testIntArray, 0, 10, test);
jint javaIndex = env->CallIntMethod(obj, mid, str1, index1, index2, testIntArray);
LOGI("javaIndex = %d", javaIndex);
delete[] test;
test = NULL;
}
static void event_callback(int eventId,const char* description) { //主进程回调可以,线程中回调失败。
if (gEventHandle == NULL)
return;
JNIEnv *env;
bool isAttached = false;
if (myVm->GetEnv((void**) &env, JNI_VERSION_1_2) < 0) { //获得当前的JNIEnv
if (myVm->AttachCurrentThread(&env, NULL) < 0)
return;
isAttached = true;
}
jclass cls = env->GetObjectClass(gEventHandle); //获得类对象
if (!cls) {
LOGE("EventHandler: failed to get class reference");
return;
}
jmethodID methodID = env->GetStaticMethodID(cls, "callbackStatic",
"(ILjava/lang/String;)V"); //静态方法或成员方法
if (methodID) {
jstring content = env->NewStringUTF(description);
env->CallVoidMethod(gEventHandle, methodID,eventId,
content);
env->ReleaseStringUTFChars(content,description);
} else {
LOGE("EventHandler: failed to get the callback method");
}
if (isAttached)
myVm->DetachCurrentThread();
}
线程中回调
把c/c++中所有线程的创建,由pthread_create函数替换为由Java层的创建线程的函数AndroidRuntime::createJavaThread。
static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)
{
return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
}
static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { //异常检测和排除
if (env->ExceptionCheck()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
LOGE_EX(env);
env->ExceptionClear();
}
}
static void receive_callback(unsigned char *buf, int len) //回调
{
int i;
JNIEnv* env = AndroidRuntime::getJNIEnv();
jcharArray array = env->NewCharArray(len);
jchar *pArray ;
if(array == NULL){
LOGE("receive_callback: NewCharArray error.");
return;
}
pArray = (jchar*)calloc(len, sizeof(jchar));
if(pArray == NULL){
LOGE("receive_callback: calloc error.");
return;
}
//copy buffer to jchar array
for(i = 0; i < len; i++)
{
*(pArray + i) = *(buf + i);
}
//copy buffer to jcharArray
env->SetCharArrayRegion(array,0,len,pArray);
//invoke java callback method
env->CallVoidMethod(mCallbacksObj, method_receive,array,len);
//release resource
env->DeleteLocalRef(array);
free(pArray);
pArray = NULL;
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
public void Receive(char buffer[],int length){ //java层函数
String msg = new String(buffer);
msg = "received from jni callback" + msg;
Log.d("Test", msg);
}
jclass cls = env->GetObjectClass(obj);//取得Java类实例
jmethodID callbackID = env->GetMethodID(cls , "callback" , "(Ljava/lang/String;)V") ;//或得该回调方法句柄
if(callbackID == NULL)
{
cout << "getMethodId is failed
" << endl ;
}
jstring native_desc = env->NewStringUTF(" I am Native");
env->CallVoidMethod(obj , callbackID , native_desc); //回调该方法,并且
5、传对象到JNI调用
jclass stu_cls = env->GetObjectClass(obj_stu); //或得Student类援用
if(stu_cls == NULL)
{
cout << "GetObjectClass failed
" ;
}
//下面这些函数操作,我们都见过的。O(∩_∩)O~
jfieldID ageFieldID = env->GetFieldID(stucls,"age","I"); //取得得Student类的属性id
jfieldID nameFieldID = env->GetFieldID(stucls,"name","Ljava/lang/String;"); // 取得属性ID
jint age = env->GetIntField(objstu , ageFieldID); //取得属性值
jstring name = (jstring)env->GetObjectField(objstu , nameFieldID);//取得属性值
const char * c_name = env->GetStringUTFChars(name ,NULL);//转换成 char *
string str_name = c_name ;
env->ReleaseStringUTFChars(name,c_name); //释放援用
cout << " at Native age is :" << age << " # name is " << str_name << endl ;
6、与C++互转
jbytearray转c++byte数组
jbyte * arrayBody = env->GetByteArrayElements(data,0);
jsize theArrayLengthJ = env->GetArrayLength(data);
BYTE * starter = (BYTE *)arrayBody;
jbyteArray 转 c++中的BYTE[]
jbyte * olddata = (jbyte*)env->GetByteArrayElements(strIn, 0);
jsize oldsize = env->GetArrayLength(strIn);
BYTE* bytearr = (BYTE*)olddata;
int len = (int)oldsize;
C++中的BYTE[]转jbyteArray
jbyte *by = (jbyte*)pData;
jbyteArray jarray = env->NewByteArray(nOutSize);
env->SetByteArrayRegin(jarray, 0, nOutSize, by);
jbyteArray 转 char *
char* data = (char*)env->GetByteArrayElements(strIn, 0);
char* 转jstring
jstring WindowsTojstring(JNIEnv* env, char* str_tmp)
{
jstring rtn=0;
int slen = (int)strlen(str_tmp);
unsigned short* buffer=0;
if(slen == 0)
{
rtn = env->NewStringUTF(str_tmp);
}
else
{
int length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, NULL, 0);
buffer = (unsigned short*)malloc(length*2+1);
if(MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, (LPWSTR)buffer, length) > 0)
{
rtn = env->NewString((jchar*)buffer, length);
}
}
if(buffer)
{
free(buffer);
}
return rtn;
}
char* jstring互转
JNIEXPORT jstring JNICALL Java_com_explorer_jni_SambaTreeNative_getDetailsBy
(JNIEnv *env, jobject jobj, jstring pc_server, jstring server_user, jstring server_passwd)
{
const char *pc = env->GetStringUTFChars(pc_server, NULL);
const char *user = env->GetStringUTFChars(server_user, NULL);
const char *passwd = env->GetStringUTFChars(server_passwd, NULL);
const char *details = smbtree::getPara(pc, user, passwd);
jstring jDetails = env->NewStringUTF(details);
return jDetails;
}
4、Android.mk、Application.mk
1、Android.mk
Android.mk文件是GNU Makefile的1小部份,它用来对Android程序进行编译,Android.mk中的变量都是全局的,解析进程会被定义。
1个Android.mk文件可以编译多个模块,模块包括:APK程序、JAVA库、CC++利用程序、CC++静态库、CC++同享库。
简单实例以下:
LOCAL_PATH := $(call my-dir) #表示是当前文件的路径
include $(CLEAR_VARS) #指定让GNU MAKEFILE该脚本为你清除许多 LOCAL_XXX 变量
LOCAL_MODULE:= helloworld #标识你在 Android.mk 文件中描写的每一个模块
MY_SOURCES := foo.c #自定义变量
ifneq ($(MY_CONFIG_BAR),)
MY_SOURCES += bar.c
endif
LOCAL_SRC_FILES += $(MY_SOURCES) #包括将要编译打包进模块中的 C 或 C++源代码文件
include $(BUILD_SHARED_LIBRARY) #根据LOCAL_XXX系列变量中的值,来编译生成同享库(动态链接库)
GNU Make系统变量
变量 | 描写 |
CLEAR_VARS | 指向1个编译脚本,几近所有未定义的 LOCAL_XXX 变量都在"Module-description"节中列出。必须在开始1个新模块之前包括这个脚本:include$(CLEAR_VARS),用于重置除LOCAL_PATH变量外的,所有LOCAL_XXX系列变量。 |
BUILD_SHARED_LIBRARY | 指向编译脚本,根据所有的在 LOCAL_XXX 变量把列出的源代码文件编译成1个同享库。 |
BUILD_STATIC_LIBRARY | 1个 BUILD_SHARED_LIBRARY 变量用于编译1个静态库。静态库不会复制到的APK包中,但是能够用于编译同享库。 |
TARGET_ARCH | 目标 CPU平台的名字, 和 android 开放源码中指定的那样。如果是arm,表示要生成 ARM 兼容的指令,与 CPU架构的修订版无关。 |
TARGET_PLATFORM | Android.mk 解析的时候,目标 Android 平台的名字.详情可参考/development/ndk/docs/stable- apis.txt. |
TARGET_ARCH_ABI | 支持目标平台 |
TARGET_ABI | 目标平台和 ABI 的组合,它事实上被定义成$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI) ,在想要在真实的装备中针对1个特别的目标系统进行测试时,会有用。在默许的情况下,它会是'android⑶-arm'。 |
| |
模块描写变量
变量 | 描写 |
LOCAL_PATH | 这个变量用于给出当前文件的路径。必须在 Android.mk 的开头定义,可以这样使用:LOCAL_PATH := $(call my-dir) 这个变量不会被$(CLEAR_VARS)清除,因此每 个 Android.mk 只需要定义1次(即便在1个文件中定义了几个模块的情况下)。 |
LOCAL_MODULE | 这是模块的名字,它必须是唯1的,而且不能包括空格。必须在包括任1的$(BUILD_XXXX)脚本之前定义它。模块的名字决定了生成文件的名字。例如,如果1个1个同享库模块的名字是,那末生成文件的名字就是 lib.so。但是,在的 NDK 生成文件中(或 Android.mk 或 Application.mk),应当只触及(援用)有正常名字的其他模块。 |
LOCAL_SRC_FILES | 这是要编译的源代码文件列表。只要列出要传递给编译器的文件,由于编译系统自动计算依赖。注意源代码文件名称都是相对 LOCAL_PATH的,你可使用路径部份。 |
LOCAL_CPP_EXTENSION | 这是1个可选变量, 用来指定C++代码文件的扩大名,默许是'.cpp',但是可以改变它。 |
LOCAL_C_INCLUDES | 可选变量,表示头文件的搜索路径。 |
LOCAL_CFLAGS | 可选的编译器选项,在编译 C 代码文件的时候使用。 |
LOCAL_CXXFLAGS | 与 LOCAL_CFLAGS同理,针对 C++源文件。 |
LOCAL_CPPFLAGS | 与 LOCAL_CFLAGS同理,但是对 C 和 C++ source files都适用。 |
LOCAL_STATIC_LIBRARIES | 表示该模块需要使用哪些静态库,以便在编译时进行链接。 |
LOCAL_SHARED_LIBRARIES | 表示模块在运行时要依赖的同享库(动态库),在链接时就需要,以便在生成文件时嵌入其相应的信息。注意:它不会附加列出的模块到编译图,也就是依然需要在Application.mk 中把它们添加到程序要求的模块中。 |
LOCAL_LDLIBS | 编译模块时要使用的附加的链接器选项。这对使用‘-l’前缀传递指定库的名字是有用的。 |
LOCAL_ALLOW_UNDEFINED_SYMBOLS | 默许情况下, 在试图编译1个同享库时,任何未定义的援用将致使1个“未定义的符号”毛病。 |
LOCAL_ARM_MODE | 默许情况下, arm目标2进制会以 thumb 的情势生成(16 位),你可以通过设置这个变量为 arm如果你希望你的 module 是以 32 位指令的情势。 |
LOCAL_MODULE_PATH 和 LOCAL_UNSTRIPPED_PATH | 在 Android.mk 文件中, 还可以用LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH指定最后的目标安装路径. 不同的文件系统路径用以下的宏进行选择: TARGET_ROOT_OUT:表示根文件系统。 TARGET_OUT:表示 system文件系统。 TARGET_OUT_DATA:表示 data文件系统。 用法如:LOCAL_MODULE_PATH :=$(TARGET_ROOT_OUT) 至于LOCAL_MODULE_PATH 和LOCAL_UNSTRIPPED_PATH的区分,暂时还不清楚。 |
GNU Make 功能宏
变量 | 描写 |
my-dir | 返回当前 Android.mk 所在的目录的路径,相对 NDK 编译系统的顶层。 |
all-subdir-makefiles | 返回1个位于当前'my-dir'路径的子目录中的所有Android.mk的列表。 |
this-makefile | 返回当前Makefile 的路径(即这个函数调用的地方) |
parent-makefile | 返回调用树中父 Makefile 路径。即包括当前Makefile的Makefile 路径。 |
grand-parent-makefile | 返回调用树中父Makefile的父Makefile的路径 |
| |
范例:
2、
编译1个简单的APK
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
# Tell it to build an APK
include $(BUILD_PACKAGE)
编译1个依赖静态.jar文件的APK
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# List of static libraries to include in the package
LOCAL_STATIC_JAVA_LIBRARIES := static-library
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
# Tell it to build an APK
include $(BUILD_PACKAGE)
注:LOCAL_STATIC_JAVA_LIBRARIES 后面应是你的APK程序所需要的JAVA库的JAR文件名。
编译1个需要platform key签名的APK
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
LOCAL_CERTIFICATE := platform
# Tell it to build an APK
include $(BUILD_PACKAGE)
注:LOCAL_CERTIFICATE 后面应当是签名文件的文件名
编译1个需要特殊vendor key签名的APK
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
LOCAL_CERTIFICATE := vendor/example/certs/app
# Tell it to build an APK
include $(BUILD_PACKAGE)
装载1个普通的第3方APK
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Module name should match apk name to be installed.
LOCAL_MODULE := LocalModuleName
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
include $(BUILD_PREBUILT)
装载需要.so(动态库)的第3方apk
LOCAL_PATH := $(my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := baiduinput_android_v1.1_1000e
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
include $(BUILD_PREBUILT)
#################################################################
####### copy the library to /system/lib #########################
#################################################################
include $(CLEAR_VARS)
LOCAL_MODULE := libinputcore.so
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)
LOCAL_SRC_FILES := lib/$(LOCAL_MODULE)
OVERRIDE_BUILD_MODULE_PATH := $(TARGET_OUT_INTERMEDIATE_LIBRARIES)
include $(BUILD_PREBUILT)
编译1个静态java库
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Any libraries that this library depends on
LOCAL_JAVA_LIBRARIES := android.test.runner
# The name of the jar file to create
LOCAL_MODULE := sample
# Build a static jar file.
include $(BUILD_STATIC_JAVA_LIBRARY)
注:LOCAL_JAVA_LIBRARIES表示生成的java库的jar文件名。
编译C/C++利用程序模板
LOCAL_
------分隔线----------------------------
------分隔线----------------------------