Android Studio NDK及so文件开发(二)

博客借鉴:

编程基础-----c++与c调用so文件
Linux 调用动态库(.SO文件)总结
android使用C/C++调用SO库

前言:

在前面一节中我们生成了一个so文件,并且可以成功调用了,但是如果你的包名更换了的话,那么你调用so文件还能成功么?大家可以动手试一下,看看会报什么错误。

so文件调用so文件

这节的问题就是为了解决so文件调用时候包名的问题,虽然可以直接去更改so文件解决这个问题,但是如果这个so文件是别人的呢(嘿嘿,你懂的),所以这里我们就再去开发一个so文件DemoB.so去调用原来的DemoA.so文件,这也是我目前通过不断的测试找到的一个办法,并不算好吧,毕竟目前知识水平有限。但还是先把这个办法写出来吧,献丑了,如果有其他更好的办法,还请大家不吝赐教。 包结构如下所示:

包结构


开发一个libDemoA.so文件

1、创建DemoA.java文件

首先在cn.cooloongwu包中新建DemoA.Java类,编写代码如下(建议本地方法名前面加上jni一词,我之前没有加jni,生成h文件后int类型的方法名后面会多出" __ "这样两个东西,但是不影响生成so以及调用):

package cn.cooloongwu;

/**
 * Created by CooLoongWu on 2016-12-01 09:05.
 *
 */
public class DemoA {

    private native String jniGetName();

    private native int jniGetAge();

    private native boolean jniIsMale();

    private native int jniGetAdditionResult(int x, int y);

    private native void jniShowSomething();

}

2、生成h文件

在控制台中切换到app\src\main\java目录下然后编译cn.cooloongwu.DemoA文件,如下图所示:

这里写图片描述

编译完成后就可以在该目录下看到cn_cooloongwu_DemoA.h文件。
文件内容如下(可以看到方法名都是按照 Java_ 包名_ 类名_ 方法名 来组织的,所以你也就可以知道为什么换了类名后调用会出错的原因了):

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class cn_cooloongwu_DemoA */

#ifndef _Included_cn_cooloongwu_DemoA
#define _Included_cn_cooloongwu_DemoA
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     cn_cooloongwu_DemoA
 * Method:    jniGetName
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_cn_cooloongwu_DemoA_jniGetName
  (JNIEnv *, jobject);

