Android Studio下的JNI开发教程

Android Studio下的JNI开发教程


这是我在CSDN的第一篇博文,想了很久自己能写什么内容,很纠结呀,感觉简单的内容不愿意写,复杂的内容,自己又没有能力讲透。所以,选了JNI作为自己在CSDN的开篇 !其实目的是希望能用写技术博客的方式,督促自己学习。并且希望读者们,可以通过阅读我的博客,能帮助你们进步!这也许会成为我持续更博的最大动力。


内容概述

  • 什么是JNI
  • Android Studio 下JNI开发环境的配置
  • 如何使用Android Studio开发JNI项目
  • Module的gradle文件配置以及其它配置
  • 常见问题分析及解决

1、什么是JNI和NDK?

NDK介绍
Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”。

众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代码语言执行部分程序。

NDK包括了:

从C / C++生成原生代码库所需要的工具和build files。将一致的原生库嵌入可以在Android设备上部署的应用程序包文件(application packages files ,即.apk文件)中。支持所有未来Android平台的一系列原生系统头文件和库

为何要用到NDK?

概括来说主要分为以下几种情况:
    1. 代码的保护,由于apk的java层代码很容易被反编译,而C/C++库被反编译的难度较大。
    2. 在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。
    3. 便于移植,用C/C++写的库可以方便在其他的嵌入式平台上再次使用。

—— [ 百度百科 ]

JNI介绍

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。—— [ 百度百科 ]


2、Android Studio 下JNI开发环境的配置

因笔者使用的是Mac OS系统的电脑,这里以Mac版Android Studio为例作为讲解

安装Android Studio的过程我默认大家都会了,不会的,参考Android Studio安装教程 ,我们在这讲下NDK的下载方法。

  • 创建一个HelloWorld工程
    创建过程过于简单,忽略跳过!

  • NDK下载
    1、 选择Android Studio -> Preferences
    这里写图片描述

    2、 Appearance&Behavior-> System Settings -> Adnroid SDK 下,选择SDK Tools 选项卡,找到NDK并且勾选,点击OK,即可自动下载,下载完成,点击Finish按钮关闭参考即可
    这里写图片描述

    3、下载完成之后,选择File->Project Structure->SDK Location 设置NDK的路径
    这里写图片描述

    还可以在AndroidDevTools网站上面下载NDK,下载完成,参考步骤3,同样的方法设置即可,这里不做详细介绍。

    当然,假如你非常熟悉Android Studio的目录结构,你也可以通过直接修改local.properties,里面也可以指定NDK的所在目录,需要指出的是,因为笔者是从Eclipse平台迁移过来Android Studio的,所以习惯使用Eclipse的目录结构,所以初学者可能会发现,我下面的截图,与你的目录结构不太一致,其实,你只需要选择以项目形式(Project)展示即可,Studio默认的目录结构显示方式为Android:

    这里写图片描述

    到这里,JNI开发的环境配置就OK啦,下面让我们一起进入正题吧,开发属于我们自己的第一个JNI项目吧,想想还是有些小激动呢!哈哈哈哈~~~~


3、如何使用Android Studio开发JNI项目

上面我们已经搭建好了JNI的开发环境,并且创建了一个HelloWorld的项目,接下来,我们开始进入正题咯,前戏N久,大家应该到猴急了吧?别着急,心急吃不了热豆腐,那么,我们开始咯!

  • 在MainActivity同一包下(个人喜欢,笔者为了方便)创建一个类TestJni.java,用于加载SO文件,同时添加一个native方法
package com.anzhi.test.anzhi;

/**
 * Created by heguowen on 2017/5/17.
 */

public class TestJni {

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

    public native  String sayHello();

}
  • 利用javah命令生成对应的JNI头文件,1、打开命令行(Terminal),2、把当前目录cd到java目录下,3、通过javah命令产生头文件。具体命令如下,javah的使用方法,麻烦自行百度,读者也可先不必纠结命令行的一串命令究竟是啥意思

    cd app/src/main/java
    javah -d ../jni -jni com.anzhi.test.anzhi.TestJni

    这里写图片描述

现在,我们已经生成了一个TestJni类对应的头文件啦,接下来,在当前目录下复制生成的头文件,重命名为AnzhiTestJni.c,名字任意取即可,打开此文件并进行修改,代码如下:

#include <com_anzhi_test_anzhi_TestJni.h>

JNIEXPORT jstring JNICALL Java_com_anzhi_test_anzhi_TestJni_sayHello
  (JNIEnv *env , jobject obj){
        return (*env) -> NewStringUTF(env,"ANZHI JNI TEST!");
  }

同时,你也可以打开刚刚生成的头文件进行对比,我们发现,这两个文件非常类似,我只是在它的基础上删掉一些注释并且将方法的形参取了一个变量名,然后加了一个方法体的实现,没错,就是这么简单!在这里,我们也将自动生成的头文件的代码贴出来,方便读者做对比

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

#ifndef _Included_com_anzhi_test_anzhi_TestJni
#define _Included_com_anzhi_test_anzhi_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_anzhi_test_anzhi_TestJni
 * Method:    sayHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_anzhi_test_anzhi_TestJni_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

