Android Framework 之HelloWorld(二)

        HelloWorld功能简单,假设这个led灯(硬件)只有我们一个HelloWorld app会去 操作它,其他app永远不去操作,那么 我们就不使用硬件访问服务(system server)了,直接使用JNI的方式操作底下硬件。

        JNI不是Android特有,有兴趣可以查阅java jni的官方接口规范文档。

        我们先写java端,接着上次创建的HelloWorld工程,在HelloWorld/app/src/main/java/com/demo/下增加一个文件夹ledctrl,里面创建一个LedCtrl.java文件,用于提供java native接口:

package com.demo.ledctrl;
public class LedCtrl {
    public static native int LedInit();
    public static native int LedCtrl(boolean blIsOn);
    public static native void LedRelease();

    static {
        try {
            System.loadLibrary("ledctrl");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

LedCtrl.java总共就暴露了三个接口:LedInit、LedCtrl和LedRelease,分别用于打开一个led灯资源、控制led灯亮灭以及释放一个led灯资源。而System.loadLibrary("ledctrl");则用于加载一个so动态链接库,说明这个C/C++库名字叫“libledctrl.so”。


       下面则是一个最简单的JNI模板:

#include 
#include 
#include 
#include 
#include 
#include 


#include 
#define PRINT_DEBUG(x,...) __android_log_print(ANDROID_LOG_DEBUG, "MyLog", ("|DBG|<%s:%d>[%s] " x),basename(__FILE__), __LINE__,__FUNCTION__,##__VA_ARGS__)
#define PRINT_ERROR(x,...) __android_log_print(ANDROID_LOG_ERROR, "MyLog", ("|ERR|<%s:%d>[%s] " x),basename(__FILE__), __LINE__,__FUNCTION__,##__VA_ARGS__)


jint LedInit(JNIEnv *env, jobject cls)
{
	PRINT_DEBUG("Entry LedInit ...");
	return 0;
}


jint LedCtrl(JNIEnv *env, jobject cls, jboolean blIsOn)
{
	PRINT_DEBUG("Entry LedCtrl : blIsOn = %d", blIsOn);	
	return 0;
}


void LedRelease(JNIEnv *env, jobject cls)
{
	PRINT_DEBUG("Entry LedRelease ...");
}


static const JNINativeMethod methods[] = {
	{"LedInit", "()I", (void *)LedInit},
	{"LedCtrl", "(Z)I", (void *)LedCtrl},
	{"LedRelease", "()V", (void *)LedRelease},
};


JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	jclass cls;

	if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_6)) {
		PRINT_ERROR("GetEnv error!");
		return JNI_ERR;
	}
	
	cls = (*env)->FindClass(env, "com/demo/ledctrl/LedCtrl");
	if (cls == NULL) {
		PRINT_ERROR("FindClass error!");
		return JNI_ERR;
	}

	if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0) {
		PRINT_ERROR("RegisterNatives error!");
		return JNI_ERR;
	}

	return JNI_VERSION_1_6;
}

       因为在jni里使用printf是不能在Android Studio的logcat上看到打印的,要使用google封装好的__android_log_print函数才行。我这里进一步封装成PRINT_DEBUG和PRINT_ERROR宏。

        值得关注的是cls = (*env)->FindClass(env, "com/demo/ledctrl/LedCtrl");这句话,这里是要根据路径找到对应的java类,而我们的LedCtrl类是定义在com/demo/ledctrl/LedCtrl.java的。

        static const JNINativeMethod methods[] = {...};则是声明了对应于java上的native接口,LedInit、LedCtrl和LedRelease,它们没有具体实现硬件相关的代码,只是加入打印而已。

        好了,我们要使用gcc编译出libledctrl.so:

aarch64-linux-gcc -fPIC -shared ledctrl.c -o libledctrl.so -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux -nostdlib ../rk3399-android-8.1/prebuilts/ndk/r11/platforms/android-24/arch-arm64/usr/lib/libc.so -I ../rk3399-android-8.1/prebuilts/ndk/r11/platforms/android-24/arch-arm64/usr/include ../rk3399-android-8.1/prebuilts/ndk/r11/platforms/android-24/arch-arm64/usr/lib/liblog.so

         这里没有使用Makefile,也没有创建NDK工程,而是直接使用gcc命令进行编译。需要关注的是,要指定正确的库路径和头文件路径。使用-nostdlib编译选项是因为我的开发板nanopc-t4只有/system/lib64/libc.so,而没有标准c库中的libc.so.6。aarch64-linux-gcc是rk3399的交叉编译工具链,在android8.1源码中用于编译uboot、linux内核、framework等相关模块。

      编译出来libledctrl.so应该放在AS工程的什么地方呢?在AS创建工程时我们看到有个“libs”的空文件夹,我们可以把libledctrl.so放进该目录,但需要用“arm64-v8a”文件夹包裹起来(即HelloWorld/app/libs/arm64-v8a/下),否则打开app后会出现类似:

....... is not accessible for the namespace "classloader-namespace"之类的错误log。当然了,如果还要依赖其他第三方库的话,还要把库push到/system/lib64/,同时修改/system/etc/public.libraries.txt文件。

        最后还要在HelloWorld/app/build.gradle中指定库的路径:

 
android {
...
...
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
...
...

       最后在按键点击事件回调中加入LED的控制的函数调用:

...
...
import com.demo.ledctrl.*;

public class MainActivity extends AppCompatActivity {

    private boolean isLedOn = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LedCtrl m_LedCtrl = new LedCtrl();
        m_LedCtrl.LedInit();

        final Button button = findViewById(R.id.Led);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // Code here executes on main thread after user presses button
                if(isLedOn == false) {
                    LedCtrl.LedCtrl(false);
                    button.setText("Led off");
                    Toast.makeText(getApplicationContext(), "Led off", Toast.LENGTH_SHORT).show();
                }
                else {
                    LedCtrl.LedCtrl(true);
                    button.setText("Led on");
                    Toast.makeText(getApplicationContext(), "Led on", Toast.LENGTH_SHORT).show();
                }

                isLedOn = !isLedOn;
            }
        });
    }
}

       OK,这样HelloWorld就完成了C库的调用。点击屏幕上的按钮,在logcat上还会有打印。

你可能感兴趣的:(Android,Framework,android,jni)