Android Gems — Fragment本质之View管理

Posted threepigs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 Gems — Fragment本质之View管理的主要内容,如果未能解决你的问题,请参考以下文章

Android Gems — Fragment本质之返回栈和事务管理

Android Gems — Fragment本质之返回栈和事务管理

Android Gems — Fragment本质之生命周期管理

Android Gems — Fragment本质之生命周期管理

Flutter Android 端 Activity/Fragment 流程源码分析

Flutter Android 端 Activity/Fragment 流程源码分析