天道酬勤,学无止境

RecyclerView 回收机制解析

概述

近期接触到RecyclerView回收机制相关的内容,于是作此文记录下相关探索。
如记录的有问题欢迎评论探讨。

定位回收机制源码

笔者使用debug的方式,在adapter的onCreateViewHolder中打断点,于是得到该任务栈。

思路:onCreateViewHolder是创建viewHolder的方法,那么在调用这个方法之前一定有是否复用的判断,因此断点在这个方法可以找到相关方法。

然后我们就可以发现是RecyclerView#Recycler的tryGetViewHolderForPositionByDeadline()方法中处理的相关的回收逻辑,源码如下:

        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            if (position < 0 || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position
                        + "(" + position + "). Item count:" + mState.getItemCount()
                        + exceptionLabel());
            }
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount() + exceptionLabel());
                }

                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap/cache via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrapOrHiddenOrCache = true;
                    }
                }
                if (holder == null && mViewCacheExtension != null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        if (holder == null) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder"
                                    + exceptionLabel());
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view." + exceptionLabel());
                        }
                    }
                }
                if (holder == null) { // fallback to pool
                    if (DEBUG) {
                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                                + position + ") fetching from shared pool");
                    }
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    long start = getNanoTime();
                    if (deadlineNs != FOREVER_NS
                            && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                        // abort - we have a deadline we can't meet
                        return null;
                    }
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (ALLOW_THREAD_GAP_WORK) {
                        // only bother finding nested RV if prefetching
                        RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                        if (innerView != null) {
                            holder.mNestedRecyclerView = new WeakReference<>(innerView);
                        }
                    }

                    long end = getNanoTime();
                    mRecyclerPool.factorInCreateTime(type, end - start);
                    if (DEBUG) {
                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
                    }
                }
            }

            // This is very ugly but the only place we can grab this information
            // before the View is rebound and returned to the LayoutManager for post layout ops.
            // We don't need this in pre-layout since the VH is not updated by the LM.
            if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
                    .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (mState.mRunSimpleAnimations) {
                    int changeFlags = ItemAnimator
                            .buildAdapterChangeFlagsForAnimations(holder);
                    changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                            holder, changeFlags, holder.getUnmodifiedPayloads());
                    recordAnimationInfoIfBouncedHiddenView(holder, info);
                }
            }

            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder
                            + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder;
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }

回收机制

tryGetViewHolderForPositionByDeadline()源码中有几个获得ViewHodler的方式:

  1. holder = getChangedScrapViewForPosition(position);
  2. holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
  3. final View view = mViewCacheExtension
    .getViewForPositionAndType(this, position, type);
  4. holder = getRecycledViewPool().getRecycledView(type);
  5. holder = mAdapter.createViewHolder(RecyclerView.this, type);

笔者整理后,根据RecyclerView#Recycler的几个参数,按照代码顺序是如下几个回收模块:

  1. mAttachedScrap
  2. mChangedScrap
  3. mCachedViews
  4. mViewCacheExtension
  5. mRecyclerPool

mAttachedScrap与mChangedScrap

  • mAttachedScrap
    用于存储ViewHolder已经从RecyclerView上移除,但是仍有可能被复用的View。
  • mChangedScrap
    用于存储ViewHolder仍在RecyclerView上,但是数据已经过时,需要被更新的View。

针对这两种场景,笔者特别做了debug实验:

  • mChangedScrap:
    场景一:通过adapter设置notifyItemChanged(index),如果当前index显示在屏幕中,这个index的ViewHodler会被存储到mChangedScrap中。
  • mAttachedScrap:
    场景一:让一个RecyclerView 隐藏后再显示,当前页面的中的item都会存储到mAttachedScrap中。
    场景二:通过adapter设置notifyItemChanged(index),当前屏幕中index以外的其他item都会被存储到mAttachedScrap中。

源码解析

在源码中查找mAttachedScrap与mChangedScrap填充内容的地方,可以找到是在同一处。

        /**
         * Mark an attached view as scrap.
         *
         * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
         * for rebinding and reuse. Requests for a view for a given position may return a
         * reused or rebound scrap view instance.</p>
         *
         * @param view View to scrap
         */
        void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                    throw new IllegalArgumentException("Called scrap view with an invalid view."
                            + " Invalid views cannot be reused from scrap, they should rebound from"
                            + " recycler pool." + exceptionLabel());
                }
                holder.setScrapContainer(this, false);
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }
        

通过上述的注释可知,这两个list都是用来存储不需要的view,然后后面准备复用的。
从源码中可以看到,需要ViewHolder本身没有失效(postsition,id,viewType都有效),否则会报异常。
另外的,两个list分别会存储ViewHolder的场景如下。

mAttachedScrap

由源码if-else中可知,mAttachedScrap有三种场景会进入:

  1. ViewHolder有ViewHolder.FLAG_REMOVED
  2. ViewHolder中没有ViewHolder.FLAG_REMOVED和ViewHolder.FLAG_UPDATE
  3. ViewHolder中没有ViewHolder.FLAG_REMOVED,但是有ViewHolder.FLAG_UPDATE,并且canReuseUpdatedViewHolder(holder)返回true

mChangedScrap

其他的场景都会存储到mChangedScrap中,需要同时满足这几个条件:

  1. ViewHolder中没有ViewHolder.FLAG_REMOVED
  2. ViewHolder有ViewHolder.FLAG_UPDATE
  3. canReuseUpdatedViewHolder(holder)返回false

ViewHolder的几个Flag

