Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Case Analysis of crash Positioning process

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

Most people do not understand the knowledge points of this "crash positioning process case analysis" article, so the editor summarizes the following content, detailed content, clear steps, and has a certain reference value. I hope you can get something after reading this article. Let's take a look at this "crash positioning process case analysis" article.

First, problems

As you can see from the stack below, RecyclerView is executing the layout at this time, and crash occurs when trying to get the ViewHolder cache. So before we analyze this problem, let's take a brief look at the layout process and caching strategy of RecyclerView.

2. Preparation

1. Layout process

Through the dispatchLayout method of RecyclerView, you can know that the layout process is roughly divided into three steps:

DispatchLayoutStep1: preLayout pre-layout phase, which mainly deals with updating the Adapter, deciding what animation to use and saving the boundaries of the current sub-View. Here, the result of the layout is the state before the data change.

DispatchLayoutStep2: change the mInPreLayout status to false, and then hand it over to LayoutManager's onLayoutChildren method. It will collect the current sub-View into various cache queues according to its ViewHolder status, then find anchor points and fill them up and down. When a sub-View is needed, request RecyclerView to provide it. The layout result is the state of the changed data. And the above-mentioned crash happens at this stage! The code is as follows:

Private void dispatchLayoutStep2 () {/ / some code here / / Step2: Run layout mState.mInPreLayout = false; mLayout.onLayoutChildren (mRecycler, mState); / / some code here}

DispatchLayoutStep3: postLayout, save the information of the current child View and combine the results of the prelayout phase, trigger the execution of the animation, and finally clean up some states.

2. Caching strategy

There are several types of caches in RecyclerView:

The ViewHolder cache in which mAttachedScrap is not separated from RecyclerView is used for temporary storage during layout. It can be simply understood as content that is being displayed on the current screen and the data has not changed, and can be reused directly. The detachViewForParent method of ChildHelper will be executed before it is added. The parent object of View will be set to null, but it will not remove; from RecyclerView. In addition, the mScrapContainer object will be set to make ViewHolder.isScrap true.

MChangedScrap is also not separated from RecyclerView, but the data has changed for use in the preLayout phase before the execution of the animation. Also execute detachViewForParent and set mScrapContainer

MCachedViews will be added here first when itemView slides off the screen and is remove from RecyclerView. The maximum capacity is 2 by default.

The caching logic defined by mVewCacheExtension business is not implemented by singing.

The last level of RecycledViewPool cache needs to be remove from RecyclerView before adding. For different viewType, 5 ViewHolder are cached by default, and data need to be rebound when reused.

Except for the need to perform animation and give priority to getting the ViewHolder from the mChangedScrap cache in the preLayout phase, the other cases are reused in the order of mAttachedScrap > mCachedViews > mViewCachedExtension > RecycledViewPool. If it is not available, call the onCreateViewHolder method of Adapter to create it.

III. Analysis

With the above understanding of the basics of RecyclerView, let's take a look at where crash occurs:

