Android技能树 — Fragment总体小结,2024年最新腾讯面试gm

简介: Android技能树 — Fragment总体小结,2024年最新腾讯面试gm
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>


2. 在代码中动态添加,比如我们添加到一个LinearLayout中:

Button button = new Button(mActivity);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
button.setLayoutParams(params);
container.addView(button);

所以Fragment也很简单,就把它当做一个简单的View(但其实更像是“子 Activity”),然后添加方式也是一样。

1. 直接在Layout.xml中添加:

<?xml version="1.0" encoding="utf-8"?>

2. 直接在代码中添加:

Fragment one = new FragmentOne();//自定义的Fragment类
//要先获取FragmentManager对象
FragmentManager fragmentManager = getSupportFragmentManager();
//开启一个FragmentTransaction事务
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.framelayout_view,one).commit();

其中添加到R.id.framelayout_view的这个idViewGourp可以是<FrameLayout/>,也可以是其他的比如<LinearLayout/>等。


2. Fragment的基本操作


看到我们上面的动态代码添加的时候需要获取FragmentTransactionFragmentManager

2.1 FragmentManager相关

1. getFragmentManager():

获取Fragment父容器的管理器,但是现在该方法在Activity中已经被标记不推荐使用了。

/**
• Return the FragmentManager for interacting with fragments associated
• with this activity.
• @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}
*/
@Deprecated
public FragmentManager getFragmentManager() {
return mFragments.getFragmentManager();
}

2. getSupportFragmentManager():

v4包下的这个方法,与上一个效果一样,不过是Android推荐使用的方法(毕竟可以兼容Android所有版本)

3. getChildFragmentManager():

我们提过,Fragment更像是一个“子Activity”,那你说"子Activity"中能否再添加Fragment,答案当然是可以。那么在Fragment内部中的Fragment的管理器,就需要使用getChildFragmentManager()来获取了。

2.2 FragmentTransaction相关


我们可以看到有添加删除各种方法操作。

1. attach/detach方法:

  • detach(Fragment fragment) : 分离指定Fragment的UI视图
  • attach(Fragment fragment) : 重新关联一个Fragment(当这个Fragment的detach执行之后)
  1. 当Fragment被detach后,Fragment的生命周期执行完onDestroyView就终止了,这意味着Fragment的实例并没有被销毁,只是UI界面被移除了(注意和remove是有区别的)。
  2. 当Fragment被detach后,执行attach操作,会让Fragment从onCreateView开始执行,一直执行到onResume。
  3. attach无法像add一样单独使用,单独使用会抛异常。方法存在的意义是对detach后的Fragment进行界面恢复。

2.add/remove方法:

我想这二个是用的最多的了,add()和remove()是将fragment添加和移除. remove()比detach()要彻底一些, 如果不加入到回退栈中, remove()的时候, fragment的生命周期会一直走到onDetach();如果加入了回退栈,则会只执行到onDestoryView(),Fragment对象还是存在的。

add一个fragment,如果加到的是同一个id的话,有点像我们的Activity栈,启动多个Activity时候,Activity一个个叠在上面,fragment也是类似,一个个fragment叠在上面。

3.replace方法:

replace = remove + add , 所以可以理解为先把相同id下的Fragment移除掉,然后再加入这个当前的fragment。

所以如果你觉得Fragment存在太多,影响性能,可以用replace来切换各个界面,就可以保证当前只有一个Fragment,但是因为每次切换后,Fragment都会重建,所以如果这个界面有网络请求相关的,你就会发现这个界面又重新去请求网络接口了,显得很多此一举。

4.hide/show方法:

就是字面意思,让一个Fragment隐藏,让一个Fragment显示。你可以理解为Button设置了View.GONE和View.VISIBLE。常常配合有多个Fragment及有TAB等切换方式的时候,选中某个按钮,然后根据相应的让对应的Fragment显示,其他Fragment隐藏。

5.commit/commitAllowingStateLoss:

我估计很多人认识这个commitAllowingStateLoss大部分是因为自己的代码有闪退异常:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

在你离开当前Activity等情况下,系统会调用onSaveInstanceState()帮你保存当前Activity的状态、数据等,直到再回到该Activity之前(onResume()之前),你执行Fragment事务,就会抛出该异常。然后网上有很多教程,叫你提交的时候使用commitAllowingStateLoss()方法,虽然说不会抛出错误,但是如果在Activity已经保存状态完之后提交了它,到时候Ativity意外崩溃,再恢复数据的时候就不会恢复在Activity保存状态之后提交的fragment的更新,造成状态丢失了。

额外补充:

