Android28和fragment问题,深入Android-细说Fragment

工作这么多年了,一直在做笔记,没有发布什么东西,总觉得网上已经有了,就懒得写。

最后想了想,还是从最基础的开始,逐步刨析原理,检验自己的理解程度,希望各路大神讨论指导,如有错误欢迎指正,轻喷!

对了,本文涉及到的源码都是基于Android-28,和AndroidX

回归正题

说到fragment,那我们肯定从生命周期开始说起,我们就一个经典的官方流程图来展示一下fragment的生命周期流程

bVcR9W2

简单说明一下各个生命周期onAttach(Context context):在Fragment和Activity关联上的时候调用,且仅调用一次。在该回调中我们可以将context转化为Activity保存下来,从而避免后期频繁调用getAtivity()获取Activity的局面,避免了在某些情况下getAtivity()为空的异常(Activity和Fragment分离的情况下)。

onCreate:在最初创建Fragment的时候会调用,和Activity的onCreate类似。

onCreateView:在准备绘制Fragment界面时调用,返回值为Fragment要绘制布局的根视图,当然也可以返回null。注意使用inflater构建View时一定要将attachToRoot指明false,因为Fragment会自动将视图添加到container中,attachToRoot为true会重复添加报错。onCreateView并不是一定会被调用,当添加的是没有界面的Fragment就不会调用,比如调用FragmentTransaction的 add(Fragment fragment, String tag)方法。

onActivityCreated:在Activity的onCreated执行完时会调用。

onStart:Fragment对用户可见的时候调用,前提是Activity已经started。

onResume:Fragment和用户之前可交互时会调用,前提是Activity已经resumed。

onPause:Fragment和用户之前不可交互时会调用。

onStop:Fragment不可见时会调用。

onDestroyView:在移除Fragment相关视图层级时调用。

onDestroy:最终清楚Fragment状态时会调用。

onDetach:Fragment和Activity解除关联时调用。

看着是不是有点眼熟?没错!fragment的生命周期和activity生命周期相似,其实fragment的生命周期和activity生命周期是有关联的,在来一张官方图说明一下

bVcR9Xa

这里补充一个,在启动阶段,即onCreate,onStart,onResume的时候,是先调用activity的生命周期在调用fragment的,而暂停关闭阶段,即onpause,onstop,ondestory的时候,是先调fragment生命周期在调用activity的。(个人理解是,activity的生命周期包裹了整个fragment生命周期,所以创建的时候,肯定是先activity,而回收的时候,如果fragment晚于activity,那不就内存泄漏了?)

上面已经简单说明了几个生命周期,我们抛出几个问题一一解答

1.fragment各个生命周期做了什么事?

2.fragment是怎么进行销毁重建的?

3.activity和fragment怎么进行交互?

4.fragment怎么实现懒加载?

我们就围绕这几个问题来了解一下fragment吧。(还有问题后面在编辑添加)

1.fragment各个生命周期做了什么事?

这个问题白问了,回到上面去,已经解答完毕!

2.fragment是怎么进行销毁重建的?

我们知道在某些场景下,我们的Activity是会被回收重建的,Fragment是依附于Activity的,当然也会销毁重建。

而Activity场景重建的方法,主要靠onSaveInstanceState,对页面数据进行状态保存,重建的时候加载保存的数据进行重建。那fragmentne呢?和Activity是一样的吗?

这里列一下fragment常见的2种重建方式

方法1.使用Fragment的setRetainInstance方法保留Fragment实例,我们看一下官方的文档Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). If set, the fragment lifecycle will be slightly different when an activity is recreated:

