【转载】Parameter must be a descendant of this view问题的解决方案

2023-10-26

转载,原文链接为:

http://www.cnblogs.com/monodin/p/3675040.html

关于ViewFlowGridView嵌套导致Parameter must be a descendant ofthis view问题的解决方案

【关于ViewFlow

ViewFlow是一款基于ViewGroup实现的可以水平滑动的开源UI Widget,可以从https://code.google.com/p/andro-views/下载。

它使用Adapter进行条目绑定,主要用于不确定数目的视图间的切换,和ViewPager功能类似,但是可扩展性更强。

本例就是使用ViewFlow来实现页面水平切换。

【关于文章所用源码】

本文所属异常由于是从Android 4.2设备上抛出,所以文章内出现的所有源码都是Android 4.2源码,具体地址如下:http://grepcode.com/snapshot/repository.grepcode.com/java/ext/com.google.android/android/4.2.1_r1.2/

一、功能描述

采用ViewFlow+GridView的方式实现手势切屏功能,每屏以九宫格模式显示。

长按GridView里的Item切换到编辑模式,可以对Item进行删除。

二、复现场景

2.1 复现环境

本人拿了多款Android 4.2系列手机进行测试,目前只在两部手机上必现,在其他非 4.2手机上偶尔出现。

华为Ascend P6Android 4.2.2

联想K900Android 4.2.1

2.2 复现步骤

进入应用后,以下三种操作都会导致所述问题:

1Home到后台,再切换回来,Crash

2、长按Item,待切换到编辑模式后,Home到后台,再切换回来,Crash

3、左右切换几次屏幕,Home到后台,再切换回来,Crash

三、Crash Stack Info

 1 java.lang.IllegalArgumentException: parameter must be a descendant ofthis view

 2     atandroid.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:4295)

 3     atandroid.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.java:4232)

 4     atandroid.view.ViewRootImpl.scrollToRectOrFocus(ViewRootImpl.java:2440)

 5     atandroid.view.ViewRootImpl.draw(ViewRootImpl.java:2096)

 6     at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2045)

 7     atandroid.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1854)

 8     atandroid.view.ViewRootImpl.doTraversal(ViewRootImpl.java:989)

 9     atandroid.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4351)

10     atandroid.view.Choreographer$CallbackRecord.run(Choreographer.java:749)

11     atandroid.view.Choreographer.doCallbacks(Choreographer.java:562)

12     atandroid.view.Choreographer.doFrame(Choreographer.java:532)

13     atandroid.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)

14     atandroid.os.Handler.handleCallback(Handler.java:725)

15     atandroid.os.Handler.dispatchMessage(Handler.java:92)

16     at android.os.Looper.loop(Looper.java:137)

17     atandroid.app.ActivityThread.main(ActivityThread.java:5041)

18     atjava.lang.reflect.Method.invokeNative(Native Method)

19     atjava.lang.reflect.Method.invoke(Method.java:511)

20     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)

21     atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)

22     at dalvik.system.NativeStart.main(NativeMethod)

View Code

四、问题分析

4.1 异常描述

首先让我们看一下这个Exception是如何抛出的。参考:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.2.1_r1.2/android/view/ViewGroup.java#ViewGroup

