ANR在SurfaceView方法" onTouchEvent(...)"在Android [英] ANR in SurfaceView method "onTouchEvent(...)" on Android

查看:471
本文介绍了ANR在SurfaceView方法" onTouchEvent(...)"在Android的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Android上,我子类 SurfaceView 所得视图是工作在大多数情况下的罚款。但是,所有用户的大约1%的报告ANR问题,这个实现。

显然,有其中 SurfaceView 由于某种问题,可能是一个僵局。

的边缘情况

不幸的是,我不知道这有什么错我的执行的onDraw(...) onTouchEvent(...)或如何提高code。你能帮忙吗?

 主PRIO = 5 TID = 1 MONITOR
|组=主SCOUNT = 1 dsCount = 0 = OBJ自我0x41920e88 = 0x4190f8d0
| sysTid = 13407漂亮= 0 =附表0/0 = CGRP处理应用= 1074618708
|状态= S schedstat =(50780242971 27570770290 130442)UTM = 4254 = STM核心824 = 0
在com.my.package.util.HandCards.onTouchEvent(的SourceFile:〜188)
- 等待锁定< 0x45b91988> (一android.view.SurfaceView $ 4)TID举行= 18(线程14297)
在android.view.View.dispatchTouchEvent(View.java:7837)
在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2216)
在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2216)
在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2216)
在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
在android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2216)
在android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
在com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2075)
在com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1522)
在android.app.Activity.dispatchTouchEvent(Activity.java:2458)
在com.android.internal.policy.impl.PhoneWindow $ DecorView.dispatchTouchEvent(PhoneWindow.java:2023)
在android.view.View.dispatchPointerEvent(View.java:8017)
在android.view.ViewRootImpl $ ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:3966)
在android.view.ViewRootImpl $ ViewPostImeInputStage.onProcess(ViewRootImpl.java:3845)
在android.view.ViewRootImpl $ InputStage.deliver(ViewRootImpl.java:3405)
在android.view.ViewRootImpl $ InputStage.onDeliverToNext(ViewRootImpl.java:3455)
在android.view.ViewRootImpl $ InputStage.forward(ViewRootImpl.java:3424)
在android.view.ViewRootImpl $ AsyncInputStage.forward(ViewRootImpl.java:3531)
在android.view.ViewRootImpl $ InputStage.apply(ViewRootImpl.java:3432)
在android.view.ViewRootImpl $ AsyncInputStage.apply(ViewRootImpl.java:3588)
在android.view.ViewRootImpl $ InputStage.deliver(ViewRootImpl.java:3405)
在android.view.ViewRootImpl $ InputStage.onDeliverToNext(ViewRootImpl.java:3455)
在android.view.ViewRootImpl $ InputStage.forward(ViewRootImpl.java:3424)
在android.view.ViewRootImpl $ InputStage.apply(ViewRootImpl.java:3432)
在android.view.ViewRootImpl $ InputStage.deliver(ViewRootImpl.java:3405)
在android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5554)
在android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5534)
在android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5505)
在android.view.ViewRootImpl $ WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:5634)
在android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
在android.os.MessageQueue.nativePollOnce(本机方法)
在android.os.MessageQueue.next(MessageQueue.java:138)
在android.os.Looper.loop(Looper.java:196)
在android.app.ActivityThread.main(ActivityThread.java:5135)
在java.lang.reflect.Method.invokeNative(本机方法)
在java.lang.reflect.Method.invoke(Method.java:515)
在com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:878)
在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
在dalvik.system.NativeStart.main(本机方法)...线程14297PRIO = 5 TID = 18悬浮
|组=主SCOUNT = 1 dsCount = 0 = OBJ自我0x45ba6358 = 0x76036b38
| sysTid = 21120漂亮= 0 =附表0/0 = CGRP处理应用= 1979936656
|状态= S schedstat =(48296386737 3088012659 22649)UTM = 4691 = STM核心138 = 0
#00的pc 00021adc /system/lib/libc.so(__futex_syscall3 + 8)
#01件0000f074 /system/lib/libc.so(__pthread_cond_timedwait_relative + 48)
#02件0000f0d4 /system/lib/libc.so(__pthread_cond_timedwait + 64)
#03件0005655f /system/lib/libdvm.so
#04件00056b21 /system/lib/libdvm.so(dvmChangeStatus(螺纹*,ThreadStatus)+34)
#05件00050fd7 /system/lib/libdvm.so(dvmCallJNIMethod(无符号整型常量*,* JValue,方法常量*,螺纹*)+ 406)
#06件00000214的/ dev / ashmem /达尔维克jit- code-缓存(删除)
在android.graphics.Canvas.native_drawBitmap(本机方法)
在android.graphics.Canvas.drawBitmap(Canvas.java:1202)
在com.my.package.util.HandCards.a(的SourceFile:178)
在com.my.package.util.HandCards.onDraw(的SourceFile:136)
在com.my.package.util.d.run(的SourceFile:36)