根据上面的面源码,根据if-else里的判断条件,可以看到两个的区别首先在三个flag上,FLAG_REMOVED,FLAG_INVALID,FLAG_UPDATE。
这三个flag的源码和注释如下:

        /**
         * The data this ViewHolder's view reflects is stale and needs to be rebound
         * by the adapter. mPosition and mItemId are consistent.
         */
        static final int FLAG_UPDATE = 1 << 1;
        
                /**
         * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
         * are not to be trusted and may no longer match the item view type.
         * This ViewHolder must be fully rebound to different data.
         */
        static final int FLAG_INVALID = 1 << 2;

        /**
         * This ViewHolder points at data that represents an item previously removed from the
         * data set. Its view may still be used for things like outgoing animations.
         */
        static final int FLAG_REMOVED = 1 << 3;

翻译后,三个flag的描述大概如下:

  • FLAG_UPDATE
    这个ViewHolder需要通过adapter来bind内容。这个ViewHolder的postion和id是统一的,有效的。
  • FLAG_REMOVED
    ViewHolder已经过时了,但是这个view仍可能被复用,比如用在跳出的动画。
  • FLAG_INVALID
    这个ViewHolder中的数据已经无效了。也无法通过postion或者id来识别这个ViewHolder(position和id无效了),也不会与这个item的viewType匹配。
    这个ViewHolder会完全与其他的数据绑定。

canReuseUpdatedViewHolder

    boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
        return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
                viewHolder.getUnmodifiedPayloads());
    }
    
    
/**
         * When an item is changed, ItemAnimator can decide whether it wants to re-use
         * the same ViewHolder for animations or RecyclerView should create a copy of the
         * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
         * <p>
         * Note that this method will only be called if the {@link ViewHolder} still has the same
         * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
         * both {@link ViewHolder}s in the
         * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
         *
         * @param viewHolder The ViewHolder which represents the changed item's old content.
         * @param payloads A non-null list of merged payloads that were sent with change
         *                 notifications. Can be empty if the adapter is invalidated via
         *                 {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of
         *                 payloads will be passed into
         *                 {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)}
         *                 method <b>if</b> this method returns <code>true</code>.
         *
         * @return True if RecyclerView should just rebind to the same ViewHolder or false if
         *         RecyclerView should create a new ViewHolder and pass this ViewHolder to the
         *         ItemAnimator to animate. Default implementation calls
         *         {@link #canReuseUpdatedViewHolder(ViewHolder)}.
         *
         * @see #canReuseUpdatedViewHolder(ViewHolder)
         */
        public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
                @NonNull List<Object> payloads) {
            return canReuseUpdatedViewHolder(viewHolder);
        }

根据源码可知,这个方法的作用如下:

  1. 首先判断item有无动画,如果没有动画直接返回true。
  2. 判断item的动画能否复用ViewHolder来播放动画,还是需要新建一个ViewHolder。

mCachedViews

用于缓存滑出屏幕的ViewHodler。
缓存的个数,由RecyclerView#Recycler#mViewCacheMax决定,可以通过RecyclerView#setItemViewCacheSize来设置。

场景
加入此时屏幕中有10个Item,当用户上滑一个,内容下移一个时,原先第一个的Item中的ViewHodler会被加入到mCachedViews中。

源码解析

首先直接找到在mCachedViews中缓存的位置。

       /**
         * internal implementation checks if view is scrapped or attached and throws an exception
         * if so.
         * Public version un-scraps before calling recycle.
         */
        void recycleViewHolderInternal(ViewHolder holder) {
            if (holder.isScrap() || holder.itemView.getParent() != null) {
                throw new IllegalArgumentException(
                        "Scrapped or attached views may not be recycled. isScrap:"
                                + holder.isScrap() + " isAttached:"
                                + (holder.itemView.getParent() != null) + exceptionLabel());
            }

            if (holder.isTmpDetached()) {
                throw new IllegalArgumentException("Tmp detached view should be removed "
                        + "from RecyclerView before it can be recycled: " + holder
                        + exceptionLabel());
            }

            if (holder.shouldIgnore()) {
                throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
                        + " should first call stopIgnoringView(view) before calling recycle."
                        + exceptionLabel());
            }
            //noinspection unchecked
            final boolean transientStatePreventsRecycling = holder
                    .doesTransientStatePreventRecycling();
            final boolean forceRecycle = mAdapter != null
                    && transientStatePreventsRecycling
                    && mAdapter.onFailedToRecycleView(holder);
            boolean cached = false;
            boolean recycled = false;
            if (DEBUG && mCachedViews.contains(holder)) {
                throw new IllegalArgumentException("cached view received recycle internal? "
                        + holder + exceptionLabel());
            }
            if (forceRecycle || holder.isRecyclable()) {
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }

                    int targetCacheIndex = cachedViewSize;
                    if (ALLOW_THREAD_GAP_WORK
                            && cachedViewSize > 0
                            && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                        // when adding the view, skip past most recently prefetched views
                        int cacheIndex = cachedViewSize - 1;
                        while (cacheIndex >= 0) {
                            int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                            if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                                break;
                            }
                            cacheIndex--;
                        }
                        targetCacheIndex = cacheIndex + 1;
                    }
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
                // NOTE: A view can fail to be recycled when it is scrolled off while an animation
                // runs. In this case, the item is eventually recycled by
                // ItemAnimatorRestoreListener#onAnimationFinished.

                // TODO: consider cancelling an animation when an item is removed scrollBy,
                // to return it to the pool faster
                if (DEBUG) {
                    Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                            + "re-visit here. We are still removing it from animation lists"
                            + exceptionLabel());
                }
            }
            // even if the holder is not removed, we still call this method so that it is removed
            // from view holder lists.
            mViewInfoStore.removeViewHolder(holder);
            if (!cached && !recycled && transientStatePreventsRecycling) {
                holder.mOwnerRecyclerView = null;
            }
        }
        void recycleCachedViewAt(int cachedViewIndex) {
            if (DEBUG) {
                Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
            }
            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
            if (DEBUG) {
                Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
            }
            addViewHolderToRecycledViewPool(viewHolder, true);
            mCachedViews.remove(cachedViewIndex);
        }

