新的开始
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
《系列学习视频》
《系列学习文档》
《我的大厂面试之旅》
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
第一种:
既然是Activity重建引发的Fragment重建,那么釜底抽薪,禁止Activity重建。
比如禁止屏幕旋转时重建Activity,可以在AndroidManifest.xml里配置:
android:configChanges="orientation|screenSize"
第二种:
不想改配置,也可以从Activity保存的状态入手,Frament重建依赖于恢复状态:
//FragmentActivity.java
private void init() {
addOnContextAvailableListener(new OnContextAvailableListener() {
@Override
public void onContextAvailable(@NonNull Context context) {
//Activity.onCreate里会调用此方法
mFragments.attachHost(null /*parent*/);
//找到需要恢复的Fragment状态
Bundle savedInstanceState = getSavedStateRegistry()
.consumeRestoredStateForKey(FRAGMENTS_TAG);
if (savedInstanceState != null) {
//不为空则进行Fragment恢复,重建
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreSaveState(p);
}
}
});
}
也就是说只要恢复状态为空,那么Fragment就不会进行重建,而该状态是从Activity.onCreate(Bundle)里传递过来的,因此只需要在Activity里进行如下设置:
override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
//状态置空
super.onCreate(null)
setContentView(R.layout.layout_fragment )
val fragment = FishPureFragment()
fragment.arguments = Bundle().apply {
putString("hello", "fragment:${count++}")
}
supportFragmentManager.beginTransaction().add(R.id.root, fragment).commitNow()
}
第三种:
将Activity下的所有恢复状态置空有点粗暴了,会影响到其它组件如View的恢复,而我们仅仅只需要禁止Fragment重建,使用如下方式:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.let {
it.getBundle("androidx.lifecycle.BundleSavedStateRegistry.key")?.remove("android:support:fragments");
}
}
注:此种方式与系统实现版本有关,有些版本取不到value
3. Fragment add/remove/replace/hide/show的快速理解
add
添加一个Fragment实例到Fragment管理栈里,此间Fragment生命周期经历了从onAttach()逐渐到onActivityCreated()。
此时Fragment所绑定的布局(View)已经添加到指定的父布局里了。
当管理栈里已有一个Fragment实例:Fragment1,此时再往里面添加Fragment2,Fragment2入栈并将其绑定的布局添加到父布局里。
依据父布局属性不同,有不同的展示方式:
- 若父布局是FrameLayout,则Fragment2绑定的布局将会覆盖Fragment1绑定的布局。
- 若父布局是LinearLayout,则Fragment2绑定的布局将会添加到Fragment1绑定的布局左边/右边。
remove
将一个Fragment实例从Fragment管理栈里移除,此间Fragment生命周期经历了从onDestroyView()逐渐到onDetach()。
此时Fragment所绑定的布局(View)已经从指定的父布局里移除了。
replace
当Fragment管理栈里没有Fragment实例时,replace与add效果一致。
当Fragment管理栈里有Fragment1实例时,replace(Fragment2)将触发Fragment1的生命周期从onDestroyView()逐渐到onDetach(),而Fragment2生命周期从onAttach()逐渐到onActivityCreated(),最终展示Fragment2。
replace 可简单理解为remove+add。
hide
不会触发Fragment生命周期,仅仅只是把Fragment绑定的布局隐藏(GONE)。
show
hide的反向操作,不会触发Fragment生命周期,仅仅只是把Fragment绑定的布局显示(VISIBLE)。
4. Fragment与ViewModel的结合
Activity ViewModel与FragmentManagerViewModel
如上图,Activity ViewModelStore里存储着不同的ViewModel实例,其中FragmentManagerViewModel是与Fragment有关的。
当Activity销毁重建时,在Activity销毁阶段ViewModelStore并没有被释放,而是被保留下来,等到Activity重建时继续使用,而ViewModelStore实例不变,其内部的ViewModel也不变,这就是ViewModel的原理。
那么FragmentManagerViewModel 是什么时候添加到ViewModelStore里的呢?
入口在这:
#FragmentActivity.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
//绑定FragmentManager
mFragments.attachHost(null /*parent*/);
}
最终调用到:
#FragmentManager.java
void attachController(@NonNull FragmentHostCallback<?> host,
@NonNull FragmentContainer container, @Nullable final Fragment parent) {
//...
if (parent != null) {
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
//host 即为承载的Activity
//取出Activity ViewModelStore
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
} else {
mNonConfig = new FragmentManagerViewModel(false);
}
}
#FragmentManagerViewModel.java
static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
FACTORY);
//从Activity的ViewModelStore 取出FragmentManagerViewModel
//若没有,则创建FragmentManagerViewModel实例
return viewModelProvider.get(FragmentManagerViewModel.class);
}
答案是:Activity.onCreate()将FragmentManagerViewModel添加到Activity ViewModelStore里。
FragmentManagerViewModel与Fragment的ViewModel
现在FragmentManagerViewModel可以在Activity重建时恢复,那么它和Fragment里的ViewModel又是如何关联的呢?
FragmentManagerViewModel 里有个成员变量:
private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();
在Fragment里声明ViewModel:
private val vm by viewModels<MyViewModel2>()
查看viewModels调用链,关键要找到对应的ViewModelStore,而Fragment的ViewModelStore获取方式如下:
#Fragment.java
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
#FragmentManagerViewModel.java
ViewModelStore getViewModelStore(@NonNull Fragment f) {
//mViewModelStores 是Map,key 是Fragment唯一标识符 value 为Fragment对应的ViewModelStore
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
//不存在则创建
viewModelStore = new ViewModelStore();
//放入map
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}
到此,流程就比较清晰了:
FragmentManagerViewModel里的mViewModelStores里存储着所有Fragment的ViewModelStore。也即是Fragment的ViewModel实际上是存储在FragmentManagerViewModel里的。
接着来整体捋一下Fragment的ViewModel是如何在重建时保持的。
显而易见,Fragment ViewModel实际上是间接依赖Activity ViewModeStore。
Fragment 关联的lifecycleScope
lifecycleScope 监听着Fragment生命周期,若是Fragment被销毁,则lifecycleScope也会被取消。
Fragment ViewModel关联的viewModelScope
viewModelScope 与ViewModel 生命周期保持一致,若是ViewModel被销毁(Fragment被销毁而非重建),则viewModelScope也会被取消。
5. DialogFragment(Dialog和Fragment的结晶)
DialogFragment 使用
普通的Dialog并没有生命周期,而不关联生命周期的Dialog处理异步请求比较麻烦,此时DialogFragment出现了。
class MyDialogFragment : DialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val tv = TextView(context).apply {
text = "hello world"
setTextColor(Color.RED)
textSize = 30f
}
return tv
}
}
显示DialogFragment:
MyDialogFragment().show(supportFragmentManager, "dd")
我们只需要定义Fragment所绑定的布局,最终布局将会显示在Dialog里。
DialogFragment 原理
Dialog 显示Fragment绑定的布局
你也许比较好奇:之前添加的Fragment都是指定父布局,将Fragment所绑定的布局添加到父布局里,那此时的DialogFragment所指定的父布局在哪呢?
从show方法入手:
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
//就是添加普通的Fragment流程
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
#FragmentTransaction.java
public FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag) {
//没有指定父布局
doAddOp(0, fragment, tag, OP_ADD);
return this;
}
与添加普通Fragment不同的是此时并没有指定Fragment的父布局。
Dialog会监听Fragment生命周期:
#DialogFragment.java
private Observer<LifecycleOwner> mObserver = new Observer<LifecycleOwner>() {
public void onChanged(LifecycleOwner lifecycleOwner) {
if (lifecycleOwner != null && mShowsDialog) {
//拿到Fragment绑定的View
View view = requireView();
if (mDialog != null) {
//将View添加到Dialog里
mDialog.setContentView(view);
}
}
}
};
当生命周期回调后拿到Fragment绑定的View添加到Dialog里。
如此一来,Fragment就完成了和Dialog的配合显示界面。
Dialog的创建时机
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return super.onCreateDialog(savedInstanceState);
}
重写该方法可以自定义Dialog或是监听默认Dialog的创建。
值得注意的是:在onAttach()/onCreate()里是拿不到Dialog对象的,因为那时候还没有创建Dialog,它在onCreateView()之前创建的。
6. ViewPager2(Fragment与RecyclerView的结晶)
ViewPager2使用
除了DialogFragment,Fragment也与RecyclerView配合,形成了ViewPager2。
创建Adapter:
public class VPAdapter extends FragmentStateAdapter {
private List<FishPureFragment> list = new ArrayList<>();
public VPAdapter(@NonNull FragmentActivity fragmentActivity, List<FishPureFragment> list) {
super(fragmentActivity);
this.list = list;
}
@NonNull
@Override
public Fragment createFragment(int position) {
return list.get(position);
}
@Override
public int getItemCount() {
return list.size();
}
}
ViewPager2绑定Adapter:
VPAdapter vpAdapter = new VPAdapter(this, list);
viewPager2.setAdapter(vpAdapter);
可以看出,使用起来很简洁,ViewPager2的显示依靠着Fragment。
总结
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的14套腾讯、字节跳动、阿里、百度等2021最新面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
(img-zH5lUCFu-1715579694436)]
[外链图片转存中…(img-EFEozt8a-1715579694436)]
[外链图片转存中…(img-bRlvkYmW-1715579694436)]
[外链图片转存中…(img-5ZIDTjJl-1715579694436)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!