1.commit()方法并不立即执行transaction中包含的动作,而是把它加入到UI线程队列中. 如果想要立即执行,可以在commit之后立即调用FragmentManager的executePendingTransactions()方法.

2. commit()方法必须在状态存储之前调用,否则会抛出异常,如果觉得状态丢失没关系, 可以调用commitAllowingStateLoss(). 但是除非万不得已, 一般不推荐用这个方法, 会掩盖很多错误.

6. addToBackStack:

我们可以看到FragmentTransaction里面有加入回退栈方法,但是没有退出的方法:popBackStack。这是因为这个方法在FragmentManager里面。

也就是如下图:


一般反应是,addToBackStack和popBackStack不是应该像上面的类似add和remove一样,都一个层级的吗??所以popBackStack不也应该是FragmentTransaction下的一个方法???

所以我们单从图片所示就能知道,popBackStackFragmentTransaction是一个层级,所以popBackStack操作的其实也是《fragment事务》(FragmentTransaction),所以可以理解为addToBackStack把我们前面的FragmentTransaction事务(比如add,remove,replace等一系列操作)加入到了回退栈(!!!记住不是把fragment加入到了回退栈),而popBackStack是操作回退栈里面的事务。

当然具体的源码过程分析,细讲的话又是很多,都可以另外专门写一篇文章,所以直接借鉴网上别人已经写好的文章:

Fragment那点事①Fragment栈管理

额外补充:

1.加入回退栈:remove掉的fragment执行onDestoryView,并没有执行onDestory,fragment实例对象还是存在,当回退时候,fragment从onCreateView处执行

2. 未加入回退栈:remove掉的fragment 执行 onDestoryView和onDestory,彻底销毁移除


3.Fragment中获取Context


我们可以直接在fragment代码里面直接使用getActivity()getContext()方法。

但是有时候获取为空,所以一般我们使用的是:

Class xxxFragment extends Fragment {
private Context mContext;
//‘高版本后,都是回调这个方法’
@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
//‘API低于 23 的版本的时候,是会回调这个方法’
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mContext = activity;
}
}

4.Fragment配合ViewPager


ViewPager配合Fragment的时候,主要使用FragmentPagerAdapterFragmentStatePagerAdapter这二个Adapter。其实使用很简单(一般的最最简单的写法):

public class FragmentAdapter extends FragmentPagerAdapter{
private ArrayList list;
//通过构造获取fragment集合
public Fragment_pager(FragmentManager fm,ArrayList list) {
super(fm);
this.list=list;
}
//设置具体position的fragment
@Override
public Fragment getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
//设置有多少个fragment
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
}

然后ViewPager.setAdapter(xxxx);

但是大家会奇怪为啥有二个Adapter:FragmentPagerAdapterFragmentStatePagerAdapter,他们的区别我们可以看具体的源码:

FragmentPagerAdapter源码:

public abstract class FragmentPagerAdapter extends PagerAdapter {
//‘初始化创建Item:’
@NonNull
public 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) {
//‘后面使用fragment是通过FragmentTransaction.attach方式加进来的,’
//‘只是重新绘制了UI,fragment对象还在。’
this.mCurTransaction.attach(fragment);
} else {
//‘我们知道刚返回fragment使用的是getItem(position)方法’
//‘我们可以看到第一次使用fragment是通过FragmentTransaction.add方式加进来的’
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;
}
//‘销毁item:’
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}
//‘我们可以看到FragmentTransaction只是单纯的detach了fragment,视图不在了,但是fragment对象还在’
this.mCurTransaction.detach((Fragment)object);
}
}

我们可以看到fragment并没有真的销毁,FragmentPageAdapter则适用于固定的,少量的Fragment情况,例如和TabLayout共同使用时。

FragmentStatePagerAdapter源码:

