Android Studio2.0 NDK开发,JNI技术实现小Demo,诠释JNI的基本开发

前段时间写过一篇关于JNI开发的文章,不小心被删了,今天补上

NDK和JNI的介绍:

ndk(Native Development Kit)是工具的集合,帮助开发者快速开发C(C++)动态库,并将.so文件和Java应用一起打包成apk;
jni(Java Native Inteface)java本地接口,是一种互通机制,建立了Java和C(C++)互通的桥梁。

Android Studio2.0使用NDK开发

最早使用Eclipse开发Android项目时,NDK开发始终是比较让人头疼的一块,因为需要手动关联代码提示,需要手动创建jni文件,Android.mk文件,手动生成.h头文件,先编译成.so文件才可以运行。AS2.0之前也是比较麻烦的,这里介绍一下Android2.0之后的NDK开发流程:
Android Studio2.0NDK的开发主要是使用了CMake工具进行开发,这就是他的简便之处
CMake:CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为 CMakeLists.txt。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是 CMake 和 SCons 等其他类似系统的区别之处。感兴趣的可以上网搜一下CMake的进一步学习下
在这之前你需要做一些准备工作,下载NDK,CMake,LLDB这三个包
LLDB包是用来调试C代码的
这里写图片描述
下载完之后,在你AS的SDK目录下,会找到下载好的这三个包,把其中的ndk-bundle配置到你的环境变量path中

第一类,创建项目时候就已经确定了使用NDK开发

在创建项目的时候勾选Include C++ Support选项,然后按照正常流程创建项目
这里写图片描述
完成项目创建之后,你会发现工程结构多了一些东西如下
这里写图片描述
其中cpp文件家里面放的就是你的C++代码,并且已经帮你自动写好了一部分C++代码,其实到这一步你就已经完成了一个简单的JNI的Demo,你可以直接运行起来试试,当然如果有报错的情况,你可以Open Module Setting来看一下NDK是不是已经自动导入,如果没有,需要你手动导入一下
这里写图片描述

CMakeLists.txt里面是相关的配置,包括你的.so文件名,你的c代码路径,以及log日志的一些配置。
再来看一下gradle文件也多出了两行相应的配置,分别配置了c代码编辑和CMakeList.txt文件
这里写图片描述
到这里就是android studio2.0的NDK项目的创建

第二类,开发中途遇到使用NDK开发

我想这个是我们比较关心的,因为很多情况下需求是中途加上来的,在项目中使用NDK开发,才是我们需要关注的。
首先我们要创建好一个jni文件夹来放我们的C代码,当然你也可以创建cpp文件夹,放我们的C++代码
这里写图片描述
然后在该文件夹下创建我们的C文件
这里写图片描述
接下来我们回忆下刚才在创建JNI项目的时候,多出来的那个CMakeLists.txt文件和在gradle文件里面多出来的那两处配置,就可以用的上了,首先在gradle文件defaultConfig节点里配置如下代码

 externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }

然后在buildTypes节点的外面配置如下代码

 externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

那么现在还缺少CMakeLists.txt文件了,这个文件里面的东西不太好记,我是直接拷贝的(随便创建一个JNI项目就可以自动或取代CMakeLists.txt文件),将CMakeLists.txt文件copy到app目录下,同时要对这个文件进行修改,因为这个文件配置了动态链接库以及C代码的路径等(我觉得和以前的Android.mk文件差不多),具体需要修改的地方如下:
这里写图片描述
这里写图片描述
到这里已经基本完成了,接下来你要Rebuild Project一下,这时候你可以尝试着写一个native方法来测试一下,发现并没有代码提示啊,不会在C中自动创建代码啊,不要着急,光标放到你的native方法名上面,然后alt+enter键弹出相应的解决方案,选择第三个
这里写图片描述
它会自动比对一下,并且gradle几秒钟,这个时候你把光标重新移到native方法名上,alt+enter就可以看到代码提示,自动帮你在C文件中创建对应的方法,有时候会自动给你创建另一个C文件,并在里面自动写好了方法,你可以把里面代码copy到你的C文件中,把自动创建的C文件删掉,下一次再写native方法,就会自动在你的C文件中自动添加方法了。到这里项目就创建完成了,代码提示有了,还可以看相应的源码,比如.h文件的源码。
这里并未详细介绍C++使用,其实创建过程和C是一样的,只不过习惯性的不需要叫你文件夹,而是叫做cpp文件夹,对应的需要配置好CMakeLists.txt文件,cpp文件夹下创建的文件需要以.cpp为后缀。C++代码使用的JNIEnv和C代码的不一样的,是进一步的封装,感兴趣的可以自己研究一下。
另外,在CMakeLists.txt文件里面的SHARED代表的是生成动态链接库
这里写图片描述
动态链接库(SHARED):.so文件,占用的体积较小
静态链接库(STATIC):.a文件,占用的体积较大
最后生成的.so文件对应的路径在:app\build\intermediates\cmake\debug\obj
接下来我把我写的JNI练习的相关代码看一下
MainActivity的代码