ViewHolder tryGetViewHolderForPositionByDeadline (int position, boolean dryRun, long deadlineNs) {/ / some code here... / / get the ViewHolder cache holder = getScrapOrHiddenOrCachedHolderForPosition (position, dryRun); if (holder! = null) {/ / a pair of ViewHolder for verification, but did not pass if (! validateViewHolderForOffsetPosition (holder)) {if (! dryRun) {/ / ready to add to RecyledViewPool holder.addFlags (ViewHolder.FLAG_INVALID) / / isScrap description is the if (holder.isScrap ()) {/ / crash obtained from mAttachedScrap. RemoveDetachedView (holder.itemView, false); holder.unScrap ();} else if (holder.wasReturnedFromScrap ()) {holder.clearReturnedFromScrapFlag () } recycleViewHolderInternal (holder);} holder = null;} else {fromScrapOrHiddenOrCache = true;}} / / some code here...

Logically, we can judge that holder is obtained in the getScrapOrHiddenOrCachedHolderForPosition method, and its internal implementation is to find and return the ItemView in mAttachedScrap, mCachedViews and ChildHelper that are not separated from RecyclerView because of animation. (ChildHelper mainly takes over the processing of sub-View by RecyclerView, and solves the problem that the data of sub-View and Adapter are not synchronized in the process of animation. You can understand it yourself if you are interested, but we will not expand it here.) it is worth noting that the cache lookup here is indexed by position. RecycledViewPool is found through viewType, which is critical.

Holder.isScrap 's judgment shows that this is the cache in mAttachedScrap, and the reason why it came to the removeDetachedView that triggered crash is that the verification of holder did not pass, which is no longer in line with the characteristics of direct reuse, so it is prepared to remove it from RecyclerView and change it to RecycledViewPool, and then crash.

But why did the check fail? Let's take a look at the source code of the check:

Boolean validateViewHolderForOffsetPosition (ViewHolder holder) {/ / if it is a removed holder, nothing to verify since we cannot ask adapter anymore / / if it is not removed, verify the type and id. If (holder.isRemoved ()) {if (DEBUG & &! mState.isPreLayout ()) {throw new IllegalStateException ("should not receive a removed view unless it" + "is pre layout" + exceptionLabel ());} return mState.isPreLayout ();} if (holder.mPosition)

< 0 || holder.mPosition >

= mAdapter.getItemCount () {throw new IndexOutOfBoundsException ("Inconsistency detected Invalid view holder" + "adapter position" + holder + exceptionLabel ());} if (! mState.isPreLayout ()) {/ / don't check type if it is pre-layout. Final int type = mAdapter.getItemViewType (holder.mPosition); if (type! = holder.getItemViewType ()) {return false;}} if (mAdapter.hasStableIds ()) {return holder.getItemId () = = mAdapter.getItemId (holder.mPosition);} return true;}

If stableId,mAdapter.hasStableIds () is not set in the singing business, it must be false;. In addition, our crash occurs in the step of dispatchLayoutStep2. MState.mInPreLayout will be set to false before calling onLayoutChildren. There are only two possibilities: either holder is in the state of FLAG_REMOVED, or the type fetched by holder and Adapter is not the same. Here as a clue first, we need to use it later.

Go back to the crash stack and see if there is any other useful information. Finally, two details of ViewHolder and FeedListView are found.

ViewHolder {394df98d position=2 id=-1, oldPos=-1, pLpos:-1}

/ / here is the summary of the ViewHolder.toString method / / some code here...if (isScrap ()) {sb.append ("scrap") .append (mInChangeScrap? "[changeScrap]": "[attachedScrap]");} / / some code here...return sb.toString ()

The ViewHolder that caused crash is in the third place in the list and does not have the word scrap, that is, isScrap is false, which is not right. It is wrong to judge that isScrap is true before calling removeDetachedView. Why does it become false when you enter the method? The original parameter is passed to itemView, and the ViewHolder is obtained through the LayoutParam of itemView in the method. Normally, there is a bidirectional reference and one-to-one correspondence between View and ViewHolder. Here, there must be a situation where ViewHolder1 points to View,View and points to another ViewHolder2, indicating that our View is shared by multiple ViewHolder.

To explain this, take a look at the code that Adapter created the ViewHolder:

Public RecyclerView.ViewHolder onCreateViewHolder (ViewGroup parent, int viewType) {if (viewType = = REFRESH_HEADER) {/ / drop-down refresh return new RefreshHeaderContainerViewHolder (mRefreshHeaderContainer);} else if (viewType = = HEADER) {/ / Header container return new HeaderContainerViewHolder (mHeaderContainer);} else if (viewType = = FOOTER) {/ / Footer container return new FooterContainerViewHolder (mFooterContainer) } else if (viewType = = FOOTER_EMPTY) {/ / the list content is small, and you want to fill the list with blank return newFooterEmptyViewHolder (mFooterEmpty);} else if (viewType = = LOAD_MORE_FOOTER) {/ / load return new LoadMoreFooterContainerViewHolder (mLoadMoreFooterContainer);} else {/ / specific business modules create return mAdapter.onCreateViewHolder (parent, viewType);}}

The RecyclerView used by the business is encapsulated with support for refresh, Header, Footer, whitespace, and loading. Among them, mAdapter.onCreateViewHolder is created in the form of new ViewHolder (new View ()), and there is no possibility of View sharing; while others, it is possible to create multiple ViewHolder for the same type of ViewHolder, but this is not normal logic, because there is one and only one of these types in the list, and you only need to create it once. If you look at the position=2 in the stack, you can lock the exception that is Footer, because except when the list is empty, the position of Footer is 2, and the other types are not 2. Checked the code related to Footer in business logic and compared it with Header. No reasonable explanation was found. Let's put it down and mark it as clue 2: RecyclerView created two ViewHolder and pointed to the same Footer.

Move on to another detail mentioned above.

FeedListView {27f84f4a IFE … .. …… ID 0231-1080th 1767 # 7f0d0416 app:id/se}

View.toString Summary:

Public String toString () {StringBuilder out = new StringBuilder; out.append (getClass (). GetName ()); out.append ('{'); out.append (Integer.toHexString (System.identityHashCode (this); out.append (''); switch (mViewFlags&VISIBILITY_MASK) {case VISIBLE: out.append ('V'); break; case INVISIBLE: out.append ('I'); break Case GONE: out.append ('G'); break; default: out.append ('.'); break;}}

Although it is called FeedListView, it actually inherits from RecyclerView. As you can see from the toString method, RecyclerView is in the state of INVISIBLE. The singing dynamic will be the status of INVISIBLE only before the data is requested to the background. As long as the data is obtained or the protocol fails, it will be changed to the status of VISIBLE.

This is a very strange phenomenon, because from the log point of view, the data is loaded successfully, and users also slide, give gifts, listen to and other interactive operations in the list, so our list must be visible. In view of the fact that there can be nothing wrong with the Crash stack, in order to explain this phenomenon, it is bold to speculate that there are two FeedListView on the user's phone, one displaying normally and the other invisible.

Compared to the above analysis, verification appears much simpler, we through the user startup, Fragment.OnCreate-related log to confirm that clue 3 is correct, and there are not only two lists, but also two FeedSubFragment, but only one FeedFragment, get clue 3: dynamic page appears two FeedSubFragment and FeedListView, a normal display, one is not visible.

OnCreate:com.tencent.karaoke.module.feed.ui.FeedFragment onCreate:com.tencent.karaoke.module.feed.ui.FeedSubFragment

OnCreate:com.tencent.karaoke.module.feed.ui.FeedSubFragment

FeedSubFragment is created in the init method of FeedFragment, and init is called in onCreateView and is executed only once:

If you rule out the possibility that the business logic creates two Fragment, it can only be created by the system. It is easy to think of the situation in which the application is killed and rebuilt by the system. Both FeedFragment and FeedSubFragment will be restored by the system, and the recovery process of FeedFragment will go to the life cycle of onCreateView, so another FeedSubFragment will be created.

The scenario is reproduced by opening "do not keep activity" in the developer option, and two FeedSubFragment are generated after the recovery, one is displayed normally, and the other does not initiate a request for data after loading the layout from xml, so the page has always been the default state of loading, while FeedListView is INVISIBLE.

As for the reason, you can first take a look at the structure of our page:

FeedFragment consists of two parts, one is Titlebar, including followers, friends, hot, nearby four Tab options, and the other is that FeedSubFragment is used to host the content of each Tab, and update the data display with Tab switching. When a user clicks on to sing, the default is to locate the friend page, but if you find that the user was not a friend when he left last time, the opening should be automatically switched to the page when the user left. This is triggered by the performClick of View in TitleBar. When FeedFragment hears the click, it informs FeedSubFragment to initiate a network request.

Because FeedFragment will have only one reference to FeedSubFragment, one will display normally and the other will always be the state of loadind, which is consistent with the state of the previous user crash. For users, this is imperceptible, because the Fragment that is normally displayed is not transparent and is covered on top of the other.

IV. Relevance

Sort out the clues we already have:

Causes the holder of crash to be in the state of FLAG_REMOVED or inconsistent with the type fetched by Adapter

RecyclerView creates two ViewHolder and points to the same Footer

Two FeedSubFragment and FeedListView appear on the dynamic page, one is displayed normally, the other is invisible

For clue 1, let's assume that in the first case, by tracing the path set by FLAG_REMOVED, we find that the FLAG_REMOVED tag is added to the ViewHolder only when the business calls the notifyXXXRemoved method of Adapter. The Footer in clue 2 is actually a container, and the layout added by the business call addFooterView will be filled in the container. No matter what users do, there is always one and only one Footer for RecyclerView, and there is no case of deleting Footer. So the first clue is corrected as follows: the type of ViewHolder taken from mAttachedScrap is not consistent with that obtained by Adapter.

The ViewHolder in mAttachedScrap is found by comparing LayoutPosition, while the result of Adapter.getItemType is to analyze the dataset. The inconsistency between the two indicates that the state of RecyclerView is out of sync with the dataset, often in the case that the list data in Adapter has changed without calling the notityXXX method to notify RecyclerView.

Crash's list does not request background data but produces changes in data, which can only be caused by false data constructed by the client itself after the user publishes the work.

As the release of the work is closely related to the business logic of singing, and it is of little reference significance, here is only a brief written description:

After the user publishes the work, a release data will be generated and displayed dynamically. This data exists in the singleton, and both FeedSubFragment can be fetched. After the publication is completed and the list is refreshed, it will be removed from the singleton. In addition, some interactive actions within the song will trigger the broadcast, such as commenting on the work on the work details page, and the feed comment count of the work will be updated in real time. There is no need to wait for the refresh operation of the list, and the broadcast is also registered.

When the work is published, the invisible page is not aware of it, and the RecyclerView is Refresh, Header, Footer, Empty, and Load, while the data of Adapter is concentrated on a fake feed between Header and Footer. Although notifyXXX is not called, when layout is triggered for other reasons, such as interactive operations or jumping other Activity returns, crash will not be caused, as follows:

Through position, ①② can correctly obtain the original ViewHolder from mAttachedScrap and reuse it directly.

③ fetches the ViewHolder of Footer through position, finds that the type is different, remove it from the layout and adds it to the cache pool RecycledViewPool, and finally creates a new ViewHolder of fake Feed

④ fetches the ViewHolder of Empty and also recycles it to RecycledViewPool, but since the ViewHolder of Footer was added to RecycledViewPool in the previous step, after processing the Empty, it will try to find it from RecycledViewPool, and here it is found through viewType, so you can find the ViewHolder added in the previous step to reuse.

⑤⑥ is the same as ④

What happens to layout when the fake feed has been layout and the data is deleted without notify?

①② can be directly reused

③ fetches the ViewHolder of the fake feed, recycles it to RecycledViewPool, and recreates a ViewHolder of Footer, which leads to the appearance of two ViewHolder pointing to the same View, one newly created to be displayed in the RecyclerView, and clears the FLAG_TMP_DETACHED tag, and the other still exists in the Scrap cache and is not used.

④ fetches the ViewHolder of Footer in the Scrap cache and tries to recycle it to RecycledViewPool, only to find that Footer is no longer in the state of FLAG_TMP_DETACHED, because it has been added to RecyclerView in the previous step, cleared this flag, and threw the IllegalArgumentException exception at the beginning of the article.

Some people may be interested in adding or deleting data and calling notifyXXXRemoved. Under normal circumstances, how can RecyclerView obtain the correct ViewHolder through position in both preLayout and postLayout phases? you can learn about the role of ViewHolder's mPreLayoutPosition and mPosition. I won't go into details here.

The above is about the content of this article "case Analysis of crash Positioning process". I believe we all have some understanding. I hope the content shared by the editor will be helpful to you. If you want to know more related knowledge, please pay attention to the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report