04. C, Bionic and Low-Level Libraries

  • Description: Low-level libraries in use by NDK, Bionic - the C standard library in Android
  • Practical part: Android NDK simple app development

Lecture

Practical

Lab archive: lab-4.zip Lab solutions: lab-4-solved.zip

Task 1 - Running long tasks in Android

Create a new application and add native support. Declare a native function in Java. Give it any name and two int parameters: loops and time.

native void loop(int loops, int wait);

Use javah to generate a header file:

javah -classpath android_workspace/Task1/bin/classes/ ndk.lab4.Task1.MainActivity

This will create the file ndk_lab4_Task1_MainActivity.h in the current folder. Move this file to the jni folder in your project.

Include the header in the generated C or CPP file. Implement the method:

for (int i = 0; i < loops; i++) {
	sleep(wait);
}

Call this function from on create using 100 and 100 as parameters. This will generate an Application Not Responding message.

To call a blocking task from a Java application use Java threads or AsyncTasks. AsyncTasks also allow you to more easily call the UI thread and change the visual state of the application in all the methods except doInBackground, which is where the blocking code should run.

Extend the AsyncTask class and implement doInBackground, such that it calls the loop method. In the onPreExecute and onPostExecute methods update a TextView to show the status of the task (started, finished).

Task 2 - Reading from files, writing to files

Reading and writing from files is done in the same way as it is done on other Unix systems, however, by default you can only have access to the files in the application's data directory. To find out what these directories are, or to create new ones, create a ContextWrapper instance in your MainActivity and then call getFilesDir(). Create two native methods: one which receives a file name as a String from Java and a string which it will write to a file, and one which receives a file name and returns the first 1024 bytes as a String to Java.

To access jstrings from native code, you will have to call GetStringUTFChars.

char *str;
 
C++:
str = env->GetStringUTFChars(jstring, jboolean * (or NULL));
C:
str = (*env)->GetStringUTFChars(JNIEnv, jstring, jboolean * (or null));

After you are done with the String call ReleaseStringUTFChars:

C++:
env->ReleaseStringUTFChars(jstring, jbyte *);
C:
(*env)->ReleaseStringUTFChars(JNIEnv, jstring, jbyte *);

To return a new String from native code:

C++:
return env->NewStringUTF(jbyte *);
C:
return (*env)->NewStringUTF(JNIEnv, jbyte *);

Call these methods multiple times to verify that they work. Display the output to logcat every time the file gets modified.

Task 3 - Shell commands

To call a shell command from native code, you can use the system function, but you will not get any output except the return code. An alternative is using the popen function which writes the output of the command to a buffer. Create a native method which executes ls on a path given as a String from Java and return the first 1024 bytes of the result back to Java. Display it using Logcat. Make sure to add READ_EXTERNAL_STORAGE permission to list files in the /sdcard folder.

Task 4 - Threads

There are two ways to get native threads: use Java threads which call native functions or use POSIX threads. Creating many threads in Java and then calling a native function can be quite expensive, so it's often better to use POSIX threads instead. However, POSIX threads need to be registered with Java if they need to use environment functionality, for example throw exceptions to Java or call Java methods.

To register the thread you need a reference to the Java Virtual Machine itself. The easiest way to obtain a reference is to add the JNI_OnLoad function to the library, which gets called automatically when the library is loaded.

JavaVM *pVM;
 
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
	pVM = vm;
	return JNI_VERSION_1_6;
}

Then, from one of the created threads you have call AttachCurrentThread.

JNIEnv *env;
pVM->AttachCurrentThread(&env, NULL);

Then, before exiting, an attached thread must call DetachCurrentThread.

pVM->DetachCurrentThread();

To throw an exception use this code:

jclass exception = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(exception, "Exception from a POSIX thread");

Throw this exception from a new POSIX thread. First, try passing env from the original thread via the argument. Then, use AttachCurrentThread, and try again. Use DetachCurrentThread before exiting the thread.

Quick POSIX threads reminder:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
	void *(*start_routine) (void *), void *arg);
int pthread_join(pthread_t thread, void **retval);

Bonus - Implement a threaded server

Create a new project and add native support. Use the following code to get you started:

JNIEXPORT void JNICALL Java_ndk_lab4_Task1_MainActivity_server
  (JNIEnv *env, jobject thiz, jint port) {
 
	int sock = socket(PF_INET, SOCK_STREAM, 0);
 
	struct sockaddr_in addr;
 
	addr.sin_family = PF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = port;
 
	bind(sock, (sockaddr *)&addr, sizeof(addr));
}

Listen on sock and accept connections. Launch a new thread when data is available for reading after accepting a connection to manage that connection.

Implement the following functions:

  • ls [folder]: it sends the contents of the application's data folder, or a folder inside the application's data folder.
  • read file: it sends the contents of a file inside application's data folder
  • send file length content: append the content to a file inside the application's data folder, creating the file if it doesn't exist

Use the mininc program from the archive to use as a client.

ndk/courses/04.txt ยท Last modified: 2014/06/16 14:52 by petre.eftime
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0