此方法逻辑如下:

  1. 首先判断ViewHolder是否可以缓存
  2. 如果mCachedViews的size已经大于等于mViewCacheMax,那么会将超index=0这个位置的ViewHolder放入到回收池中。
  3. 此时mCachedViews的size不会大于mViewCacheMax,将ViewHodler添加到mCachedViews中

mViewCacheExtension

直接看源码。

    /**
     * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
     * be controlled by the developer.
     * <p>
     * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
     * first level cache to find a matching View. If it cannot find a suitable View, Recycler will
     * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
     * {@link RecycledViewPool}.
     * <p>
     * Note that, Recycler never sends Views to this method to be cached. It is developers
     * responsibility to decide whether they want to keep their Views in this custom cache or let
     * the default recycling policy handle it.
     */
    public abstract static class ViewCacheExtension {

        /**
         * Returns a View that can be binded to the given Adapter position.
         * <p>
         * This method should <b>not</b> create a new View. Instead, it is expected to return
         * an already created View that can be re-used for the given type and position.
         * If the View is marked as ignored, it should first call
         * {@link LayoutManager#stopIgnoringView(View)} before returning the View.
         * <p>
         * RecyclerView will re-bind the returned View to the position if necessary.
         *
         * @param recycler The Recycler that can be used to bind the View
         * @param position The adapter position
         * @param type     The type of the View, defined by adapter
         * @return A View that is bound to the given position or NULL if there is no View to re-use
         * @see LayoutManager#ignoreView(View)
         */
        @Nullable
        public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                int type);
    }

ViewCacheExtension是提供给开发者自定义View缓存的一个帮助类。
如果使用ViewCacheExtension来自定义缓存,其内容与RecyclerView#Recycler是无关的,需要开发者自己定义View的缓存与回收逻辑。

RecycledViewPool

RecycledViewPool能够共享多个RecyclerView之间的View。(同一个Adapter)
默认情况下每个RecyclerView会自己创建一个RecycledViewPool,开发者如果需要多个RecycledView复用一个Pool可以通过RecycledView#setRecycledViewPool()来设置。

场景:当用户上下滑动页面触发回收ViewHolder逻辑,并且mCachedViews中已经存放满了的时候,会放到mRecyclerPool中.

存储结构

        private static final int DEFAULT_MAX_SCRAP = 5;
        static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            int mMaxScrap = DEFAULT_MAX_SCRAP;
            long mCreateRunningAverageNs = 0;
            long mBindRunningAverageNs = 0;
        }
        SparseArray<ScrapData> mScrap = new SparseArray<>();  
        
  • ViewHolder存储与RecycledViewPool的ScapData数据结构的ArrayList中。
  • ScrapData则存储在RecycledViewPool中的SparseArray中。
  • ScrapData中的ArrayList默认的存储上限是5.

获取ViewHolder

        /**
         * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
         * present.
         *
         * @param viewType ViewHolder type.
         * @return ViewHolder of the specified type acquired from the pool, or {@code null} if none
         * are present.
         */
        @Nullable
        public ViewHolder getRecycledView(int viewType) {
            final ScrapData scrapData = mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
                final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
                return scrapHeap.remove(scrapHeap.size() - 1);
            }
            return null;
        }
  1. 根据viewType去SparseArray中获取到ScrapData。
  2. 从ScrapData的ArrayList中返回一个ViewHodler出去,并且从该mScrapHeap中移除。

回收ViewHolder

        /**
         * Add a scrap ViewHolder to the pool.
         * <p>
         * If the pool is already full for that ViewHolder's type, it will be immediately discarded.
         *
         * @param scrap ViewHolder to be added to the pool.
         */
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }
        void resetInternal() {
            mFlags = 0;
            mPosition = NO_POSITION;
            mOldPosition = NO_POSITION;
            mItemId = NO_ID;
            mPreLayoutPosition = NO_POSITION;
            mIsRecyclableCount = 0;
            mShadowedHolder = null;
            mShadowingHolder = null;
            clearPayload();
            mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
            mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
            clearNestedRecyclerViewIfNotNested(this);
        }
  1. 先获取到ViewHolder的viewType,然后根据viewType从SparseArray中获取到ScrapData
  2. 如果ScrapData中的ArrayList已经到达上限,那么久直接返回,不回收。
  3. 如果ScrapData中的ArrayList已经包含了该ViewHolder,那么直接报错。
  4. 在ViewHolder放入到回收池之前清除数据。
  5. 将该ViewHolder放入到ScrapData的ArrayList中。

总结

RecyclerView的回收机制中主要是以下几个模块:

