Android冰淇淋三明治ICS(4.0+)JNI局部引用的变化(zz)

原文地址:http://cowboy.1988.blog.163.com/blog/static/7510579820143405437470/
但是我觉得这个文章应该是它从其他地方copy来的,因为下面的代码格式一眼就看出来了,原文格式很乱,我稍微整理了下,虽然还是很乱


译序:
这篇文章的内容实际是在我发现一个项目bug后寻找解决方案时找到的,当时项目原有target为8(
ICS 4.0之前的2.X版本),在4.0+的S3上运行一切正常,而后target升级到14时再在S3上运行时就

会出现类似如下的native crash:
05-13 14:07:13.139: E/dalvikvm(22265): JNI ERROR (app bug): attempt to use stale 
localreference 0x20d00001
05-13 14:07:13.139: E/dalvikvm(22265): VM aborting
05-13 14:07:13.139: A/libc(22265): Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1), 

thread 22457 (Thread-1276)
05-13 14:07:13.239: I/DEBUG(1894): *** *** *** *** *** *** *** *** *** *** *** *** *** 

*** *** ***
05-13 14:07:13.249: I/DEBUG(1894): Build fingerprint: 
‘samsung/m0zc/m0chn:4.1.2/JZO54K/I9300ZCEMB1:user/release-keys’
05-13 14:07:13.249: I/DEBUG(1894): pid: 22265, tid: 22457, name: Thread-1276 >>> 
cn.android.app <<<
05-13 14:07:13.249: I/DEBUG(1894): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault 
addr deadd00d
05-13 14:07:13.489: I/DEBUG(1894): r0 00000000 r1 00000000 r2 deadd00d r3 00000000
05-13 14:07:13.489: I/DEBUG(1894): r4 408cb1a8 r5 0000020c r6 20d00001 r7 fffff86c
05-13 14:07:13.489: I/DEBUG(1894): r8 5ee308dc r9 00004e58 sl fffff870 fp 5ee307b8
05-13 14:07:13.489: I/DEBUG(1894): ip 00004000 sp 5ee30540 lr 400f7c95 pc 40866e50 
cpsr 60000030
05-13 14:07:13.489: I/DEBUG(1894): d0 3ff000003f800000 d1 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d2 0000000000000000 d3 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d4 0000000000000000 d5 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d6 0000000000000000 d7 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d8 0000000000000000 d9 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d10 0000000000000000 d11 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d12 0000000000000000 d13 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d14 0000000000000000 d15 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d16 0000000000000000 d17 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d18 0000000000000000 d19 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d20 0000000000000000 d21 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d22 0000000000000000 d23 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d24 0000000000000000 d25 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d26 0000000000000000 d27 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d28 0000000000000000 d29 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): d30 0000000000000000 d31 0000000000000000
05-13 14:07:13.489: I/DEBUG(1894): scr 60000010
05-13 14:07:13.499: I/DEBUG(1894): backtrace:
05-13 14:07:13.499: I/DEBUG(1894): #00 pc 00045e50 /system/lib/libdvm.so (dvmAbort+75)
05-13 14:07:13.499: I/DEBUG(1894): #01 pc 00028c3c /system/lib/libdvm.so 
(IndirectRefTable::get(void*) const+336)
05-13 14:07:13.499: I/DEBUG(1894): #02 pc 00049eeb /system/lib/libdvm.so 
(dvmDecodeIndirectRef(Thread*, _jobject*)+30)
05-13 14:07:13.499: I/DEBUG(1894): #03 pc 0004ca77 /system/lib/libdvm.so
05-13 14:07:13.499: I/DEBUG(1894): #04 pc 00653480 
/data/data/cn.android.app/lib/libgameapp.so (CKSoundManager::LoadBGM(char const*)+56)
05-13 14:07:13.509: I/DEBUG(1894): memory map around fault addr deadd00d:
05-13 14:07:13.509: I/DEBUG(1894): be9ae000-be9cf000 [stack]
05-13 14:07:13.509: I/DEBUG(1894): (no map for address)
05-13 14:07:13.509: I/DEBUG(1894): ffff0000-ffff1000 [vectors]
05-13 14:07:13.674: I/DEBUG(1894): !@dumpstate -k -t -z -d -o 
/data/log/dumpstate_app_native -m 22265

