文章目录

  • 一、高度设置wrap_content无效?
  • 二、setOffscreenPageLimit(0)不起作用?
  • 三、Viewpager+Fragment组合使用

一、高度设置wrap_content无效?

当给ViewPager的高度设置为wrap_content,并不会生效,原因在于onMeasure方法。onMeasure方法实用类测量宽高的,so每个View都会有自己的onMeasure方法。讲道理人家(的onMeasure方法)会先测量child的高度,最后再通过setMeasuredDimension方法测量并设置自己的宽高。

然而,ViewPager并没有这么做!上来就调用setMeasuredDimension方法设置自己的宽高,根本没有管它的child,简直就不是亲生的!

this.setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec));

从这行代码可以看出,VP将自己默认的size设为0,优先使用父布局传过来的widthMeasureSpec、heightMeasureSpec。结果就是如果不指定宽高,就使用match_parent。 只要VP符合它自己的parent的要求,VP就会快速的它的parent填充满,其他的一概不管。也许VP如此对待它的child的原因在于child可被动态的添加、删除,宽高并不确定!

要想解决这个问题,其实也很简单:只需要自定义ViewPager并重写onMeasure方法即可。在onMeasure方法中,先遍历自己所有的child并计算它们的高度,最后再调用父类的onMeasure方法,将得到的宽高传入:

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

二、setOffscreenPageLimit(0)不起作用?

Fragment+ViewPager组合使用时,调用setOffscreenPageLimit(0)设置当前页面下左右两边缓存的item个数为0,无效。

public void setOffscreenPageLimit(int limit) {if (limit < 1) {Log.w("ViewPager", "Requested offscreen page limit " + limit + " too small; defaulting to " + 1);limit = 1;}if (limit != this.mOffscreenPageLimit) {this.mOffscreenPageLimit = limit;this.populate();}}

从以上代码中就能找到证据,当limit < 1时,limit = 1。也就是说即使设置缓存数为0,ViewPager始终还是会额外的去缓存下一个。对于VP+Fragment组合的使用场景而言,curItem是可见的,curItem+1无需展示,这样也会在请求网络上造成时间压力。

ViewPager + Adapter结合使用,VP是如何从Adapter处获取、销毁的数据呢?这就引出了一个神秘方法–populate() 。