模块描述场景
mAttachedScrap用于存储ViewHolder已经从RecyclerView上移除,但是仍有可能被复用的View。通过adapter设置notifyItemChanged(index),当前屏幕中index以外的其他item都会被存储到mAttachedScrap中。
mChangedScrap用于存储ViewHolder仍在RecyclerView上,但是数据已经过时,需要被更新的View。通过adapter设置notifyItemChanged(index),如果当前index显示在屏幕中,这个index的ViewHodler会被存储到mChangedScrap中。
mCachedViews用于缓存滑出屏幕的ViewHodler。当mCachedViews满了之后,会把最老的ViewHolder的放入到RecyclerViewPool。加入此时屏幕中有10个Item,当用户上滑一个,内容下移一个时,原先第一个的Item中的ViewHodler会被加入到mCachedViews中。
mViewCacheExtensionViewCacheExtension是提供给开发者自定义View缓存的一个帮助类。如果使用ViewCacheExtension来自定义缓存,需要开发者自己定义View的缓存与回收逻辑。不常用,开发者可自由定义。
mRecyclerPoolRecycledViewPool能够共享多个RecyclerView之间的View。(同一个Adapter)默认情况下每个RecyclerView会自己创建一个RecycledViewPool,开发者如果需要多个RecycledView复用一个Pool可以通过RecycledView#setRecycledViewPool()来设置。屏幕上下滑动缓存ViewHolder,且mCachedViews中放满的情况下会放到RecycledViewPool中

