Android技能树 — Fragment总体小结

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);
}

}

所以我们知道了FragmentStatePagerAdapter是真的会把fragment对象都销毁,所以如果fragment数量很多的话,使用这个会更好,因为fragment存在太多,对应用性能造成很大影响,所以要remove掉fragment。


5.无UI的fragment:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.1 使用Fragment 保持需要恢复对象

调用setRetainInstance(true)方法可保留fragment,如下:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);

}

比如旋转屏幕,已保留的fragment不会随着activity一起被销毁(但会销毁fragment的视图); 相反,它会一直保留(进程不消亡的前提下),并在需要时原封不动地传递给新的Activity。

所以我们比如一些对象可以保持在fragment中,这时候Activity重新恢复后,其他对象可以从fragment中找回。

可以大概看下其他作者文章介绍:

Fragment调用setRetainInstance的原理

5.2 类似RxPermission用于处理回调

RxPermission里有一个Fragment用于分发权限回调。这个是什么意思??

我们知道原生请求权限:

//发出权限请求:
int requestCode = 1;
requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE},requestCode);

//权限处理结果回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

是不是觉得在要看复写这个回调方法很麻烦???而且没有美感。

而RxPermission是这样申请权限的:

RxPermissions rxPermissions = new RxPermissions(this);

rxPermissions.requestEach(
//请求的权限
Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE)
.subscribe(new Consumer() {
@Override
public void accept(@io.reactivex.annotations.NonNull Permission permission) throws Exception {
//权限通知回调
}
});

感觉就是一步呵成的感觉,很棒。但是RxPermission只是对系统的原生权限申请做了封装而已,那系统的原本的回调函数:onRequestPermissionsResult去哪里了呢???

public class RxPermissionsFragment extends Fragment {



//‘申请权限’
@TargetApi(23)
void requestPermissions(@NonNull String[] permissions) {
this.requestPermissions(permissions, 42);
}

//‘申请权限后结果回调’
@TargetApi(23)
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 42) {
boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];

for(int i = 0; i < permissions.length; ++i) {
shouldShowRequestPermissionRationale[i] = this.shouldShowRequestPermissionRationale(permissions[i]);
}

this.onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale);
}
}

//‘回调后的具体处理方法’
void onRequestPermissionsResult(String[] permissions, int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
int i = 0;

for(int size = permissions.length; i < size; ++i) {
this.log("onRequestPermissionsResult " + permissions[i]);
PublishSubject subject = (PublishSubject)this.mSubjects.get(permissions[i]);
if (subject == null) {
Log.e(RxPermissions.TAG, “RxPermissions.onRequestPermissionsResult invoked but didn’t find the corresponding permission request.”);
return;
}

this.mSubjects.remove(permissions[i]);
boolean granted = grantResults[i] == 0;

//‘subject主动调用onNext方法发送结果’
subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));
subject.onComplete();
}

}



}

我们可以到这个fragment内部已经帮我们复写了请求权限的原生方法和权限回调通知的原生方法。然后再通过subject在结果处发送通知即可。

这里我不会细讲整个RxPermission源码,我以前写过的相关文章,大家可以具体看下:

项目需求讨论 - 动态权限申请分析及相关第三方库源码分析

项目需求讨论 — 手把手带你写RxPermission


6.构造函数和数据传递

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.1 构造函数传递数据

我们知道fragment也就是普通的对象,可以通过new的方式,我们平常使用对象传递值都是可以直接在构造函数里面定义参数值,直接赋值进去,那fragment是否可以这样??答案是可以的,但是不推荐。

public class FragmentOne extends Fragment {

//‘在其他地方直接FragmentOne one = new FragmentOne(“青蛙要fly”);进行值传递’
//‘但是我们不推荐这样’
public FragmentOne(String value) {

}

//‘而是通过bundle来传递,Fragment.setArguments(Bundle)赋值进去’
public static FragmentOne newInstance(Bundle args) {
FragmentOne fragment = new FragmentOne();
if(args != null){
fragment.setArguments(args);
}
return fragment;
}

}

原因:我们可以知道Activity重新创建时,会重新构建它所管理的Fragment,原先的Fragment的字段值将会全部丢失(因为当切换横竖屏时,Fragment会调用自己的无参构造函数,那么在构造函数传参就会失效),但是通过 Fragment.setArguments(Bundle bundle)方法设置的bundle会保留下来,从而数据又可以恢复,所以尽量使用 Fragment.setArguments(Bundle bundle)方式来传递参数

6.2 其他数据传递方式

Activity 与 Fragment 数据传递:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Fragment 与 Fragment 数据传递

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

重点说下setTargetFragment,因为很多人都不知道。

我们的目标:FragmentA 启动FragmentB ,然后FragmentB做完事情,返回结果给FragmentA

FragmentB.setTargetFragment(FragmentA);

然后在B中:
getTargetFragment().onActivityResult(getTargetRequestCode(), resultOK, i);

然后再FragmentA中:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}


7. Fragment重建恢复数据

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

推荐下面这篇文章:

[译] 保存/恢复 Activity 和 Fragment 状态的最佳实践

引用一段话:

完全分开处理Fragment状态和view状态 为了使你的代码变得干净和可扩展,你最好把Fragment状态和View状态分开处理。如果这里有任何属性是属于View的,在View内部进行保存和恢复.如果这里有任何属性是属于Fragment的,在Fragment内部进行保存和恢复。


8.常用监听Fragment显示方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这块比较基础,就不细讲了。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

13/H4lCoPEF.jpg" />

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

[外链图片转存中…(img-SEvrZcZV-1712648946760)]

[外链图片转存中…(img-rmtBeFoY-1712648946761)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Fragment中适应字体大小主要有两种方法。一种是使用不同的dimen资源来适配不同分辨率和屏幕大小,另一种是通过代码动态设置字体大小。 第一种方法是使用不同的dimen资源。我们可以在res目录下新建文件夹values-xxxx(xxxx表示对应的屏幕尺寸或分辨率),然后在这个文件夹下创建dimens.xml文件。在dimens.xml文件中定义不同屏幕下的字体大小,如: ```xml <!-- values-xxxhdpi/dimens.xml --> <dimen name="text_size">24sp</dimen> <!-- values-hdpi/dimens.xml --> <dimen name="text_size">18sp</dimen> <!-- values-mdpi/dimens.xml --> <dimen name="text_size">14sp</dimen> ``` 然后在布局文件中使用这个dimen资源来设置字体大小,如: ```xml <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="@dimen/text_size" android:text="Hello World" /> ``` 这样就可以根据不同的屏幕尺寸和分辨率适配字体大小了。 另一种方法是通过代码动态设置字体大小。可以通过TextView的setTextSize()方法来设置字体大小,如: ```java TextView textView = findViewById(R.id.text_view); textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); ``` 其中,第一个参数是设置字体大小的单位,这里使用COMPLEX_UNIT_SP表示使用sp作为单位,第二个参数是具体的字体大小。可以根据实际情况动态计算字体大小,以适应不同的屏幕尺寸和分辨率。 综上所述,可以通过使用不同的dimen资源或者动态设置字体大小的方式来适应Android Fragment中的字体大小。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Android字体大小自适应不同分辨率的解决办法](https://download.csdn.net/download/weixin_38748740/14023953)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Android Fragment的使用 六 适应屏幕大小](https://blog.csdn.net/weixin_31433527/article/details/117513320)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值