populate() 是专门用来计算、处理缓存的,populate() 函数的生命周期是和Adapter的生命周期同步的。ViewPager能够顺利的对数据进行获取、销毁等操作,全部都依赖于populate() 。

 void populate(int newCurrentItem) {ViewPager.ItemInfo oldCurInfo = null;if (this.mCurItem != newCurrentItem) {oldCurInfo = this.infoForPosition(this.mCurItem);this.mCurItem = newCurrentItem;}if (this.mAdapter == null) {this.sortChildDrawingOrder();} else if (this.mPopulatePending) {this.sortChildDrawingOrder();} else if (this.getWindowToken() != null) {this.mAdapter.startUpdate(this);// --->更新int pageLimit = this.mOffscreenPageLimit;int startPos = Math.max(0, this.mCurItem - pageLimit);int N = this.mAdapter.getCount();// --->获取Adapter的数据量int endPos = Math.min(N - 1, this.mCurItem + pageLimit);// 缓存空间[startPos,endPos] ---> [mCurItem-pageLimit,mCurItem+pageLimit]if (N != this.mExpectedAdapterCount) {String resName;try {resName = this.getResources().getResourceName(this.getId());} catch (NotFoundException var17) {resName = Integer.toHexString(this.getId());}throw new IllegalStateException("The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: " + this.mExpectedAdapterCount + ", found: " + N + " Pager id: " + resName + " Pager class: " + this.getClass() + " Problematic adapter: " + this.mAdapter.getClass());} else {int curIndex = true;ViewPager.ItemInfo curItem = null;int curIndex;for(curIndex = 0; curIndex < this.mItems.size(); ++curIndex) {ViewPager.ItemInfo ii = (ViewPager.ItemInfo)this.mItems.get(curIndex);if (ii.position >= this.mCurItem) {if (ii.position == this.mCurItem) {curItem = ii;}break;}}//curItem没找到,即没有缓存,就先加载一个itemif (curItem == null && N > 0) {curItem = this.addNewItem(this.mCurItem, curIndex);}int itemIndex;ViewPager.ItemInfo ii;int i;if (curItem != null) {//先对左边的item进行缓存处理float extraWidthLeft = 0.0F;itemIndex = curIndex - 1;ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;i = this.getClientWidth();float leftWidthNeeded = i <= 0 ? 0.0F : 2.0F - curItem.widthFactor + (float)this.getPaddingLeft() / (float)i;for(int pos = this.mCurItem - 1; pos >= 0; --pos) {if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {if (ii == null) {break;}// 缓存区间之外的item,要destory掉if (pos == ii.position && !ii.scrolling) {this.mItems.remove(itemIndex);this.mAdapter.destroyItem(this, pos, ii.object);// --->销毁item--itemIndex;--curIndex;ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;}} else if (ii != null && pos == ii.position) {extraWidthLeft += ii.widthFactor;--itemIndex;ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;} else { //如果item没有出现过,就去addNewItemii = this.addNewItem(pos, itemIndex + 1);extraWidthLeft += ii.widthFactor;++curIndex;ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;}}//再对右边的item进行缓存处理float extraWidthRight = curItem.widthFactor;itemIndex = curIndex + 1;if (extraWidthRight < 2.0F) {ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;float rightWidthNeeded = i <= 0 ? 0.0F : (float)this.getPaddingRight() / (float)i + 2.0F;for(int pos = this.mCurItem + 1; pos < N; ++pos) {if (extraWidthRight >= rightWidthNeeded && pos > endPos) {if (ii == null) {break;}if (pos == ii.position && !ii.scrolling) {this.mItems.remove(itemIndex);this.mAdapter.destroyItem(this, pos, ii.object);ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;}} else if (ii != null && pos == ii.position) {extraWidthRight += ii.widthFactor;++itemIndex;ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;} else {ii = this.addNewItem(pos, itemIndex);++itemIndex;extraWidthRight += ii.widthFactor;ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;}}}this.calculatePageOffsets(curItem, curIndex, oldCurInfo);this.mAdapter.setPrimaryItem(this, this.mCurItem, curItem.object); // --->设置当前的item}this.mAdapter.finishUpdate(this);// --->完成更新int childCount = this.getChildCount();for(itemIndex = 0; itemIndex < childCount; ++itemIndex) {View child = this.getChildAt(itemIndex);ViewPager.LayoutParams lp = (ViewPager.LayoutParams)child.getLayoutParams();lp.childIndex = itemIndex;if (!lp.isDecor && lp.widthFactor == 0.0F) {ViewPager.ItemInfo ii = this.infoForChild(child);if (ii != null) {lp.widthFactor = ii.widthFactor;lp.position = ii.position;}}}this.sortChildDrawingOrder();if (this.hasFocus()) {View currentFocused = this.findFocus();ii = currentFocused != null ? this.infoForAnyChild(currentFocused) : null;if (ii == null || ii.position != this.mCurItem) {for(i = 0; i < this.getChildCount(); ++i) {View child = this.getChildAt(i);ii = this.infoForChild(child);if (ii != null && ii.position == this.mCurItem && child.requestFocus(2)) {break;}}}}}}}

首先关注一点:

curItem = this.addNewItem(this.mCurItem, curIndex);

一旦没找到curItem,即没有缓存,就先加载一个item。curItem是通过addNewItem方法来确定的:

ViewPager.ItemInfo addNewItem(int position, int index) {ViewPager.ItemInfo ii = new ViewPager.ItemInfo();ii.position = position;//ItemInfo中的object就是缓存的Viewii.object = this.mAdapter.instantiateItem(this, position);ii.widthFactor = this.mAdapter.getPageWidth(position);if (index >= 0 && index < this.mItems.size()) {this.mItems.add(index, ii);} else {this.mItems.add(ii);}return ii;}

而在addNewItem函数中,我们可以发现Adapter的身影:this.mAdapter.instantiateItem(this, position)。也就是说创建新的Item要通过Adapter,恰巧instantiateItem方法的作用就是用来构建新的View并return。

其实,只要仔细观察Adapter的方法:

public abstract int getCount();
public void startUpdate(@NonNull ViewGroup container)
public Object instantiateItem(@NonNull ViewGroup container, int position)
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object)
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object)
public void finishUpdate(@NonNull ViewGroup container)
......

在populate()方法中都能看到他们被调用的身影:

this.mAdapter.startUpdate(this);// --->更新
int N = this.mAdapter.getCount();// --->获取Adapter的数据量
this.mAdapter.destroyItem(this, pos, ii.object);// --->销毁item
this.mAdapter.setPrimaryItem(this, this.mCurItem, curItem.object); // --->设置当前的item
this.mAdapter.finishUpdate(this);// --->完成更新
......

这意味着,populate()函数贯穿了整个Adapter的各个函数的调用过程,等同于populate()方法的生命周期和Adapter已经融合在一起了。换句话说,Adapter的所有动作都是由populate()方法操控的,即每个Item的管理实际上都是由populate()方法控制的。可以得出结论:VP的缓存的幕后黑手是populate()。就好比是宇智波带土一直在用写轮眼控制三尾人柱力矢仓,对鬼鲛下达命令。

说道缓存,仔细看populate()方法中的startPos和endPos。VP的缓存空间就是[startPos,endPos] —> [mCurItem-pageLimit,mCurItem+pageLimit]。这里的pageLimit就是我们在Java代码中调用setOffscreenPageLimit(n)时传入的值。这样计算来,VP缓存的是[当前Item-pageLimit,当前Item+pageLimit]。

在完成更新之前,要对左右两边的缓存进行处理:

if (curItem != null) {//先对左边的item进行缓存处理float extraWidthLeft = 0.0F;itemIndex = curIndex - 1;ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;i = this.getClientWidth();float leftWidthNeeded = i <= 0 ? 0.0F : 2.0F - curItem.widthFactor + (float)this.getPaddingLeft() / (float)i;for(int pos = this.mCurItem - 1; pos >= 0; --pos) {if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {if (ii == null) {break;}// 缓存区间之外的item,要destory掉if (pos == ii.position && !ii.scrolling) {this.mItems.remove(itemIndex);this.mAdapter.destroyItem(this, pos, ii.object);// --->销毁item--itemIndex;--curIndex;ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;}} else if (ii != null && pos == ii.position) {extraWidthLeft += ii.widthFactor;--itemIndex;ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;} else { //如果item没有出现过,就去addNewItemii = this.addNewItem(pos, itemIndex + 1);extraWidthLeft += ii.widthFactor;++curIndex;ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;}}//再对右边的item进行缓存处理float extraWidthRight = curItem.widthFactor;itemIndex = curIndex + 1;if (extraWidthRight < 2.0F) {ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;float rightWidthNeeded = i <= 0 ? 0.0F : (float)this.getPaddingRight() / (float)i + 2.0F;for(int pos = this.mCurItem + 1; pos < N; ++pos) {if (extraWidthRight >= rightWidthNeeded && pos > endPos) {if (ii == null) {break;}if (pos == ii.position && !ii.scrolling) {this.mItems.remove(itemIndex);this.mAdapter.destroyItem(this, pos, ii.object);ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;}} else if (ii != null && pos == ii.position) {extraWidthRight += ii.widthFactor;++itemIndex;ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;} else {ii = this.addNewItem(pos, itemIndex);++itemIndex;extraWidthRight += ii.widthFactor;ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;}}}this.calculatePageOffsets(curItem, curIndex, oldCurInfo);this.mAdapter.setPrimaryItem(this, this.mCurItem, curItem.object); // --->设置当前的item}

处理顺序为:先左右后。缓存区间之外的Item要及时destroyItem销毁掉。当然,如果Item没有出现,就先去addNewItem构建出一个Item来,并保存到mItems中。这个mItems是专门用来缓存的ArrayList:

//mItems是专门用来缓存的ArrayList
private final ArrayList<ViewPager.ItemInfo> mItems = new ArrayList();

泛型为ItemInfo:

    static class ItemInfo {Object object; // --->缓存的就是Viewint position;boolean scrolling;float widthFactor;float offset;ItemInfo() {}}

这个ItemInfo类中有众多信息,其中的 Object object 就是专门用来缓存View的。因此,ViewPager才会去缓存界面。仔细看addNewItem方法中的一行代码:

//ItemInfo中的object就是缓存的View
ii.object = this.mAdapter.instantiateItem(this, position);

想想看Adapter调用instantiateItem是干什么的?同时又返回了什么?
这样一来就解释的通了:当需要构建View的时候,addNewItem函数会通过调用Adapter的instantiateItem方法来创建,return的View会被保存到专门用来缓存的ItemInfo的object中。

如果对方是Fragment,那么观察FragmentPagerAdapter:

   @NonNullpublic Object instantiateItem(@NonNull ViewGroup container, int position) {if (this.mCurTransaction == null) {this.mCurTransaction = this.mFragmentManager.beginTransaction();}long itemId = this.getItemId(position);String name = makeFragmentName(container.getId(), itemId);Fragment fragment = this.mFragmentManager.findFragmentByTag(name);if (fragment != null) {this.mCurTransaction.attach(fragment);} else {fragment = this.getItem(position);this.mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));}if (fragment != this.mCurrentPrimaryItem) {fragment.setMenuVisibility(false);fragment.setUserVisibleHint(false);}return fragment;}

新建并返回、保存的就是fragment了。至此,ViewPager的缓存原理就十分清晰了。

三、Viewpager+Fragment组合使用

缓存有好处,也有不友好的地方。对Fragment而言,如果不进行懒加载处理,那是非常糟糕的。一般对Fragment进行懒加载处理,不能单一的根据setUserVisibleHint或者onResume来判断。Fragment的界面可见状态分为3种:第一次可见、可见、不可见。这两个函数都不能很好的对其进行精准区分。

在这方面,我们需要关心Fragment的几个生命周期函数:onCreateView、onActivityCreated、onResume、onPause、onDestroyView。

onCreateView,最先想到的就是解析xml布局(这也是最根本的),其次就是初始化控件。
onActivityCreated,表示Activity创建完毕。
onResume、onPause和onDestoryView无需多言,常规认识。

关于setUserVisibleHint,它与Fragment的生命周期无关,仅仅是为了表达页面是否对用户可见。还记得FragmentPagerAdapter吗?这个适配器它内部的setPrimaryItem方法是用来设置当前Item是否可见的,方法内调用的就是setUserVisibleHint(true)方法:

关于setUserVisibleHint方法修改Fragment可见性,调用场景如下:

  • ① 切换tab时,会优先于所有Fragment生命周期函数调用
  • ② Fragment之前已经调用过该方法,但是后续要让Fragment的状态在可见与不可见之间切换

setUserVisibleHint(false)表示通知Fragment不可见,即终止一切网络请求!但是如果从未创建过就要怎样怎样,那就会报空指针异常。所以需要一个标志位来判断界面是否已经创建。只有当页面被创建出来了,才会去分发可见性。否则xml都还没加载,分发个球?不报空指针才怪呢。

所以,在setUserVisibleHint方法的处理上,我们根本不用考虑isViewCreated==false的情况。当isViewCreated为true时,我们再根据传进来的可见状态与当前页面可见状态,分发可见性。

    @Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);//只考虑xml加载出来的情况if (isViewCreated) {//根据传进来的状态 和 当前页面的可见性,判断分发是否可见if (isVisibleToUser && !currentVisibleState ) {dispatchUserVisibleHint(true);} else if (!isVisibleToUser && currentVisibleState) {dispatchUserVisibleHint(false);}}}