其中, HandCards.onTouchEvent(的SourceFile:〜188)是:

 同步(mRenderThread.getSurfaceHolder()){

HandCards.a(的SourceFile:178)是:

  canvas.drawBitmap(drawCardBitmap,空,mDrawingRect,mGraphicsPaint);

满code为 SurfaceView 子类是:

 公共类HandCards扩展SurfaceView实现SurfaceHolder.Callback {    / **隐藏在一张卡被高亮显示其他的卡,并覆盖了它的另一个玩家的回合(其中0表示透明,255不透明)的所有卡的阴影层的不透明度* /
    私有静态最终诠释SHADOW_ALPHA = 150;
    私有静态SparseArray<&位图GT; mCardCache =新SparseArray<&位图GT;(); //缓存阵列自己的卡片位图
    私人HandThread mRenderThread;
    私人挥发性名单<卡> mCards;
    私人挥发性INT mCardCount;
    私人挥发性INT mScreenWidth;
    私人挥发性INT mScreenHeight;
    私人挥发性INT mCardWidth;
    私人挥发性INT mCardHeight;
    私人挥发性INT mHighlightedCard = -1;
    私人CardClickCallback mCardClickCallback;
    私人挥发性INT mBlattID = 1;
    私人挥发性INT mCurrentCardSpacing;
    私人最终涂料mGraphicsPaint;
    私人最终涂料mShadowPaint;
    私人最终矩形mDrawingRect;
    私人挥发性INT mTouchEventAction;
    私人挥发性INT mTouchEventCard;
    私人位图drawCardBitmap;
    私人挥发性INT mOnDrawX1;
    私人最终BitmapFactory.Options mBitmapOptions;
    私人挥发性布尔mIsActive = TRUE;
    私人最终诠释[] = mCardSelection新INT [GameState.MAX_SWAP_CARDS]
    / **表示卡视图目前用于选择一些卡片来创建一个选择* /
    私人挥发性布尔mIsChooseMode;
    / **举行,将在所有选择插槽全*旁边更换所选卡的指数/
    私人挥发性INT mNextReplacePosition;
    / **中的drawcard(仅用于本地),但在这里宣布,以节省重复的分配* /
    私人挥发性INT mCardOffsetY;
    私人挥发性INT mRequiredSelectionCount;    公共HandCards(上下文activityContext,AttributeSet中的AttributeSet){
        超(activityContext,AttributeSet中的);
        。getHolder()的addCallback(本);
        setFocusable(真); //触摸事件应该由这个类进行处理
        mCards =新的ArrayList<卡>();
        mGraphicsPaint =新的油漆();
        mGraphicsPaint.setAntiAlias​​(真);
        mGraphicsPaint.setFilterBitmap(真);
        mShadowPaint =新的油漆();
        mShadowPaint.setARGB(SHADOW_ALPHA,20,20,20);
        mShadowPaint.setAntiAlias​​(真);
        mBitmapOptions =新BitmapFactory.Options();
        mBitmapOptions.inInputShareable =真;
        mBitmapOptions.inPurgeable =真;
        mDrawingRect =新的矩形();
    }    市民卡getCard(INT位置)抛出异常{
        如果(mCards!= NULL){
            同步(mCards){
                返回mCards.get(位置); //卡可能不会被发现(抛出异常的话)
            }
        }
        返回null;
    }    公共静态位图cardCacheGet(INT键){
        同步(mCardCache){
            返回mCardCache.get(键);
        }
    }    公共静态无效cardCachePut(INT键,位图对象){
        同步(mCardCache){
            mCardCache.put(键,对象);
        }
    }    公众诠释[] getSelectedCards(){
        返回mCardSelection;
    }    公共无效SETACTIVE(布尔有效){
        如果(mCardSelection!= NULL){
            的for(int i = 0; I< GameState.MAX_SWAP_CARDS;我++){//通过选定卡的所有插槽循环
                mCardSelection [I] = -1; //取消设置插槽,使得它默认为空
            }
        }
        mIsActive =有效;
    }    公共布尔isActive(){
        返回mIsActive;
    }    公共无效setChooseMode(布尔活跃,诠释swapCardCount){
        mNextReplacePosition = 0;
        mIsChooseMode =有效;
        mRequiredSelectionCount = swapCardCount;
    }    公共布尔isChooseMode(){
        返回mIsChooseMode;
    }    公共无效stopThread(){
        如果(mRenderThread!= NULL){
            mRenderThread.setRunning(假);
        }
    }    @覆盖
    公共无效的onDraw(帆布油画){
        如果(帆布!= NULL){
            同步(mCards){
                mCardCount = mCards.size();
                canvas.drawColor(Color.BLACK);
                如果(mCardCount大于0){
                    mCurrentCardSpacing = Math.min(mScreenWidth / mCardCount,mCardWidth);
                    对于(INT C = 0;℃下mCardCount; C ++){
                        如果(C!= mHighlightedCard ||!isActive()){
                            尝试{
                                的drawcard(帆布,mCards.get(C).getDrawableID(mBlattID),假的,C * mCurrentCardSpacing,C * mCurrentCardSpacing + mCardWidth,C);
                            }
                            赶上(例外五){}
                        }
                    }
                    如果(mHighlightedCard -1个&放大器;&放大器; isActive()){
                        mOnDrawX1 = Math.min(mHighlightedCard * mCurrentCardSpacing,mScreenWidth-mCardWidth);
                        尝试{
                            的drawcard(帆布,mCards.get(mHighlightedCard).getDrawableID(mBlattID),真实,mOnDrawX1,mOnDrawX1 + mCardWidth,mHighlightedCard);
                        }
                        赶上(例外五){}
                    }
                    否则如果(!isActive()){
                        的drawcard(帆布,0,真实,0,mScreenWidth,0);
                    }
                }
            }
        }
    }    私人无效的drawcard(帆布油画,INT RESOURCEID,布尔强调,诠释xLeft,诠释xRight,诠释cardPosition){
        如果(帆布!= NULL){
            尝试{
                如果(高亮){
                    canvas.drawRect(0,0,mScreenWidth,mScreenHeight,mShadowPaint);
                }
                如果(RESOURCEID!= 0){
                    drawCardBitmap = cardCacheGet(RESOURCEID);
                    如果(drawCardBitmap == NULL){
                        drawCardBitmap = BitmapFactory.de codeResource(getResources(),RESOURCEID,mBitmapOptions);
                        cardCachePut(RESOURCEID,drawCardBitmap);
                    }
                    mCardOffsetY = 0; //默认情况下,提请所有卡的权利在底部(不按位置突出)
                    如果(mCardSelection!= NULL){
                        的for(int i = 0; I< GameState.MAX_SWAP_CARDS;我++){//通过选定卡的所有插槽循环
                            如果(mCardSelection [Ⅰ] == cardPosition){//如果当前的卡已被选择(在该时隙)
                                mCardOffsetY = mScreenHeight * 1/4; //通过四分之一提起插卡以突出显示它
                                打破; //已经检测卡进行选择,以便停在这里
                            }
                        }
                    }
                    mDrawingRect.set(xLeft,mCardOffsetY,xRight,mCardHeight + mCardOffsetY);
                    canvas.drawBitmap(drawCardBitmap,空,mDrawingRect,mGraphicsPaint);
                }
            }
            赶上(例外五){}
        }
    }    @覆盖
    公共布尔onTouchEvent(MotionEvent事件){
        如果(mRenderThread == NULL){返回false; }
        同步(mRenderThread.getSurfaceHolder()){//同步,因此没有并发访问
            mTouchEventAction = event.getAction();
            如果(isActive()){
                如果(mTouchEventAction == || MotionEvent.ACTION_DOWN == mTouchEventAction MotionEvent.ACTION_MOVE){
                    如果(event.getY()> = 0&放大器;&放大器; event.getY()&下; mScreenHeight){
                        mTouchEventCard =(INT)event.getX()/ mCurrentCardSpacing;
                        如果(mTouchEventCard -1个和放大器;&安培; mTouchEventCard< mCardCount){
                            mHighlightedCard = mTouchEventCard;
                        }
                        其他{
                            mHighlightedCard = -1;
                        }
                    }
                    其他{
                        mHighlightedCard = -1;
                    }
                }
                否则,如果(mTouchEventAction == MotionEvent.ACTION_UP){
                    如果(mCardClickCallback = NULL&放大器;!&安培; mHighlightedCard -1个和放大器;&安培; mHighlightedCard< mCardCount){
                        如果(isChooseMode()){//卡已被选择作为一个交换卡
                            INT freeSelectionIndex = -1; //记得有一个自由选择插槽的指标(可默认为无)
                            的for(int i = 0; I< mRequiredSelectionCount;我++){//通过选定卡的所有允许插槽循环
                                如果(mCardSelection [I] == mHighlightedCard){//如果此卡已被选中
                                    mCardSelection [I] = -1; //取消选择卡
                                    freeSelectionIndex = -2; //标志,没有必要选择新卡
                                    打破; //当前卡插槽已经发现这样停在这里
                                }
                                否则,如果(mCardSelection [I] == -1放大器;&安培; freeSelectionIndex == -1){//如果插槽仍然可用,没有免费的插槽已经发现尚未
                                    freeSelectionIndex = I; //记住这个空闲插槽指数
                                }
                            }
                            如果(freeSelectionIndex> -2){//如果新卡被放置在选择阵列
                                如果(freeSelectionIndex> = 0){//如果空闲插槽可用
                                    mCardSelection [freeSelectionIndex] = mHighlightedCard; //只需将卡有
                                }
                                其他{//如果没有空闲插槽可用了
                                    mCardSelection [mNextReplacePosition] = mHighlightedCard; //在一个插槽更换另一张卡
                                    mNextReplacePosition =(mNextReplacePosition + 1)%mRequiredSelectionCount; //提前指向将被下一个替换槽光标
                                }
                            }
                        }
                        其他{//卡已被选定要在桌子上
                            尝试{
                                mCardClickCallback.chooseCard(mCards.get(mHighlightedCard));
                            }
                            赶上(例外五){
                                //指数走出mCards界(只是忽略这一点,用户可以横置卡再次)
                            }
                        }
                    }
                    mHighlightedCard = -1;
                }
            }
            其他{
                尝试{
                    mCardClickCallback.resyncManually();
                }
                赶上(例外五){}
            }
        }
        返回true;
    }    @覆盖
    公共无效surfaceChanged(SurfaceHolder为arg0,ARG1 INT,INT ARG2,诠释ARG3){}    公共无效setCards(列表<卡> currentCards){
        同步(mCards){
            mCards.clear();
            mCards.addAll(currentCards);
        }
    }    @覆盖
    公共无效surfaceCreated(SurfaceHolder为arg0){
        mScreenWidth =的getWidth();
        mScreenHeight =的getHeight();
        mCardHeight = mScreenHeight;
        mCardWidth = mCardHeight *一百五十零分之九十九;
        mCurrentCardSpacing = mCardWidth;
        mRenderThread =新HandThread(getHolder(),这一点);
        mRenderThread.setRunning(真);
        mRenderThread.start();
    }    @覆盖
    公共无效surfaceDestroyed(SurfaceHolder持有人){
        布尔重试= TRUE;
        mRenderThread.setRunning(假); //停止线程
        而(重试){//等待线程关闭
            尝试{
                mRenderThread.join();
                重试= FALSE;
            }
            赶上(InterruptedException的E){}
        }
    }    公共同步无效setCardClickCallback(CardClickCallback回调){
        mCardClickCallback =回调;
    }    公共无效setBlattID(INT blattID){
        mBlattID = blattID;
    }}

然后,另外还有渲染线程:

 公共类HandThread继承Thread {    私人最终SurfaceHolder mSurfaceHolder;
    私人最终HandCards mSurface;
    私人挥发性布尔mRunning = FALSE;    公共HandThread(SurfaceHolder surfaceHolder,HandCards表面){
        mSurfaceHolder = surfaceHolder;
        mSurface =表面;
    }    公共SurfaceHolder getSurfaceHolder(){
        返回mSurfaceHolder;
    }    公共无效setRunning(布尔运行){
        mRunning =运行;
    }    @覆盖
    公共无效的run(){
        帆布℃;
        而(mRunning){
            C = NULL;
            尝试{
                C = mSurfaceHolder.lockCanvas(NULL);
                同步(mSurfaceHolder){
                    如果(C!= NULL){
                        mSurface.onDraw(C);
                    }
                }
            }
            最后{//上面的时候,我们可能不会在表面留下不一致的状态异常被抛出
                如果(C!= NULL){
                    尝试{
                        mSurfaceHolder.unlockCanvasAndPost(C);
                    }
                    赶上(例外五){}
                }
            }
        }
    }}


解决方案

该ANR正在发生的事情,因为你的 onTouchEvent()方法对TID = 18日举行的锁同步,一位不愿透露姓名的线程只知道用Thread-14297。

很多人跟随,其中,在一点上,他们锁定SurfaceView帆布的模式,他们还锁定SurfaceHolder对象。这是一个糟糕的主意,与公众知名度的对象同步,和一个更糟糕的主意,在与GUI框架共享对象同步,所以这是可悲的,这种模式仍然存在。 (但是,我离题。)

您正在绘制在重写的onDraw()方法,它没有任何意义,如果你从一个线程渲染绘制 - 在的onDraw()方法所使用的视图层次结构,并会从UI线程调用,但在这里,它显然正从其他地方调用。你应该把它叫做别的东西,也许只是 myDraw()。 (但是,我离题。)

主题-14297处于暂停状态,这意味着它是在执行,但是当堆栈跟踪被抓获停止。由于最上面的帧是一个本地方法 - 这不要被虚拟机暂停 - 它可能是进入或退出该帧。该线程的系统和用户的时间,蜱如UTM =和STM =的价值观,是相当低的,这表明它没有做过多的CPU工作。除非,当然,渲染线程是一次性的,在这种情况下,它是相当繁忙的(并且可能尚未完成)。

好消息是,你不似乎陷入僵局。渲染线程只是运行缓慢。或者,也许你有一个循环,未能退出(虽然没有从发布的code是显而易见的)。在一个缓慢的设备,有很多其他活动的系统,和一个大的 mCards 列表,它可以得到急需CPU和故障迅速作出反应。假设你遵循共同的模式和锁定SurfaceHolder当你抢画布,你的 onTouchEvent()是要锁定在UI线程抽签的全部时间。在logcat中的ANR总结通常会列出最近的线程活动的水平;如果你有机会到,这些信息可以告诉你的渲染线程多忙了。

并非所有ANR是致命的。如果应用程序变得反应迟钝永久,这是从当用户点击等待扫清了一个临时的ANR很大的不同。你知道这是哪一种?

您需要:


  1. 重新评估您的数据同步。使用较短的窗户,也许读写锁是传递数据。 java.util.concurrent中闲逛。失速UI线程较长时间是坏的。

  2. 确定为什么你的渲染似乎花费很长的时间,以及它是否只是运行缓慢或永远旋转。

On Android, I've subclassed SurfaceView and the resulting view is working fine in most cases. However, roughly 1% of all users report an ANR problem with this implementation.

Apparently, there's an edge case where the SurfaceView fails due to some problem, probably a deadlock.

Unfortunately, I don't know what's wrong with my implementation of onDraw(...) and onTouchEvent(...) or how to improve the code. Can you help?

"main" prio=5 tid=1 MONITOR
| group="main" sCount=1 dsCount=0 obj=0x41920e88 self=0x4190f8d0
| sysTid=13407 nice=0 sched=0/0 cgrp=apps handle=1074618708
| state=S schedstat=( 50780242971 27570770290 130442 ) utm=4254 stm=824 core=0
at com.my.package.util.HandCards.onTouchEvent(SourceFile:~188)
- waiting to lock <0x45b91988> (a android.view.SurfaceView$4) held by tid=18 (Thread-14297)
at android.view.View.dispatchTouchEvent(View.java:7837)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2216)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2216)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2216)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2216)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2075)
at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1522)
at android.app.Activity.dispatchTouchEvent(Activity.java:2458)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2023)
at android.view.View.dispatchPointerEvent(View.java:8017)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:3966)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:3845)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3405)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3455)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3424)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3531)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3432)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3588)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3405)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3455)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3424)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3432)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3405)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5554)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5534)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5505)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:5634)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:138)
at android.os.Looper.loop(Looper.java:196)
at android.app.ActivityThread.main(ActivityThread.java:5135)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
at dalvik.system.NativeStart.main(Native Method)

