关闭

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

标签: view.postsdkandroid7.0新版sdk
1114人阅读 评论(3) 收藏 举报
分类:

我们熟知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);
    }



3
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:16193次
    • 积分:371
    • 等级:
    • 排名:千里之外
    • 原创:20篇
    • 转载:4篇
    • 译文:0篇
    • 评论:5条
    最新评论