{@link #onDestroy()} will not be called (but {@link #onDetach()} still will be, because the fragment is being detached from its current activity).

{@link #onCreate(Bundle)} will not be called since the fragment is not being re-created.

{@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} will still be called.

大概的意思是,控制fragment在Activity重建时是否被保留,如果设置了,对应的生命周期调用会有所不同。

翻译软件真是生涩难懂,简单来讲意思就是,在Activity被销毁的时候,Fragment会被保留下来,在下次Activity重建的时候保留的Fragment实例会传递给新的Activity。这里要注意的一点是,这个方法主要是用在设备配置变化,托管的Activity正在被销毁的时候,Fragment的状态才会短暂的被保留,而应用由于内存不足被系统回收的时候,对应的Fragment也会随之被销毁。(很好理解吧,本来内存就需要销毁,那不可能还保留fragment实例来占用内存)

具体原理流程是

当设备配置发生变化时,FragmentManager首先销毁队列中fragment的视图

紧接着,FragmentManager将检查每个fragment的retainInstance属性值;

如果mRetainInstance为true,则保留Fragment本身实例。当新的Activity创建后,新的FragmentManager会找到保留的Fragment,重新创建其视图;

如果mRetainInstance为false,FragmentManager会直接销毁Fragment实例,当新的Activity创建后,新的FragmentManager会创建一个新的Fragment

这里扩展一下,因为我们的Fragment实例保留了,所以我们在Activity重建的时候不能重新创建实例,否则会有两个Fragment实例而出现问题。这时候可以给Fragment设置一个Tag,通过Tag获取Fragment,不存在才进行创建。

方法2.FragmentActivity对Fragment的重建

我们知道,Activity在被在被销毁的时候会调用onSaveInstanceState(Bundle outState)进行状态的保存,FragmentActivity继承于Activity,我们看看FragmentActivity的onSaveInstanceState(Bundle outState)做了什么@Override

protected void onSaveInstanceState(@NonNull Bundle outState) {

super.onSaveInstanceState(outState);

markFragmentsCreated();

mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);

//保存所有状态,返回FragmentManagerState实例

Parcelable p = mFragments.saveAllState();

if (p != null) {

outState.putParcelable(FRAGMENTS_TAG, p);

}

if (mPendingFragmentActivityResults.size() > 0) {

outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);

int[] requestCodes = new int[mPendingFragmentActivityResults.size()];

String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];

for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {

requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);

fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);

}

outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);

outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);

}

}

从代码可以看出,在销毁之前,FragmentActivity会保存队列中所有Fragment的状态,具体在Parcelable p = mFragments.saveAllState()里面,感兴趣可以点进去仔细看看,我下面就说个大概,不想写太冗长就不贴代码了

我们看到保存的方法返回的Parcelable实际是FragmentManagerState实例,FragmentManagerState包含了成员FragmentState,FragmentState包含描述Fragment的各个数据变量,足够从零重新创建一个被销毁的Fragment

在保存了Fragment数据之后,Activity实例以及没有调用setRetainInstance(true)的Fragment实例都被销毁。

在Activity重建的时候,FragmentActivity通过调用FragmentManager的restoreAllState方法,重建之前保存下来并被销毁的Fragment

这样Fragment就重建完成了。

3.Activity和Fragment怎么进行交互?

这里我们分成几个部分,Activity调用Fragment,Fragment调用Activity,Fragment调用Fragment

1.Activity调用Fragment

这个其实有很多种方式,通过Bundle进行初始传值,通过Fragment的实例调用Fragment的方法

甚至还可以通过Fragment的构造函数直接传值(这里不建议,因为Fragment重建时调用的是无参构造函数,传递的数据可能丢失)

那应该怎么做呢?废话不多说,上个简单的demo代码public class DemoFragment extends Fragment {

public DemoFragment() {

}

//采用这种传参方式可以保证用户在横竖屏切换时传递的参数不会丢失

public static DemoFragment getInstance(String data){

DemoFragment demoFragment = new DemoFragment();

Bundle bundle = new Bundle();

bundle.putString("data",data);

demoFragment.setArguments(bundle);

return demoFragment;

}

}

我们建议初始化传值的时候,我们创建一个方法用户接收参数,然后进行创建并且通过bundle传值。

2.Fragment调用Activity

这个调用方法就有很多种了,通过接口进行回调传值,通过强转获取Activity实例传值,通过广播EventBus进行传值

我们重点说下接口回调传值吧,其他方式都有一定弊端,代码不一定优雅

