《Android的设计与实现:卷I》——第2章 2.7JNI应用层实例分析

简介: 本节书摘来自华章出版社《Android的设计与实现:卷I》——第章,第2.7节。作者: 杨云君著.更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.7 JNI应用层实例分析

2.2节讲解了JNI在应用框架层的使用,那么应用层又是如何使用JNI的呢?本节将通过一个实例来演示在应用层如何配合NDK开发基于JNI的应用程序。

NDK给基于JNI的应用开发带来了极大的便利。只需要以下三步:

步骤1 在Eclipse中建立Android工程,并在项目根目录建立jni目录,然后在jni目录加入JNI层的实现代码和对应的Android.mk文件。

步骤2 将项目复制到NDK samples目录,运行ndk-build命令。NDK会自动编译出共享库,并置于armeabi目录下。

步骤3 将新生成的目录和文件从NDK中复制回Eclipse。工程目录如图2-4所示。


image

接下来将分步实现应用层JNI。

2.7.1 Java层分析

在Eclipse中建立Android工程,工程名为AppJni,并生成一个启动Activity: AppJniActivity。内容如下:
package com.allongriver.jni;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class AppJniActivity extends Activity {

   private static final String TAG = "AppJniActivity";

@Override
public void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  Log.d(TAG, show());

}
//声明一个Native方法,需要在JNI中实现

 private  native String  show();

/JNI中需要调用callback方法,用来演示在JNI中如何操作Java类。为了

演示JNI函数调用过程中如何捕获Java异常,我们故意在callback函数
中抛出NullPointerException,而且没有捕获这个异常*/

private void callback(){

  Log.d(TAG, "call back from native");  
  throw new NullPointerException();

}

/在静态代码库中加载libapp_jni.so共享库,这个库在安装
 该应用程序的时候,由PackageManager从apk中解压输出到
/data/data/om.allongriver.jni/lib/libapp_jni.so */
static {
   System.loadLibrary("app_jni");//不需要带共享库的前缀lib和后缀so
}

}

以上代码实现在Java层中对Native 方法show()的调用,并在调用过程中,由JNI函数回调Java层callback方法。

2.7.2 JNI层代码和异常处理

接下来看如何在JNI层实现这个Native方法。代码如下:

image

Jstring Java_com_allongriver_jni_AppJniActivity_show( JNIEnv env,jobject thiz )
{

/通过JNI函数GetObjectClass得到传入对象的类信息。
 这里传入的对象,就是调用Native方法的那个对象/
jclass jcls = (env)->GetObjectClass(env,thiz);
//根据类信息得到callback方法的jmethodID
jmethodID jmId=(env)->GetMethodID(env,jcls,"callback","()V");
//调用callback方法
(env)->CallVoidMethod(env,thiz,jmId);
/因为在Java层的callback中抛出了未捕获的异常,所以上面的JNI函数调用必然
 出现异常,这里必须检查并处理异常,否则异常将抛给Java层的callback方法
 而此时Java层callback也没有捕获异常,此时,进程将死掉/
if((env)->ExceptionCheck(env))

{

(env)->ExceptionDescribe(env);
(env)->ExceptionClear(env);//清除异常
   }
    //处理异常后响应Java层的调用   
    return (env)->NewStringUTF(env, "Show message from JNI !");

}
如果把以上代码清除异常部分(如下所示)注释掉,看看会出现什么结果。
//(env)->ExceptionClear(env);//清除异常

运行程序后,logcat中的日志信息如下:

D/dalvikvm(4734): Trying to load lib //加载共享库
/data/data/com.allongriver.jni/lib/libapp_jni.so 0x4051b7f0
D/dalvikvm(4734): Added shared lib
/data/data/com.allongriver.jni/lib/libapp_jni.so 0x4051b7f0
D/dalvikvm(4734): No JNI_OnLoad found in //执行共享库中的第一个方法JNI_OnLoad
/data/data/com.allongriver.jni/lib/libapp_jni.so 0x4051b7f0, skipping init
D/AppJniActivity(4734): call back from native //JNI层发现异常
W/System.err(4734): java.lang.NullPointerException
……
D/AndroidRuntime(4734): Shutting down VM
//异常被抛给Java层,Java层继续打印出异常信息
E/AndroidRuntime(4734): FATAL EXCEPTION: main// 进程终止
在JNI编程中,一定要处理好JNI函数调用过程中可能出现的异常。

至此,读者可能会发现,应用层JNI编程跟应用框架层JNI编程没有多少相关性。难道JNI提供两套编程机制?
答案是肯定的。AppJni例子演示的是传统的JNI编程方式,符合JNI规范。但其缺点很明显,具体有三个:

