[BYM] Android录音异常 倒推

blow your mind

bym系列意在除开技术分享,还分享下思路,不止是做一个代码的搬运工。

1.背景简介

在将现有的录音功能改造的过程中,把原有的DialogFragment里添加Interface与Activity的形式,改造成通过ViewModel的方式来进行Activity和DialogFragment通信。

第一次点击按钮弹出DialogFragment,录音流程正常。
第二次点击按钮再次弹出DialogFragment的同时,LogCat报如下错误。解决触发ViewModel的重复触发很简单,直接判断DialogFragment的isVisible就行了。但是这也引申出一个问题,可能对于我来说是个知识盲区。ViewModel的机制中是什么导致它重复触发。

觉得分析麻烦的同学可以点击右方菜单,直接看结论。

java.lang.RuntimeException: stop failed.
    at android.media.MediaRecorder.stop(Native Method)
    at xxx.TapeDialogFragment.stopRecord(TapeDialogFragment.kt:218)
    at xxx.TapeDialogFragment.access$stopRecord(TapeDialogFragment.kt:28)
    at xxx.TapeDialogFragment$onCreate$4.onChanged(TapeDialogFragment.kt:94)
    at xxx.TapeDialogFragment$onCreate$4.onChanged(TapeDialogFragment.kt:28)
    at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
    at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:144)
    at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:443)
    at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:395)
    at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
    at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
    at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
    at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
    at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
    at androidx.fragment.app.Fragment.performStart(Fragment.java:2735)
    at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:355)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1192)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1354)
    at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1432)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1495)
    at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:447)
    at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2167)
    at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1990)
    at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1945)
    at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1847)
    at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:207)
    at android.app.ActivityThread.main(ActivityThread.java:6878)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
复制代码

2.倒看源码 了解为什么

前题:每次点击按钮都通过TapeDialogFragment.newInstance()方法重新创建了实例。

DialogFragment.show

//将framgent交友FragmentTransaction来处理。
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
    }
复制代码

FragmentTransaction的实现是BackStackRecord,commit方法本质是调用的commitInternal,如下图

int commitInternal(boolean allowStateLoss) {
        ...
        //本次事物提交到队列
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }
复制代码

mManagerFragmentManagerImpl的实例。接着看enqueueAction

public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        ...
        synchronized (this) {
            ...
            //action列表,并把这次action添加到列表中
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<>();
            }
            mPendingActions.add(action);
            scheduleCommit();//任务提交
        }
    }
    //接着往下看
    /**
     * 该方法将在第一次调用#enqueueAction(OpGenerator, boolean)方法时调用或者通过startPostponedEnterTransition()启动延时任务时调用
     */
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void scheduleCommit() {
        synchronized (this) {
            boolean postponeReady =
                    mPostponedTransactions != null && !mPostponedTransactions.isEmpty();//延迟队列是否就绪
            boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;//执行队列是否就绪
            if (postponeReady || pendingReady) {
                //移除旧的runnable,提交新的runnable
                mHost.getHandler().removeCallbacks(mExecCommit);
                mHost.getHandler().post(mExecCommit);
            }
        }
    }
    
    Runnable mExecCommit = new Runnable() {
        @Override
        public void run() {
            execPendingActions();
        }
    };

复制代码

看反推到了我们的这一行异常androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1847)
思路没错,继续反推着走。

 boolean execPendingActions(boolean allowStateLoss) {
        ensureExecReady(allowStateLoss);

        boolean didSomething = false;
        //while循环从临时存储的记录列表里删除
        while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
            mExecutingActions = true;
            try {
           //删除冗余的BackStackRecord操作并执行它们。此方法合并允许重新排序的近似记录的操作。
           //例如,一个事务添加到后堆栈,然后另一个事务弹出该堆栈后栈记录将被优化,以消除不必要的操作。
           //同样,将优化同时执行的两个提交的事务删除冗余操作以及同时执行的两个pop操作。
                removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
            } finally {
                cleanupExec();
            }
            didSomething = true;
        }

        updateOnBackPressedCallbackEnabled();
        doPendingDeferredStart();
        mFragmentStore.burpActive();

        return didSomething;
    }
