ITIL Una visión inicial
8.1 Materiales y Métodos Materiales
As in any ordinary NDK application, we’ll separate the code in modules. We’ll create a module called text-
formatter containing the library we want to port, and a second one called main, which will be in charge of the communication between Java and the TextFormatter class.
109
Running Objective-C in Android
33.2.1 The ItoaApp.mk and the ItoaModule.mk files
In a way similar to how the Android NDK uses the Application.mk and the Android.mk make files, Itoa has the ItoaApp.mk and the ItoaModule.mk files.
Inside our Android project directory, we’ll create a folder called jni. This jni folder will contain two make files, ItoaApp.mk and ItoaModule.mk, and two folders
to hold the modules—one folder for the
textformatter module and a second one for the main module. Inside each module folder, we’ll create an ItoaModule.mk file. The resulting directory structure can be seen in figure 33.2.
Let’s take a look at what we’ll place inside the ItoaApp.mk and ItoaModule.mk files. In the ItoaModule.mk make file, we’ll point to the module’s ItoaModule.mk files relative to the jni folder. The content is the following:
THIS_PATH := $(call my-dir)
include $(THIS_PATH)/main/ItoaModule.mk
include $(THIS_PATH)/TextFormatter/ItoaModule.mk
The ItoaApp.mk file contains more interesting information. The content is the following:
APP_IS_LIBRARY := true
B
Turn on library mode
APP_LIBRARY_BIN_PATH = ../libs/$(TARGET_ABI)
C
Set path for .so filesThe default settings for the ItoaApp.mk file are enough for what we want to create. Since we don’t want to create an Android APK from the Objective-C code, we need to turn on the library mode
B
. The second setting is to set the path where the .so files will be savedC
.33.2.2 The textformatter module
The library to port is very simple. It only has a class method that returns an NSString *. The Objective-C code for this library is comprised of a .h file and a .m file. Here’s the code:
#import <Foundation/Foundation.h>
TextFormatter.h file
@interface TextFormatter: NSObject + (NSString *)format:(NSString *)text; @end
...
#import "TextFormatter.h"
TextFormatter.m file
@implementation TextFormatter
+ (NSString *)format:(NSString *)text { NSString *objc = @"Text from Objective-c";
NSString *string = [NSString stringWithFormat:@"%@ with %@",
objc, text];
TextFormatter.m file
return string; }
@end
As you can see, the library doesn’t need any modification. It’s just a .h and .m like you would use in an Objective-C application. Now let’s see how to configure the ItoaModule.mk file to compile this. Itoa NDK build scripts were derived from Android
NDK, but they were refactored. For example, the ItoaModule.mk file renames all the LOCAL_* variables to MODULE_*. The content of the make file is the following:
MODULE_PATH := $(call my-dir) include $(CLEAR_VARS)
MODULE_NAME := textformatter Module name
MODULE_SRC_FILES := \
TextFormatter.m Source files to compile
MODULE_C_INCLUDES += \
$(MODULE_PATH) \ Path to the include files
include $(BUILD_SHARED_LIBRARY)
Very similar to Android NDK make files, right?
33.2.3 The main module
The main module holds two source files:
JNIOnLoad.cpp, where we’ll use the JNI_OnLoad method
main.mm, where we’ll link JNI calls with the TextFormatter implementation Let’s create the JNIOnLoad.cpp file first:
#include <CoreFoundation/CFRuntime.h> #include <jni.h>
extern "C" {
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
_CFInitialize(); Initialize CoreFoundation
extern void call_dyld_handlers(); Load Objective-C classes
call_dyld_handlers(); return JNI_VERSION_1_6; }
}
Because the virtual machine calls the JNI_OnLoad method when the native library is loaded, it’s a great place to make the initialization needed by Itoa.
111
Running Objective-C in Android
#include <jni.h> #import <Foundation/Foundation.h> #import <objc/runtime.h> #import <TextFormatter.h> extern "C" { jstring Java_com_manning_androidhacks_hack033_TextFormatter_formatString( JNIEnv* env, jobject thiz, jstring text)
B
TextFormatter JNI call{
jstring result = NULL;
NSAutoreleasePool *pool = [NSAutoreleasePool new]; const char *nativeText = env->GetStringUTFChars(text, 0);
C
Convert jstring to NSString * NSString *objcText = [NSString stringWithUTF8String:nativeText]; env->ReleaseStringUTFChars(text, nativeText);NSString *formattedText = [TextFormatter format: objcText]; result = env->NewStringUTF([formattedText UTF8String]);
D
Return a jstring with result [pool drain]; return result; } }In the previous example, we have a mixture of C, C++, and Objective-C in the same file. From the method signature, we can learn that the TextFormatter Java native call will get a String as a parameter and will return a String
B
. Another interesting con- cept to learn here is that we can’t send the jstring we get as a parameter to the TextFormatter implementation directly. We need to convert the jstring to a char * and then convert that char * to an NSString *C
. After calling the TextFormatter implementation, we’ll get an NSString * that will need to be converted to a jstring. This is done by converting it to char * first, and using the env to be able to return a jstringD
.The ItoaModule.mk file for main is the following: MODULE_PATH := $(call my-dir)
include $(CLEAR_VARS)
MODULE_NAME := main Module’s name
MODULE_SRC_FILES := \
JNIOnLoad.cpp \ Source files to compile
main.mm \
MODULE_C_INCLUDES += \ Include TextFormatter.h path
$(MODULE_PATH)/../textformatter \
MODULE_SHARED_LIBRARIES += textformatter textformatter dependency include $(BUILD_SHARED_LIBRARY)
Let’s talk about what the APP_SHARED_LIBRARIES is for
B
. For that variable, we used the macro $(TARGET_ITOA_LIBRARIES), which means that the .so files located at $ITOA_NDK/itoa/platform/arch-arm/usr/lib will be included in the libs directory. If you check what’s inside that directory, you’ll notice there are more .so files than we actually need. Before building it, you’ll need to delete (or move) the following librar- ies from $ITOA_NDK/itoa/platform/arch-arm/usr/lib: libcg.so libcore.so libjnipp.so libuikit.so
33.2.4 Compiling
Now that we have all the native code in place, we need to compile all the .so files. Run this code
$ITOA_NDK/itoa-build from the jni folder.
ITOA-BUILD -C You can also use $ITOA_NDK/itoa-build -C /path/to/jni to avoid having to move to the jni folder.
After the compilation procedure finishes, we’ll get every .so file needed to run our Objective-C code in Android. In the next section, we’ll see how to call it from the Java layer.