public abstract class FragmentStatePagerAdapter extends PagerAdapter {
@NonNull
public Object instantiateItem(@NonNull ViewGroup container, int position) {
Fragment fragment;
if (this.mFragments.size() > position) {
fragment = (Fragment)this.mFragments.get(position);
if (fragment != null) {
return fragment;
}
}
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}
fragment = this.getItem(position);
if (this.mSavedState.size() > position) {
SavedState fss = (SavedState)this.mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while(this.mFragments.size() <= position) {
this.mFragments.add((Object)null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
this.mFragments.set(position, fragment);
//‘我们可以看到fragment都是add进来的’
this.mCurTransaction.add(container.getId(), fragment);
return fragment;
}
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (this.mCurTransaction == null) {
this.mCurTransaction = this.mFragmentManager.beginTransaction();
}
while(this.mSavedState.size() <= position) {
this.mSavedState.add((Object)null);
}
this.mSavedState.set(position, fragment.isAdded() ? this.mFragmentManager.saveFragmentInstanceState(fragment) : null);
this.mFragments.set(position, (Object)null);
//‘可以看到都是通过remove的方式移除了’
this.mCurTransaction.remove(fragment);
}
相关文章
|
2天前
|
Android开发
Android面试题之activity启动流程
该文探讨了Android应用启动和Activity管理服务(AMS)的工作原理。从Launcher启动应用开始,涉及Binder机制、AMS回调、进程创建、Application和Activity的生命周期。文中详细阐述了AMS处理流程,包括创建ClassLoader、加载APK、启动Activity的步骤,以及权限校验和启动模式判断。此外,还补充了activity启动流程中AMS的部分细节。欲了解更多内容,可关注公众号“AntDream”。
6 1
|
2天前
|
vr&ar 数据库 Android开发
Android面试题之ActivityManagerService的启动流程
本文探讨了Android系统的SystemServer启动过程,包括创建SystemContext、引导服务、启动各类核心服务以及AMS的启动和初始化。AMS负责管理activity、广播队列、provider等,并设置SystemProcess,安装系统Provider。当AMS调用SystemReady时,系统UI准备启动,启动Launcher。文中还对比了init、zygote和system_server进程的角色。最后推荐了两本关于Android内核剖析的书籍:柯元旦教授的《Android内核剖析》和罗升阳的《Android系统源代码情景分析》。关注公众号AntDream获取更多内容。
4 0
|
3天前
|
JSON 安全 调度
Android面试题之Kotlin协程一文搞定
本文介绍了协程的基础知识,强调它是轻量级线程,用于处理耗时任务而不阻塞主线程,确保主线程安全。协程特点包括使异步逻辑同步化,并允许函数挂起和恢复。挂起函数由`suspend`关键字标识,只能在协程内部调用。挂起与阻塞的主要区别在于挂起不会导致主线程ANR。 结构化并发和协程作用域(如`CoroutineScope`、`GlobalScope`、`MainScope`等)提供了任务管理,文章还探讨了并发、启动模式、协程取消、超时任务以及资源释放等主题。
13 0
|
3天前
|
存储 Java 调度
Android面试题之Kotlin协程到底是什么?它是线程吗?
本文探讨了协程与线程的区别,指出协程并非线程,而是轻量级的线程替代。协程轻量体现在它们共享调用栈,内存占用少,仅需几个KB。协程切换发生在用户态,避免了昂贵的内核态切换。在Kotlin中,协程通过Continuation对象实现上下文保存,允许高效并发执行,而不会像线程那样消耗大量资源。通过`runBlocking`和`launch`示例展示了协程的非阻塞挂起特性。总结来说,协程的轻量主要源于内存占用少、切换开销低和高并发能力。
9 0
|
3天前
|
安全 Android开发 Kotlin
Android面试题之Kotlin的几种常见的类
这篇文章探讨了Kotlin编程语言中的初始化顺序、延迟初始化、惰性初始化、`lateinit`与`by lazy`的区别、初始化注意事项、继承、嵌套类、数据类、单例类和枚举类的使用,以及密封类的概念。文中通过示例代码详细解释了各种特性,并提醒读者关注初始化顺序和线程安全问题。同时,鼓励读者关注作者的公众号“AntDream”获取更多相关文章。
10 1
|
4天前
|
存储 Java 调度
Android面试题之Kotlin 协程的挂起、执行和恢复过程
了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。
8 2
|
4天前
|
Java Linux Android开发
Android面试题之说说系统的启动流程(总结)
这篇文章概述了Android系统的启动流程,从Boot Rom到Zygote进程和SystemServer的启动。init进程作为用户级别的第一个进程,负责创建文件目录、初始化服务并启动Zygote。Zygote通过预加载资源和创建Socket服务,使用fork函数生成SystemServer进程。fork过程中,子进程继承父进程大部分信息但具有独立的进程ID。Zygote预加载资源以减少后续进程的启动时间,而SystemServer启动众多服务并最终开启Launcher应用。文中还讨论了为何从Zygote而非init或SystemServer fork新进程的原因。
11 2
|
11天前
|
存储 算法 Java
JAVA后端开发面试题库
JAVA后端开发面试题库
20 1
|
16天前
|
缓存 安全 Java
【Java面试——并发基础、并发关键字】
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略: 先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。 乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是: 比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。
|
24天前
|
SQL 存储 Java
致远互联java实习生面试
致远互联java实习生面试
34 0