复制代码
//这里是在取出BackStackRecord列表中的每一项执行
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1990)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:447)
at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2167)
复制代码

当上述语句执行到BackStackRecord.executeOps方法时

void executeOps() {
        ...
        if (!mReorderingAllowed) {
            // 将新增的Framgent以初始化的状态添加到末尾
            mManager.moveToState(mManager.mCurState={Fragment.INITIALIZING}, true);
        }
    }
复制代码

接着看FragmentManager.moveToState方法,该方法的描述是:
将FragmentManager的状态更改为{@code newState}。如果FragmentManager改变了状态或者{@code always}是{@code true},那么其中的任何片段的状态都会被更新

 void moveToState(int newState, boolean always) {
        ...
        // Must add them in the proper order. mActive fragments may be out of order
        //必须按正确的顺序添加。活动Framgenets可能不正常
        for (Fragment f : mFragmentStore.getFragments()) {
            //将Fragment改变到预期的最终状态或FragmentManager的状态,这取决于FragmentManager的状态是否正确提升。
            moveFragmentToExpectedState(f);
        }
}
void moveFragmentToExpectedState(@NonNull Fragment f) {
    moveToState(f);//改变fragment的状态
}
void moveToState(@NonNull Fragment f, int newState) {
    ...
     switch (f.mState) {
         case Fragment.ACTIVITY_CREATED:
             if (newState > Fragment.ACTIVITY_CREATED) {
                fragmentStateManager.start();//Fragment状态管理器启动
            }
     }
}
# FragmentStateManager.java
 void start() {
        //对应的framgent准备启动
        mFragment.performStart();
        //通知fragment已启动
        FragmentLifecycleCallbacksDispatcher.dispatchOnFragmentStarted(mFragment, false);
}
# Fragment.java
 void performStart() {
        mChildFragmentManager.noteStateNotSaved();
        mChildFragmentManager.execPendingActions(true);
        mState = STARTED;
        mCalled = false;
        onStart();
        if (!mCalled) {
            throw new SuperNotCalledException("Fragment " + this
                    + " did not call through to super.onStart()");
        }
        //Lifecycle生命周期发送Event
        mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
        if (mView != null) {
            mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START);
        }
        mChildFragmentManager.dispatchStart();
    }

复制代码

到了这里终于与我们的ViewModel扯上关系了LifeCycle。我们都知道LiveData内部是通过Lifecycle实现监听回调的,来知道Fragment和Activity生命周期改变的。

androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
//执行到这里
void dispatchEvent(LifecycleOwner owner, Event event) {
            State newState = getStateAfter(event);
            mState = min(mState, newState);
            //通知观察者状态发生改变了
            mLifecycleObserver.onStateChanged(owner, event);
            mState = newState;
        }

# LiveData.java
public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            ...触发状态改变
            activeStateChanged(true);
        }

void activeStateChanged(boolean newActive) {
            ...
            if (mActive) {
                //通知观察者值发生改变
                dispatchingValue(this);
            }
        }

 void dispatchingValue(@Nullable ObserverWrapper initiator) {
        ...
        do {
            ...
            if (initiator != null) {
                //发送通知
                considerNotify(initiator);
                initiator = null;
            } else {
            ...
            //观察者为空,则循环查看观察者集合并进行通知
            }
        } while (mDispatchInvalidated);
        ...
    }
  private void considerNotify(ObserverWrapper observer) {
        ...
        observer.mObserver.onChanged((T) mData);
        //mData被volatile修饰。并且由于Fragment和Activity共用一个ViewModel的关系。
        //次数的mData的值为之前postValue的值。
    }
复制代码

结论

由此可以看出,当我们重建Fragment时,共用的ViewModel在Framgent状态为START时将触发onChange方法,从而使得Framgent虽为不可见状态,但是依然会执行ViewModel.xxxLiveData.observe。从而导致异常出现。

当Fragment和Activity共用时,重建Fragment需警惕LiveData的触发。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享