接口交互主要时在Fragment中定义一个接口,让宿主Activity进行回调监听,上代码public class DemoFragment extends Fragment implements View.OnClickListener{

Button demoButton;

public DemoFragment() {

}

//采用这种传参方式可以保证用户在横竖屏切换时传递的参数不会丢失

public static DemoFragment getInstance(String data){

DemoFragment demoFragment = new DemoFragment();

Bundle bundle = new Bundle();

bundle.putString("data",data);

demoFragment.setArguments(bundle);

return demoFragment;

}

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//通过getActivity()获取用于回调修改文本方法的接口

callBackListener= (CallBackListener) getActivity();

}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

View view =inflater.inflate(R.layout.demo_layout,container,false);

demoButton = (Button) view.findViewById(R.id.demoButton);

demoButton.setOnClickListener(this);//为按钮设置监听事件

return view;

}

@Override

public void onClick(View v) {

switch (v.getId()){

case R.id.panhouye:

callBackListener.setData("data");

break;

}

//定义一个接口

public static interface CallBackListener{

public void setData(String data);

}

}

public class MainActivity extends AppCompatActivity implements DemoFragment.CallBackListener{

FragmentManager fragmentManager;

FragmentTransaction fragmentTransaction;

DemoFragment demoFragment;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//初始化主布局(主要目的是为主布局填充fragments)

initActivity();

}

private void initActivity() {

fragmentManager = getFragmentManager();

fragmentTransaction = fragmentManager.beginTransaction();

demoFragment = new DemoFragment();

fragmentTransaction.add(R.id.demo,DemoFragment);

fragmentTransaction.commit();

}

//接口实现方法,用于回调RightFragment类中定义的修改文本的方法

@Override

public void setData(String data) {

//这里接收到值进行处理

}

}

3.Fragment调用Fragment

Fragment和Fragment之间需要进行传值,要通过宿主Activity,我们可以结合上面两种,通过回调通知Activity,然后Activity再去调用Fragment的方法进行调用。

这里提一句,我们还可以使用JetPack的LiveData,多个Fragment获取宿主Activity的ViewModel,拿到同一个LiveData,然后就可以进行数值传递通知了,具体的内容等讲到了JetPack的时候在细说。

4.Fragment怎么实现懒加载?

一般提到这个问题,使用的是ViewPager+Fragment的页面架构。我们知道ViewPager默认会预加载当前Fragment前一个和后一个的数据。这会存在两个问题,我们前面和后面的页面还没展示,但是就进行数据加载请求,造成了数据流量的浪费;假如我们做了数据埋点上报,这时候没有展示的数据就进行上报,不符合需求。

解决方法有两种,一种是禁用ViewPager禁止预加载,这里不进行说明了,简单也不是我们主要想说的。

另外一种就是使用懒加载,在旧版本的包里,是通过setUserVisibleHint()来实现懒加载的,网上的资源较多,感兴趣可以自己去看下。

在androidX中,我们使用setUserVisibleHint()会发现改方法已经弃用了。那要怎么实现懒加载呢?我们看下方法的备注,一般弃用会告诉我们替代方式的/**

*

* @deprecated Use {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)}

* instead.

*/

@Deprecated

public void setUserVisibleHint(boolean isVisibleToUser) {

...

}

可以看到,告诉我们可以使用FragmentTransaction的setMaxLifecycle方法来替代,那么setMaxLifecycle是什么呢?我们看下源码/**

* Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is

* already above the received state, it will be forced down to the correct state.

*

*

The fragment provided must currently be added to the FragmentManager to have it's

* Lifecycle state capped, or previously added as part of this transaction. The

* {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise

* an {@link IllegalArgumentException} will be thrown.

*

* @param fragment the fragment to have it's state capped.

* @param state the ceiling state for the fragment.

* @return the same FragmentTransaction instance

*/

@NonNull

public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,

@NonNull Lifecycle.State state) {

addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));

return this;

}

这里其实很简单,就是将操作封装成一个op对象,最后在commit()的时候,根据op对象执行响应的操作,具体代码比较简单也不贴了,可以自己去看看

