NFC问题分析之死锁引起的ANR

   2016-11-03 0
核心提示:【摘要】对于Android平台的工程师来说,ANR应该是每个人都会遇到的问题,因为导致它的原因有很多,例如在主线程进行耗时操作,调用大量cpu资源进行复杂的预算等,并且可能在大多数情况下,这类问题不会发生,只会在极端特殊的情况下暴露(例如很长时间的自动化

【摘要】对于Android平台的工程师来说,ANR应该是每个人都会遇到的问题,因为导致它的原因有很多,例如在主线程进行耗时操作,调用大量cpu资源进行复杂的预算等,并且可能在大多数情况下,这类问题不会发生,只会在极端特殊的情况下暴露(例如很长时间的自动化脚本测试,mon key测试),所以我们必须得学会如何去分析这类问题,才能让模块的性能经得住考验。

一. 什么是ANR?为什么会有ANR发生?

如果当你进行一些操作之后,发现手机屏幕上出现类似上面的dialog,那么很不幸,你中招了。。。

ANR,Application Not Responding ,即应用无响应。

一般来说,当应用对用户的交互没有反应时,系统就会弹出上述的ANR dialog。这种情况一般发生在如主线程被IO操作block住了,主线程进行了大量的例如读取数据库的操作等。

从google官方文档上介绍来看,主要由以下两种情况引起:

1. 应用在5秒内对于用户的输入事件无响应

2. BroadcastReceiver在10秒内不能完成onReceive()方法的执行

Note: 上述的时间是基于Google原生的Code,国内不少厂商因为某些原因会把这些时间延长,请以具体的vendor代码为准

二. NFC为什么会有ANR问题发生

首先和没接触过NFC的朋友介绍下NFC。

NFC,Near field communication,即近场通讯,是由 非接触式射频识别(RFID) 演变而来的短距离无线电技术,由Nokia, Sony, NXP共同研发。在国外,如日本,这种技术运用的已经十分广泛,无论从出行到购物,哪里都有Felica(日本使用的NFC标准)的身影。然而国内因为某宝过于强大和人性化,NFC技术推动任重而道远。但是随着如小米钱包等应用开始使用NFC来模拟公交卡以及银行卡方便用户的生活,个人认为,未来是美好的!!!

作为一个Local Connectivity的重要模块,NFC不仅可以进行Read/Write Tag,而且可以通过Android Beam(Android 4.0开始支持的点对点传输的feature)传输文件。handover的功能更是让NFC成为一个wifi和bt快速建立链接的桥梁,极大的方便了用户的近距离传输的需求。也正是因为这些原因,在特殊情况下的并发操作,就会导致NFC出现ANR的问题,下面以一个简单的NFC相互调用死锁导致的ANR案例进行分析。

三. 案例分析

首先推荐给各位一个查看源代码的网站, http://androidxref.com /, 如果没有VPN的话,这个网站看源码还是比较给力的,可能大多数哥们都知道,呵呵~

下面先简单介绍下导致ANR发生的操作:

在NFC关闭的情况下,(Android Beam必须是随着NFC的关闭自动关闭的,否则因为相应的component被disable,分享列表中找不到Android Beam选项),通过Android Beam去分享一个文件,这种情况下会弹出一个提示需要开启NFC功能的Dialog,点击确定,正常情况下,会出现Android Beam的图片缩放界面如下图,但是ANR发生时整个界面没有任何反应,几秒钟后,系统就会弹出Settings ANR的dialog。

对于ANR问题的分析,我们应该首先去找问题发生时,手机自动保存在data/anr目录下的trace.txt文件,这是最能直观反应问题发生时堆栈的信息以及各种资源的使用情况。

因为NfcService是NFC上层最核心的一个文件,底层的所有处理都会一层层往上抛给NfcService,上层的API接口也只会通过NfcService去调用具体的底层实现。所有我们现在trace.txt中以NfcService作为关键字进行搜索,看到如下trace.log

"Binder_1" prio=5 tid=8 Blocked(prio 进程号, tid 线程号)
  | group="main" sCount=1 dsCount=0 obj=0x12c8b0a0 self=0x7f8ef53400
  | sysTid=2903 nice=0 cgrp=default sched=0/0 handle=0x7f93af5440
  | state=S schedstat=( 81420425 126587653 792 ) utm=3 stm=5 core=0 HZ=100
  | stack=0x7f939f9000-0x7f939fb000 stackSize=1013KB
  | held mutexes=
  at com.android.nfc.NfcService$NfcAdapterService.getState(NfcService.java:1813)
  - waiting to lock <0x0196c7d2> (a com.android.nfc.NfcService) held by thread 18  --->被线程18阻塞
  at android.nfc.INfcAdapter$Stub.onTransact(INfcAdapter.java:95)
  at android.os.Binder.execTransact(Binder.java:477)</span>

从上面的trace log可以看到,NfcService$NfcAdapterService.getState()想获得0x0196c7d2,即NfcService对象锁,代码如下

@Override
        public int getState() throws RemoteException {
            synchronized (NfcService.this) {
                return mState;
            }
        }

但是这个对象锁并不能马上获得,因为thread 18正在占用,看下线程18的堆栈信息

"Binder_3" prio=5 tid=18 Blocked
  | group="main" sCount=1 dsCount=0 obj=0x12e120a0 self=0x7f94114200
  | sysTid=3414 nice=0 cgrp=default sched=0/0 handle=0x7f7c9a0440
  | state=S schedstat=( 67563697 66692461 439 ) utm=0 stm=6 core=0 HZ=100
  | stack=0x7f7c8a4000-0x7f7c8a6000 stackSize=1013KB
  | held mutexes=
  at com.android.nfc.P2pLinkManager.isLlcpActive(P2pLinkManager.java:410)
  - waiting to lock <0x0c8140a3> (a com.android.nfc.P2pLinkManager) held by thread 1 --->被线程1阻塞
  at com.android.nfc.NfcService$NxpExtrasService._open(NfcService.java:3173)
  - locked <0x0196c7d2> (a com.android.nfc.NfcService)
  at com.android.nfc.NfcService$NxpExtrasService.open(NfcService.java:3149)
  at com.nxp.intf.INxpExtrasService$Stub.onTransact(INxpExtrasService.java:55)
  at android.os.Binder.execTransact(Binder.java:477)

上面的log可以看出,NfcService的对象锁正在被NfcService$NxpExtrasService._open()方法所持有,代码如下:

private int _open(IBinder b) {
            synchronized(NfcService.this) {
                if (!isNfcEnabled()) {
                    return EE_ERROR_NFC_DISABLED;
                }
                if (mInProvisionMode) {
                    // Deny access to the NFCEE as long as the device is being setup
                    return EE_ERROR_IO;
                }
                if (mP2pLinkManager.isLlcpActive()) {
                    // Don't allow PN544-based devices to open the SE while the LLCP
                    // link is still up or in a debounce state. This avoids race
                    // conditions in the NXP stack around P2P/SMX switching.
                    return EE_ERROR_EXT_FIELD;
                }

上面的这个方法迟迟不能执行完毕,是因为调用了mP2pLinkManager.isLlcpActive(),这个方法希望获得0x0c8140a3,即P2pLinkManager的对象锁,但是这个对象锁也不能马上获得,正在被thread1挂起。

public boolean isLlcpActive() {
        synchronized (this) { ---> P2pLinkManager对象锁
            return mLinkState != LINK_STATE_DOWN;
        }
    }

那么我们继续看下thread1的trace信息:

"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 obj=0x762a8fb8 self=0x7f955fba00
  | sysTid=2880 nice=0 cgrp=default sched=0/0 handle=0x7f98da8fe8
  | state=S schedstat=( 344423025 553179190 922 ) utm=24 stm=10 core=3 HZ=100
  | stack=0x7fca15d000-0x7fca15f000 stackSize=8MB
  | held mutexes=
  at com.android.nfc.NfcService.playSound(NfcService.java:1509)
  - waiting to lock <0x0196c7d2> (a com.android.nfc.NfcService) held by thread 18
  at com.android.nfc.P2pEventManager.onP2pNfcTapRequested(P2pEventManager.java:81)
  at com.android.nfc.P2pLinkManager.onManualBeamInvoke(P2pLinkManager.java:455)
  - locked <0x0c8140a3> (a com.android.nfc.P2pLinkManager)
  at com.android.nfc.NfcService$NfcServiceHandler.handleMessage(NfcService.java:4366)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:148)
  at android.app.ActivityThread.main(ActivityThread.java:5541)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:935)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:726)

