开发笔记-Fragment

开发笔记主要是记录开发中遇到的一些问题和解决方案,以及引发的一些思考,不会太深入地记录问题,但是会尽可能广泛地记录涉及到的内容,方便之后整理归纳和查阅

No.1 Fragment not attached to a context

可以检查以下是否是下面的情况产生的

1
2
3
4
5
6
7
8
9
10
11
val dialog = SquareConfirmDialog().apply {
// 以下代码会报错 not attached to a context
// 因为调用的是 Dialog 的 Fragment.getString() 方法,而此时 dialog 并未 attached to a context
positive = getString(R.string.ai_base_policy_agree_and_continue)

// 以下代码是正确用法,调用当前 Activity 的 Context.getString() 方法
negative = this@CurrentActivity.getString(R.string.somt_string)
contentView = view
onPositiveListener = { }
onNegativeListener = { finish() }
}

No.2 相邻的 Fragment 中使用 webview,出现加载异常的问题

在项目中,一个页面中同时使用多个不同的 Fragment,比如 ViewPager 中有几个 Fragment 的场景,这几个 Fragment 同时加载,且其中有几个是有 WebView 的,这个时候就会出现当中的某一个或者几个出现异常的情况,原因是使用 Webview 加载不同的 url 时,会存在 js 及 css 动画执行异常的问题,当时使用的布局,Viewpager 中存在两个不同的 Fragment,在这两个 Fragment 中分别使用 Webview 加载了不同的页面(页面中使用了比较接近的js及样式),这个问题最后的解决办法是,当需要加载 Viewpager 时,只加载一个 Fragment 或者一个 Webview,切换 tab 标签时,再去动态更换加载其他的 URL,也就是懒加载

No.3 commit 和 commitAllowingStateLoss 区别及应用场景

做 Android 开发,Fragment 是我们经常用的,我们在使用 Fragment 时,会像如下这样写:

1
2
3
4
5
6
7
FragmentManager fm = getFragmentManager();
testFragment = (TestFragment) fm.findFragmentById(R.id.fragment_container);

if (testFragment == null) {
testFragment = new TestFragment();
fm.beginTransaction().add(R.id.fragment_container, testFragment).commit();
}

我们偶尔会碰到这样一个异常:

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

大意是在 activity 的 onSaveInstanceState 调用过后,再 commit 的 Transaction 导致的异常,看到网上一些建议用 commitAllowingStateLoss 来代替 commit,那么 commit 和 commitAllowingStateLoss 有什么区别呢?查看下相关的代码:

1
2
3
4
5
6
7
public int commit() {
return commitInternal(false);
}

public int commitAllowingStateLoss() {
return commitInternal(true);
}

发现他们都是调用了commitInternal()方法,只是一个传了false,一个传了true,接着往下看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int commitInternal(boolean allowStateLoss) {
if (mCommitted) {
throw new IllegalStateException("commit already called");
}
...
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}

主要是 mManager.enqueueAction(this, allowStateLoss) 来执行这个任务,根据传入的参数继续往下走,可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}

这里就是最后了,可以看到最开始传进来的 allowStateLoss 在这里只做了检查状态的操作

1
2
3
4
5
6
7
8
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException("Can not perform this action inside of " + mNoTransactionsBecause);
}
}

如果activity的状态被保存了,这里再提交就会检查这个状态,符合条件就抛出一个异常来终止应用进程,也就是说在 activity 调用了 onSaveInstanceState()之后,再 commit 一个事务就会出现该异常,那如果不想抛出异常,也可以很简单调用 commitAllowingStateLoss() 方法来略过这个检查就可以了,但是 Google 说这是危险的,在官方文档上有如下描述:

Like {@link #commit} but allows the commit to be executed after an activity’s state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user.

大意是如果 activity 随后需要从它保存的状态中恢复,这个 commit 是会丢失的,因此它仅仅适用在ui状态的改变对用户来说是可以接受的

因此可以得出几点结论:

  • 在 activity 的生命周期方法中提交事务要小心,越早越好,比如 onCreate,也可以在接收用户的输入时来提交,尽量避免在 onActivityResult() 方法中提交
  • 避免在异步的回调方法中执行 commit,因为他们感知不到当前 activity 生命周期的状态
  • 使用 commitAllowingStateLoss() 代替 commit(),相比于 crash,我觉得 ui 状态的改变对用户来说是可以接受的,但是也要看场景,比如涉及到金融相关的场景,用户可能突然觉得钱没了(从ui上看),那还不如crash让系统再重新拉起 App 呢

原文:Android commit和commitAllowingStateLoss区别及应用场景


开发笔记-Fragment
https://enderhoshi.github.io/2024/03/05/开发笔记-Fragment/
作者
HoshIlIlI
发布于
2024年3月5日
许可协议