...

"Thread-14297" prio=5 tid=18 SUSPENDED
| group="main" sCount=1 dsCount=0 obj=0x45ba6358 self=0x76036b38
| sysTid=21120 nice=0 sched=0/0 cgrp=apps handle=1979936656
| state=S schedstat=( 48296386737 3088012659 22649 ) utm=4691 stm=138 core=0
#00 pc 00021adc /system/lib/libc.so (__futex_syscall3+8)
#01 pc 0000f074 /system/lib/libc.so (__pthread_cond_timedwait_relative+48)
#02 pc 0000f0d4 /system/lib/libc.so (__pthread_cond_timedwait+64)
#03 pc 0005655f /system/lib/libdvm.so
#04 pc 00056b21 /system/lib/libdvm.so (dvmChangeStatus(Thread*, ThreadStatus)+34)
#05 pc 00050fd7 /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+406)
#06 pc 00000214 /dev/ashmem/dalvik-jit-code-cache (deleted)
at android.graphics.Canvas.native_drawBitmap(Native Method)
at android.graphics.Canvas.drawBitmap(Canvas.java:1202)
at com.my.package.util.HandCards.a(SourceFile:178)
at com.my.package.util.HandCards.onDraw(SourceFile:136)
at com.my.package.util.d.run(SourceFile:36)