/*
 * Class:     cn_cooloongwu_DemoA
 * Method:    jniGetAge
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_cn_cooloongwu_DemoA_jniGetAge
  (JNIEnv *, jobject);

/*
 * Class:     cn_cooloongwu_DemoA
 * Method:    jniIsMale
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_cn_cooloongwu_DemoA_jniIsMale
  (JNIEnv *, jobject);

/*
 * Class:     cn_cooloongwu_DemoA
 * Method:    jniGetAdditionResult
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_cn_cooloongwu_DemoA_jniGetAdditionResult
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     cn_cooloongwu_DemoA
 * Method:    jniShowSomething
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_cn_cooloongwu_DemoA_jniShowSomething
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

3、编写DemoA.c文件

根据生成的cn_cooloongwu_DemoA.h我们来写下简单的DemoA.c文件,代码如下:

#include <string.h>
#include <jni.h>
#include <android/log.h>

jstring Java_cn_cooloongwu_DemoA_jniGetName(JNIEnv* env, jobject obj){
    //定义一个C语言字符串
    char * str = "我来自DemoA.so,我的昵称是CooLoongWu !";
    //转换为Java语言的字符串
    return (* env) -> NewStringUTF(env, str);
}

jint Java_cn_cooloongwu_DemoA_jniGetAge(JNIEnv * env, jobject obj){
    return 18;
}

jboolean Java_cn_cooloongwu_DemoA_jniIsMale(JNIEnv * env, jobject obj){
    //jboolean在C语言中取值为0或者1,也可以直接返回0或者1
    return JNI_TRUE;
}

jint Java_cn_cooloongwu_DemoA_jniGetAdditionResult(JNIEnv * env, jobject obj, jint x, jint y){
    return x + y;
}

void Java_cn_cooloongwu_DemoA_jniShowSomething(JNIEnv * env, jobject obj){
    //参数一是日志级别,参数二是日志TAG,参数三就是日志内容
    __android_log_print(ANDROID_LOG_ERROR, "DemoA.so", "这是来自DemoA.so的jniShowSomething()方法!");
}

4、编写Android.mk文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

#加入这句可以使得so文件打印日志
LOCAL_LDLIBS:=-L$(SYSROOT)/usr/lib -llog

LOCAL_MODULE:=DemoA
LOCAL_SRC_FILES:=DemoA.c

include $(BUILD_SHARED_LIBRARY)

5、 生成so文件

切换到Terminal,然后进入app文件夹,输入命令 * ndk-build * 来生成so文件,如下图所示:

生成so文件

6、调用so文件

首先需要将生成的so文件全部复制到app\src\main\jniLibs文件夹内(如果没有jniLibs文件夹那么你就手动创建一个就好),然后在DemoA.java中添加如下代码,来调用libDemoA.so文件:

static {
    System.loadLibrary("DemoA");
}

private void showResult() {
    Log.e("来自DemoA的打印信息", jniGetName() + " " + (jniIsMale() ? "男" : "女") + "," + jniGetAge());
    int x = 1;
    int y = 2;
    Log.e("来自DemoA的打印信息", "" + x + "+" + y + "=" + jniGetAdditionResult(x, y));
    jniShowSomething();
}

在MainActivity.java中调用如下所示:

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

    DemoA demoA = new DemoA();
    demoA.showResult();
}  

7、 打印信息如下图所示:

调用libDemoA.so文件的信息


开发一个libDemoB.so文件

1、创建DemoB.java文件

首先在com.cooloongwu包中新建DemoB.Java类,编写代码如下:

package com.cooloongwu;

/**
 * Created by CooLoongWu on 2016-12-01 09:08.
 *
 */
public class DemoB {

    private native String jniGetName();

    private native int jniGetAge();

    private native boolean jniIsMale();

    private native int jniGetAdditionResult(int x, int y);

    private native void jniShowSomething();

}

2、直接编写c文件

这个libDemoB.so文件就是为了调用libDemoA.so文件里面的方法的,所以,我们直接仿照DemoA.c文件来写DemoB.c文件了。如果你自己不熟悉的话可以再按照上面的步骤重新来一遍来熟悉下方法名的组织规范,代码如下:

#include <string.h>
#include <jni.h>
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <android/log.h>

JNIEXPORT jstring JNICALL Java_com_cooloongwu_DemoB_jniGetName(JNIEnv * env, jobject obj){
    jstring result = "小明";
    void * handle = dlopen("libDemoA.so", RTLD_LAZY );
    if(handle){
        jstring (* getName)(JNIEnv*, jobject);
        getName = (jstring ( * ) (JNIEnv*, jobject)) dlsym(handle, "Java_cn_cooloongwu_DemoA_jniGetName");
        if(getName){
            result = getName(env, obj);
        }
        dlclose(handle);
        handle = NULL;
    }
    return result;
}

JNIEXPORT jint JNICALL Java_com_cooloongwu_DemoB_jniGetAge(JNIEnv * env, jobject obj){
    jint result = 16;
    void * handle = dlopen("libDemoA.so", RTLD_LAZY );
    if(handle){
        jint (* getAge)(JNIEn*, jobject);
        getAge = (jint ( * ) (JNIEnv*, jobject)) dlsym(handle, "Java_cn_cooloongwu_DemoA_jniGetAge");
        if(getAge){
            result = getAge(env, obj);
        }
        dlclose(handle);
        handle = NULL;
    }
    return result;
}

