The Android system does not allow a program that uses C/C ++, it requires that Native C/C ++ be embedded through Java code, that is, Native code is used through JNI. Therefore, JNI is very important to Android Developers.
How to package the. so file to. APK
Let's start with the simplest case. If there is a JNI implementation -- libxxx. so file, how can we use it in APK?
When I first write a similar program, I will. push the so file to the/system/lib/directory, and then execute System in Java code. loadLibrary (xxx), which is feasible, but requires the write permission of the/system/lib directory (the simulator obtains this permission through adb remount ). However, after the simulator is restarted, The libxxx. so file will disappear. Now I have found a better way to package the. so file in the apk and send it to the end user. Whether it is a simulator or a real machine, the write permission of the system partition is no longer required. The implementation steps are as follows:
1. Create the libs/armeabi directory under the root directory of your project;
2. copy the libxxx. so file to libs/armeabi;
3. The. APK file automatically compiled and output by using the adtplug-in already contains the. so file;
4. Install the APK file and you can directly use the method in JNI;
I want to briefly describe libxxx. so naming rules follow the Linux tradition, lib <something>. so is the format of the class library file name, but in Java System. when the library name is specified in the loadLibrary ("something") method, the prefix -- lib and suffix --. so.
Prepare your own JNI Module
You must know how to write your own xxx. so, but this involves too much knowledge about JNI. To put it simply, JNI is a Java standard defined by the Java platform to interact with local code on the host platform. It generally has two application scenarios: 1. use (previously developed using c/c ++ and delphi) Legacy Code; 2. for better and more direct interaction with hardware and higher performance.
1. First, create a Java class containing the native method:
Package com. okwap. testjni;
Public final class MyJNI {
// Native method,
Public static native String sayHello (String name );
}
2. Use the javah command to generate a. h file. The content is as follows (com_okwap_testjni.h file ):
/* Do not edit this file-it is machine generated */
# Include <jni. h>
/* Header for class com_okwap_testjni_MyJNI */
# Ifndef _ Included_com_okwap_testjni_MyJNI
# Define _ Included_com_okwap_testjni_MyJNI
# Ifdef _ cplusplus
Extern "C "{
# Endif
/*
* Class: com_okwap_testjni_MyJNI
* Method: sayHello
* Signature: (Ljava/lang/String;) Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello
(JNIEnv *, jclass, jstring );
# Ifdef _ cplusplus
}
# Endif
# Endif
This is a standard C header file, where JNIEXPORT and JNICALL are the JNI keywords (in fact, it is a macro without any content and is only used for indicative instructions ), jint and jstring are for int and java in JNI environment. lang. string type ing. The definitions of these keywords can be seen in jni. h.
3. Implement the above methods in the com_okwap_testjni.c file:
# Include <string. h>
# Include <jni. h>
# Include "com_okwap_testjni.h"
JNIEXPORT jstring JNICALL Java_com_okwap_testjni_MyJNI_sayHello (JNIEnv * env, jclass, jstring str ){
// Obtain the char * type in C language from the jstring type
Const char * name = (* env)-> GetStringUTFChars (env, str, 0 );
// Local constant string
Char * hello = "hello ,";
// Dynamically allocate the target string Space
Char * result = malloc (strlen (name) + strlen (hello) + 1) * sizeof (char ));
Memset (result, 0, sizeof (result ));
// String Link
Strcat (result, hello );
Strcat (result, name );
// Release the memory allocated by jni
(* Env)-> ReleaseStringUTFChars (env, str, name );
// Generate the returned value object
Str = (* env)-> NewStringUTF (env, "Hello JNI ~! ");
// Release the dynamically allocated memory
Free (result );
//
Return str;
}
4. Compile-two different compiling Environments
There are two ways to compile the above C language code into the final. so dynamic library file:
Android NDK: Native Developer Kit is a tool used to compile local JNI source code. It is convenient for developers to integrate local methods into Android applications. In fact, NDK is the same as the full source code compiling environment. It uses the Android compilation system-that is, it controls the compilation through the Android. mk file. NDK can run on Linux, Mac, and Window (+ cygwin) platforms. For more information about how to use NDK, see the following documents:
Complete source code compilation environment: the Android platform provides a make-based compilation system, which can be used to compile the correct Android. mk file for the App. The environment needs to get a complete copy of the source code from the official website through git and successful compilation, for more details, see: http://source.android.com/index.html
No matter which of the above two methods you choose, you must write your own Android. mk file. For how to write this file, see the relevant documentation.
JNI component entry functions-JNI_OnLoad () and JNI_OnUnload ()
When the JNI component is successfully loaded and uninstalled, function callback is performed. When the VM runs to System. when loadLibrary (xxx) is used, the JNI_OnLoad () function in the JNI component is executed first. When the VM releases the component, the JNI_OnUnload () function is called. First look at the sample code:
// OnLoad method, called during System. loadLibrary () Execution
Jint JNI_OnLoad (JavaVM * vm, void * reserved ){
LOGI ("JNI_OnLoad startup ~~! ");
Return JNI_VERSION_1_4;
}
// OnUnLoad method, called when the JNI component is released
Void JNI_OnUnload (JavaVM * vm, void * reserved ){
LOGE ("call JNI_OnUnload ~~!! ");
}
JNI_OnLoad () has two important roles:
Specify the JNI version: Tell the VM that the component uses the JNI version (if the JNI_OnLoad () function is not provided, the VM will use the oldest JNI 1.1 version by default ), to use a new version of JNI, such as JNI 1.4, the JNI_OnLoad () function must return the constant JNI_VERSION_1_4 (defined in jni. h) To inform the VM.
Initialization settings. When the VM executes the System. loadLibrary () function, it immediately calls the JNI_OnLoad () method. Therefore, it is most appropriate to initialize various resources in this method.
JNI_OnUnload () corresponds to JNI_OnLoad (). It is called when the VM releases the JNI component. Therefore, the method is used to clean up the resources.
Use the registerNativeMethods Method
For Java programmers, we may always follow: 1. write Java classes with native methods; ---> 2. use the javah command to generate. h header file; ---> 3. compile the code to implement the method in the header file, such an "official" process, but some people may not be able to endure the "ugly" method name, the RegisterNatives method can help you hide the methods in c/c ++ into the native method in Java without the need to follow the specific method naming format. Let's take a look at the sample code:
// Define the name of the target class
Static const char * className = "com/okwap/testjni/MyJNI ";
// Define the implicit method relationship
Static JNINativeMethod methods [] = {
{"SayHello", "(Ljava/lang/String;) Ljava/lang/String;", (void *) sayHello },
};
Jint JNI_OnLoad (JavaVM * vm, void * reserved ){
// Declare Variables
Jint result = JNI_ERR;
JNIEnv * env = NULL;
Jclass clazz;
Int methodsLenght;
// Obtain the JNI environment object
If (* vm)-> GetEnv (vm, (void **) & env, JNI_VERSION_1_4 )! = JNI_ OK ){
LOGE ("ERROR: GetEnv failed \ n ");
Return JNI_ERR;
}
Assert (env! = NULL );
// Register the local method. Load target class
Clazz = (* env)-> FindClass (env, className );
If (clazz = NULL ){
LOGE ("Native registration unable to find class '% S'", className );
Return JNI_ERR;
}
// Establish method implicit relationship
// Obtain the method length www.2cto.com
MethodsLenght = sizeof (methods)/sizeof (methods [0]);
If (* env)-> RegisterNatives (env, clazz, methods, methodsLenght) <0 ){
LOGE ("RegisterNatives failed for '% S'", className );
Return JNI_ERR;
}
//
Result = JNI_VERSION_1_4;
Return result;
}
The key to establishing the ing between the c/c ++ method and the Java method is the JNINativeMethod structure, which is defined in jni. h. The specific definitions are as follows:
Typedef struct {
Const char * name; // java method name
Const char * signature; // java method signature
Void * fnPtr; // function pointer of c/c ++
} JNINativeMethod
Refer to the Code for initializing the structure in the above example:
// Define the implicit method relationship
Static JNINativeMethod methods [] = {
{"SayHello", "(Ljava/lang/String;) Ljava/lang/String;", (void *) sayHello },
};
What is hard to understand is the value of the second parameter -- signature field. In fact, these characters correspond to the parameter type/return type of the function, and the characters in "()" indicate parameters, the following indicates the return value. For example, "() V" indicates void func (), "(II) V" indicates void func (int, int). The relationship between each character is as follows:
Character Java type C/C ++ type
V void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
The array starts with "[" and represents with two characters:
Character java type c/c ++ type
[Z jbooleanArray boolean []
[I jintArray int []
[F jfloatArray float []
[B jbyteArray byte []
[C jcharArray char []
[S jshortArray short []
[D jdoubleArray double []
[J jlongArray long []
The above are all basic types. If the parameter is a Java class, it starts with "L" and ends with ";", and uses "/" to separate packages and class names, the corresponding C function parameter is jobject, and an exception is the String class, which corresponds to the C type jstring, such as Ljava/lang/String; and Ljava/net/Socket; if a JAVA function is located in an embedded class (also called an internal class), $ is used as the delimiter between class names, for example, "Landroid/OS/FileUtils $ FileStatus ;".
The registerNativeMethods method is used not only to change the ugly long-square method name, but also to improve efficiency, because when Java classes call local functions through VMS, it usually relies on VMS for dynamic search. so local functions (so they need the naming format of specific rules). If a method needs to call multiple times consecutively, it needs to be searched once each time, therefore, you can use RegisterNatives to register a local function with the VM to find the function more efficiently.
Another important purpose of the registerNativeMethods method is to dynamically adjust the ing between the local function and the Java function value at runtime. You only need to call the registerNativeMethods () method multiple times, and pass in different ing table parameters.
Log output in JNI
You must be very familiar with using Log. x (TAG, "message") series methods in Java code, which is also the same in c/c ++ code, but first you must include the relevant header files. Unfortunately, you use different compiling environments (refer to the introduction of the two compiling environments above), and the corresponding header files are slightly different ..
If it is in the complete source code compiling environment, you only need to include <utils/Log. h> the header file can use the corresponding LOGI, LOGD, and other methods. At the same time, define macro values such as LOG_TAG and LOG_NDEBUG. The sample code is as follows:
# Define LOG_TAG "HelloJni"
# Define LOG_NDEBUG 0
# Define LOG_NIDEBUG 0
# Define LOG_NDDEBUG 0
# Include <string. h>
# Include <jni. h>
# Include <utils/Log. h>
Jstring Java_com_inc_android_ime_HelloJni_stringFromJNI (JNIEnv * env, jobject thiz ){
LOGI ("Call stringFromJNI! \ N ");
Return (* env)-> NewStringUTF (env, "Hello from JNI (Chinese )! ");
}
The log-related. h header file is in the following source code path:
Myeclair \ frameworks \ base \ include \ utils \ Log. h
Myeclair \ system \ core \ include \ cutils \ log. h
If you compile in the NDK environment, # include <android/log. h> is required. The sample code is as follows:
/*
* Jnilogger. h
*
* Created on: 2010-11-15
* Author: INC062805
*/
# Ifndef _ JNILOGGER_H _
# Define _ JNILOGGER_H _
# Include <android/log. h>
# Ifdef _ cplusplus
Extern "C "{
# Endif
# Ifndef LOG_TAG
# Define LOG_TAG "MY_LOG_TAG"
# Endif
# Define LOGD (...) _ android_log_print (ANDROID_LOG_DEBUG, LOG_TAG, __va_args __)
# Define LOGI (...) _ android_log_print (ANDROID_LOG_INFO, LOG_TAG, __va_args __)
# Define LOGW (...) _ android_log_print (ANDROID_LOG_WARN, LOG_TAG, __va_args __)
# Define LOGE (...) _ android_log_print (ANDROID_LOG_ERROR, LOG_TAG ,__ VA_ARGS __)
# Define LOGF (...) _ android_log_print (ANDROID_LOG_FATAL, LOG_TAG, __va_args __)
# Ifdef _ cplusplus
}
# Endif
# Endif/* _ JNILOGGER_H _*/
You can download the above header file to unify the usage differences in two different environments. In addition, do not forget to add the application to the class library in your Android. mk file. The two environments are
Ifeq ($ (HOST_ OS), windows)
# NDK Environment
LOCAL_LDLIBS: =-llog
Else
# Full source code Environment
LOCAL_SHARED_LIBRARIES: = libutils
Endif
Helper methods provided by Android for JNI
Myeclair \ dalvik \ libnativehelper \ include \ nativehelper
In the complete source code compiling environment, Android is in myeclair \ dalvik \ libnativehelper \ include \ nativehelper \ JNIHelp. the h header file provides helper functions for local method registration, exception handling, and other tasks. There is also a macro definition for calculating the length of a method implicit table:
# Ifndef NELEM
# Define NELEM (x) (int) (sizeof (x)/sizeof (x) [0])
# Endif
// With the above macro definition, the registration method can be written as follows. The macro definition can be directly copied to the NDK environment for use:
(* Env)-> RegisterNatives (env, clazz, methods, NELEM (methods ));
From programming tips