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