上面setMaxLifecycle()注释大概的意思是设置Fragment生命周期的上限,如果超过上线会被强制降到相应的状态。具体该怎么去理解呢?我们这里先去弄清楚两个东西Fragment状态和LifeCycle状态

Fragment状态

我们在Fragment类中,可以看到几个常量,用来表示了Fragment的状态static final int INITIALIZING = 0; // Not yet created.

static final int CREATED = 1; // Created.

static final int ACTIVITY_CREATED = 2; // Fully created, not started.

static final int STARTED = 3; // Created and started, not resumed.

static final int RESUMED = 4; // Created started and resumed.

LifeCycle状态

LifeCycle是JetPack中的架构组件之一,主要是用于管理Activity和Fragment生命周期的,具体的一些详细介绍和使用后面在补一篇,网上也有很多教程,不了解的可以去了解一下

源码中也可以看到,LifeCycle中使用枚举来定义状态public enum State {

DESTROYED,

INITIALIZED,

CREATED,

STARTED,

RESUMED;

public boolean isAtLeast(@NonNull State state) {

return compareTo(state) >= 0;

}

}

这时候我们回到setMaxLifecycle(),方法需要传递两个参数fragment和state。fragment不用多说,就是要设置的目标Fragment,不过需要注意的是此时Fragment必须已经被添加到了FragmentManager中,也就是调用了add()方法,否则会抛出异常。state就是LifeCycle中的枚举类型State,同样需要注意传入的state应该至少为CREATED,换句话说就是只能传入CREATED、STARTED和RESUMED,否则同样会抛出异常。

这里我们应该先理解一下Fragment状态和生命周期的关系,上表格Fragment状态过程生命周期INITIALIZING --> CREATEDonAttach, onCreate

CREATED --> INITIALIZINGonDetach, onDestory

CREATED --> ACTIVITY_CREATEDonCreateView, onActivityCreated

ACTIVITY_CREATED --> CREATEDonDestroyView

ACTIVITY_CREATED --> STARTEDonStart

STARTED --> ACTIVITY_CREATEDonStop

STARTED --> RESUMEDonResume

RESUMED --> STARTEDonPause

上面表格展示的是,各个状态切换时会调用的生命周期

这时候应该疑问了,Fragment和Lifecycle的状态是怎么关联的呢?由于State至少传入CREATED,所以我们就只要研究3个状态就好了,同样来个表格传入的Lifecycle状态Fragment状态Lifecycle.State.CREATEDLifecycle.State.CREATED对应Fragment的CREATED状态,如果当前Fragment状态低于CREATED,也就是INITIALIZING,那么Fragment的状态会变为CREATED,依次执行onAttach()、onCreate()方法;如果当前Fragment状态高于CREATED,那么Fragment的状态会被强制降为CREATED,以当前Fragment状态为RESUMED为例,接下来会依次执行onPause()、onStop()和onDestoryView()方法。如果当前Fragment的状态恰好为CREATED,那么就什么都不做。

Lifecycle.State.STARTEDLifecycle.State.STARTED对应Fragment的STARTED状态,如果当前Fragment状态低于STARTED,那么Fragment的状态会变为STARTED,以当前Fragment状态为CREATED为例,接下来会依次执行onCreateView()、onActivityCreate()和onStart()方法;如果当前Fragment状态高于STARTED,也就是RESUMED,那么Fragment的状态会被强制降为STARTED,接下来会执行onPause()方法。如果当前Fragment的状态恰好为STARTED,那么就什么都不做。

Lifecycle.State.RESUMEDLifecycle.State.RESUMED对应Fragment的RESUMED状态,如果当前Fragment状态低于RESUMED,那么Fragment的状态会变为RESUMED,以当前Fragment状态为STARTED为例,接下来会执行onResume()方法。如果当前Fragment的状态恰好为RESUMED,那么就什么都不做。

不知道大家看懂了没有,没看懂就多看几次,或者自己手动写一下demo,看看生命周期怎么打印的,多动手总不会错的。

现在回到问题,ViewPager的懒加载该怎么做?

