[Android]View.post(),android7.0(sdk24以上)不执行的问题(部分Click点击事件无效的原因)

原创 2017年01月03日 19:11:07

我们熟知View.post()和Handler.post(),虽然最后执行过程还会走到Handler的post()方法中,但是View.post()做了许多额外的工作,所以我认为如非迫不得己,建议直接使用Handler.post()方法,详情见此文。

如果在android7.0(sdk 24及以上)开发过程中,如果你的view没有通过addView添加到视图的时候,就会导致对应view的点击事件无效,以及view.post不执行,可能就是本文原因了,

以下是view.post在sdk版本24及以上的post方法

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

下面是sdk23及以下的post方法

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }


我们很容易的发现,24以上的sdk使用的是        getRunQueue().post(action);而sdk23以及以下是用的ViewRootImpl.getRunQueue().post(action);

其中,getRunQueue()的方法是

   /** 
     *  Returns the queue of runnable for this view.
     *
     * @return the queue of runnables for this view
     */
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

这个mRunQueue是View类中一个私有变量。

ViewRootImpl可以理解是一个activity的View树的树根,每个ViewRootImpl管理对应的DecoView和View树

ViewRootImpl中的队列是一个静态变量,也就是只有一个这个队列存在于这个app的生命周期中

static HandlerActionQueue getRunQueue() {
        HandlerActionQueue rq = sRunQueues.get();
        if (rq != null) {
            return rq;
        }
        rq = new HandlerActionQueue();
        sRunQueues.set(rq);
        return rq;
    }

在view.post中,并不是post完毕后就会执行,无论高低版本的View.post,只是把Runnable添加到队列,等待进行操作,这和Handler.post不同

其中,SDK24以上是HandlerActionQueue类中

public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

sdk 23以下是ViewRootImpl的静态方法

void executeActions(Handler handler) {
            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;
                final int count = actions.size();

                for (int i = 0; i < count; i++) {
                    final HandlerAction handlerAction = actions.get(i);
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }

                actions.clear();
            }
        }

在SDk24及以上我们可以了解到只有在View的dispatchAttachedToWindow方法中执行,如果这个view不是通过addview等方法加入父视图的话,就无法调用dispatchAttachedToWindow,从而无法执行View.post,而post方法影响着click方法,即为

case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

从而高版本sdk24及以上可能导致点击事件失效。

而在23及以下版本中,ViewRootImpl的executeActions会频繁的调用,ViewRootImpl中的TraversalRunnable进行调用doTraversa来()进行调用。而TraversalRunnable是通过Choreographer的postCallBack循环调用,这个Choreographer通过doScheduleCallback进行一个MSG_DO_SCHEDULE_CALLBACK类型的循环操作(它每隔一段时间操作(ms级别))。详情请查看以后的Choreographer文章

说完了问题原因,解决方法如下:

关于点击事件,首先一定保证对应的view已经addview到父视图中,这样可能解决问题,当然不一定满足业务需求,也不一定能完美解决,那么可以通过重写View的对应post方法进行处理,如下

 private Handler mHandler;
    @Override
    public boolean post(Runnable action) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && action!= null && !isAttachedToWindow()) {
            mHandler = new Handler();
            return mHandler.post(action);
        }
        return super.post(action);
    }

    @Override
    public boolean removeCallbacks(Runnable action) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && action != null && !isAttachedToWindow()&& mHandler != null) {
            mHandler.removeCallbacks(action);
            return true;
        }
        return super.removeCallbacks(action);
    }



关于android中postDelayed方法的讲解

From: http://blog.csdn.net/xiabo851205/article/details/7991529 这是一种可以创建多线程消息的函数 使用方法: 1,首先创建一个H...
  • tdstds
  • tdstds
  • 2014年08月03日 08:51
  • 57529

延迟执行PostDelayed

Android程序中,使用postDelayed()方法,表示,在一段时间之后,执行新的线程,即可以达到一段特定程序延迟执行的目的。 使用:                             ...

关于Android中调用了post方法后貌似没有执行run方法的解释及解决办法

(真纠结,刚刚发了之后才发现排版太乱了,稍作修改再发了哈~) 哎……之前纠结过Handler的运行机制,后来貌似懂了,但是近几天又被自己的工程绕的好像又不懂了一样!! 其实之前理解...

Android 自定义View的post(Runnable)方法非100%执行的原因和处理方法解析

最近在写一个需求,需要在view.post(Runnable)方法当中进行一些操作。但是实际使用中(特定场景)发现并不靠谱。得到的现象是: 如果调用了view的post(Runnable)方法,该...

从源码分析Handler的postDelayed为什么可以延时?

昨天一个朋友去面试,回来说面试官问了他一个有意思的问题,然后被面试官各种调侃。。 什么问题呢?中国人都能看得懂的一个问句:Handler的postDelayed为什么可以延时??? 握草~我只知道Ha...

android之postDelayed是否运行在主线程中解答

知识点 1、postDelayde是否运行在主线程中?答案是肯定的。 代码如下 在绝对的UI线程中打印线程ID: System.out.println("-=-=-=>>xianchengid0...

view.performClick()触发点击事件

1、主要作用      自动触发控件的点击事件 2、界面的布局文件  activity_main.xml "http://schemas.android.com/apk/res/androi...

解决Android按住空白区域,导致点击其他按钮无法触发点击事件的问题

问题描述:按住了Android屏幕空白区域,这时候去点击其他按钮,不触发按钮的click事件 原因:按住空白区域后,你再点击其他按钮,这时候系统判断这次点击事件不是单纯的点击,而是 多点触控。 方...

Android触摸屏事件派发机制详解与源码分析一(View篇)

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN。因为CSDN也支持MarkDown语法了,牛逼啊!【工匠若水 http://blog.csdn.net/yanbobe...
  • yanbober
  • yanbober
  • 2015年05月21日 17:03
  • 48424

addview的view何时显示的问题

我的项目是一个在任何时候都可以弹出的待机界面,界面是一张图和一个倒计时提示。 我的界面是通过在activty中通过WindowManager.addview上去的。因为addview的window可...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:[Android]View.post(),android7.0(sdk24以上)不执行的问题(部分Click点击事件无效的原因)
举报原因:
原因补充:

(最多只允许输入30个字)