我们可以注意到,这个头文件的第一行注释写的是:DO NOT EDIT THIS FILE - it is machine generated 。意思就是说:不要对这个文件进行任何的编辑操作,这是机器自动生成的。所以我们就不要再手贱啦!哈哈哈~~~

到这里,我们就已经完成了代码的编写并且可以尝试运行一下啦,我们试试结果如何?不知道读者现在感觉如何?反正我已经差不多high啦~脸红ing哦,可能有读者要说啦:老司机! 哈哈,我只能回一句:表在意这些细节好不好!!!

话不多说,我们尝试运行一下,看看结果如何,不要激动啦各位!接下来就是见证奇迹的时刻!

沉默N秒,what are you 弄啥嘞?居然没编译通过?什么原因?代码写得有问题吗?还是其它的原因呢?不要着急,我们进入下一个话题!


Module的gradle文件配置以及其它配置

我们可以看到,编译不通过的报错日志:

Error:Execution failed for task ‘:app:compileDebugNdk’.
Error: Your project contains C++ files but it is not using a supported native build system.
Consider using CMake or ndk-build integration with the stable Android Gradle plugin:
https://developer.android.com/studio/projects/add-native-code.html
or use the experimental plugin:
https://developer.android.com/studio/build/experimental-plugin.html.

其实,我们需要对Project下面的gradle.properties文件进行必要的设置,我们加上:android.useDeprecatedNdk=true,再试试吧!话说,双手合十,对着电脑拜托拜托的话,编译出错概率更好哦!

果然,重新编译,编译通过了,我说的方法有效吧!以后编译时,记得双手合十的动作哦!但是,我们发现我们的APP崩溃啦!发生了什么?就是一个helloworld的程序而已,没有几行代码居然会崩溃?我们跟踪一下报错日志看下:

com.anzhi.test.anzhi E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.anzhi.test.anzhi, PID: 6782
java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.anzhi.test.anzhi-1/base.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_dependencies_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_0_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_1_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_2_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_3_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_4_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_5_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_6_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_7_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_8_apk.apk”, zip file “/data/app/com.anzhi.test.anzhi-1/split_lib_slice_9_apk.apk”],nativeLibraryDirectories=[/data/app/com.anzhi.test.anzhi-1/lib/x86, /vendor/lib, /system/lib]]] couldn’t find “libanzhi_testjni.so”
at java.lang.Runtime.loadLibrary(Runtime.java:366)
at java.lang.System.loadLibrary(System.java:988)
at com.anzhi.test.anzhi.TestJni.(TestJni.java:10)
at com.anzhi.test.anzhi.MainActivity.onCreate(MainActivity.java:13)
at android.app.Activity.performCreate(Activity.java:5990)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
at android.app.ActivityThread.access 800(ActivityThread.java:151)atandroid.app.ActivityThread H.handleMessage(ActivityThread.java:1303)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

couldn’t find “libanzhi_testjni.so” ,这个文件没找到,有几种可能,一个是我们的C代码写错了,导致没有自动生成这个文件,另外一个就是Module里面没有配置正确,在这里,其实是第二种原因,我们没有对Module的gradle文件进行必要的设置。话不多少,撸起袖子就是干,如下:

defaultConfig {
        applicationId "com.anzhi.test.anzhi"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        ndk {
            moduleName "anzhi_testjni"
        }
    }

修改Module中Build.gradle文件,在defaultConfig段落中加入ndk编译配置。即:

ndk {
moduleName “anzhi_testjni”
}

好了 ,我们再运行一遍试试!成功了,没有报错,期待已久的helloworld终于出现了
这里写图片描述

但是,因为我们现在为止,还没有调用SO里面的方法,是否真的成功了,我们还未知,那么,我们需要改造一下我们的helloworld工程!

首先将MainActivity的布局文件做一个修改,以下内容过于简单,读者选择性忽略!别看吐咯!

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context="com.anzhi.test.anzhi.MainActivity">

    <TextView
        android:id="@+id/mtv_helloworld"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="28sp"
        android:textColor="@android:color/holo_red_dark"
        android:gravity="center"
        android:text=" " />

</LinearLayout>

我们让一个TextView居中显示,设置了文字大小,字体颜色设置为深红色,并且加了一个id,布局文件,修改完毕!

接下来,我们修改以下MainActivity这个类:

package com.anzhi.test.anzhi;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView mTvHelloWorld = (TextView) findViewById(R.id.mtv_helloworld);
        mTvHelloWorld.setText(new TestJni().sayHello());
    }
}

接下来,我们看下效果,成功了,我们在C代码里面写的字符串,成功显示到TextView里面啦!
这里写图片描述

至此,我们就成功完成了我们的第一个JNI项目!


4、常见问题分析及解决

问题分析参考第三模块,我写作途中,觉得,合并起来讲,比较合理并且易于分析问题!

5、END

由于篇幅有些长,对于一些其它细节问题,我们下次有机会再讨论。同时,我也完成了我的第一篇博客,如有错误,烦请指出,必将虚心学习并改正。但求不误导他人!

  • 转载请注明出处,谢谢理解!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值