fragment切换时,transaction调用commit()出现卡死ANR

问题

最近遇到个问题,当多个fragment被添加到activity中后,如果在当前fragment中停留过久的话,再去操作fragment之间的切换,可能会造成ANR(PS.这个应用是运行在一种配置很低的设备上)。

思索

当时就想,是不是由于Android系统在资源吃紧的情况下,会自动回收后台的进程,但是,只有一个activity+多个fragment,对所有的生命周期进行了日志观察,并未有任何异常;然后,我又对内存dump文件进行了分析,所有关键的对象都没有被回收;当我对调用的函数进行trace分析后,发现在执行fragmentTransaction.commit()的时候,出现了“卡死”,也可以认为是阻塞了

fragmentTransaction.commit()

既然在这里出现阻塞,那就看看里面到底是什么

/**
     * Schedules a commit of this transaction.  The commit does
     * not happen immediately; it will be scheduled as work on the main thread
     * to be done the next time that thread is ready.
     *
     * <p class="note">A transaction can only be committed with this method
     * prior to its containing activity saving its state.  If the commit is
     * attempted after that point, an exception will be thrown.  This is
     * because the state after the commit can be lost if the activity needs to
     * be restored from its state.  See {@link #commitAllowingStateLoss()} for
     * situations where it may be okay to lose the commit.</p>
     * 
     * @return Returns the identifier of this transaction's back stack entry,
     * if {@link #addToBackStack(String)} had been called.  Otherwise, returns
     * a negative number.
     */
    public abstract int commit();

上面的注释第一段已经说明了原因,大致意思是,在主线程中,commit()并不会立即执行,而是要等到下一次主线程准备好后,才能执行。当然,作为一个工作几年的码农来说,怎么可能就这样浅尝辄止呢,既然都查到这里了,那就看看commit()里面到底是如何实现的。

在BackStackState.java中,找到了commit()的具体实现

@Override
public int commit() {
     return commitInternal(false);
}

int commitInternal(boolean allowStateLoss) {
        if (mCommitted) throw new IllegalStateException("commit already called");
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(TAG);
            PrintWriter pw = new PrintWriter(logw);
            dump("  ", null, pw, null);
            pw.close();
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);//调用了Fragment中的方法
        return mIndex;
    }

关键函数是FragmentManager中的enqueueAction()方法,这个方法是将trasaction.commit()等这样的动作Action加到队列中

/**
     * Adds an action to the queue of pending actions.
     *
     * @param action the action to add
     * @param allowStateLoss whether to allow loss of state information
     * @throws IllegalStateException if the activity has been destroyed
     */
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                if (allowStateLoss) {
                    // This FragmentManager isn't attached, so drop the entire transaction.
                    return;
                }
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<>();
            }
            mPendingActions.add(action);//将commit()这样的一个action添加到延迟动作队列中
            scheduleCommit();//提交action,并不会立即执行,需要按照action的先后顺序来执行
        }
    }

scheduleCommit()后面调用的一系列函数,大致意思就是要在主线程中按照队列的先后来执行action,所以当前需要执行action提交后,如果mPendingActions前面还有其他的action未执行完,那当前的action就会被卡住,最后,可能就出现了ANR。

解决方案

既然设计了队列,也应该可以插队了。于是就找到了commitNow()

/**
     * Commits this transaction synchronously. Any added fragments will be
     * initialized and brought completely to the lifecycle state of their host
     * and any removed fragments will be torn down accordingly before this
     * call returns. Committing a transaction in this way allows fragments
     * to be added as dedicated, encapsulated components that monitor the
     * lifecycle state of their host while providing firmer ordering guarantees
     * around when those fragments are fully initialized and ready. Fragments
     * that manage views will have those views created and attached.
     *
     * <p>Calling <code>commitNow</code> is preferable to calling
     * {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
     * as the latter will have the side effect of attempting to commit <em>all</em>
     * currently pending transactions whether that is the desired behavior
     * or not.</p>
     *
     * <p>Transactions committed in this way may not be added to the
     * FragmentManager's back stack, as doing so would break other expected
     * ordering guarantees for other asynchronously committed transactions.
     * This method will throw {@link IllegalStateException} if the transaction
     * previously requested to be added to the back stack with
     * {@link #addToBackStack(String)}.</p>
     *
     * <p class="note">A transaction can only be committed with this method
     * prior to its containing activity saving its state.  If the commit is
     * attempted after that point, an exception will be thrown.  This is
     * because the state after the commit can be lost if the activity needs to
     * be restored from its state.  See {@link #commitAllowingStateLoss()} for
     * situations where it may be okay to lose the commit.</p>
     */
    public abstract void commitNow();

commitNow字面意思就是立即提交,但是注释中也警告了我们,可能会失去fragment的state,根据我当前的业务来看,不需要保存fragment的state,因此,就可以使用该方法。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值