0x0c8140a3这个锁正在被P2pLinkManager.onManualBeamInvoke()方法占用,代码如下:

public void onManualBeamInvoke(BeamShareData shareData) {
        synchronized (P2pLinkManager.this)    {
            if (mLinkState != LINK_STATE_DOWN) {
                return;
            }
            if (mForegroundUtils.getForegroundUids().contains(mNdefCallbackUid)) {
                // Try to get data from the registered NDEF callback
                prepareMessageToSend(false);
            } else {
                mMessageToSend = null;
                mUrisToSend = null;
            }
            if (mMessageToSend == null && mUrisToSend == null && shareData != null) {
                // No data from the NDEF callback, get data from ShareData
                if (shareData.uris != null) {
                    mUrisToSend = shareData.uris;
                } else if (shareData.ndefMessage != null) {
                    mMessageToSend = shareData.ndefMessage;
                }
                mUserHandle = shareData.userHandle;
            }
            if (mMessageToSend != null ||
                    (mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) {
                mSendState = SEND_STATE_PENDING;
                mEventListener.onP2pNfcTapRequested();
                scheduleTimeoutLocked(MSG_WAIT_FOR_LINK_TIMEOUT, WAIT_FOR_LINK_TIMEOUT_MS);
            }
        }
    }

上面的方法里会去调用mEventListener.onP2pNfcTapRequested(),代码如下:

@Override
    public void onP2pNfcTapRequested() {
        mNfcService.playSound(NfcService.SOUND_START);
        mNdefSent = false;
        mNdefReceived = false;
        mInDebounce = false;

        mVibrator.vibrate(VIBRATION_PATTERN, -1);

这个方法会调用NfcService里面的mNfcService.playSound()。trace log显示playSound()会去想持有0x0196c7d2即NfcService对象。看下代码是不是这样:

public void playSound(int sound) {
        synchronized (this) { ---> NfcService对象锁
            if (mSoundPool == null) {
                Log.w(TAG, "Not playing sound when NFC is disabled");
                return;
            }

这里请注意,上面 NfcService$NxpExtrasService._open()正在持有的对象也是这个。

现在基本知道什么情况下,我们回过头来再捋一捋。

NfcAdapterService.getState希望持有NfcService对象,无法获得block

NfcService对象正在被 NxpExtrasService._open()持有, 这个方法无法执行完毕,被 mP2pLinkManager.isLlcpActive() block

mP2pLinkManager.isLlcpActive()希望持有P2pLinkManager的对象,无法获得 block

P2pLinkManager对象正在被onManualBeamInvoke()方法持有,这个方法无法执行完毕,被mEventListener.onP2pNfcTapRequested() block

mEventListener.onP2pNfcTapRequested() 无法执行完毕,被mNfcService.playSound() block

mNfcService.playSound() 希望持有NfcService的对象,这个对象被最上面的 NxpExtrasService._open()持有

所以总体来说,就是正在占用NfcService锁的 NxpExtrasService._open()需要P2pLinkManager的锁释放,而正在占用P2pLinkManager这个锁的onManualBeamInvoke()方法需要NfcService的锁释放,双方互不让步,造成死锁。

暂时想到的解决的策略就是将

public void playSound(int sound) {
        synchronized (this) { <---> NfcService对象锁
            if (mSoundPool == null) {
                Log.w(TAG, "Not playing sound when NFC is disabled");
                return;
            }

这个锁的范围缩小,换成一个私有锁。

即 Object mPlaySoundLock = new Obejct(),然后将this替换成 mPlaySoundLock即可。

注:本人水平有限,欢迎各位大牛批评指正,谢谢~

 
标签: 安卓开发
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 安卓中通知功能的具体实现
    安卓中通知功能的具体实现
    通知[Notification]是Android中比较有特色的功能,当某个应用程序希望给用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知实现。使用通知的步骤1、需要一个NotificationManager来获得NotificationManager manager = (NotificationManager
    02-05 安卓开发
  • Android view系统分析-setContentView
    Android view系统分析-setContentView
    第一天上班,列了一下今年要学习的东西。主要就是深入学习Android相关的系统源代码,夯实基础。对于学习Android系统源代码,也没什么大概,就从我们平常使用最基础的东西学起,也就是从view这个切入点开始学习Android的源码,在没分析源码之前,我们有的时候
    02-05 安卓开发
  • 如何进行网络视频截图/获取视频的缩略图
    如何进行网络视频截图/获取视频的缩略图
    小编导读:获取视频的缩略图,截图正在播放的视频某一帧,是在音视频开发中,常遇到的问题。本文是主要用于点播中截图视频,同时还可以获取点播视频的缩略图进行显示,留下一个问题,如下图所示, 如果要获取直播中节目视频缩略图,该怎么做呢?(ps:直播是直
  • Android NDK 层发起 HTTP 请求的问题及解决
    Android NDK 层发起 HTTP 请求的问题及解决
    前言新的一年,大家新年快乐~~鸡年大吉!本次给大家带来何老师的最新文章~虽然何老师还在过节,但依然放心不下广大开发者,在此佳节还未结束之际,给大家带来最新的技术分享~ 事件的起因不说了,总之是需要实现一个 NDK 层的网络请求。为了多端适用,还是选择
  • Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突
    Android插件化(六): OpenAtlasの改写aapt以防
    引言Android应用程序的编译中,负责资源打包的是aapt,如果不对打包后的资源ID进行控制,就会导致插件中的资源ID冲突。所以,我们需要改写aapt的源码,以达到通过某种方式传递资源ID的Package ID,通过aapt打包时获取到这个Package ID并且应用才插件资源的命名
    02-05 安卓开发
  • Android架构(一)MVP架构在Android中的实践
    Android架构(一)MVP架构在Android中的实践
    为什么要重视程序的架构设计 对程序进行架构设计的原因,归根结底是为了 提高生产力 。通过设计是程序模块化,做到模块内部的 高聚合 和模块之间的 低耦合 (如依赖注入就是低耦合的集中体现)。 这样做的好处是使得程序开发过程中,开发人员主需要专注于一点,
    02-05 安卓开发
  • 安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件
    安卓逆向系列教程 4.2 分析锁机软件 作者: 飞龙 这个教程中我们要分析一个锁机软件。像这种软件都比较简单,完全可以顺着入口看下去,但我这里还是用关键点来定位。首先这个软件的截图是这样,进入这个界面之后,除非退出模拟器,否则没办法回到桌面。上面那
    02-05 安卓开发
  • Android插件化(二):OpenAtlas插件安装过程分析
    Android插件化(二):OpenAtlas插件安装过程分析
    在前一篇博客 Android插件化(一):OpenAtlas架构以及实现原理概要 中,我们对应Android插件化存在的问题,实现原理,以及目前的实现方案进行了简单的叙述。从这篇开始,我们要深入到OpenAtlas的源码中进行插件安装过程的分析。 插件的安装分为3种:宿主启动时立
    02-05 安卓开发
  • [译] Android API 指南
    [译] Android API 指南
    众所周知,Android开发者有中文网站了,API 指南一眼看去最左侧的菜单都是中文,然而点进去内容还是很多是英文,并没有全部翻译,我这里整理了API 指南的目录,便于查看,如果之前还没有通读,现在可以好好看一遍。注意,如果标题带有英文,说明官方还没有翻
  • 使用FileProvider解决file:// URI引起的FileUriExposedException
    使用FileProvider解决file:// URI引起的FileUri
    问题以下是一段简单的代码,它调用系统的相机app来拍摄照片:void takePhoto(String cameraPhotoPath) {File cameraPhoto = new File(cameraPhotoPath);Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);takePhotoIntent.putExtra(Medi
    02-05 安卓开发
点击排行