我们通过dispatchUserVisibleHint方法来统一处理用户可见信息的分发,在分发方法中,再区分是否是第一次可见。因为只有当第一次可见时,才会去主动做一些事的。否则其他情况的可见性,就通知用户可见状态,履行onResume()的职责。

当然,这里还需要用到currentVisibleState表示当前页面可见性,借助mIsFirstVisible 标记区分是否是第一次可见。

    private void dispatchUserVisibleHint(boolean isVisible) {//为了代码严谨if (currentVisibleState == isVisible) {return;}currentVisibleState = isVisible;if (isVisible) {if (mIsFirstVisible) {mIsFirstVisible = false;onFragmentFirstVisible();}onFragmentResume();} else {onFragmentPause();}}

另外,当FragmentTransaction来控制fragment的hide和show时,就会调用onHiddenChanged方法。此时,Fragment的生命周期函数不会再执行,任何生命周期都不会再走了(具体原因点击查看)。在这种情况下,数据就不能根据生命周期函数来判断刷新,此时可以依赖onHiddenChanged方法来继续判断处理:

    @Overridepublic void onHiddenChanged(boolean hidden) {super.onHiddenChanged(hidden);if (hidden) {dispatchUserVisibleHint(false);} else {dispatchUserVisibleHint(true);}}

剩下的工作,就是处理边边角角了。在onResume和onPause中根据是否第一次可见、当前页面可见性、getUserVisibleHint等对是否可见、是否第一次可见进行区分,并以此来分发可见性。

当然,如果考虑Fragment内嵌套Fragment,这里还得再多处理一层,那就是对内层Fragment分发可见性。代码不一样,但是性质差不多。首先要判断其Parent是否可见:

    private boolean isParentInvisible() {Fragment parentFragment = getParentFragment();if (parentFragment instanceof LazyFragment) {LazyFragment fragment = (LazyFragment)parentFragment;return !fragment.isSupportVisible();}return false;}

然后对统一分发进行小改,综合考虑Parent之后,再对Child的可见性向下分发:

    private void dispatchChildVisibleState(boolean visible) {FragmentManager fragmentManager = getChildFragmentManager();List<Fragment> fragments = fragmentManager.getFragments();if (fragments != null) {for (Fragment fragment: fragments) {if (fragment instanceof LazyFragment &&!fragment.isHidden() &&fragment.getUserVisibleHint()) {((LazyFragment)fragment).dispatchUserVisibleHint(visible);}}}}

对ViewPager的理解相关推荐

  1. ViewPager的简单使用说明

    ViewPager的简单使用说明 2013-12-14 00:55 by ...平..淡..., 7 阅读, 0 评论, 收藏, 编辑 前提:工程中使用ViewPager,需要导入google提供的j ...

  2. 关于ViewPager.PageTransformer的一些理解

    今天早上在看hongyang的推送,说已经有了ViewPager2,是google的sample,地址为:https://github.com/googlesamples/android-viewpa ...

  3. 友盟页面统计 - 关于Viewpager中的Fragment的生命周期

    Activity和Fragment各自理论上的生命周期 Activity的生命周期是较为经典也最清晰的,在此不表: Fragment从出现到广泛运用也有一段时间了,其标准生命周期也仅比Activity ...

  4. android view的隐藏和显示_Android使用Viewpager实现3D卡片翻动效果

    作者 |  被代码淹没的小伙子地址 |  https://www.jianshu.com/p/ee8a37ea736d 先看效果图: 效果: 1.竖向的Viewpager 2.3D翻动效果 3.Gli ...

  5. viewpager初始化fragment没有绘制_Fragment在ViewPager中的正确应用(3)FragmentStatePagerAdapter优化了什么...

    前言 Fragment在ViewPager中的正确应用(2)内存泄漏?内存溢出 错误终结者:Fragment在ViewPager中的正确应用 OK,填坑篇的文章来了. 当我打开官方文档准备开始了解Fr ...

  6. ViewPager 详解(三)---PagerTabStrip与PagerTitleStrip添加标题栏的异同

     相关文章: 1.<ViewPager 详解(一)---基本入门> 2.<ViewPager 详解(二)---详解四大函数> 3.<ViewPager 详解(三)-- ...

  7. Android PullToRefreshListView和ViewPager的结合使用

    其实这个不是什么新东西了,在介绍(一)中我们就知道了PullToRefreshListView的用法,这里只要将其放入到ViewPager中就行啦.ViewPager还是和以往一样的定义和使用,在适配 ...

  8. android view 源码分析,Android ViewPager源码详细分析

    1.问题 由于Android Framework源码很庞大,所以读源码必须带着问题来读!没有问题,创造问题再来读!否则很容易迷失在无数的方法与属性之中,最后无功而返. 那么,关于ViewPager有什 ...

  9. ViewPager刷新问题详解

    原文链接:简书diygreen,http://www.jianshu.com/p/266861496508 一.PagerAdapter介绍 先看效果图 PagerAdapter简介 ListView ...

最新文章

  1. 深入理解 Linux 的 epoll 机制
  2. win10定时关机c语言,Win10系统怎么定时关机?Windows10设置定时关机的两种方法
  3. mycat从0到成功进行分表操作
  4. idea工作台输出的日志详解_详解linux下nohup日志输出过大问题解决方案--分批切割...
  5. CCTF部分赛题分析
  6. telnet用法 测试端口号是否可以使用
  7. JavaScript 中的函数介绍
  8. 搭建一个互联网公司后台服务架构及运维架构需要的技术
  9. power bi图表_Power BI中的图表类型概述
  10. Eclipse启动项目报启动上下文失败问题解决方案总结
  11. 双11你玩AR捉猫猫游戏了吗?来看看游戏背后的项目实战经验吧
  12. Windows远程桌面开发之九-虚拟显示器(Windows 10 Indirect Display 虚拟显示器驱动开发)
  13. 订单量排行 php,订单量增速最快B2C电商未来电子商务的趋势网站
  14. lcx 通过端口转发实现内网穿透
  15. 词根词缀 按字母划分
  16. oracle10g dblink优化,dblink如果很慢可以用这种方式优化
  17. #今日论文推荐# 阿里达摩院最新FEDformer,长程时序预测全面超越SOTA | ICML 2022
  18. CCC之I类设备、II类设备、III类设备的对比
  19. cesium 显示纯色地球
  20. MATLAB_LSB_隐藏水印和提取,附代码

热门文章

  1. orcad快捷键_在orcad同一页面的连接关系应该怎么处理呢?
  2. mac 4k分辨率 字太小 27寸 hidpi_4K,就在眼前!视网膜级桌面显示器AOC U2790PQU评测报告...
  3. 如何在 Kubernetes 中对无状态应用进行分批发布
  4. Fortinet“安立方”架构获得NSS Labs BDS 组测试多攻击维度100%检出率佳绩
  5. Macbook pro安装MacOS系统
  6. 谁动了我的主机? 之活用History命令|Linux安全运维
  7. 个人项目中的WCF使用
  8. 百度之星12月30号题目之维基解密
  9. F5 配置手册 -F5 BIG-IP 10.1-2-配置-基本参数
  10. [技术速递]MSDN在线改版,新风格新体验