Android 4.2.1_r1.2ViewGroupoffsetRectBetweenParentAndChild方法如下:

 1    /**

 2     * Helper method that offsets arect either from parent to descendant or

 3     * descendant to parent.

 4     */

 5    void offsetRectBetweenParentAndChild(View descendant, Rect rect,

 6            boolean offsetFromChildToParent, boolean clipToBounds) {

 7 

 8        // already in thesame coord system :)

 9        if (descendant == this) {

10            return;

11         }

12 

13         ViewParent theParent =descendant.mParent;

14 

15        // search andoffset up to the parent

16        while ((theParent != null)

17                 && (theParentinstanceof View)

18                 && (theParent !=this)) {

19 

20            if (offsetFromChildToParent) {

21                 rect.offset(descendant.mLeft -descendant.mScrollX,

22                         descendant.mTop -descendant.mScrollY);

23                if (clipToBounds) {

24                     View p = (View) theParent;

25                     rect.intersect(0, 0,p.mRight - p.mLeft, p.mBottom - p.mTop);

26                 }

27             }else {

28                if (clipToBounds) {

29                     View p = (View) theParent;

30                     rect.intersect(0, 0,p.mRight - p.mLeft, p.mBottom - p.mTop);

31                 }

32                rect.offset(descendant.mScrollX - descendant.mLeft,

33                         descendant.mScrollY -descendant.mTop);

34             }

35 

36             descendant = (View) theParent;

37             theParent = descendant.mParent;

38         }

39 

40        // now that we areup to this view, need to offset one more time

41        // to get into ourcoordinate space

42        if (theParent == this) {

43            if (offsetFromChildToParent) {

44                 rect.offset(descendant.mLeft -descendant.mScrollX,

45                         descendant.mTop -descendant.mScrollY);

46             }else {

47                 rect.offset(descendant.mScrollX- descendant.mLeft,

48                         descendant.mScrollY -descendant.mTop);

49             }

50         }else {

51            throw new IllegalArgumentException("parameter must be a descendant of thisview");

52         }

53     }

View Code

在方法最后可以看到该异常。那么该异常到底表示什么意思呢?若想知道答案,我们需要从该方法的实现入手。

通过注释可知,offsetRectBetweenParentAndChild方法的功能有两个:

1、计算一个Rect在某个Descendant View所在坐标系上所表示的区域或者是在该坐标系上和该Descendant View重叠的区域;

2、计算一个Rect从某个Descendant View所在坐标系折回到Parent View所在坐标系所表示的区域,即与功能1相反。

分析实现代码可以看出,它是通过所给Descendant View逐级向上寻找Parent View,同时将Rect转换到同级坐标系。在方法末尾处指出:如果最后寻找的Parent View和当前View(即调用offsetRectBetweenParentAndChild方法的View)不一致,则会抛出IllegalArgumentException("parameter must be a descendant of this view")异常,亦即该文所指异常。

说白了,就是所给Descendant View必须是当前View的子孙.

那么,什么时候最后的Parent View和当前View不一致呢?请看下节分析。

4.2 原因探究

4.2.1 异常条件

我们来看offsetRectBetweenParentAndChild里的这段代码:

1 ViewParenttheParent = descendant.mParent;

2 

3// search and offset up to the parent