Where HandCards.onTouchEvent(SourceFile:~188) is:

synchronized (mRenderThread.getSurfaceHolder()) {

And HandCards.a(SourceFile:178) is:

canvas.drawBitmap(drawCardBitmap, null, mDrawingRect, mGraphicsPaint);

The full code for the SurfaceView subclass is:

public class HandCards extends SurfaceView implements SurfaceHolder.Callback {

    /** Opacity of the shadow layer that hides other cards when one card is highlighted and covers all cards when it's another player's turn (where 0 is transparent and 255 is opaque) */
    private static final int SHADOW_ALPHA = 150;
    private static SparseArray<Bitmap> mCardCache = new SparseArray<Bitmap>(); // cache array for own card bitmaps
    private HandThread mRenderThread;
    private volatile List<Card> mCards;
    private volatile int mCardCount;
    private volatile int mScreenWidth;
    private volatile int mScreenHeight;
    private volatile int mCardWidth;
    private volatile int mCardHeight;
    private volatile int mHighlightedCard = -1;
    private CardClickCallback mCardClickCallback;
    private volatile int mBlattID = 1;
    private volatile int mCurrentCardSpacing;
    private final Paint mGraphicsPaint;
    private final Paint mShadowPaint;
    private final Rect mDrawingRect;
    private volatile int mTouchEventAction;
    private volatile int mTouchEventCard;
    private Bitmap drawCardBitmap;
    private volatile int mOnDrawX1;
    private final BitmapFactory.Options mBitmapOptions;
    private volatile boolean mIsActive = true;
    private final int[] mCardSelection = new int[GameState.MAX_SWAP_CARDS];
    /** Indicates that the card view is currently used for choosing some cards to create a selection */
    private volatile boolean mIsChooseMode;
    /** Holds the index of the selected card that will be replaced next if all selection slots are full */
    private volatile int mNextReplacePosition;
    /** Used only locally in drawCard() but is declared here to save repeated allocations */
    private volatile int mCardOffsetY;
    private volatile int mRequiredSelectionCount;

    public HandCards(Context activityContext, AttributeSet attributeSet) {
        super(activityContext, attributeSet);
        getHolder().addCallback(this);
        setFocusable(true); // touch events should be processed by this class
        mCards = new ArrayList<Card>();
        mGraphicsPaint = new Paint();
        mGraphicsPaint.setAntiAlias(true);
        mGraphicsPaint.setFilterBitmap(true);
        mShadowPaint = new Paint();
        mShadowPaint.setARGB(SHADOW_ALPHA, 20, 20, 20);
        mShadowPaint.setAntiAlias(true);
        mBitmapOptions = new BitmapFactory.Options();
        mBitmapOptions.inInputShareable = true;
        mBitmapOptions.inPurgeable = true;
        mDrawingRect = new Rect();
    }

    public Card getCard(int location) throws Exception {
        if (mCards != null) {
            synchronized (mCards) {
                return mCards.get(location); // card may not be found (throw exception then)
            }
        }
        return null;
    }

    public static Bitmap cardCacheGet(int key) {
        synchronized (mCardCache) {
            return mCardCache.get(key);
        }
    }

    public static void cardCachePut(int key, Bitmap object) {
        synchronized (mCardCache) {
            mCardCache.put(key, object);
        }
    }

    public int[] getSelectedCards() {
        return mCardSelection;
    }

    public void setActive(boolean active) {
        if (mCardSelection != null) {
            for (int i = 0; i < GameState.MAX_SWAP_CARDS; i++) { // loop through all slots for selected cards
                mCardSelection[i] = -1; // unset the slot so that it is empty by default
            }
        }
        mIsActive = active;
    }

    public boolean isActive() {
        return mIsActive;
    }

    public void setChooseMode(boolean active, int swapCardCount) {
        mNextReplacePosition = 0;
        mIsChooseMode = active;
        mRequiredSelectionCount = swapCardCount;
    }

    public boolean isChooseMode() {
        return mIsChooseMode;
    }

    public void stopThread() {
        if (mRenderThread != null) {
            mRenderThread.setRunning(false);
        }
    }

    @Override
    public void onDraw(Canvas canvas) {
        if (canvas != null) {
            synchronized (mCards) {
                mCardCount = mCards.size();
                canvas.drawColor(Color.BLACK);
                if (mCardCount > 0) {
                    mCurrentCardSpacing = Math.min(mScreenWidth/mCardCount, mCardWidth);
                    for (int c = 0; c < mCardCount; c++) {
                        if (c != mHighlightedCard || !isActive()) {
                            try {
                                drawCard(canvas, mCards.get(c).getDrawableID(mBlattID), false, c*mCurrentCardSpacing, c*mCurrentCardSpacing+mCardWidth, c);
                            }
                            catch (Exception e) { }
                        }
                    }
                    if (mHighlightedCard > -1 && isActive()) {
                        mOnDrawX1 = Math.min(mHighlightedCard*mCurrentCardSpacing, mScreenWidth-mCardWidth);
                        try {
                            drawCard(canvas, mCards.get(mHighlightedCard).getDrawableID(mBlattID), true, mOnDrawX1, mOnDrawX1+mCardWidth, mHighlightedCard);
                        }
                        catch (Exception e) { }
                    }
                    else if (!isActive()) {
                        drawCard(canvas, 0, true, 0, mScreenWidth, 0);
                    }
                }
            }
        }
    }

    private void drawCard(Canvas canvas, int resourceID, boolean highlighted, int xLeft, int xRight, int cardPosition) {
        if (canvas != null) {
            try {
                if (highlighted) {
                    canvas.drawRect(0, 0, mScreenWidth, mScreenHeight, mShadowPaint);
                }
                if (resourceID != 0) {
                    drawCardBitmap = cardCacheGet(resourceID);
                    if (drawCardBitmap == null) {
                        drawCardBitmap = BitmapFactory.decodeResource(getResources(), resourceID, mBitmapOptions);
                        cardCachePut(resourceID, drawCardBitmap);
                    }
                    mCardOffsetY = 0; // by default draw all cards right at the bottom (without highlighting by position)
                    if (mCardSelection != null) {
                        for (int i = 0; i < GameState.MAX_SWAP_CARDS; i++) { // loop through all slots for selected cards
                            if (mCardSelection[i] == cardPosition) { // if current card has been selected (in that slot)
                                mCardOffsetY = mScreenHeight*1/4; // lift the card by one quarter to highlight it
                                break; // card has already been detected to be selected so stop here
                            }
                        }
                    }
                    mDrawingRect.set(xLeft, mCardOffsetY, xRight, mCardHeight+mCardOffsetY);
                    canvas.drawBitmap(drawCardBitmap, null, mDrawingRect, mGraphicsPaint);
                }
            }
            catch (Exception e) { }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mRenderThread == null) { return false; }
        synchronized (mRenderThread.getSurfaceHolder()) { // synchronized so that there are no concurrent accesses
            mTouchEventAction = event.getAction();
            if (isActive()) {
                if (mTouchEventAction == MotionEvent.ACTION_DOWN || mTouchEventAction == MotionEvent.ACTION_MOVE) {
                    if (event.getY() >= 0 && event.getY() < mScreenHeight) {
                        mTouchEventCard = (int) event.getX()/mCurrentCardSpacing;
                        if (mTouchEventCard > -1 && mTouchEventCard < mCardCount) {
                            mHighlightedCard = mTouchEventCard;
                        }
                        else {
                            mHighlightedCard = -1;
                        }
                    }
                    else {
                        mHighlightedCard = -1;
                    }
                }
                else if (mTouchEventAction == MotionEvent.ACTION_UP) {
                    if (mCardClickCallback != null && mHighlightedCard > -1 && mHighlightedCard < mCardCount) {
                        if (isChooseMode()) { // card has been chosen as a swap card
                            int freeSelectionIndex = -1; // remember the index of a free selection slot (default = none available)
                            for (int i = 0; i < mRequiredSelectionCount; i++) { // loop through all allowed slots for selected cards
                                if (mCardSelection[i] == mHighlightedCard) { // if this card has already been selected
                                    mCardSelection[i] = -1; // unselect the card
                                    freeSelectionIndex = -2; // mark that there is no need to select a new card
                                    break; // slot of current card has already been found so stop here
                                }
                                else if (mCardSelection[i] == -1 && freeSelectionIndex == -1) { // if slot is still available and no free slot has been found yet
                                    freeSelectionIndex = i; // remember the index of this free slot
                                }
                            }
                            if (freeSelectionIndex > -2) { // if a new card is to be placed in the selection array
                                if (freeSelectionIndex >= 0) { // if a free slot was available
                                    mCardSelection[freeSelectionIndex] = mHighlightedCard; // just place the card there
                                }
                                else { // if no free slot was available anymore
                                    mCardSelection[mNextReplacePosition] = mHighlightedCard; // replace another card in one of the slots
                                    mNextReplacePosition = (mNextReplacePosition+1) % mRequiredSelectionCount; // advance the cursor that points to the slot which will be replaced next
                                }
                            }
                        }
                        else { // card has been selected to be played on the table
                            try {
                                mCardClickCallback.chooseCard(mCards.get(mHighlightedCard));
                            }
                            catch (Exception e) {
                                // index was out of mCards' bounds (just ignore this, user may tap on card again)
                            }
                        }
                    }
                    mHighlightedCard = -1;
                }
            }
            else {
                try {
                    mCardClickCallback.resyncManually();
                }
                catch (Exception e) { }
            }
        }
        return true;
    }

    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { }

    public void setCards(List<Card> currentCards) {
        synchronized (mCards) {
            mCards.clear();
            mCards.addAll(currentCards);
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder arg0) {
        mScreenWidth = getWidth();
        mScreenHeight = getHeight();
        mCardHeight = mScreenHeight;
        mCardWidth = mCardHeight*99/150;
        mCurrentCardSpacing = mCardWidth;
        mRenderThread = new HandThread(getHolder(), this);
        mRenderThread.setRunning(true);
        mRenderThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        mRenderThread.setRunning(false); // stop thread
        while (retry) { // wait for thread to close
            try {
                mRenderThread.join();
                retry = false;
            }
            catch (InterruptedException e) { }
        }
    }

    public synchronized void setCardClickCallback(CardClickCallback callback) {
        mCardClickCallback = callback;
    }

    public void setBlattID(int blattID) {
        mBlattID = blattID;
    }

}

Then, there's also the render thread:

public class HandThread extends Thread {

    private final SurfaceHolder mSurfaceHolder;
    private final HandCards mSurface;
    private volatile boolean mRunning = false;

    public HandThread(SurfaceHolder surfaceHolder, HandCards surface) {
        mSurfaceHolder = surfaceHolder;
        mSurface = surface;
    }

    public SurfaceHolder getSurfaceHolder() {
        return mSurfaceHolder;
    }

    public void setRunning(boolean run) {
        mRunning = run;
    }

    @Override
    public void run() {
        Canvas c;
        while (mRunning) {
            c = null;
            try {
                c = mSurfaceHolder.lockCanvas(null);
                synchronized (mSurfaceHolder) {
                    if (c != null) {
                        mSurface.onDraw(c);
                    }
                }
            }
            finally { // when exception is thrown above we may not leave the surface in an inconsistent state
                if (c != null) {
                    try {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                    catch (Exception e) { }
                }
            }
        }
    }

}

解决方案

The ANR is happening because your onTouchEvent() method is synchronizing on a lock held by tid=18, an unnamed thread known only as Thread-14297.

Many people follow a pattern where, at the point they lock the SurfaceView canvas, they also lock the SurfaceHolder object. It's a bad idea to synchronize on objects with public visibility, and an even worse idea to synchronize on objects shared with the GUI framework, so it's sad that this pattern persists. (But I digress.)

You're drawing in an overridden onDraw() method, which doesn't make sense if you're drawing from a renderer thread -- the onDraw() method is used by the View hierarchy and would be called from the UI thread, but here it's clearly being called from elsewhere. You should call it something else, maybe just myDraw(). (But I digress.)

Thread-14297 is in the "suspended" state, which means it was executing but stopped when the stack trace was captured. Since the topmost frame is a native method -- which don't get suspended by the VM -- it was probably entering or exiting the frame. The system and user time for the thread, shown in ticks as "utm=" and "stm=" values, are fairly low, suggesting that it's not doing excessive CPU work. Unless, of course, your render thread is a one-shot, in which case it was fairly busy (and may not be done yet).

The good news is that you don't appear to be deadlocked. The render thread is just running slowly. Or, perhaps, you have a loop that is failing to exit (though none is apparent from the posted code). On a slow device, with lots of other activity on the system, and a big mCards list, it could get starved for CPU and fail to respond quickly. Assuming you're following the common pattern and locking the SurfaceHolder when you grab the Canvas, your onTouchEvent() is going to lock up the UI thread for the full duration of the draw. The ANR summary in logcat usually lists recent thread activity levels; if you have access to that, the information could tell you how busy the render thread has been.

Not all ANRs are fatal. If the app becomes permanently unresponsive, that's very different from a temporary ANR that clears up when the user hits "wait". Do you know which kind this is?

You need to:

  1. Re-evaluate your data synchronization. Use shorter windows and maybe a read-write lock to pass data around. Poke around in java.util.concurrent. Stalling the UI thread for an extended period is bad.
  2. Determine why your rendering seems to be taking a long time, and whether it's just running slowly or spinning forever.

这篇关于ANR在SurfaceView方法&QUOT; onTouchEvent(...)&QUOT;在Android的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