注:

  • 与当前的RecyclerView一一对应:
    mAttachedScrap、mChangedScrap、mCachedViews
  • 可能多个RecyclerView共享:
    mViewCacheExtension、mRecyclerPool

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • RecyclerView源码解析
    基础 RecyclerView相对于以前的ListView来说,更加灵活。其所拆分出来的各个类的分工更加明确,很好地体现了我们经常所说的职责单一原则。我们这里先对其中使用到的类进行一下讲解 LayoutManager:RecyclerView的布局管理者,主要负责对于RecyclerView子View的测量和布局工作。RecyclerView.Recycler:缓存的核心类。RecyclerView强大的缓存能力都是基于这个类来实现的。是缓存的核心工具类。Adapter:Adapter的基类。负责将ViewHolder中的数据和RecyclerView中的控件进行绑定处理。ViewHolder:视图和元数据类。它持有了要显示的数据信息,包括位置、View、ViewType等。 源码 无论是View还是ViewGroup的子类,都是通过 onMeasure() 来实现测量工作的,那么我们对于RecyclerView的源码解析就把onMeasure当作我们的切入点 自身测量 //RecyclerView.java protected void onMeasure(int widthSpec, int heightSpec) { //dispatchLayoutStep1,dispatchLayoutStep2,dispatchLayoutStep3肯定会执行
  • Android中高级面试题汇总(2021年)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LxOXzNuG-1616402294221)(https://i.loli.net/2020/12/28/hWJbL4ptZUG7anF.png)] 基础篇 Java基础 静态内部类和非静态内部类有什么区别谈谈你对java多态的理解+java方法的多态性理解java中接口和继承的区别线程池的好处+线程池的优点及其原理+线程池的优点 (重点)为什么不推荐通过Executors直接创建线程池不怕难之BlockingQueue及其实现深入理解ReentrantLock与ConditionJava多线程:线程间通信之LockSynchronized 关键字原理ReentrantLock原理HashMap中的Hash冲突解决和扩容机制 Java并发 Java虚拟机 JVM常见面试题 Java虚拟机(JVM)面试题(2020最新版)JVM 面试题汇总JVM方法区存储内容 是否会动态扩展 是否会出现内存溢出 出现的原因有哪些。如何解决同时存在的对象创建和对象回收问题?JVM中最大堆大小有没有限制?Java运行时数据区域,导致内存溢出的原因。java中一个对象从创建到销毁的过程和 JVM类加载过程 https://blog.csdn.net/m0_37914467/article/details/106441824
  • 差点跳起来!借助阿里技术博主分享的Android面试笔记,我刚拿到了字节跳动的28K的offer
    前言 事情是这样的,今年年初,在某个阿里技术大博主那里拿到一份Android面试宝典,然后就一直躺在盘里吃灰,直到6月份的时候,有了“金九银十”要跳槽的计划和打算,就想着要刷刷面试题,所以就把这套“积灰”的面试宝典拿出看了看,这一看就看了一个多月才算是完整的吃透。 7月上旬就准备开始面试,前后一共三面,原本以为没啥太大希望,等到月底29号收到了offer,通知8月3号到公司报到,看到邮件那一刻差点跳起来了! 再说一下我个人情况吧,华中地区本科生,非985和211,毕业以后就在一家小公司工作,赚的不多但起码不是996,这一晃就差不多过去三年了,突然考虑自己的将来,想要改变现状了,所以决心狠心复习,准备跳槽,当然除了刷面试题以外,前期还狠狠地复习了一波。 本文内容包含了832页阿里技术博主的Android面试宝典内容,包括Android复习笔记(组件化、View、线程、Binder、Handler、Bitmap、RecyclerView、Webview、Android架构、NDK、jetpack等等)、一线互联网大厂面试题和我的字节跳动面试经历分享,但苦于篇幅有限,不能完全展现,需要完整pdf版,文章末尾会有领取链接; 832页Android面试宝典,啃了我足足一个月 Java基础(JVM、类、集合框架、单例、Synchronized、动态代理) 以JVM面试题为例: Java
  • 寒冬将过,金三银四你是否已准备好!这份Android中高级面试题你应该吃透(纯干货整理)
    关于自己 2020 年即将结束了,作为Android开发者的我已不知不觉在这个圈子里深耕了8年之久,期间完成过出色的项目、也进行过系统的进阶学习、当然,也免不了写出了成百上千的Bug。 这所发生的一切只不过是我职业生涯的一处缩影而已,从原来四处碰壁的Android小白到如今小有所成的架构师,全是靠着自己自律学习、不断提升自我的技术所带来的成果,当然,最大的功臣还是来源于全球最大的同性交友社区“Github”。我的GitHub。 关于这份面试题 身为程序员,自我提升有多重要?我相信大家应该都是心照不宣的, 由于技术的深度、广度以后是决定你在职业发展过程中所带给你的竞争力,而自我提升时拥有一份好的学习资料就显得尤为重要了,因为有太多人就是输在起跑线上的。 网络上,Android免费资料发送的铺天盖地。但是,知识的完整度、深度和系统的完整性在你心里真正能打几分,恐怕只有你自己才能够知道了。而这份小编通过这些年的经历耗时三个月整理出来的:《 Android 中高级面试题合集》一经发布,两个月内在 Github 走红,犹如一匹脱缰的黑马,有着 10.8K+Star 的红绸加持,迅速登顶了Android圈内热榜! 第一章Java部分: 你所知道的设计模式有哪些单例设计模式工厂设计模式建造者模式(Builder)适配器设计模式装饰模式(Decorator)观察者模式(Observer
  • 解析RecyclerView的缓存机制
    文章目录 复用:layoutChunk(recycler, state, layoutState, layoutChunkResult)1. getChangedScrapViewForPosition方法--->mChangedScrap2.getScrapOrHiddenOrCachedHolderForPosition--->mAttachedScrap、mCachedViews3. getScrapOrCachedViewForId--->mAttachedScrap、mCachedViews4.getViewForPositionAndType()--->mViewCacheExtension5.getRecycledViewPool()缓存池机制--->Pool6.mAdapter.createViewHolder最后,bindViewHolder 回收:recycleByLayoutState(recycler, layoutState) 在使用RecyclerView的时候,都遇到过滑动显示错乱,这时我们分析原因就得从RecyclerView的onTouchEvent方法的ACTION_MOVE事件开始入手。在MOVE事件内调用了scrollByInternal()方法,在scrollByInternal()方法内对x、y分情况调用了mLayout
  • 安卓串口开发!如何才能通过一线互联网公司面试?详细的Android学习指南
    前言 对于字节跳动的二面三面而言,Framework+MVP架构+HashMap原理+性能优化+Flutter+源码分析等问题都成高频问点!然而很多的朋友在面试时却答不上或者答不全!今天在这分享下这些问点的视频解析给大家,希望对有需要的朋友有所帮助! 80%的人答不出的字节跳动面试问题—Framework 视频内容概要: 1.framework层整体执行流程分析 2.XML文件加载源码分析 3.自定义VIEW源码分析 4.切入源码执行流程实现屏幕适配 Java基础知识点 Jvm相关 Java内存结构及分区Java对象的创建、存储及访问Java判断对象是否存活及垃圾回收算法(GC)Jvm中的常见的垃圾回收器Java类加载过程Java类加载器(双亲委派模型) 集合相关 ArrayList分析LinkedList分析HashMap分析HashTable分析LinkedHashMap分析HashSet分析LinkedHashSet分析ArrayMap、SparseMap、与HashMap的对比ConcurrentHashMap分析 并发相关 Java内存模型volatile原理Synchronized的原理AQS原理Condition原理ReentrantLock 原理公平锁与非公平锁ReentrantReadWriteLock原理 线程相关 线程和进程的区别线程的启动和终止线程间通信等待
  • 本来只想蹭一蹭,没想到还真让进去了!双非渣本小Android大厂面试历程
    前言 笔者2016年双非不知名小本科毕业,到2020年6月已经开发4年了,毕业后一直辗转于各种小公司做小开发。但是闲鱼也有翻身梦,一直想去大厂看看。就蹭蹭,不进去也行啊。于是计划2020年初金三银四的时候去碰碰运气,正巧碰上疫情,计划也就被搁置了。一来是怕死。出门都战战兢兢的,别说各个城市跑面试了。二是各种公司都在裁员,只有大厂还在招少部分人,大厂的门槛怕是都要被踩烂了。 疫情稳定下来后,便开始慢慢的为跳槽做准备了,到2020年金九银十的时候,先去面了些不大不小的公司,最后才选择了几家心仪的大厂投简历、面试的,春节节前成功入职了字节抖音,现在正式入职了,将自己的面试经验分享出来,希望可以给大家做个参考、对大家金三银四跳槽有所帮助。 简历 首先是简历,一般找一个模板,写清楚掌握的技能和自己的项目经历即可。 简历建议2页就行,太长太短都不好。 建议用掌握与熟练掌握取代了解与精通。 这里教给大家一个小套路,可以提高收到面试邀请的机会。那就是在你简历的左上角印上准备面试公司的Logo。面试官在一堆简历中突然翻到一张印着自己公司Logo的简历,不免会多看你的几眼。 关于算法 算法可以说是现在找工作必须的知识储备,具体得看公司的业务。以我的面试经验来看,总体来说问的不多,还有些公司基本不问算法。 但是如果去面试字节,网易,快手这种每轮必问算法的公司,因为算法题拿不到offer就很可惜了。
  • 双非渣本小Android四年磨一剑,秋招大厂(字节、腾讯、B站)面经分享
    前言 笔者2016年双非不知名小本科毕业,到今年6月已经开发4年啦,毕业后一直辗转于各种小公司做小开发。但是闲鱼也有翻身梦,一直想去大厂康康。就蹭蹭,不进去也行啊。于是计划几年年初金三银四的时候去碰碰运气,正巧碰上疫情,计划也就被搁置了。一来是怕死。出门都战战兢兢的,别说各个城市跑面试了。二是各种公司都在裁员,只有大厂还在招少部分人,大厂的门槛怕是都要被踩烂了。 疫情稳定下来后,便开始慢慢的为跳槽做准备了,到金九银十的时候,先去面了些不大不小的公司,最后才选择了几家心仪的大厂投简历、面试的,前段时间成功入职了腾讯,将自己的面试经验分享出来,希望可以给大家做个参考、有所帮助。 简历 首先是简历,一般找一个模板,写清楚掌握的技能和自己的项目经历即可。 简历建议2页就行,太长太短都不好。 建议用掌握与熟练掌握取代了解与精通。 这里教给大家一个小套路,可以提高收到面试邀请的机会。那就是在你简历的左上角印上准备面试公司的Logo。面试官在一堆简历中突然翻到一张印着自己公司Logo的简历,不免会多看你的几眼。 关于算法 算法可以说是现在找工作必须的知识储备,具体得看公司的业务。以我的面试经验来看,总体来说问的不多,还有些公司基本不问算法。 但是如果去面试字节,网易,快手这种每轮必问算法的公司,因为算法题拿不到offer就很可惜了。 算法题就好像高考语文的古诗词默写一样,分不多,但丢了就很可惜了
  • 安卓开发实例!全网最具深度的三次握手、四次挥手讲解,最强技术实现
    前言 笔者是某211非计算机相关专业2018届本科生,在校期间有半年多的互联网小公司实习经历。 毕业之后投递360 ,入职了360企业安全成为专门的前端开发工程师,有幸进入一个很赞的团队,遇到很棒的导师和leader ,成长杠杠的。 跳槽计划 工作一年多后,于今年七月有明确的跳槽计划,这个想法的导火线是4月份薪资不太理想。 此外,北京环境不太好,反正我是不太喜欢,待着也不舒服,当时就开始断断续续的刷技术题,各题型各难度都有认真琢磨。 后面我总结了一下,差不多刷了100多道,每个难度1/3 ,目前的水平是easy几乎bugfree , 差不多都OK ,但需要debug多次, hard多数有思路,但只能写伪代码。 Java基础知识点 Jvm相关 Java内存结构及分区Java对象的创建、存储及访问Java判断对象是否存活及垃圾回收算法(GC)Jvm中的常见的垃圾回收器Java类加载过程Java类加载器(双亲委派模型) 集合相关 ArrayList分析LinkedList分析HashMap分析HashTable分析LinkedHashMap分析HashSet分析LinkedHashSet分析ArrayMap、SparseMap、与HashMap的对比ConcurrentHashMap分析 并发相关
  • 精选Android中高级面试题:性能优化,JNI,设计模式
    性能优化 1、图片的三级缓存中,图片加载到内存中,如果内存快爆了,会发生什么?怎么处理? 参考回答:首先我们要清楚图片的三级缓存是如何的: 如果内存足够时不回收。内存不够时就回收软引用对象 2、内存中如果加载一张 500 * 500 的 png 高清图片。应该是占用多少的内存? 不考虑屏幕比的话:占用内存 = 500 * 500 * 4 = 1000000B ≈ 0.95MB 考虑屏幕比的的话:占用内存 = 宽度像素 x (inTargetDensity /inDensity) x 高度像素 x (inTargetDensity /inDensity)x 一个像素所占的内存字节大小 inDensity 表示目标图片的 dpi(放在哪个资源文件夹下),inTargetDensity 表示目标屏幕的 dpi 3、WebView 的性能优化? 参考回答:一个加载网页的过程中,native、网络、后端处理、CPU 都会参与,各自都有必要的工作和依赖关系;让他们相互并行处理而不是相互阻塞才可以让网页加载更快: WebView 初始化慢,可以在初始化同时先请求数据,让后端和网络不要闲着。 常用 JS 本地化及延迟加载,使用第三方浏览内核 后端处理慢,可以让服务器分 trunk 输出,在后端计算的同时前端也加载网络静态资源。 脚本执行慢,就让脚本在最后运行,不阻塞页面解析。 同时,合理的预加载
  • RecyclerView的缓存机制
    1.概述 1.1 四级缓存 缓存级别实际变量含义一级缓存mAttachedScrap和mChangedScrap这是优先级最高的缓存,RecyclerView在获取ViewHolder时,优先会到这两个缓存来找。其中mAttachedScrap存储的是当前还在屏幕中的ViewHolder,mChangedScrap存储的是数据被更新的ViewHolder,比如说调用了Adapter的notifyItemChanged方法二级缓存mCachedViews默认大小为2,通常用来存储预取的ViewHolder,同时在回收ViewHolder时,也会可能存储一部分的ViewHolder三级缓存ViewCacheExtension自定义缓存,通常用不到四级缓存RecyclerViewPool根据ViewType来缓存ViewHolder,每个ViewType的数组大小为5,可以动态的改变 mAttachedScrap:上表中说,它表示存储的是当前还在屏幕中ViewHolder。实际上是从屏幕上分离出来的ViewHolder,但是又即将添加到屏幕上去的ViewHolder。比如说,RecyclerView上下滑动,滑出一个新的Item,此时会重新调用LayoutManager的onLayoutChildren方法,从而会将屏幕上所有的ViewHolder先scrap掉(含义就是废弃掉)
  • 2021Android大厂面试合集,精选(建议收藏!)
    简历 首先是简历,一般找一个模板,写清楚掌握的技能和自己的项目经历即可。 简历建议2页就行,太长太短都不好。 建议用掌握与熟练掌握取代了解与精通。 这里教给大家一个小套路,可以提高收到面试邀请的机会。那就是在你简历的左上角印上准备面试公司的Logo。面试官在一堆简历中突然翻到一张印着自己公司Logo的简历,不免会多看你的几眼。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qe6bzune-1611123109279)(//upload-images.jianshu.io/upload_images/24142630-6fa55bb3c179cdba.png?imageMogr2/auto-orient/strip|imageView2/2/w/791/format/webp)] 关于算法 算法可以说是现在找工作必须的知识储备,具体得看公司的业务。以我的面试经验来看,总体来说问的不多,还有些公司基本不问算法。 但是如果去面试字节,网易,快手这种每轮必问算法的公司,因为算法题拿不到offer就很可惜了。 算法题就好像高考语文的古诗词默写一样,分不多,但丢了就很可惜了。 主要还是平时力扣的刷题积累 面经 接下来就是各公司的面经了,分享出来供大家参考。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjlW9r1i
  • Android高级面试题精选
    作者:Focusing 链接:https://juejin.im/post/5c85cead5188257c6703af47 Handler 1、谈谈消息机制Handler作用 ?有哪些要素 ?流程是怎样的 ? 参考回答: 负责跨线程通信,这是因为在主线程不能做耗时操作,而子线程不能更新UI,所以当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。具体分为四大要素 Message(消息):需要被传递的消息,消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息。MessageQueue(消息队列):负责消息的存储与管理,负责管理由 Handler发送过来的Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在其next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。Handler(消息处理器):负责Message的发送及处理。主要向消息池发送各种消息事件(Handler.sendMessage())和处理相应消息事件(Handler.handleMessage()),按照先进先出执行,内部使用的是单链表的结构。Looper(消息池):负责关联线程以及消息的分发,在该线程下从 MessageQueue获取 Message,分发给Handler,Looper创建的时候会创建一个 MessageQueue
  • android面试2019
    本人菜鸟一个,哪里有错误,欢迎指出啊!多谢!!! 阿里的android开发手册一、activity1、activity作用2、activity生命周期3、activity四种启动模式4、activity启动流程5、如何加速启动actviity问题:AActivity启动BActivity,此时B启动模式为singleTask并且有实例存在栈中,此时的B的生命周期? 二、fragment1、fragment作用2、fragment生命周期3、fragment的回退栈 三、service1、service作用2、service的两种启动方式和生命周期3、service与activity的两种通信方式4、IntentService5、service保活 四、ContentProvider(内容提供者)五、BroadcastReceiver(广播)1、作用域全局广播本地广播 2、两种注册方式静态注册动态注册 3、广播类型有序广播无序广播通知事例Notification 六、http协议流程七、网络请求框架八、数据解析框架九、三大图片加载框架对比图片的三级缓存总结加载超长图 十、屏幕适配十一、RecyclerView优化重点:不能在adapter中修改控件的一些属性,要从数据源上改变。因为RecyclerView的回收复用机制
  • 请查收:2020互联网大厂高频面试题!
    ListView和RecyclerView区别 参考链接: https://blog.csdn.net/shu_lance/article/details/79566189 既然RecyclerView在很多方面能取代ListView,Google为什么没把ListView划上一条过时的横线? 答案: 可以沿着回收机制来回答。ListView采用的是RecyclerBin的回收机制在一些轻量级的List显示时效率更高 你用过MVP和MVVM的区别 参考链接: https://www.cnblogs.com/dubo-/p/5619077.html HashMap的内部实现原理? HashMap可以接受null键值和值,而HashTable则不能,HashMap是非synchronized的;存储的是键值对。 HashMap是基于hashing原理,使用put(key,value)存储对象到HashMap中,使用get(key)从HashMap中获取对象,当我们给put方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来存储键对象和值对象,作为Map.Entry. 如果两个对象hashCode相同:存储时:他们会找到相同的bucket位置,发生碰撞,因为HashMap使用链表存储对象(每个Map.Entry都有一个next指针)
  • 2020年度Android面经总结,写给朝夕不倦的你,2021拒绝当“小丑”!
    前言 转眼间2020就接近尾声了,年后有跳槽想法的小伙伴们心里应该也有自己的决定了。金三银四青铜五,明年形势严峻,切勿临时抱佛脚。在博主认为,对于Android面试以及进阶的最佳学习方法莫过于刷题+博客+书籍+总结,前三者博主将淋漓尽致地挥毫于这篇博客文章中,至于总结在于个人,实际上越到后面你会发现面试并不难,其次就是在刷题的过程中有没有去思考,刷题只是次之,这又是一个层次了,这里暂时不提后面再谈。 其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。 下面开始进入正文,以下是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2020年度的高频面试题,希望对你有帮助。 一、计算机网络 1、Tcp和Udp的区别? 2、TCP可靠传输原理实现(滑动窗口)。 3、描述TCP三次握手与四次挥手的过程与意义。 4、Http与Https的关系是什么? 5、Http1.1和Http1.0及2.0的区别 6、Http的报文结构。 7、HTTPS 如何防范中间人攻击? 8、Http的请求方法。 9、Https加密原理。 10、网络请求缓存处理,okhttp如何处理网络缓存的 11、权限管理系统(底层的权限是如何进行 grant 的)? 12
  • android动画框架!Android跨进程通信导论,复习指南
    写在文章前面的话: “工欲行其事,必先利其器”,英雄和侠客更需要宝剑助己成功。同样,在现代软件开发环境中,每个Android开发者都需要更好的工具,帮助我们增强功能、提高效率。 在这个竞争激烈的行业中,只有优秀的工程师能够生存,需要我们能够为客户提供的最佳技术和资源,需要有优秀的开发工具,保证以最佳质量以及高效时间来构建。 1、Java 相关 容器(HashMap、HashSet、LinkedList、ArrayList、数组等) 需要了解其实现原理,还要灵活运用,如:自己实现 LinkedList、两个栈实现一个队列,数组实现栈,队列实现栈等。 HashMap、HashTable 和 CurrentHashMap 的核心区别(并发),其次内部数据结构的实现、扩容、存取操作,再深一点 哈希碰撞,哈希计算,哈希映射,为什么是头插法,扩容为什么是 2 的幂次等。 内存模型 垃圾回收算法(JVM) JVM 类加载机制、垃圾回收算法对比、Java 虚拟机结构 当你讲到分代回收算法的时候,不免会被追问到新生对象是怎么从年轻代到老年代的,以及可以作为 root 结点的对象有哪些两个问题。 1、谈谈对 JVM 的理解? 2、JVM 内存区域,开线程影响哪块区域内存? 3、对 Dalvik、ART 虚拟机有什么了解?对比? ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候
  • apk瘦身的实现方式,深度解析跳槽从开始到结束完整流程,附面试题答案
    前言 编程是一个江湖,江湖之大,鱼龙混杂,一部分江湖人士乃虾兵蟹将,一不小心就被一箭射死,我们称之为“码农”,这些人事江湖的重要组成部分,他们承担着堆砌代码,实现功能设计的使命,他们在江湖中虽为龙套,但不可或缺。另一部分人,华山论剑,刀光剑影,矗立江湖之巅,他们是系统分析师、架构师等,他们内功深厚,视野开阔,一招一式,举手投足间蕴藏着对可维护性、可扩展性等的深思熟虑。当然,更多的一部分人,他们不甘于现状,天资聪慧,正由“码农”向高手的身份努力中。 由于涉及到的面试题较多导致篇幅较长,我根据这些面试题所涉及到的常问范围总结了并做出了一份学习进阶路线图​​​​​​​及面试题答案免费分享给大家,文末有免费领取方式! java方面 基本知识点 集合类的区别和适用场景 1次 java静态内部类和非静态内部类的区别 内存分配 创建过程 内存泄漏问题? 如何保证线程安全? 多次 (面试必问) 哪几种加锁方式(最好扯到monitor) 乐观锁和悲观锁 (适用场景) 锁的适用场景 线程池的概念 构成 适用场景 为什么要用线程池(扯到线程分配资源的过程,和进程分配资源过程的区别) 内核方面(面试必问) GC垃圾回收机制 n次 GC是怎么找要清除的对象的? java的四种引用和适用场景 GC内核清理用什么算法? 新生代复制清理,老生代标记整理 拓展:老生代标记整理如何有效的整理内存碎片?
  • Android开发3年,九月份面试12家大厂跳槽成功,我有一些面试经验想分享给你们
    在下2017年毕业,目前从事android开发工作已经3年了,前段时间刚完成一次跳槽,面试了几家公司,将一些面试经验分享给大家,希望对大家有所帮助。 简历 首先是简历,一般找一个模板,填写掌握的技能和项目经历即可。 github上有一个不错的模板,详情可见:github.com/CyC2018/Mar… 项目clone后,个性Resuem.md即可,然后导出为pdf文件。 简历建议长度为2页,太长太短都不好。 建议用掌握与熟练掌握取代了解与精通。 刷题 算法题可以说是现在找工作必刷的了。不过根据我面试的经验,算法题总得来说问的还是比较少,还有一些公司是基本不问算法的。 当然如果碰到字节,快手这种每轮都问算法的公司,如果因为算法题拿不到offer就很可惜了。 算法题就好像高考语文的古诗词默写一样,分不多,但丢了就很可惜了。 根据我的经验,大概刷了,掌握100道题就足以应付面试了。 当然完全掌握需要重复练习,不是刷了一遍就可以的。 这里推荐一下极客时间上覃超的五遍刷题法: 五遍刷题法 1.第一遍,直接看解法 多解法,比较解法优劣,默写好的解法 2.第二遍,打开leetcode,直接开始写 多种解法比较,调优 3.第三遍,过一天之后,再重复做题 4.第四遍,过了一周,再来反复练 5.第五遍,面试前一周,恢复性训练 通过以上方法重复练习,大概刷100题就足以应付面试,如果每天刷3道
  • Android RecyclerView 之缓存机制篇
    源码的世界及其复杂,要是每一步都去深究,很容易迷失在里面,这里将RecyclerView的缓存机制抽出来重点分析,结合图文的方式,希望可以给您带来帮助! RecyclerView的缓存机制犹如一个强大的引擎,为RecyclerView的畅滑运行提供了强有力的保障;Android的大部分视图都是列表形式的,那么RecyclerView的出现无疑大大的提升了开发效率;那么RecyclerView的缓存究竟是如何工作的呢,那就让我们来揭开谜底吧! RecyclerView的缓存机制就是依附于Recycler这个类来实现的,让我们先来看一下这个类的成员变量: public final class Recycler { final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); ArrayList<ViewHolder> mChangedScrap = null; final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap)