RecyclerView源码解析(四):RecyclerView对ViewHolder的回收

2023-10-31

RecyclerView源码解析(四):RecyclerView对ViewHolder的回收

在这里插入图片描述

导言

前面几篇文章我们已经介绍了RecyclerView绘图的三大流程和其四层缓存机制,不过对于我来说还有一个疑问,那就是RecyclerView是如何将分离的ViewHolder进行回收的,最终又回收到了哪一层缓存之中。所以本篇文章就是为了解决这个问题。

创建ViewHolder和回收ViewHolder的方法

在缓存机制中我们提到过了,RecyclerView尝试获取ViewHolder并不是一开始就直接创建一个ViewHolder的,它会首先尝试从它的四层缓存中获取一个可用的ViewHolder,如果没有找到,最后才会调用方法创建一个ViewHolder,而这个方法正是onCreateViewHolder,也就是我们在适配器中重写的方法。所以每次创建ViewHolder时才会调用该方法。

从我具体测试的结果来看当我们一共有五个数据项的时候RecyclerView只会创建四个ViewHolder,也就是说有一个数据项的显示会通过ViewHolder复用的方式来显示。**也就是说,之前必定有至少一个ViewHolder被回收了。**这个回收过程在哪里呢?我们可以从RecyclerView中可重写的方法中找到突破口。

有以下五个比较重要的回调,下面由我的蹩脚英语进行翻译:

  • onAttachedToRecyclerView(recyclerView: RecyclerView):当RecyclerView开始监听其Adapter时调用。
  • onDetachedFromRecyclerView(recyclerView: RecyclerView):当RecyclerView不再监听其Adapter时调用。
  • onViewAttachedToWindow(holder: ViewHolder):当由适配器创建的View被添加到Window时调用,这可以被视为是用户即将见到该View的标志。在onViewDetachedFromWindow(holder: ViewHolder)释放的资源应当在这里被恢复
  • onViewDetachedFromWindow(holder: ViewHolder):当由适配器创建的View被从Window分离时调用,这可能并不是一个永久的状态,RecyclerView可能会选择缓存这些被分离的不可见的View。
  • onViewRecycled(holder: ViewHolder):当ViewHolder被RecyclerView回收时调用。当LayoutManager判断一个View不再需要被绑定在其父RecyclerView时,这个View就会被回收。LayoutManager这样判断的原因可能是因为该View消失在视线之外或者或者是因为一组被缓存的视图仍然由附加到父 RecyclerView 的视图表示。RecyclerView将在清除Holder内部的数据和将其发送到RecycledViewPool之前调用该方法。

这五个方法之中显然最后一个方法关联度是最高的,不过其后面的那一句“或者是因为一组被缓存的视图仍然由附加到父 RecyclerView 的视图表示”的意思可能比较迷惑。在 RecyclerView 中,为了提高性能,通常会维护一个视图池(View Pool),用于存储那些已经创建但当前不可见的视图。这些视图池中的视图仍然附加(attached)到父 RecyclerView,但它们当前可能不可见,因为可能滚动到了屏幕之外或者被遮挡住了。当用户滚动 RecyclerView 时,系统会检查这个视图池,看看是否有可以重用的视图,而不是每次都创建新的视图。这就是所谓的 “缓存的视图”,它们被缓存起来以备将来使用。当需要显示新的数据时,可以从视图池中获取这些缓存的视图,并根据新的数据进行更新,而不需要重新创建整个视图。这样可以显著提高性能,因为重用现有视图比创建新视图要快得多。

因此,这句话的含义是,RecyclerView 中的视图可能不可见,要么因为已经滚动到屏幕之外,要么因为被其他视图遮挡住了,但它们仍然存在于 RecyclerView 的视图池中,可以被重用,以提高性能。

源码分析

分离视图

在上面的回调中onViewDetachedFromWindow(holder: ViewHolder)是用来分离视图的,所以我们可以从这个方法入手看看RecyclerView中哪里调用了该方法。层层向前推,可以发现这个方法会指向RecyclerView中的一个回调类中:

    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
            new ViewInfoStore.ProcessCallback() {
                @Override
                public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
                        @Nullable ItemHolderInfo postInfo) {
                    mRecycler.unscrapView(viewHolder);
                    animateDisappearance(viewHolder, info, postInfo);
                }
                @Override
                public void processAppeared(ViewHolder viewHolder,
                        ItemHolderInfo preInfo, ItemHolderInfo info) {
                    animateAppearance(viewHolder, preInfo, info);
                }

                @Override
                public void processPersistent(ViewHolder viewHolder,
                        @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
                    viewHolder.setIsRecyclable(false);
                    if (mDataSetHasChangedAfterLayout) {
                        // since it was rebound, use change instead as we'll be mapping them from
                        // stable ids. If stable ids were false, we would not be running any
                        // animations
                        if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
                                postInfo)) {
                            postAnimationRunner();
                        }
                    } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
                        postAnimationRunner();
                    }
                }
                @Override
                public void unused(ViewHolder viewHolder) {
                    mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
                }
            };

会指向最后的一个unused(ViewHolder viewHolder)方法中,这个方法会调用mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler)方法,最终会触发onViewDetachedFromWindow(holder: ViewHolder)方法。这个方法最后会在我们之前在布局过程中说过的dispatchLayoutStep3()中触发,最终触发process方法:

    void process(ProcessCallback callback) {
        for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
            final RecyclerView.ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
            final InfoRecord record = mLayoutHolderMap.removeAt(index);
            if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) {
                // Appeared then disappeared. Not useful for animations.
                callback.unused(viewHolder);
            } else if ((record.flags & FLAG_DISAPPEARED) != 0) {
                // Set as "disappeared" by the LayoutManager (addDisappearingView)
                if (record.preInfo == null) {
                    // similar to appear disappear but happened between different layout passes.
                    // this can happen when the layout manager is using auto-measure
                    callback.unused(viewHolder);
                } else {
                    callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
                }
            } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
                // Appeared in the layout but not in the adapter (e.g. entered the viewport)
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) {
                // Persistent in both passes. Animate persistence
                callback.processPersistent(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_PRE) != 0) {
                // Was in pre-layout, never been added to post layout
                callback.processDisappeared(viewHolder, record.preInfo, null);
            } else if ((record.flags & FLAG_POST) != 0) {
                // Was not in pre-layout, been added to post layout
                callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
            } else if ((record.flags & FLAG_APPEAR) != 0) {
                // Scrap view. RecyclerView will handle removing/recycling this.
            } else if (DEBUG) {
                throw new IllegalStateException("record without any reasonable flag combination:/");
            }
            InfoRecord.recycle(record);
        }
    }

这个方法就是通过判断FLAG标志位来决定到底回调哪个方法,可以发现当FLAG的值为FLAG_DISAPPEARED或者FLAG_APPEAR_AND_DISAPPEAR时会触发上面的unused方法,总的来说就是当View消失(DISAPPEARED)时就会触发这个方法。也许你可能会有疑问:“dispatchLayout这一系列方法不是在布局中调用的吗,难道会反复进行布局吗?”实际上dispatchLayout方法除了在layout中触发之外还会在scrollByInternal方法中触发,显然这是滑动时会调用到的方法,在onTouchEvent中执行。所以说,每当我们滑动时会先执行dispatchLayout方法,然后在这其中一旦当前View为不可见状态就会触发unused方法,最后回调到onViewDetachedFromWindow(holder: ViewHolder)中去。整个调用链如下所示:
在这里插入图片描述

回收视图

至于这个分离视图的过程到底做了什么,我们再来继续往前看,之前提到unused回调,该回调如下所示:

public void unused(ViewHolder viewHolder) {
    mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
}

可以看到会触发mLayout.removeAndRecyclerView方法,实际上我们上面给的那张图进行了一定的简化,我们将其详细化,如下:
在这里插入图片描述
可以看到unused方法出发了mLayout.removeAndRecycleView,这个方法是有默认实现的:

public void removeAndRecycleView(@NonNull View child, @NonNull Recycler recycler) {
    removeView(child);
    recycler.recycleView(child);
}

可以看到他在触发removeView方法之后还触发了recycler自身的recycleView方法,不出所料这块应该就是重头戏了,该方法的注释:
在这里插入图片描述
注释上也写得很清楚了,该方法是用于回收一个分离的view的,特定的view还会被添加进入view Pool(视图池)中以便之后的重新绑定数据和复用。一个View在被回收之前必须被完全地从父Recycler中分离。当一个View被报废时,它将从scrap List中被移除。接下来看其方法:

public void recycleView(@NonNull View view) {
            // This public recycle method tries to make view recycle-able since layout manager
            // intended to recycle this view (e.g. even if it is in scrap or change cache)
            ViewHolder holder = getChildViewHolderInt(view);//获得目标View的ViewHolder
            if (holder.isTmpDetached()) {//判断该holder是不是暂时分离状态
                removeDetachedView(view, false);//移除分离的view,将触发ViewGroup的removeDetachedView方法
            }
            if (holder.isScrap()) {//判断holder是否有mScrapContainer,这是一个Recycler类
                holder.unScrap();//将其从mScrapContainer中移除
            } else if (holder.wasReturnedFromScrap()) {
                holder.clearReturnedFromScrapFlag();
            }
            recycleViewHolderInternal(holder);
            // In most cases we dont need call endAnimation() because when view is detached,
            // ViewPropertyAnimation will end. But if the animation is based on ObjectAnimator or
            // if the ItemAnimator uses "pending runnable" and the ViewPropertyAnimation has not
            // started yet, the ItemAnimatior on the view may not be cleared.
            // In b/73552923, the View is removed by scroll pass while it's waiting in
            // the "pending moving" list of DefaultItemAnimator and DefaultItemAnimator later in
            // a post runnable, incorrectly performs postDelayed() on the detached view.
            // To fix the issue, we issue endAnimation() here to make sure animation of this view
            // finishes.
            //
            // Note the order: we must call endAnimation() after recycleViewHolderInternal()
            // to avoid recycle twice. If ViewHolder isRecyclable is false,
            // recycleViewHolderInternal() will not recycle it, endAnimation() will reset
            // isRecyclable flag and recycle the view.
            if (mItemAnimator != null && !holder.isRecyclable()) {
                mItemAnimator.endAnimation(holder);
            }
        }

看起来这个方法大段大段的都是注释,首先开头的这两句的意思是:“当LayoutManager想要回收这个view时,该公共方法将尝试使该view变成可回收的。”上面的holder.isScrap()判断报废状态以我的理解holder的第一级缓存是mAttachedScrap,这个mAttachedScrap是被装在一个Recycler中的,所以holder需要通过这个Recycler才能找到这个mAttachedScrap,这时候就需要一个变量来指向存储holder的Recycler,这个参数就是ScrapContainer。当一个ViewHolder被添加进入一个mAttachedScrap时同时还需要给它设置一个ScrapContainer来指向存储mAttachedScrap的Recycler。也就是说这个holder.isScrap()判断的就是当前holder是否在第一级缓存mAttachedScrap中的。

好了,继续回到正题接下来会调用recycleViewHolderInternal(holder)方法进一步回收holder,进入到该方法中:

        void recycleViewHolderInternal(ViewHolder holder) {
        ......
            final boolean transientStatePreventsRecycling = holder
                    .doesTransientStatePreventRecycling();
            @SuppressWarnings("unchecked")
            final boolean forceRecycle = mAdapter != null //是否强制回收
                    && transientStatePreventsRecycling
                    && mAdapter.onFailedToRecycleView(holder);
            boolean cached = false;
            boolean recycled = false;
			......
            if (forceRecycle || holder.isRecyclable()) { //如果强制回收或者可回收的话
                if (mViewCacheMax > 0//判断,如果ViewHolder仍然有效的话
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID 
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();//获得二级缓存CachedView的大小
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { //如果CachedView的大小已经超出了限制
                        recycleCachedViewAt(0); //将CachedView中的第一个元素回收
                        cachedViewSize--;
                    }

                    int targetCacheIndex = cachedViewSize; //CachedView中的目标下标,也就是最后一位
                    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);//将holder添加进入二级缓存中
                    cached = true;//将cached标志位置为true,代表该holder成功添加入二级缓存中
                }
                if (!cached) {//如果缓存进一级缓存时失败
                    addViewHolderToRecycledViewPool(holder, true);//将其添加进入RecycledViewPool四级缓存中
                    recycled = 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;//说明当前holder已经没有宿主RecyclerView了
            }
        }

上面方法中重要的部分我都已经注释出来了简单来说就是先尝试将回收的holder加入二级缓存mCachedView中,如果mCachedView已满就将第一个元素移除再添加。如果缓存二级缓存失败的话再尝试调用addViewHolderToRecycledViewPool尝试将其加入到四级缓存中。

说到现在我们发现这个回收方法只涉及到了第二级缓存和第四级缓存,除去我们自定义的第三级缓存,那第一级缓存去哪里了?实际上,具体将View添加进入第一级缓存需要调用scrapView方法:

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);
    }
}

这个方法向前追踪的话是指向了getScrapOrHiddenOrCachedHolderForPosition,这个方法也是在布局过程中提到的:

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();
			
			//尝试从第一级缓存中获取holder,如果获取成功就直接返回
			........
			//若没有在第一级缓存中找到holder
            if (!dryRun) {
                View view = mChildHelper.findHiddenNonRemovedView(position);//找到隐藏的且没有被移除的View
                if (view != null) {//如果View不为空
                    // This View is good to be used. We just need to unhide, detach and move to the
                    // scrap list.
                    final ViewHolder vh = getChildViewHolderInt(view);//将其包装成一个ViewHolder
                    mChildHelper.unhide(view);//解除其隐藏状态
                    int layoutIndex = mChildHelper.indexOfChild(view);
                    if (layoutIndex == RecyclerView.NO_POSITION) {
                        throw new IllegalStateException("layout index should not be -1 after "
                                + "unhiding a view:" + vh + exceptionLabel());
                    }
                    mChildHelper.detachViewFromParent(layoutIndex);//将其从父RecylerView中分离
                    scrapView(view);//将其添加进入第一级缓存中
                    vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                            | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    return vh;
                }
            }

            ..........
            }
            return null;
        }

这里我们也截取最重要的部分并且我已经加入了部分注释了。可以看到这所有的第一级缓存中的ViewHolder的来源都是一个叫做mHiddenViews中的List中获取的。这个mHiddenViews中的View按照我的理解都是已经不可见但是仍未从被从RecyclerView分离的View的列表。所以RecyclerView将ViewHolder回收进这三级缓存的逻辑已经很清楚了。

总结

我们最后来总结一下RecyclerView回收ViewHolder的逻辑,首先RecyclerView尝试填充内容时会先尝试从这四级缓存中获取可用的ViewHolder,首先就是从第一级缓存mScrapViews中查找,如果找到了就直接返回;如果第一级缓存中没有可用的Holder,那么接下来RecyclerView还会额外查看是否有不可见但是仍然附加在RecyclerView的View,如果有的话就将其包装成Holder添加进入第一级缓存中,并将其从之前的父RecyclerView中分离,这就是第一级缓存的来源

之后在RecyclerView的滑动过程中又有一个新的View不可见的话就会触发新的回调,该回调中首先会判断当前的View是否已经被第一级缓存存储了,如果已经被第一级缓存存储就直接返回;否则就会先尝试将其回收进入第二级缓存中,当第二级缓存中缓存失败时又会尝试进行第四级缓存;至于第三级缓存是我们自定义的,一般也不用。

最后上一张总结图:
在这里插入图片描述

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

RecyclerView源码解析(四):RecyclerView对ViewHolder的回收 的相关文章

  • 如何在PreferenceActivity中添加工具栏

    我已经使用首选项创建了应用程序设置 但我注意到 我的 PreferenceActivity 中没有工具栏 如何将工具栏添加到我的 PreferenceActivity 中 My code 我的 pref xml
  • Google 云端硬盘身份验证异常 - 需要许可吗? (v2)

    我一直在尝试将 Google Drive v2 添加到我的 Android 应用程序中 但无法获得授权 我收到 UserRecoverableAuthIOException 并显示消息 NeedPermission 我感觉 Google A
  • 十进制到八进制的转换[重复]

    这个问题在这里已经有答案了 可能的重复 十进制转换错误 https stackoverflow com questions 13142977 decimal conversion error 我正在为一个类编写一个程序 并且在计算如何将八进
  • 如何使用InputConnectionWrapper?

    我有一个EditText 现在我想获取用户对此所做的所有更改EditText并在手动将它们插入之前使用它们EditText 我不希望用户直接更改中的文本EditText 这只能由我的代码完成 例如通过使用replace or setText
  • 如何默认在 ActionOpenDocument 意图中显示“内部存储”选项

    我需要用户选择一个自定义文件类型的文件 并将其从 Windows 文件资源管理器拖到 Android 设备上 但默认情况下内部存储选项不可用 当我使用以下命令启动意图时 var libraryIntent new Intent Intent
  • 尝试在 ubuntu 中编译 android 内核时出错

    我正在尝试从源代码编译 Android 内核 并且我已经下载了所有正确的软件包来执行此操作 但由于某种原因我收到此错误 arm linux androideabi gcc error unrecognized command line op
  • 在 android DatePickerDialog 中将语言设置为法语

    有什么办法可以让日期显示在DatePickerDialog用法语 我已经搜索过这个但没有找到结果 这是我的代码 Calendar c Calendar getInstance picker new DatePickerDialog Paym
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • 加密 JBoss 配置中的敏感信息

    JBoss 中的标准数据源配置要求数据库用户的用户名和密码位于 xxx ds xml 文件中 如果我将数据源定义为 c3p0 mbean 我会遇到同样的问题 是否有标准方法来加密用户和密码 保存密钥的好地方是什么 这当然也与 tomcat
  • 如何在控制器、服务和存储库模式中使用 DTO

    我正在遵循控制器 服务和存储库模式 我只是想知道 DTO 在哪里出现 控制器应该只接收 DTO 吗 我的理解是您不希望外界了解底层域模型 从领域模型到 DTO 的转换应该发生在控制器层还是服务层 在今天使用 Spring MVC 和交互式
  • 在 Mac 上正确运行基于 SWT 的跨平台 jar

    我一直致力于一个基于 SWT 的项目 该项目旨在部署为 Java Web Start 从而可以在多个平台上使用 到目前为止 我已经成功解决了由于 SWT 依赖的系统特定库而出现的导出问题 请参阅相关thread https stackove
  • Android Studio - Windows 7 上的 Android SDK 问题

    我对 Google i o 2013 上发布的最新开发工具 Android Studio 有疑问 我已经成功安装了该程序并且能够正常启动 我可以导入现有项目并对其进行编辑 但是 当我尝试单击 SDK 管理器图标或 AVD 管理器图标时 或者
  • 使用Spring将war文件WEB-INF目录下的资源导入到applicationContext文件中

    我在我的项目中使用 Spring 框架 我想导入下面的所有 xml 资源 文件 WEB INF CustomerService spring integration Jobs applicationContext配置文件中war文件的目录
  • 我的设备突然没有显示在“Android 设备选择器”中

    我正在使用我的三星 Galaxy3 设备来测试过去两个月的应用程序 它运行良好 但从今天早上开始 当我将设备连接到系统时 它突然没有显示在 Android 设备选择器 窗口中 我检查过 USB 调试模式仅在我的设备中处于选中状态 谁能猜出问
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • 在mockito中使用when进行模拟ContextLoader.getCurrentWebApplicationContext()调用。我该怎么做?

    我试图在使用 mockito 时模拟 ContextLoader getCurrentWebApplicationContext 调用 但它无法模拟 here is my source code Mock org springframewo
  • 如何确定对手机号码的呼叫是本地呼叫还是 STD 或 ISD

    我正在为 Android 开发某种应用程序 但不知道如何获取被叫号码是本地或 STD 的号码的数据 即手机号码检查器等应用程序从哪里获取数据 注意 我说的是手机号码 而不是固定电话 固定电话号码 你得到的数字是字符串类型 因此 您可以获取号
  • 在 Maven 依赖项中指定 jar 和 test-jar 类型

    我有一个名为 commons 的项目 其中包含运行时和测试的常见内容 在主项目中 我添加了公共资源的依赖项
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • android sdk 的位置尚未在 Windows 操作系统的首选项中设置

    在 Eclipse 上 我转到 windows gt Android SDK 和 AVD Manager 然后弹出此消息 Android sdk 的位置尚未在首选项中设置 进入首选项 在侧边栏找到 Android 然后会出现一个 SDK 位