JNIEXPORT jboolean JNICALL Java_com_cooloongwu_DemoB_jniIsMale(JNIEnv * env, jobject obj){
    jboolean result = 16;
    void * handle = dlopen("libDemoA.so", RTLD_LAZY );
    if(handle){
        jboolean (* isMale)(JNIEnv*, jobject);
        isMale = (jboolean ( * ) (JNIEnv*, jobject)) dlsym(handle, "Java_cn_cooloongwu_DemoA_jniIsMale");
        if(isMale){
            result = isMale(env, obj);
        }
        dlclose(handle);
        handle = NULL;
    }
    return result;
}

JNIEXPORT jint JNICALL Java_com_cooloongwu_DemoB_jniGetAdditionResult(JNIEnv * env, jobject obj, jint x, jint y){
    jint result = 0;
    void * handle = dlopen("libDemoA.so", RTLD_LAZY );
    if(handle){
        jint (* getResult)(JNIEnv*,jobject, jint, jint);
        getResult = (jint ( * ) (JNIEnv*, jobject, jint, jint)) dlsym(handle, "Java_cn_cooloongwu_DemoA_jniGetAdditionResult");
        if(getResult){
            result = getResult(env, obj, x, y);
        }
        dlclose(handle);
        handle = NULL;
    }
    return result;
}

JNIEXPORT void JNICALL Java_com_cooloongwu_DemoB_jniShowSomething(JNIEnv * env, jobject obj){
    void * handle = dlopen("libDemoA.so", RTLD_LAZY );
    if(handle){
        void (* showSomething)(JNIEnv*,jobject);
        showSomething = (void ( * ) (JNIEnv*, jobject)) dlsym(handle, "Java_cn_cooloongwu_DemoA_jniShowSomething");
        if(showSomething){
            showSomething(env, obj);
        }
        dlclose(handle);
        handle = NULL;
    }
}

可以看到其实代码基本都是相同的,细心的读着可能会发现DemoA.c和DemoB.c文件中方法写法不同。好了,具体去看代码,其中三处要注意的地方如下:

  • dlopen方法,该方法主要为了将so库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载其他依赖库。第一个参数是库的路径,建议所有的库都放在jniLibs文件夹下,这样使用起来也比较方便。第二个参数可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。

  • dlsym方法,该方法根据动态链接库操作句柄与符号,返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。

  • dlclose方法,该方法表示将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。

3、修改Android.mk文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

#加入这句可以使得so文件打印日志
LOCAL_LDLIBS:=-L$(SYSROOT)/usr/lib -llog

LOCAL_MODULE:=DemoB
LOCAL_SRC_FILES:=DemoB.c

include $(BUILD_SHARED_LIBRARY)

4、生成so文件

切换到Terminal,然后进入app文件夹,输入命令 * ndk-build * 来生成so文件。

5、调用so文件

首先需要将生成的so文件全部复制到app\src\main\jniLibs文件夹内(如果没有jniLibs文件夹那么你就手动创建一个就好),然后在DemoB.java中添加如下代码,来调用libDemoB.so文件,因为libDemo.so文件依赖libDemoA.so文件,所以要先把libDemoA.so文件加载进来:

static {
    System.loadLibrary("DemoA");
    System.loadLibrary("DemoB");
}

private void showResult() {
    Log.e("来自DemoB的打印信息", jniGetName() + " " + (jniIsMale() ? "男" : "女") + "," + jniGetAge());
    int x = 10;
    int y = 20;
    Log.e("来自DemoB的打印信息", "" + x + "+" + y + "=" + jniGetAdditionResult(x, y));
    jniShowSomething();
}

在MainActivity.java中调用如下所示:

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

    DemoA demoA = new DemoA();
    demoA.showResult();

    DemoB demoB = new DemoB();
    demoB.showResult();
}

6、 运行程序

以上步骤确认完毕后,运行app,看看是不是调用成功了呢,如下图所示:

成功效果图

发布了40 篇原创文章 · 获赞 47 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/u010976213/article/details/53424364