1)需要遵守繁琐的JNI实现方法的命名规则。比如要严格遵守show函数的命名规则,如果出错,将无法调用到JNI层的实现方法:

Jstring Java_com_allongriver_jni_AppJniActivity_show( JNIEnv env,jobject thiz )

2)如果采用应用层的JNI使用方式,就需要在框架的Java层加入System.loadLibrary

("app_jni")这样的加载共享库的代码。而应用框架层会频繁调用,严重影响效率。

3)虚拟机在共享库中搜索定位JNI实现方法效率也受影响。Android应用框架层采用函数注册的方法回避这些问题。

本章开头Log系统的例子就是采用函数注册的方法。这是不是意味着在应用层就不能使用这种方法?答案显然是否定的。接下来把AppJni改造成函数注册。只需要借鉴Log系统JNI注册流程,在原有的app_jni中添加如下代码即可:


image

//这里已经可以不用遵守JNI的函数命名规则,因为我们已经做了函数映射
Jstring Java_com_allongriver_jni_AppJniActivity_show( JNIEnv env,jobject thiz )
{
// 这部分代码不需要任何改变
……
}
//下面都是为了完成函数注册添加的代码。这里是Java层方法和JNI层方法的映射

static JNINativeMethod gmethods[] = {

   {"show", "()Ljava/lang/String;”,

(void)Java_com_allongriver_jni_AppJniActivity_show},
};
/
Register several native methods for one class.
/
static int registerNativeMethods(JNIEnv env, const char className,

       JNINativeMethod gMethods, int numMethods)

{

jclass clazz;
clazz = (env)->FindClass(env,className);
if (clazz == NULL) {
   return JNI_FALSE;
}
//调用JNIEnv提供的注册函数向虚拟机注册
if ((env)->RegisterNatives(env,clazz, gMethods, numMethods) < 0) {
   return JNI_FALSE;
}
return JNI_TRUE;

}
/
Register native methods for all classes we know about.
returns JNI_TRUE on success.
/
static int registerNatives(JNIEnv env)
{
if (!registerNativeMethods(env, "com/allongriver/jni/AppJniActivity",

            methods, sizeof(methods) / sizeof(methods[0]))) {

return JNI_FALSE;
}
return JNI_TRUE;
}
/虚拟机执行System.loadLibrary("app_jni")后,进入libapp_jni.so后
会首先执行这个方法,所以我们在这里做注册的动作*/
jint JNI_OnLoad(JavaVM vm, void reserved)
{

jint result = -1;
JNIEnv env = NULL;
if ( (vm)->GetEnv(vm, (void ) &env, JNI_VERSION_1_4) )  {
   goto fail;
}
//最终调用(env)->RegisterNatives,这跟Log系统是一样的
if (registerNatives(env) != JNI_TRUE) {
   goto fail;
}
   result = JNI_VERSION_1_4;

fail:
return result;
}

至此读者可能会问,为什么同样是以函数注册方式调用JNI,在Log系统中没有执行System.loadLibrary, 也没有在JNI_OnLoad中执行注册函数呢?
那是因为系统在启动的过程中已经帮我们做了。Android启动篇会介绍这部分内容。如果应用框架层某些模块不是在系统启动过程中自动load并注册,也需要上述JNI_OnLoad步骤。读者可以参考android_media_MediaPlayer.cpp的例子。

2.8 本章小结

本章以Log系统的JNI实例为引线,贯穿了JNI技术的主要方面,让读者对JNI有足够的认识,具备深入学习框架层代码的基础。

本章首先概括了JNI在应用层和框架层中的地位;然后以Log系统为例,介绍了框架层JNI调用和注册流程,深入分析了JNIEnv的设计和实现;然后详细介绍了JNI数据类型转换、方法签名、对象操作、域和方法操作、全局引用、局部引用、弱引用以及异常处理等JNI核心内容;最后以一个应用层JNI实例演示了NDK的使用和异常处理流程。

相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
160 4
|
1月前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
24天前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
24天前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
29 8
|
2月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
81 15
Android 系统缓存扫描与清理方法分析
|
28天前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
34 1
|
1月前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。
|
2月前
|
开发工具 Android开发 Swift
安卓与iOS开发环境的差异性分析
【10月更文挑战第8天】 本文旨在探讨Android和iOS两大移动操作系统在开发环境上的不同,包括开发语言、工具、平台特性等方面。通过对这些差异性的分析,帮助开发者更好地理解两大平台,以便在项目开发中做出更合适的技术选择。
|
20天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
41 19
|
20天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
45 14