随机推荐

  • Java 网络安全

    1 常见的 Web 攻击 1 1 CSRF攻击 1 1 1 CSRF如何防护 1 2 XSS 攻击 1 2 1 XSS 攻击分类 1 2 2 XSS 防护 1 3 DOS 攻击 1 3 1 防护 1 4 SQL 注入 1 4 1 SQL 注
  • Allegro中走线长度的设置

    PROPAGATION DELAY PROPAGATION DELAY这个设定主要用来对Net绝对长度的设定 如要求设定一组Net的长度要在Min Mil到 Max Mil之间的话 就可以用这种设定来完成 要求走线Net长度在Min与Max
  • 如何去除页面上的空格

    let a this nsrsbh this nsrsbh replace s s g 左边是处理完以后的 let a 也就是a这个变量才是处理过的 结果是 页面上写空格不会再出现报错的情况了
  • 关于php unset的随笔

    我们可能都知道C 在使用了构造函数后再使用析构函数来释放内存 那么PHP需不需要使用unset来进行变量释放内存呢 偶然的 我们遇到 out of memory 的错误 但是服务器内存明明还有很多 memory limit也是得很大 但是就
  • JAVA CPU过高异常处理

    1 首先用 ps aux grep jar包名字 找到进程的PID 直接top命令也能看到CPU最高的进程PID 2 用 top Hp 进程PID 获取CPU占用高的线程 下图中PID的1091 1063就是线程ID 网上的文章用 ps m
  • opengl模拟太阳效果

    参考 http www cnblogs com tkgamegroup p 4198811 html 我决定开个新坑了 以后每周五更新 这是GLSL的学习周记 GLSL就是OPENGL SHADER LANGUAGE的简称 就是着色器语言
  • NLP 利器 Gensim 中 word2vec 模型词嵌入 Word Embeddings 的可视化

    本文为系列文章之一 前面的几篇请点击链接 NLP 利器 gensim 库基本特性介绍和安装方式 NLP 利器 Gensim 库的使用之 Word2Vec 模型案例演示 NLP 利器 Gensim 来训练自己的 word2vec 词向量模型
  • 字符串方法

    字符串方法 slice 字符串截取 第一个参数 开始索引 下标 包括开始 第二个参数 结束索引 下标 不包括结束 截取字符串 var str elephant var rel str slice 0 3 console log rel 截取
  • ArcMap显示XY数据时部分字段未显示的解决办法之曲线救国

    ArcMap显示XY数据时部分字段未显示的解决办法之曲线救国 要解决的问题 问题之所在 曲线救国解决问题 要解决的问题 当我们要把xls xlsx csv等表格数据转换成shp文件的时候 其中有一步是要显示xy数据 如下图 正常情况下里面最
  • latex学习(1)

    1 latex中插入图片 需要调用 usepackage graphicx 和 usepackage float 宏包 begin figure H small centering includegraphics width 13cm he
  • [传统图像处理]-------DOG算子(高斯差分算子)和高斯模糊

    一 DOG算子的作用 DOG Difference of Gaussian 意为高斯函数的差分 是灰度图像增强和角点检测的一种方法 二 高斯模糊 由于DOG是利用高斯模糊 也叫高斯平滑 实现的 所以有必要先讲一下高斯模糊是什么 对于一个图像
  • Qt信号和槽机制

    1 第一个参数 信号的发送者 2 第二个参数 发送的信号 3 第三个参数 信号的接收者 4 第四个参数 处理信号的槽函数 正常来说 用到前面四个参数就可以了 第五个参数 如果是多线程 默认使用Qt QueuedConnection 队列方式
  • 顺序表基本运算的实现(第二章:线性表)

    顺序表基本运算的实现 基本运算 初始化线性表InitList L 需求 构造一个空的线性表L 方法 分配空间 并将length成员设置为0 算法 void InitList SqList L 指针的引用 L SqList malloc si
  • C语言abs函数

    C语言编程入门教程 abs 函数是用来求整数的绝对值的 函数名 abs 功 能 求整数的绝对值 用 法 int abs int i 程序例 include
  • 树莓派换源、vim更新:树莓派更换国内可用镜像源

    参考 树莓派vim更新 树莓派更换国内可用镜像源 作者 丶PURSUING 发布时间 2021 02 01 22 40 48 网址 https blog csdn net weixin 44742824 article details 11
  • spark Dataframe数据处理常用方法总结

    以下方法使用scala df类型为Dataframe 常用的包 import org apache spark sql functions import spark implicits 1 新增一列 df df withColumn new
  • 半导体(Die及成品)产品标准大汇总包括可靠性等(IEC+JEDEC+EIAJ+AECQ+国标)

    半导体 Die及成品 产品标准大汇总包括可靠性等 IEC JEDEC EIAJ AECQ 国标 1 IEC标准 IEC 60747全系列 Semiconductor devices 半导体器件 包含全部38份最新英文标准文件 rar IEC
  • 21 个简洁的 JavaScript单行代码技巧

    JavaScript 发展至今已经变得越来越强大 且广泛用于前端和后端开发 作为一名前端程序员 不断的学习精进技巧 了解JS的最新发展也是非常必要的 而简洁的一行代码示例就是很好的方法 今天 我们有 21 个JavaScript单行代码技巧
  • 剑指offer: 最小k个元素

    思路 大顶堆 最小k个元 求前k个最小用最大堆 求前k个最大用最小堆 步骤如下 1 取数组前k个元素初始化堆 从最后一个非叶子节点开始到根节点来构建大顶堆 2 当某个元素大于堆顶元素时 直接抛弃 3 当某个元素小于堆顶元素时 替换堆顶元素
  • RecyclerView源码解析(四):RecyclerView对ViewHolder的回收

    RecyclerView源码解析 四 RecyclerView对ViewHolder的回收 导言 前面几篇文章我们已经介绍了RecyclerView绘图的三大流程和其四层缓存机制 不过对于我来说还有一个疑问 那就是RecyclerView是