上面crash内容中比较关键的提示是attempt to use stale local reference和调用栈上的
dvmDecodeIndirectRef,实际指的是JNI调用时对Java部分对象引用的错误,按照关键内容找到了
貌似是android dalvik team开发人员写的一篇相关的文章,按照解释顺利改正了不严谨的JNI使用
代码,问题解决!感觉有必要翻译一下全文,加深一下理解(由于本人水平有限,翻译不当之处欢
迎指出!):

正文:
[本文作者是Elliott Hughes,Dalvik小组的软件工程师。- Tim Bray]
如果你不写用到JNI的原生代码的话,那么这篇文章对你没什么用。如果你写的话,那么你真应该
好好读读本文。
什么东西变了?为嘛呢?
每个开发者都想要一个好用垃圾回收器(garbage collector,简称GC)。做的好的GC是会随时移动
对象(objects)的。这样就能便于提供更高效的内存分配和批量内存回收,避免堆内存碎片,并可
能提高定位性能(locality)。如果你把指向这些对象的指针递交给原生代码的话,随时移动对象就
是个问题了。JNI使用像jobject这样的类型来解决这个问题:不是直接递交指针,而是给你一个能
够在必要时兑换为实际指针的透明句柄(opaque handle,概念上对开发人员透明)。通过使用句柄
,当GC移动对象时,只需要更新句柄对应表使其指向对象的新位置就可以了。这就意味着原生代码
不用在每次GC运行时被留下一堆不可用的指针了。

在之前的Android版本中,我们并没有使用间接句柄;我们用的是直接指针。由于我们并没有实现
会移动对象的GC所以这看起来没嘛大问题,可是这却会导致开发人员写出看似工作正常而实际是有
bug的代码。在ICS中,即使我们依然没有实现一个会移动对象的GC,可我们已经转为使用间接引用
了所以你们也会开始检查出你们原生代码中的bug了。

ICS提供了一种JNI bug兼容模式:只要AndroidManifest.xml中的targetSdkVersion版本号是低于
ICS的(14-),你的代码就能得到“豁免”。可是一旦你更新了targetSdkVersion的话,你的代码就必
须是正确的!

CheckJNI已经被更新为会检测并报告这些错误,并且在ICS中,如果manifest中的
debuggable=”true”的话CheckJNI默认就已经开启了。

JNI引用的一些基础知识
在JNI中,有一些不同的引用。其中最重要的两种就是局部引用(local references)和全局引用
(global references)。任意一个给定的jobject都可以是局部或是全局的。(另外还有弱全局weak 
globals,但这种有一个单独的类型,jweak,在此并不涉及。)

全局/局部的区别同时影响生命周期和作用域。全局的可以在任意线程通过本线程的JNIEnv*使用,
并且可以有效到明确调用DeleteGlobalRef()之时。局部的只能在其最初被递交到的线程中使用,
并且可以有效到明确调用DeleteLocalRef()之时,或者,更普遍的,到你从你的原生函数中返回为
止。当原生函数返回时,所有的局部引用都会被自动删除掉。

在之前的系统中,局部引用是直接的指针,局部引用永远不会真正变为不可用的。那就意味着你可
以无限使用一个局部引用,即使你已经明确对它调用过DeleteLocalRef()了,或者使用
PopLocalFrame()明确删除了它。

虽然任意JNIEnv*只能在一个线程中可用,但由于Android在JNIEnv*中从来没有保存过每个线程的
状态,所以之前在错误的线程中使用JNIEnv*也不会出问题。现在每个线程都有一个局部引用表,
在正确的线程中使用JNIEnv*就是至关重要的了。

以上讲的就是ICS会进行检测的bug。我会过一些常见的实例来具体说明这些问题,如果发现他们,
并且如何进行修复。你确实需要修复这些问题,这是很重要的,因为很有可能未来版本的Android
就会加入能移动对象的回收器。我们也不可能一直提供bug兼容模式。

常见JNI引用bug
Bug:在原生代码接口类中长期保存jobject时忘记调用NewGlobalRef()
如果你用了原生接口类(native peer)(一个长期存在的对应Java对象的原生对象,通常在Java对象
创建时创建,在Java对象的finalizer运行时销毁),一定不能在原生对象中长期保存jobject,因
为下次你再使用它的时候它就已经不再可用了。(JNIEnv*也有类似的情况。在同一线程内发生的原
生调用时它可能还是可用的,否则就不可用了。)

 class MyPeer {
 public:
   MyPeer(jstring s) {
     str_ = s; // 错误: 没有确定是全局就长期保存引用
   }
   jstring str_;
 };
 
 static jlong MyClass_newPeer(JNIEnv* env, jclass) {
   jstring local_ref = env-&gt;NewStringUTF("hello, world!");
   MyPeer* peer = new MyPeer(local_ref);
   return static_cast&lt;jlong&gt;(reinterpret_cast&lt;uintptr_t&gt;(peer));
   // 错误: local_ref 在我们返回时将变得不再可用, 但我们已经将其保存在'peer'中了.
 }

 static void MyClass_printString(JNIEnv* env, jclass, jlong peerAddress) {
   MyPeer* peer = reinterpret_cast&lt;MyPeer*&gt;(static_cast&lt;uintptr_t&gt;
(peerAddress));
   // 错误: peer-&gt;str_ is 不可用!
   ScopedUtfChars s(env, peer-&gt;str_);
   std::cout &lt;&lt; s.c_str() &lt;&lt; std::endl;
 }

这个问题的解决方法是只保存JNI全局引用。由于JNI全局引用永远不会被自动释放,所以很重要的
一点就是你得自己自己释放他们。这个问题会由于你的析构函数里没有JNIEnv*而变得稍微有点囧
。最简单的解决方法通常就是在你的原生接口类中加入一个明确的销毁函数,并在Java接口类的析
构(finalizer)中调用。

 class MyPeer {
 public:
   MyPeer(JNIEnv* env, jstring s) {
     this-&gt;s = env-&gt;NewGlobalRef(s);
   }
   ~MyPeer() {
     assert(s == NULL);
   }
   void destroy(JNIEnv* env) {
     env-&gt;DeleteGlobalRef(s);
     s = NULL;
   }
   jstring s;
 };

你应该总是保持NewGlobalRef()/DeleteGlobalRef()成对调用。CheckJNI会捕获到全局引用的泄漏
,不过上限很高(默认2000),所以要小心。

如果你的代码里确实有这类错误的话,会收到类似这样的崩溃信息:
    JNI ERROR (app bug): accessed stale local reference 0x5900021 (index 8 in a table 
of size 8)
    JNI WARNING: jstring is an invalid local reference (0x5900021)
                 in LMyClass;.printString:(J)V (GetStringUTFChars)
    "main" prio=5 tid=1 RUNNABLE
      | group="main" sCount=0 dsCount=0 obj=0xf5e96410 self=0x8215888
      | sysTid=11044 nice=0 sched=0/0 cgrp=[n/a] handle=-152574256
      | schedstat=( 156038824 600810 47 ) utm=14 stm=2 core=0
      at MyClass.printString(Native Method)
      at MyClass.main(MyClass.java:13)
如果你使用了另一个线程的JNIEnv*,会收到类似这样的崩溃信息:
 JNI WARNING: threadid=8 using env from threadid=1
                 in LMyClass;.printString:(J)V (GetStringUTFChars)
    "Thread-10" prio=5 tid=8 NATIVE
      | group="main" sCount=0 dsCount=0 obj=0xf5f77d60 self=0x9f8f248
      | sysTid=22299 nice=0 sched=0/0 cgrp=[n/a] handle=-256476304
      | schedstat=( 153358572 709218 48 ) utm=12 stm=4 core=8
      at MyClass.printString(Native Method)
      at MyClass$1.run(MyClass.java:15)

Bug:错误的认为FindClass()返回全局引用
FindClass()返回的是局部引用。许多人认为是全局的。在像Android这样不具备类卸载(class 
unloading)的系统中,你可以把jfieldID和jmethodID当作全局处理。(他们实际上不是引用,但在
支持类卸载的系统中也存在类似的生存周期问题。)但是jclass是引用,而且FindClass()返回的是
局部引用。一种常见的错误就是“静态jclass”。除非你手动将局部引用转换为全局引用,否则你的

代码就会有问题。下面是正确代码的写法:

 static jclass gMyClass;
 static jclass gSomeClass;
 static void MyClass_nativeInit(JNIEnv* env, jclass myClass) {
   // ‘myClass’ (和其他非主要参数) 仅仅是局部引用.
   gMyClass = env-&gt;NewGlobalRef(myClass);
   // FindClass仅返回局部引用.
   jclass someClass = env-&gt;FindClass("SomeClass");
   if (someClass == NULL) {
     return; // FindClass 已经抛出了 NoClassDefFoundError 的异常.
   }
   gSomeClass = env-&gt;NewGlobalRef(someClass);
 }
如果你的代码确实有这类错误的话,会收到类似这样的崩溃信息:
    JNI ERROR (app bug): attempt to use stale local reference 0x4200001d (should be 
0x4210001d)
    JNI WARNING: 0x4200001d is not a valid JNI reference
                 in LMyClass;.useStashedClass:()V (IsSameObject)

Bug:调用DeleteLocalRef()后继续使用已被删除的引用
我想这事不用说也应该知道,调用DeleteLocalRef()删除引用后再使用会出现非法访问,但是因为
这以前是可以正常工作的,所以你也许已经犯了这个错误但还没意识到。常见的模式像是这样:原
生代码部分有一个长期运行的循环,开发人员为了要避免达到局部引用上限而尝试清理每一个局部
引用,但可能会意外地将想要作为返回值的引用也给删除掉!
解决方法很简单:别对你还要用到的(包括作为返回值的)引用调用DeleteLocalRef()。

Bug:调用PopLocalFrame()后继续使用已被弹出的引用
这其实是上面那个bug的微妙变种。PushLocalFrame()和PopLocalFrame()调用能批量删除局部引用
。当调用PopLocalFrame()时,你将frame上的一个想要保留的引用传入为参数(通常是要用作返回
值),或者NULL。过去,你会发现像这样的错误代码不会有任何问题:

 static jobjectArray MyClass_returnArray(JNIEnv* env, jclass) {
   env-&gt;PushLocalFrame(256);
   jobjectArray array = env-&gt;NewObjectArray(128, gMyClass, NULL);
   for (int i = 0; i &lt; 128; ++i) {
       env-&gt;SetObjectArrayElement(array, i, newMyClass(i));
   }
   env-&gt;PopLocalFrame(NULL); // 错误: 应当传递 'array'.
   return array; // 错误: 数组已经不可用.
 }
解决方法通常是将引用传递给PopLocalFrame()。注意在上面的例子中,你不用保存单独数组元素
的引用;只要GC知道数组本身,它就会处理元素(并且包含他们指向的任意对象)本身。

如果你的代码确实有这类错误的话,会收到类似这样的崩溃信息:
  JNI ERROR (app bug): accessed stale local reference 0x2d00025 (index 9 in a table of 
size 8)
    JNI WARNING: invalid reference returned from native code
                 in LMyClass;.returnArray:()[Ljava/lang/Object;
总结
是的,我们要求你在JNI编码时要更注意一些细节,这是额外的工作。但是我们认为随着我们做出
更好更佳的内存管理代码你们也能走在更前面。

原文(有墙!):
JNI Local Reference Changes in ICS -http://android-
developers.blogspot.com/2011/11/jni-local-reference-changes-in-ics.html
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: linux-android-韦根驱动 jni.rar 是一个文件名,其中包含了与韦根驱动相关的文件。 Linux是一种开源的操作系统,广泛应用于各种计算机设备。它具有优秀的稳定性、安全性和可定制性。Android是基于Linux内核开发的移动操作系统,目前在智能手机等智能设备上广泛使用。 韦根(Wiegand)驱动是一种用于读取磁卡、感应卡等卡片数据的驱动程序。它使用韦根协议来传输卡片的数据。韦根协议是一种串行通信协议,常用于门禁系统、考勤系统等场景中。这个驱动程序可以使得Linux系统和Android系统能够读取和处理韦根卡片的数据。 JNI(Java Native Interface)是一种Java编程语言的编程框架,用于在Java程序中调用其他语言(如C/C++)编写的本地代码。在这里,JNI用来链接Linux或Android系统的驱动程序和Java程序之间的接口。通过JNI,Java程序可以调用韦根驱动的功能,读取韦根卡片的数据并进行相应的处理。 而 ".rar" 则是一种常见的文件压缩格式,它可以将多个文件或文件夹压缩成一个单独的文件,以方便传输和存储。所以,"linux-android-韦根驱动 jni.rar" 这个文件可能是包含了与韦根驱动功能相关的Linux和Android系统驱动程序以及JNI接口的压缩文件。 ### 回答2: "linux-android-韦根驱动 jni.rar" 是一个压缩文件,包含了用于在 Linux 和 Android 系统上支持韦根驱动的 JNI(Java Native Interface)代码。 Linux 是一个开源的操作系统内核,Android 则是基于 Linux 内核开发的移动设备操作系统。韦根驱动是一种用于读取条码信息的通信协议,常用于条码扫描仪等设备。 这个压缩文件包含了 JNI 的相关代码,JNI 是 Java 提供的机制,用于在 Java 程序中调用本地(C/C++)代码。由于韦根驱动是由本地代码实现的,所以需要使用 JNI 将其与 Java 程序进行交互。 在 Linux 系统上,可以使用这个 JNI 文件来编译并生成与韦根驱动相关的动态链接库(.so 文件),以便在 Java 程序中调用。在 Android 系统上,可以将这个 JNI 文件与其它 Android 项目一起编译,并将生成的 .so 文件集成到 Android 应用中,以实现对韦根驱动的调用。 这个压缩文件的具体内容可能包括 JNI 源代码、头文件以及必要的编译脚本。如果你想使用这个韦根驱动,你可能需要先解压这个压缩文件,然后参考其中的文档或指南,按照指导进行相应的编译和集成工作。 总之,"linux-android-韦根驱动 jni.rar" 是一个提供了在 Linux 和 Android 系统上支持韦根驱动的 JNI 代码文件,通过使用它,你可以将韦根驱动融入到你的 Java 或 Android 项目中,并实现对该驱动的功能调用。 ### 回答3: Linux是一种开源的操作系统,广泛用于服务器、桌面电脑和嵌入式设备。而Android是基于Linux内核开发的移动操作系统,主要运行于智能手机、平板电脑和其他便携式设备上。韦根驱动是一种用于与韦根协议进行通信的驱动程序,通过JNI(Java Native Interface)将Java代码与本地代码进行交互。 在开发Android应用程序时,我们经常会使用JNI技术来调用C/C++编写的本地代码,以实现一些特定功能或与底层进行交互。在这个过程中,我们需要将本地代码打包成库文件,然后从Java层进行调用。 韦根驱动JNI.rar可能是一个包含了韦根协议通信所需的本地代码的压缩文件。其中可能包含了以C/C++语言编写的代码和一些与之相关的资源文件。通过解压缩这个文件,我们可以得到驱动程序所需的源代码和资源文件,从而进行进一步的开发和调试。 这个韦根驱动JNI.rar文件可能为开发者提供了一种在Android平台上与韦根协议设备进行通信的解决方案。通过将相关的代码集成到Android应用程序中,开发者可以实现与韦根协议设备之间的数据交互,从而实现特定的功能或满足特定的需求。 总之,Linux是操作系统,Android是基于Linux的移动操作系统,韦根驱动是用于与韦根协议进行通信的驱动程序,而韦根驱动JNI.rar可能是一个包含了韦根驱动的本地代码的压缩文件,用于在Android平台上实现与韦根协议设备的通信。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值