package com.herenit.jnidemo05;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.lang.reflect.Method;

/**
 * C代码中调用java某一个类中的方法,用到了C的反射技术
 */
public class MainActivity extends Activity {

    private JavaSimple javaSimple;
    private EditText et_num1,et_num2;
    private TextView tv_result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        javaSimple = new JavaSimple();
        et_num1 = (EditText) findViewById(R.id.et_num1);
        et_num2 = (EditText) findViewById(R.id.et_num2);
        tv_result = (TextView) findViewById(R.id.tv_sum);
    }

    /**
     * 使用反射技术调用某一个类的某个方法
     * @param view
     */
    public void reflectOnclick(View view){
        try {
            //1,获取该类的字节码文件
            Class clazz = JavaSimple.class;
            //2,根据获取该类的实例对象
            Object javaSimple = clazz.newInstance();
            //3,获取该类的某一个方法对象(Method)
            Method mGetSum = clazz.getDeclaredMethod("getSum",int.class,int.class);
            //4,调用对应的方法
            int result = (int) mGetSum.invoke(javaSimple,3,2);
            Toast.makeText(this, "反射调用成功:result="+result, Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 点击按钮,用C调用java的JavaSimple的sayHello方法
     * Log日志看现象
     * @param view
     */
    public void cCallJava_sayHello(View view){
        javaSimple.cCallJava_sayHello();
    }

    /**
     * 点击按钮,C调用java的JavaSimple的getSum方法
     * 界面操作看现象
     * @param view
     */
    public void cCallJava_getSum(View view){
        String str1 = et_num1.getText().toString().trim();
        String str2 = et_num2.getText().toString().trim();
        if(!TextUtils.isEmpty(str1) && !TextUtils.isEmpty(str2)){
            int num1 = Integer.valueOf(str1);
            int num2 = Integer.valueOf(str2);
            int result = javaSimple.cCallJava_getSum(num1,num2);
            tv_result.setText(""+result);
        }
    }

    /**
     * 点击按钮,C调用java的JavaSimple的LogString方法
     * 从log日志看现象
     * @param view
     */
    public void cCallJava_LogString(View view){
        javaSimple.cCallJava_LogString();
    }

    /**
     * 点击按钮,C调用java的JavaSimple的staticFunction静态方法
     * 从Log日志看现象
     * @param view
     */
    public void cCallJava_staticFunction(View view){
        javaSimple.cCallJava_staticFunction();
    }
}

JavaSimple中的代码

package com.herenit.jnidemo05;

import android.util.Log;

/**
 * Created by HouBin on 2017/3/8.
 */

public class JavaSimple {
    /**
     * 加载动态链接库
     */
    static {
        System.loadLibrary("native-lib");
    }

    private static final String TAG = JavaSimple.class.getSimpleName();


    public void sayHello() {
        Log.e(TAG, "C调用Java的无参数无返回值方法:sayHello()");
    }

    public int getSum(int a, int b) {
        return a + b;
    }

    public void LogString(String text) {
        Log.e(TAG, "C调用Java的String参数方法:LogString(String text)---" + text);
    }

    public static void staticFunction(String text) {
        Log.e(TAG, "C调用Java的静态String参数方法:staticFunction(String text)---" + text);
    }

    public native void cCallJava_sayHello();

    public native int cCallJava_getSum(int num1, int num2);

    public native void cCallJava_LogString();

    public native void cCallJava_staticFunction();
}

native-lib.c中的代码

#include <jni.h>
#include <syslog.h>

/**
 * 在这个方法中,使用C代码调用JavaSimple中的sayHello方法,使用的是C的反射技术
 */
JNIEXPORT void JNICALL
Java_com_herenit_jnidemo05_JavaSimple_cCallJava_1sayHello(JNIEnv *env, jobject instance) {
    /*1,获取该类的字节码文件
     * jclass      (*FindClass)(JNIEnv*, const char*);
     * char*参数 传入的是全类名(需要把点(.)改成斜线(/)
     *
     * */
    jclass clazz = (*env)->FindClass(env, "com/herenit/jnidemo05/JavaSimple");


    /*2,获取该类的某一个方法对象(jmethodID)
     * jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char* str1, const char* str2);
     * jclass 要调用的java类的字节码
     * str1 要调用的类的方法的方法名
     * str2 对应的这个方法的签名(可以在该类的.class文件所在的根目录下执行命令行:javap -s 全类名(不包括.java))得到
     * */
    jmethodID methodId = (*env)->GetMethodID(env, clazz, "sayHello", "()V");

//    3,根据获取该类的实例对象
    jobject javaSimple = (*env)->AllocObject(env, clazz);
    //4,调用对应的方法
    (*env)->CallVoidMethod(env, javaSimple, methodId);
}

/**
 * C调用java中的带参数的方法
 */
JNIEXPORT jint JNICALL
Java_com_herenit_jnidemo05_JavaSimple_cCallJava_1getSum(JNIEnv *env, jobject instance, jint num1,
                                                        jint num2) {
    jclass clazz = (*env)->FindClass(env, "com/herenit/jnidemo05/JavaSimple");
    jmethodID methodId = (*env)->GetMethodID(env, clazz, "getSum", "(II)I");
    jobject javaSimple = (*env)->AllocObject(env, clazz);
    int result = (*env)->CallIntMethod(env, javaSimple, methodId, num1, num2);
    return result;
}

/**
 * C调Java的 LogString(String text)方法
 */
JNIEXPORT void JNICALL
Java_com_herenit_jnidemo05_JavaSimple_cCallJava_1LogString(JNIEnv *env, jobject instance) {

    jclass clazz = (*env)->FindClass(env, "com/herenit/jnidemo05/JavaSimple");
    jmethodID methodId = (*env)->GetMethodID(env, clazz, "LogString", "(Ljava/lang/String;)V");
    jobject javaSimple = (*env)->AllocObject(env, clazz);

    jstring text = (*env)->NewStringUTF(env, "I am param");
    (*env)->CallVoidMethod(env, javaSimple, methodId, text);
}
/**
 * C调用java的静态方法static void staticFunction(String text)
 */
JNIEXPORT void JNICALL
Java_com_herenit_jnidemo05_JavaSimple_cCallJava_1staticFunction(JNIEnv *env, jobject instance) {
    //1,获取该方法对应的类的字节码文件类
    jclass clazz = (*env)->FindClass(env,"com/herenit/jnidemo05/JavaSimple");
    //2,获取methodId
    jmethodID methodId = (*env)->GetStaticMethodID(env,clazz,"staticFunction","(Ljava/lang/String;)V");
    //3,调用java的静态方法
    jstring text = (*env)->NewStringUTF(env,"I am from C,call Java staticFunction!");
    (*env)->CallStaticVoidMethod(env,clazz,methodId,text);
}

最后给大家推荐个网址,我学习JNI看过的感觉比较有帮助的网址:
JNI技术的一些基础知识
最后给大家我的Demo源码地址
源码下载
希望各位大神多多给出评价和意见,谢谢!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值