关键词:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder,
Added View has RecyclerView as parent but view is not a real child
关于recyclerview就不用多做介绍了,做android开发的相信大家都知道,大多数人会使用它,利用一些三方adapter库几行代码实现复杂item的加载效果,但是真正说到rv中涉及到的一些源码可能很多人也都是一知半解,这段时间真的是花了时间去研究rv的源码,研究rv源码最主要的起因是由于公司内部使用adapter所引起的一些疑难杂症,项目集成上线后会时不时的报一些rv崩溃的异常,问题存在有一些时间,但是苦于rv巨多的代码,崩溃一直没人处理,虽说发生的频率不是很高,但是看着那些无从下手,莫名其妙的崩溃,内心其实是比较煎熬的。这里要说下公司内部使用的adapter库并没有直接使用现有的一些开源库,而是自己进行的代码封装,自己封装adapter的好处显而易见代码可控性高,出现问题可以快速定位,随便还能学一波和rv相关的东西。谁知道不小心翻了车,还是对rv的源码没有理解到位造成的。
bug如风,常伴吾身,上报的崩溃有两个,都是磨人的小妖精,曾抱着侥幸的心理,想是不是rv自身的bug导致的崩溃,直到我亲手把这两个bug给复现出来才知道原来是封装adapter有问题导致的崩溃。先来说一下第一个bug
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder
不知道看到这个崩溃的各位有没有一种似曾相似,其实这个崩溃算是比较常见的崩溃,网上也有相关的解决方法,解释的比较清楚了,就是data数据更新后没有及时调用notify方法引起的,可以在rv的源码中找到这个崩溃的根源
if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
+ "adapter position" + holder + exceptionLabel());
}
引起bug的根源就是满足了holder.mPosition >= mAdapter.getItemCount()这个条件,现在的问题是如何复现这个bug,其实方法很简单,当你清除数据后没有及时调用notify方法直接去滑动rv的时候,这个bug就会出现,现在解释下引起bug的原因
当清除完数据后此时mAdapter.getItemCount()的值将为0,而holder.mPosition的值实际上就是各个item的对应位置,很显然任意·一个item都会满足holder.mPosition >= mAdapter.getItemCount()这个条件。而如果在清除完数据后调用notifiyDatachange方法,代码最终会通知rv进行重新onlayout,该方法中会调用到layoutmanager一个非常重要的函数fill,fill函数的主要作用就是将各个item排布到rv上,内部通过while循环依次排布各个item,关键判断如下
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
......
}
此时layoutState.hasMore(state)为false,意思就是rv中没有更多的item可以排布,所以抛异常的代码根本不会执行到。
在清除完数据源除了调用notifiyDatachange同步下数据源之外,还可以调用adapter提供的若干局部刷新方法如notifyItemRemove等,这些方法的原理和notifiyDatachange有些不同,是通过更改holder.mposition值来避免
holder.mPosition >= mAdapter.getItemCount()成立,从而避开进入抛异常的代码。
经过上述分析可以知道一个事实就是当数据源改变后一定要及时调用一系列的notify方法来避免这个崩溃的产生。实际上除了数据源清空没有调用notify方法会引发崩溃外,当对数据源进行删除若干项的操作而没有调用notify也会引起崩溃,只不过这种情况下崩溃没有这么容易触发,需要滑动rv到一定程度才会引发崩溃。将数据直接清空不调用notify的操作是让崩溃重现的最佳方法。
既然找到了崩溃引发的根源,那么接下来要去解决的问题在项目代码中去查找究竟是哪里的逻辑引发的崩溃。最终根据bugly崩溃日志信息定位到了大致位置,剩下的就是需要自己根据代码逻辑去仔细排查,只要抓住数据源改变后没及时调用notify这个原因,那么发现问题代码就是时间问题。
这里说下项目问题代码的逻辑,项目中有这么个场景,当用户没有登录时rv会展示数据,当用户登录后rv展示的数据会根据网络请求进行改变,很常见的一个逻辑,问题就出在写代码的同事不知道数据源改变同步调用notify,当进行用户登录操作时会进行一个数据源清空的操作,然后在登陆完毕后请求到数据执行add操作和notifydatachange来展示新的数据。就是这么个操作会有概率引起崩溃问题,当清空数据源之后notifydatachange不是紧接着调用而是在数据返回后才调用,如果这时网络有延迟notifydatachange就会一直得不到调用,如果此时有用户在屏幕上执行滑动操作那么恭喜你app crash!!。这个问题没有被测试发现的一个原因就在于内网网络速度很快,可以说notifydatachange几乎都是跟在数据清空后调用的,还有一个比较坑的地方在于明明是清空数据不同步调用notify引起的崩溃,但bugly会把崩溃发生的代码定位到登陆逻辑里面,导致问题查了半天找不到原因,这个崩溃在项目中存在有一段时间都没人解决,主要原因还是在于没有对rv有一定了解。
很难搞定的一个bug,但最终还是在我的不懈努力下找到了解决方法。此时有掌声,啪啪啪...在bugly是一个比较稀有的bug,常出没于一些比较低端点的手机上,时有时无若隐若现,这个崩溃不是rv自身代码bug,而是我们自身代码存在一定缺陷引起的,好在网上可以搜到一些关于这个bug的传闻,当快速滑动rv到加载更多的时候有时候会触发这个bug,但是网上没有一篇文章有对这个bug的原因有过分析,只是知道触发条件是快递滑动rv到加载更多,不停重复这个操作会复现这个bug。
但是我按照这个操作来来回回操作了几十次,换了n台测试机都没触发过这个崩溃,程序没崩溃这就让我很崩溃了,让我们看下这个崩溃的源码所在,addviewint内部有如下代码
if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
// ensure in correct position
int currentIndex = mChildHelper.indexOfChild(child);
if (index == -1) {
index = mChildHelper.getChildCount();
}
if (currentIndex == -1) {
throw new IllegalStateException("Added View has RecyclerView as parent but"
+ " view is not a real child. Unfiltered index:"
+ mRecyclerView.indexOfChild(child) + mRecyclerView.exceptionLabel());
}
if (currentIndex != index) {
mRecyclerView.mLayout.moveView(currentIndex, index);
}
}
在这里我们发现了引起崩溃的具体条件child.getParent() == mRecyclerView并且currentIndex==-1,第一个条件getParent为rv,当addviewint的时候,被添加的child的parent居然不为null,一种似曾相似的感觉,相信很多人之前都遇到过viewpager中左右滑动view时可能会报出一个类似 child already has parent的崩溃,没想到rv中居然也报了这么个崩溃。
这个child.getParent()==mRecyclerView的条件着实让人费解,结合网上流传滑动到底部加载更多引起崩溃的原因大致可以猜测引起崩溃的view是一个"加载更多"view,那么问题来了为什么其他view都没有问题唯独"加载更多"这个view会有问题。在我们封装adapter的时候一般都会提供加载更多这么个功能,加载更多严格来说并不是真正的数据源,而是adapter在getitemcount的时候我们通过对数据源.size+1来忽悠adapter,让adapter误以为我们有这么多数据要加载,当加载到最后一个item时,通过inflate加载更多的layoutid来展示“加载更多”布局,项目原来封装的adapter里面有这样一段逻辑足以引起怀疑,这段代码在oncreateviewholder时会被调用到,生成的view作为一个rootview传递给holder
public final View onCreateView(ViewGroup parent) {
if (itemView == null) {
itemView = inflate(layoutRes, parent, false);
onViewCreate(itemView);
}
return itemView;
}
注意这个条件当itemView==null才inflate,如果不为null则直接复用itemView,理论上每一个rv只会存在一个"加载更多"布局,以下简称loadmore,我们每次滑到底部看到的loadmore实际上都是通过rv的缓存机制得到的,但是在个别极端情况下是会存在两个loadmore布局的!!
这是个很坑爹的情况,但是却是真实有可能发生的,只不过概率没这么高而已,我们都知道rv默认情况下都是有动画效果的,而loadmore的动画效果就是loadmore布局下移,然后新获取的数据展示出来,相信这种动画效果大家都见过,经过对源码分析发现一个情况就是当loadmore还在执行下移动画此时快速滑动rv到底部的时候就会出现这种双生loadmore情况,何为双生laodmore,说白了就是同时会存在两个loadmore,而这两个loadmore用的却是同一个itemview,这种情况最大的一个特点就是此时第一个loadmore还在执行动画中,还并没有从rv上移除所以此时getParent==mRecyclerView,至此第一个崩溃条件就成立了,这二个条件 int currentIndex = mChildHelper.indexOfChild(child);值为-1,具体就不想分析了,原因就是执行动画的时候得到的值就是-1就对了,到此两条件满足崩溃重现江湖!!
bug知道根源都好解决,难得是发现崩溃的整个过程,想解决很简单只要破坏第一个条件成立的情况就可以了,修改onCreateView中的代码如下
public final View onCreateView(ViewGroup parent) {
itemView = inflate(layoutRes, parent, false);
onViewCreate(itemView);
return itemView;
}
到此世界就清净了。。。让每一个loadmore创造出来的时候独立拥有一个itemview即可。写到这里就不免想到那个笑话,考核程序员绩效通过代码量来体现,修一个bug只删了两行代码,但是整个过程却让我把rv的源码来回翻了N次。这个bug之所有这么难复现主要原因还是这个bug太细节了,低端手机性能比较差在loadmore执行动画还没结束的时候的快速下拉有更大概率触发崩溃。还有一个大坑就是这个bug在compat 27的包中会被提前报出,导致我在demo使用compat 27的代码复现这个bug的时候死活不能复现。
通过代码对比就能发现问题所在,compat26中rv的oncreateviewholder代码
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
接下来是compat 27代码中的oncreateviewholder
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
try {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
if (holder.itemView.getParent() != null) {
throw new IllegalStateException("ViewHolder views must not be attached when"
+ " created. Ensure that you are not passing 'true' to the attachToRoot"
+ " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
}
holder.mItemViewType = viewType;
return holder;
} finally {
TraceCompat.endSection();
}
}
这么一对比是不是就发现问题所在了,27版本中直接将这个问题在createViewHolder的时候就暴露出来了,也就是这个原因导致我在27版本的代码中脑袋想冒烟也没复现出那个bug,所以在调试一些bug的时候尽量让demo的sdk版本和线上sdk版本一致!!这是一个血一样的教训。
到此自己解决的两个bug问题就已经全部阐述完毕了,第一个bug比较好解决因为复现的条件比较简单,只要bug能复现剩下的一切都好商量。第二个bug就有点恶心了,各种无法复现,最后还是通过源码去定位问题,饶了一大圈首先发现是sdk版本问题,然后在线上相同的sdk版本上继续深究问题原因,也正是在解决这两个bug的过程,结合网上一些rv优化技术,让自己对之前封装的adapter又进行了一次重构,可以说在理解rv源码基础上封装起adapter才能更有把握,否则出现一个问题就两眼一抹黑,扔在那边没人处理,在使用一些三方库的时候虽然很爽,但是很多时候还是自己亲手做些东西才能更有收获。
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
由于fast-stemmer的问题,我很难安装我想要的任何rubygem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=
当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。
首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有, 也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加