4while ((theParent != null)

5         && (theParentinstanceof View)

6         && (theParent !=this)) {

View Code

Descendant ViewParentnull、非View实例、当前View时,会跳出循环进入最后的判断。排除当前View,就只剩下两个原因:null和非View实例

这就需要探究ViewParent是如何被赋值的。

4.2.2 ViewParent的赋值入口

首先,我们从最根本的View入手。

View源码里找到mParent的声明和赋值代码分别如下:

声明:

1    /**

2     * The parent this view isattached to.

3     * {@hide}

4     *

5     * @see #getParent()

6     */

7    protected ViewParent mParent;

View Code

赋值:

 1    /*

 2     * Caller is responsible forcalling requestLayout if necessary.

 3     * (This allows addViewInLayoutto not request a new layout.)

 4     */

 5    void assignParent(ViewParent parent) {

 6        if (mParent == null) {

 7             mParent = parent;

 8         }else if (parent ==null) {

 9             mParent =null;

10         }else {

11            throw new RuntimeException("view " +this + " being added, but"

12                     + " it already has aparent");

13         }

14     }

View Code

透过上述代码,我们可以猜测mParent的赋值方式有两种:直接赋值和调用assignParent方法赋值

4.2.3 ViewGroupDescendant指定Parent

接下来查看ViewGroupaddView方法,并最终追踪到addViewInner方法内,注意下图红框所示代码:

红框内的代码验证了我们的猜想,即:一旦一个View被添加进ViewGroup内,其mParent所指向的就是该ViewGroup实例。很显然,ViewGroupView的实例。这样异常条件就只剩下一种可能:Descendant ViewParentnull

但是,什么情况下为null呢?

4.2.4 ViewGroup如何移除Descendant

查找并筛选ViewGroup内所有确定最后将Parent设置为null的方法,最后找到四个方法:

·        removeFromArray(int index)------------------移除指定位置的Child

·        removeFromArray(int start, int count)-------移除指定位置开始的countChild

·        removeAllViewsInLayout()---------------------移除所有Child

·        detachAllViewsFromParent--------------------把所有ChildParent中分离

从上述四个方法中不难看出,当ViewViewGroup中移除的时候,其Parent将被设为null

由此可以断定,ViewGroup使用了一个已经被移除的Descendant View来通过offsetRectBetweenParentAndChild方法计算坐标。

那么,既然使用被移除的Descendant View必定会导致该异常,ViewGroup又为何要使用它呢?

4.3 原因深究

4.3.1 ViewGroup为何使用被移除的Descendant

我们根据Crash Stack Info追溯到ViewRootImpl类的booleanscrollToRectOrFocus(Rect rectangle, boolean immediate)方法,注意图片中红框所圈代码:

由标记13处代码可知,ViewGroup使用的Descendant View其实就是焦点当前真正所在的View,即Focused View

问题就出在这里,如果Focused View是一个正常的View倒是可以,但是如果它是一个已经被移除的View,根据我们在4.2的分析可知,它的Parentnull,势必会导致所述异常。

但是,Focused View是为什么会被移除呢?

4.3.2 Focused View为什么会被移除

4.2提到的四个方法中,第三个方法removeAllViewsInLayout在移除Child Views的同时清除了Focused View的标记,排除。第四个方法detachAllViewsFromParentActivity Destory后才调用,排除。方法一和方法二是重载方法,实现类似,可以断定Focused View肯定是在这两个方法中被移除的。

分析ViewFlow移除Child的操作,一共有两处,分别在recycleView(View v)resetFocus()方法内。

resetFocus方法内调用了removeAllViewsInLayout方法,根据上一段分析可以安全排除。那么就剩下recycleView(View v)方法,我们来看代码:

1     protected void recycleView(View v) {

2         if (v == null)

3             return ;

4 

5         mRecycledViews.add(v);

6         detachViewFromParent(v);

7     }

View Code

该方法是把ViewFlowChild移除,并回收到循环利用列表。注意最后一行,调用了detachViewFromParent(View v)方法,代码如下:

 1    /**

 2     * Detaches a view from itsparent. Detaching a view should be temporary and followed

 3     * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}

 4     * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached,

 5     * its parent is null and cannotbe retrieved by a call to {@link #getChildAt(int)}.

 6     *

 7     * @param child the child to detach

 8     *

 9     * @see #detachViewFromParent(int)

10     * @see #detachViewsFromParent(int, int)

11     * @see #detachAllViewsFromParent()

12     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)

13     * @see #removeDetachedView(View, boolean)

14     */

15    protected void detachViewFromParent(View child) {

16         removeFromArray(indexOfChild(child));

17     }

View Code

很明显,直接调用了removeFromArray(int index)方法,正是在4.2.4节中指出的第一个方法,而该方法已经在本节开头被确定为真凶

设想一下,如果recycleView(View v)的参数v正是Focused View的话,Focused View就会从ViewFlow中被移除,但是当前焦点仍然在其上边。这时候offsetRectBetweenParentAndChild方法使用它必定会导致本文所指异常,这正是症结所在!

五、解决方案

5.1 普通方案与文艺方案

经过上述分析,不难想到解决方案:ViewFlowrecycleView(View v)方法内移除View的时候,判断如果恰好是Focused View,则将焦点一并移除。

详细代码如下:

 1protected void recycleView(View v) {

 2    if (v == null)

 3        return;

 4 

 5    // 方法一:普通方案,已验证可行

 6    // 如果被移除的View恰好是ViewFlow内当前焦点所在View

 7    // 则清除焦点(clearChildFocus方法在清除焦点的同时

 8    // 也把ViewGroup内保存的Focused View引用清除)

 9    if (v == findFocus()) {

10         clearChildFocus(v);

11     }

12 

13    // 方法二:文艺方案,请自行验证!

14    // 下面这个方法也是把View的焦点清除,但是其是否起作用

15    // 这里不讲,请读者自行验证、比较。

16    // v.clearFocus();

17 

18     mRecycledViews.add(v);

19     detachViewFromParent(v);

20 }

View Code

注意代码内的注释。

下面附上ViewGroup.clearChildFocus(View v)View.clearFocus()这两个方法的源码以供参考:

ViewGroup.clearChildFocus(View v):

 1/**

 2* {@inheritDoc}

 3*/

 4public void clearChildFocus(View child) {

 5    if (DBG) {

 6         System.out.println(this + " clearChildFocus()");

 7     }

 8 

 9     mFocused =null;

10    if (mParent != null) {

11         mParent.clearChildFocus(this);

12     }

13 }

View Code

View.clearFocus():

 1/**

 2* Called when this view wants to give up focus. This will cause

 3* {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called.

 4*/

 5public void clearFocus() {

 6    if (DBG) {

 7         System.out.println(this + " clearFocus()");

 8     }

 9 

10    if ((mPrivateFlags & FOCUSED) != 0) {

11         mPrivateFlags &= ~FOCUSED;

12 

13        if (mParent != null) {

14             mParent.clearChildFocus(this);

15         }

16 

17         onFocusChanged(false, 0,null);

18         refreshDrawableState();

19     }

20 }

View Code

当然,解决问题方法不止一种!

5.2 2B方案

注意,该方案仅适用于ViewGroupChild不需要获取焦点的情况,其他情况下请使用上一节介绍的方案。

既然是ViewGroup内的Focused View惹的祸,那干脆把这家伙斩草除根一了百了!

ViewGroup内的Child在获取焦点的时候会调用requestChildFocus(Viewchild, View focused)方法,代码如下:

 1/**

 2* {@inheritDoc}

 3*/

 4public void requestChildFocus(View child, View focused) {

 5    if (DBG) {

 6         System.out.println(this + " requestChildFocus()");

 7     }

 8    if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {

 9        return;

10     }

11 

12    // Unfocus us, ifnecessary

13    super.unFocus();

14 

15    // We had aprevious notion of who had focus. Clear it.

16    if (mFocused != child) {

17         if (mFocused !=null) {

18             mFocused.unFocus();

19         }

20 

21         mFocused = child;

22     }

23    if (mParent != null) {

24         mParent.requestChildFocus(this, focused);

25     }

26 }

View Code

注意第二个判断条件:如果ViewGroup当前的焦点传递策略是不向下传递,则不指定Focused View。

So,下面该如何做,你懂的!整个世界清静了~

 


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【转载】Parameter must be a descendant of this view问题的解决方案 的相关文章

随机推荐

  • 笔记:java、android网络交互频繁gc

    背景 纯手写没用三方异步方式网络通讯 现象 execute 之后log出现大量gc 内存总体没有多大变化 频繁出现的gc导致UI等待界面卡顿也着实吓了我一跳 解决思路 1 断点调试跟踪了一下发现是execute的问题 因为其他交互没有出现过
  • 信息学竞赛中的时间复杂度以及算法内容

    信息学竞赛一般的时间限制是1秒或2秒 在这种情况下 C 代码中的操作次数控制在 1 0 7 1 0 8 10 7 10 8 107
  • score-based generative models (Yang Song) 笔记

    20220524 Yang Song s blog 文章目录 20220524 方法 方法 The score function score based models and score matching Fisher divergence
  • Mybatisplus报错@TableId can‘t more than one in Class解决方案

    Mybatisplus报错 TableId can t more than one in Class解决方案 问题背景 解决方案 Lyric 能不能原谅我 问题背景 Caused by org springframework beans f
  • 安装es-header插件

    1 官网下载https github com mobz elasticsearch head 2 解压 3 进入解压目录 调出cmd窗口 确定自己已经安装了node 如下确定 没有自行下载安装 4 输入前端包安装命令 等待几分钟下载 npm
  • 在android中opencv视频采集,【Android】【opencv】实现摄像头拍照和录像

    1 0 需求 在安卓开发板上实现视频监控功能 并能后台监控 由于后期可能跑视频识别 所以考虑用OpenCV实现 通过OpenCV Manager进行动态库的链接 实现帧的预览和保存 android版本 5 0 1 开发平台 Android
  • 1、VScode汉化以及如何设置跳转到函数定义

    一 汉化 1 首先打开软件 软件默认是英文配置状态 如图所示 2 使用快捷键 Ctrl Shift P 进入如下界面 3 然后在弹出的界面中 输入 configure language 然后选择 install additional lan
  • CocosCreator在电脑Web打印vConsole日志的问题

    忘了什么时候开始 Web端的日志打印的文件输入信息全是vconsole min js文件 很纠结啊有木有 完全不知道日志的出处 日志输入如以下图片 官方还没有给出对于这个问题的配置是怎么样解决的 所以我们自己搞定 我们进入CocosCrea
  • [linux kernel] 内核下RX8025对接系统时钟

    系统版本 Ubuntu18 04 64 编译器版本 gcc version 7 4 0 Ubuntu Linaro 7 4 0 1ubuntu1 18 04 1 uboot版本 2018 07 linux4sam 6 0 板子型号 at91
  • [JavaWeb] 登录页面记住密码,账号或密码错误

    个人主页 沫洺的主页 系列专栏 JavaWeb专栏 JavaSE专栏 Java基础专栏 vue3专栏 MyBatis专栏 Spring专栏 SpringMVC专栏 SpringBoot专栏 Docker专栏 Reids专栏 MQ专栏 Spr
  • 区块链概念介绍

    区块链定义 区块链是一些技术集成的 适用于多方博弈 由多方共同对数据背书的数据存储工具 区块链的核心技术包含块链式存储 点对点通讯 密码学 共识机制 智能合约等 区块链是利用块链式数据结构来验证与存储数据 利用分布式节点共识算法来生成和更新
  • myBatis的xml大于小于不等于模糊查询

    and o create time lt endTime jdbcType TIMESTAMP 小于 and o create time gt startTime jdbcType TIMESTAMP 大于 and i status fla
  • Multimedia Tools and Applications(MTAP)期刊投稿经验分享

    Multimedia Tools and Applications ISSN号 1380 7501 2022年影响因子 2 577 出版社 Springer US 学科分类 计算机科学 工程技术 中科院4区 JCR Q2 CCFC 我投递的
  • 达芬奇无法播放视频,黑屏,不能预览画面

    说一下其中一个原因 是因为用了像是xdisplay super display这类软件 他们会安装一个显卡驱动而这个驱动就会导致这个问题 方法很简单 在设备管理器里面 显示卡一览 卸载掉除了你的电脑显卡以外的虚拟显卡驱动
  • element ui 中table表格刷新、input输入框添加enter触发搜索、连续点击的处理办法

    8 25小结 1 table表格刷新 elementy ui有一个v loading 我们可以给它绑定一个布尔值 truer就是转圈圈 false就是停止转圈圈 在刷新按钮上绑定一个事件来控制这个布尔值的改变 但是需要加一个定时器才能看出来
  • spring-boot-maven-plugin爆红解决方案,亲测有效

    报错信息提示如下 Plugin org springframework boot spring boot maven plugin not found 我使用idea中的spring initialer 来创建的 maven项目 但是在下载
  • 揭秘——STL空间配置器

    为什么要有空间配置器呢 这主要是从两个方面来考虑的 1 小块内存带来的内存碎片问题 单从分配的角度来看 由于频繁分配 释放小块内存容易在堆中造成外碎片 极端情况下就是堆中空闲的内存总量满足一个请求 但是这些空闲的块都不连续 导致任何一个单独
  • java中常见的异常类型

    Throwable 类是 Java 语言中所有错误或异常的超类 只有当对象是此类 或其子类之一 的实例时 才能通过 Java 虚拟机或者 Java throw 语句抛出 类似地 只有此类或其子类之一才可以是 catch 子句中的参数类型 两
  • 基于MATLAB的多聚类相位展开算法实现

    基于MATLAB的多聚类相位展开算法实现 相位展开是一种常见的信号处理算法 用于从相位差模糊的信号中恢复出准确的相位信息 多聚类相位展开算法是相位展开的一种改进方法 能够有效处理多个相位聚类的情况 本文将介绍如何使用MATLAB实现多聚类相
  • 【转载】Parameter must be a descendant of this view问题的解决方案

    转载 原文链接为 http www cnblogs com monodin p 3675040 html 关于ViewFlow和GridView嵌套导致Parameter must be a descendant ofthis view问题