其实Android提出了使用setMaxLifecycle的新方案,那么肯定会告诉我们该怎么做,那突破口在哪里呢?不知道大家在使用ViewPager的时候,有没有发现FragmentPagerAdapter的构造方法已经过时了,这个就是突破口,我们看下源码/**

* Constructor for {@link FragmentPagerAdapter} that sets the fragment manager for the adapter.

* This is the equivalent of calling {@link #FragmentPagerAdapter(FragmentManager, int)} and

* passing in {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.

*

*

Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the

* current Fragment changes.

*

* @param fm fragment manager that will interact with this adapter

* @deprecated use {@link #FragmentPagerAdapter(FragmentManager, int)} with

* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}

*/

@Deprecated

public FragmentPagerAdapter(@NonNull FragmentManager fm) {

this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);

}

这里告诉我们,要使用新的构造函数,传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

我们继续看构造方法,这里面又默认将BEHAVIOR_SET_USER_VISIBLE_HINT赋值给了mBehaviorpublic FragmentPagerAdapter(@NonNull FragmentManager fm,

@Behavior int behavior) {

mFragmentManager = fm;

mBehavior = behavior;

}

我们看看mBehavior在什么地方使用了呢?

全局查找了一下,找到两个地方instantiateItem()和setPrimaryItem()方法。

instantiateItem()主要用来初始化Viewpager每个Item的方法。

setPrimaryItem()我们简单看一下源码,发现主要是用来设置即将显示的item,ViewPager切换的时候都会调用。源码上一下public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {

Fragment fragment = (Fragment)object;

if (fragment != mCurrentPrimaryItem) {

if (mCurrentPrimaryItem != null) {

mCurrentPrimaryItem.setMenuVisibility(false);

if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

if (mCurTransaction == null) {

mCurTransaction = mFragmentManager.beginTransaction();

}

mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);

} else {

mCurrentPrimaryItem.setUserVisibleHint(false);

}

}

fragment.setMenuVisibility(true);

if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

if (mCurTransaction == null) {

mCurTransaction = mFragmentManager.beginTransaction();

}

mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);

} else {

fragment.setUserVisibleHint(true);

}

mCurrentPrimaryItem = fragment;

}

}

方法的逻辑还是很简单的,如果mBehavior的值为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,那么就调用setMaxLifecycle()方法将上一个Fragment的状态设置为STARTED,将当前要显示的Fragment的状态设置为RESUMED;反之如果mBehavior的值为BEHAVIOR_SET_USER_VISIBLE_HINT,那么依然使用setUserVisibleHint()方法设置Fragment的可见性,相应地可以根据getUserVisibleHint()方法获取到Fragment是否可见,从而实现懒加载,具体做法我就不说了。

这里我们结合上面的表格,其实我们可以发现,每次Fragment由不可见变为可见都会执行onResume()方法

同时如果我们使用的是FragmentPagerAdapter,切换导致Fragment被销毁时是不会执行onDestory()和onDetach()方法的,只会执行到onDestroyView()方法。

那应该知道怎么做了吧?代码来了public abstract class BaseLazyFragment extends Fragment {

//懒加载调用只有第一次显示的时候才进行调用

private boolean isFirstLoad = true;

@Override

public void onResume() {

super.onResume();

if (isFirstLoad) {

isFirstLoad = false;

lazyInit();

}

}

@Override

public void onDestroyView() {

super.onDestroyView();

isFirstLoad = true;

}

//初始化方法

public abstract void lazyInit();

}

总结

这里我们主要介绍了Fragment各个生命周期的作用,以及Fragment在使用过程中基本上肯定会遇到的问题,其实还有基于JetPack的LiveData进行Fragment间的数据共享没有降讲,准备放在JetPack文章中在进行细说

一般使用Fragment,我们关心的也就这几个点,1.数据传递,2.销毁重建,3.懒加载。本文就简单的讲了一下,一般是足够进行使用了,虽然很多细节的没有讲,后续会陆续补上。

最后说一句,本人技术不精,如有错漏,欢迎指正交流,轻喷!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值