Android(安卓)Gems — Fragment本质之View管理
上篇文章我们讲了Fragment的生命周期管理,对Fragment整个运行机制都会比较清楚了,这节我们分析Fragment的View管理。
一,Fragment的两种定义方式
1,在layout xml里通过fragment标签定义,这种方式定义的fragment是由LayoutInflater在解析xml文件的时候创建。在Activity的onCreate方法里,会通过setContentView方法设置layout id,继而调用LayoutInflater的inflate方法解析xml。LayoutInflater在解析xml的时候,就会实例化出Fragment。
2,Fragment还可以通过代码动态的new Fragment实例,通过FragmentTransaction的add/replace方法随时添加,上篇文章已经介绍过FragmentManager的addFragment流程。
二,Fragment的fragmentId、containerId、tag的意义
fragmentId和tag都是用来标识fragment自身的,containerId是fragment的View的父控件的id,是View的容器。静态和动态两种Fragment的containerId、fragmentId、tag的生成方式也是不一样的。
1,代码动态创建的Fragment
FragmentTransaction的add和replace都有containerViewId和tag参数,containerViewId就是fragment的containerId,并且fragmentId也同时会设置成和containerId一样。从这点也可以看出如果往同一个ViewGroup里add两个不同的Fragment,那么他们的fragmentId是一样的。fragmentId一样的话,会影响FragmentManager的findFragmentById的结果,这个方法只会返回第一个fragmentId等于目标id的Fragment。tag和fragmentId一样,都是用来标识Fragment自身,如果两个fragment的tag相同,则会影响findFragmentByTag的结果。
2,Layout xml里定义的Fragment
Layout xml方式的fragment的实例化流程是: Activity.onCreate -> Activity.setContentView -> PhoneWindow.setContentView -> LayouInflater.inflate->LayoutInflater.createViewFromTag -> Activity.onCreateView -> FragmentManager.onCreateView,因此在Activity的onCreate的时候会开始Fragment的创建。我们看看FragmentManager的onCreateView方法:
@Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { String fname = attrs.getAttributeValue(null, "class"); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment); if (fname == null) { fname = a.getString(com.android.internal.R.styleable.Fragment_name); } int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID); String tag = a.getString(com.android.internal.R.styleable.Fragment_tag); a.recycle(); int containerId = parent != null ? parent.getId() : 0; Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null; if (fragment == null && tag != null) { fragment = findFragmentByTag(tag); } if (fragment == null && containerId != View.NO_ID) { fragment = findFragmentById(containerId); } if (fragment == null) { fragment = Fragment.instantiate(context, fname); fragment.mFromLayout = true; fragment.mFragmentId = id != 0 ? id : containerId; fragment.mContainerId = containerId; fragment.mTag = tag; fragment.mInLayout = true; fragment.mFragmentManager = this; fragment.mHost = mHost; fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); addFragment(fragment, true); } if (mCurState < Fragment.CREATED && fragment.mFromLayout) { moveToState(fragment, Fragment.CREATED, 0, 0, false); } else { moveToState(fragment); } if (id != 0) { fragment.mView.setId(id); } if (fragment.mView.getTag() == null) { fragment.mView.setTag(tag); } return fragment.mView; }
FragmentManager的onCreateView只会处理fragment的tag。系统定义了Fragment的style,这个style里包含了三个属性attr: android:name,android:tag,android:id。android:name指定fragment的类名,Layout xml定义的fragment里,优先使用class属性作为类名,没有的话就看android:name属性,后面实例化的时候会使用这个fname作为类名。android:id是指定的fragmentId,android:tag是指定的fragment的tag,Fragment的mContainerId则会使用fragment标签所在的ViewGroup的id作为其值。我们后续可以看到,View将会被add到ViewGroup里,而这个ViewGroup则是通过findViewById(mContainerId)获得,所以不要随意的指定一个无效的containerId,这样后面在找ViewGroup的时候会抛异常。 接着就是实例化Fragment了,优先会通过id和tag从mActive列表里查找是否有已经实例化的fragment,这个逻辑主要是进程被杀后,恢复Fragment状态的时候会走到。case就是当进程被杀再启动之后,由于被杀之前FragmentManager会将其内部的fragment列表的状态都save,等下次启动的时候,在调用Fragment的dispatchCreate之前,会调用restoreAllState方法将之前save的mActive等fragment队列都恢复。之后Layout xml定义的fragment再onCreateView的时候就不用再创建新的实例了,所以才会先通过findFragmentByTag和findFragmentById先查找fragment实例。不过一般情况下,这里都是查不到的,就会调用Fragment.instantiate实例化Fragment,并且将mFromLayout设为true,表明是从Layout xml里定义的。之后fragmentId,containerId,tag都被赋值,addFragment到FragmentManager之后,就完成Fragment的实例化。
三,Fragment的View创建
1,Layout xml定义的Fragment
Layout xml定义的Fragment的实例化时机是Activity onCreate的调用FragmentManager的onCreateView,我们接着上面的源码分析。当Fragment被实例化,并且addFragment加入之后,接着就会调用moveToState将Fragment的状态和FragmentManager的curState状态进行同步。而我们知道Fragment初始化时的状态是INITIALIZING状态,这里moveToState,就将Fragment的状态从INITIALIZING状态,提升至CREATED状态或者更高(这主要看Activity当前的生命周期走到哪一步了)。那么我们可以看INITIALIZING->CREATED的状态切换代码:
case Fragment.INITIALIZING: if (f.mSavedFragmentState != null) { // 这是Fragment状态恢复相关的,暂时不分析 } f.mHost = mHost; f.mParentFragment = mParent; f.mFragmentManager = mParent != null ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); f.mCalled = false; // 执行Fragment的onAttach生命周期 f.onAttach(mHost.getContext()); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onAttach()"); } if (f.mParentFragment == null) { mHost.onAttachFragment(f); } // 执行Fragment的onCreate生命周期 if (!f.mRetaining) { f.performCreate(f.mSavedFragmentState); } f.mRetaining = false if (f.mFromLayout) { // 如果Fragment是在layout文件里定义的,那么就在这时执行Fragment的onCreateView生命周期 f.mView = f.performCreateView(f.getLayoutInflater( f.mSavedFragmentState), null, f.mSavedFragmentState); if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); if (f.mHidden) f.mView.setVisibility(View.GONE); f.onViewCreated(f.mView, f.mSavedFragmentState); } }
之前已经看到了,Layout xml定义的fragment实例化的时候,mFromLayout为true,因此在moveToState的时候,就将调用performCreateView创建View。虽然此时View已经创建,但是并没有addView到container里,所以UI上是不可见的。得等到state进入到ACTIVITY_CREATED/STARTED状态之后才会addView,也就是说CREATED状态的Fragment的UI元素还未加到View Tree,用户无感知。 2,代码动态创建的Fragment
代码动态创建、加入的Fragment,会通过addFragment加到FragmentManager里面,而此时的mFromLayout并不为true,在CREATED之后即ACTIVITY_CREATED和STARTED时才会完成View的创建:
case Fragment.CREATED: if (newState > Fragment.CREATED) { if (!f.mFromLayout) { // 代码添加的Fragment,而不是layout文件定义的 ViewGroup container = null; if (f.mContainerId != 0) { // 找到Fragment的Container,用于addView container = (ViewGroup)mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { throwException(new IllegalArgumentException()); } } f.mContainer = container; // 执行Fragment的onCreateView的生命周期 f.mView = f.performCreateView(f.getLayoutInflaterf.mSavedFragmentState), container, f.mSavedFragmentState); if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); if (container != null) { container.addView(f.mView); } if (f.mHidden) f.mView.setVisibility(View.GONE); f.onViewCreated(f.mView, f.mSavedFragmentState); } } // 执行Fragment的onActivityCreated生命周期 f.performActivityCreated(f.mSavedFragmentState); if (f.mView != null) { f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; }
从这段代码就能看到,fragment这时会performCreateView创建View,并且通过containerId找到ViewGroup来装这个View,这里就比较清楚了,containerId是很重要的,千万不要乱给,否则直接抛异常。 分析到这里就比较清楚了,Fragment的View创建时机两种方式定义的Fragment是不一样的,但View被加到View Tree的时机则是一样的,都是在CREATED状态之后。
四,Fragment的View销毁
View销毁比创建要更简单一点,两种Fragment的时机都是一样的,都是Fragment的状态被降低到CREATED的时候:
case Fragment.STOPPED: case Fragment.ACTIVITY_CREATED: if (newState < Fragment.ACTIVITY_CREATED) { if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) { saveFragmentViewState(f); } // 执行Fragment的onDestroyView生命周期 f.performDestroyView(); if (f.mView != null && f.mContainer != null) { if (anim != null) { final ViewGroup container = f.mContainer; final View view = f.mView; final Fragment fragment = f; container.startViewTransition(view); f.mAnimatingAway = anim; f.mStateAfterAnimating = newState; ...... // 省略的部分是删除动画,这里不展开分析 } f.mContainer.removeView(f.mView); } f.mContainer = null; f.mView = null; }
上面的代码就比较显然了,Fragment状态从STOPPED/ACTIVITY_CREATED状态变成CREATED的时候就会performDestroyView,接着将Fragment的View从mContainer里删除,这样UI元素就从View Tree里删除了。这和View创建也高度的吻合,CREATED状态的Fragment只是个就绪状态的 Fragment,UI元素是不可见的。而我们总结一下 CREATED状态的Fragment有哪些case: 1,Fragment刚实例化的时候,曾经很短暂的时间是CREATED状态,因为ACTIVITY_CREATED状态是紧接着CREATED的,中间几乎没有间隔。 2,Fragment被detach了,detach了之后Fragment的状态就转为CREATED 3,Fragment被remove了,但其还在Back Stack里,那么removeFragment就不会将其彻底删除,还继续保留在mActive队列里,并且状态是CREATED而非INITIALIZING。 至此,Fragment的View管理就分析完了,整个过程还是比较简单的。
作者简介: 田力,网易彩票Android端创始人,小米视频创始人,现任roobo技术经理、视频云技术总监 欢迎关注微信公众号 磨剑石,定期推送技术心得以及源码分析等文章,谢谢
更多相关文章
- Android常用功能实例----(十一)小功能(获取IMEI|手机号等)
- AndroidStudio实现按钮按下时状态改变以及选择器属性及基本用法
- Android(安卓)ScrollView 中放入 ImageView 的出现上下空白
- StatusBar (状态栏)的架构(Android(安卓)2.3)
- 自定义控件:onDraw 方法实现仿 iOS 的开关效果
- android扁平化界面设计—在android中使用Font-Awesome
- Android之三种实现自定义ProgressBar的方式
- 自定义Android注解Part3:绑定
- android binder机制之——(创建binder服务)