我爱编程Android 进阶技术篇专题

Android 内存管理之Fragment回退栈管理

2018-09-09  本文已影响7人  SwitchLife

开篇

  又是周末了,有一段时间没有给童鞋们分享点什么东西了。今天熬夜给童鞋们分享一个Fragment回退栈管理。

意欲何为

  Fragment是3.0API加入的组件,它已被广泛用于应用开发中。support-v4包迭代到当前版本,已经是非常成熟非常好用的一个组件了。但是,API里提供的往往不能满足现实开发中。今天就说说Fragment回退栈的管理。
先看看以下需求:

BackStackRecord源码分析

且看support-v4(27.1.1)源码:
一、FragmentManangerbeginTransaction():

    @Override
    public FragmentTransaction beginTransaction() {
        return new BackStackRecord(this);
    }

二、BackStackRecord继承FragmentTransaction

final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
    //省略部分代码
    @Override
    public FragmentTransaction addToBackStack(String name) {
        if (!mAllowAddToBackStack) {
            throw new IllegalStateException(
                    "This FragmentTransaction is not allowed to be added to the back stack.");
        }
        mAddToBackStack = true;
        mName = name;
        return this;
    }
   //省略部分代码
}

1、实现了addToBackStack(@Nullable String name)方法。标识本次transaction是被加入回退栈中的。当然,前提是本次是允许被加入回退栈的。

    @Override
    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
        return replace(containerViewId, fragment, null);
    }

    @Override
    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
        if (containerViewId == 0) {
            throw new IllegalArgumentException("Must use non-zero containerViewId");
        }

        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
        return this;
    }

2、实现replace(int containerViewId, Fragment fragment, String tag)方法。其中调用了doAddOp(containerViewId, fragment, tag, OP_REPLACE);。opcmd是OP_REPLACE。且往下看在doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd)中做了什么样的逻辑操作:

    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
        final Class fragmentClass = fragment.getClass();
        final int modifiers = fragmentClass.getModifiers();
        //匿名类、非public类、成员类且非static类,
        //如果是这三种类其中之一都会抛出异常
        //当且仅当基础类才有可能是匿名类、成员类
        if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
                || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
            throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                    + " must be a public static class to be  properly recreated from"
                    + " instance state.");
        }

        fragment.mFragmentManager = mManager;

        if (tag != null) {
            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
                throw new IllegalStateException("Can't change tag of fragment "
                        + fragment + ": was " + fragment.mTag
                        + " now " + tag);
            }
            fragment.mTag = tag;
        }

        if (containerViewId != 0) {
            if (containerViewId == View.NO_ID) {
                throw new IllegalArgumentException("Can't add fragment "
                        + fragment + " with tag " + tag + " to container view with no id");
            }
            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
                throw new IllegalStateException("Can't change container ID of fragment "
                        + fragment + ": was " + fragment.mFragmentId
                        + " now " + containerViewId);
            }
            fragment.mContainerId = fragment.mFragmentId = containerViewId;
        }

        //新增一条操作记录
        addOp(new Op(opcmd, fragment));
    }

3、我们很清楚地看到每一次replace都会新增一条操作记录:addOp(new Op(opcmd, fragment));OpBackStackRecord中的一个内部静态类。

    static final class Op {
        int cmd;
        Fragment fragment;
        int enterAnim;
        int exitAnim;
        int popEnterAnim;
        int popExitAnim;

        Op() {
        }

        Op(int cmd, Fragment fragment) {
            this.cmd = cmd;
            this.fragment = fragment;
        }
    }

4、每一条操作记录都有fragment实例,这是强引用。这里的cmd就是前面传递进来的OP_REPLACE

    ArrayList<Op> mOps = new ArrayList<>();

    void addOp(Op op) {
        mOps.add(op);
        op.enterAnim = mEnterAnim;
        op.exitAnim = mExitAnim;
        op.popEnterAnim = mPopEnterAnim;
        op.popExitAnim = mPopExitAnim;
    }

5、本次op被存储到操作列表中。

三、提交本次transaction

    @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);
        return mIndex;
    }

6、如果允许本次op加入回退栈,则分配本次op的在回退栈中的序号。交给FragmentManager执行本次事务:mManager.enqueueAction(this, allowStateLoss);this指向OpGenerator实例。

    /**
     * An add or pop transaction to be scheduled for the UI thread.
     */
    interface OpGenerator {
        /**
         * Generate transactions to add to {@code records} and whether or not the transaction is
         * an add or pop to {@code isRecordPop}.
         *
         * records and isRecordPop must be added equally so that each transaction in records
         * matches the boolean for whether or not it is a pop in isRecordPop.
         *
         * @param records A list to add transactions to.
         * @param isRecordPop A list to add whether or not the transactions added to records is
         *                    a pop transaction.
         * @return true if something was added or false otherwise.
         */
        boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop);
    }

7、Api注释很明白。开头就已经告诉我们:BackStackRecord implements FragmentManagerImpl.OpGenerator。看看BackStackRecordgenerateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop)方法的实现:

   /**
     * Implementation of {@link FragmentManagerImpl.OpGenerator}.
     * This operation is added to the list of pending actions during {@link #commit()}, and
     * will be executed on the UI thread to run this FragmentTransaction.
     *
     * @param records Modified to add this BackStackRecord
     * @param isRecordPop Modified to add a false (this isn't a pop)
     * @return true always because the records and isRecordPop will always be changed
     */
    @Override
    public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Run: " + this);
        }

        records.add(this);
        isRecordPop.add(false);
        if (mAddToBackStack) {
            //添加到FragmentManager回退栈管理中
            mManager.addBackStackState(this);
        }
        return true;
    }

四、回退
AppCompatActivityvoid onBackPressed():

    /**
     * Take care of popping the fragment back stack or finishing the activity
     * as appropriate.
     */
    @Override
    public void onBackPressed() {
        FragmentManager fragmentManager = mFragments.getSupportFragmentManager();
        final boolean isStateSaved = fragmentManager.isStateSaved();
        if (isStateSaved && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
            // Older versions will throw an exception from the framework
            // FragmentManager.popBackStackImmediate(), so we'll just
            // return here. The Activity is likely already on its way out
            // since the fragmentManager has already been saved.
            return;
        }
        if (isStateSaved || !fragmentManager.popBackStackImmediate()) {
            super.onBackPressed();
        }
    }

fragmentManager.popBackStackImmediate()回退到回退栈里的上一次操作。

BackStackRecord就分析到这里了。FragmentManager就是管理和执行BackStackRecord并在UI线程中显示Fragment界面。总的来说,每一次transaction可能有多个op,而FragmentManager可能会有N条BackStackRecord

基于Api的Fragment回退栈效果

    int index = -1;
    ArrayMap<Integer, String> contentCache = new ArrayMap<>();

    private void showNext1(){
        Fragment fragment = new DefaultFragment();
        //绑定data
        String content = "fragment" + index;
        Bundle bundle = new Bundle();
        bundle.putString(DefaultFragment.EXTRA_CONTENT, content);
        fragment.setArguments(bundle);
        contentCache.put(index, content);

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.addToBackStack(null);
        transaction.replace(R.id.fragment_container, fragment);
        transaction.commit();
    }

👌,在所有Fragment都加入回退栈后,方式一能正常回退。

    int index = -1;
    ArrayMap<Integer, String> contentCache = new ArrayMap<>();

    private void showNext2(){
        boolean returnable = new Random().nextBoolean();
        Fragment fragment = new DefaultFragment();
        //绑定data
        String content = "fragment" + index + (returnable ? "" : "\u2000X");
        Bundle bundle = new Bundle();
        bundle.putString(DefaultFragment.EXTRA_CONTENT, content);
        fragment.setArguments(bundle);
        contentCache.put(index, content);

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        if (returnable)
            transaction.addToBackStack(null);
        else
            transaction.disallowAddToBackStack();
        transaction.replace(R.id.fragment_container, fragment);
        transaction.commit();
    }

😳,请告诉我这是什么鬼👻?很显然,当部分Fragment不加入回退栈时回退是有问题的。还是我哪里漏写code了?(如果这是这样,大神可以在评论中给我回复,非常感谢!)

在回头看看需求,我就想说一句:MMP。
很显然,API这时候就不能满足我们的日常开发了。不过别慌,接下来福利来了。

😊😊😊😊😊😊...

新福利:效果截屏

说明:后面带有X的说明这个Fragment实例不加入到Fragment回退栈中。GO创建新的Fragment实例并显示。BACK返回上一个回退栈里的Fragment。CLEAR清空Fragment回退栈除了当前显示的Fragment。

立即体验

扫描以下二维码下载体验App(从0.2.3版本开始,体验App内嵌版本更新检测功能):


JSCKit库传送门:https://github.com/JustinRoom/JSCKit

简析源码

    @IdRes
    private int containerViewId;// container view id for add fragment
    private WeakReference<Fragment> fragmentWeakReference;//fragment cache, it will be recycled by system if necessary
    private String clzName;//class name for creating a instance when fragment's cache is recycled by system.
    private boolean returnable;//true, add to back stack
    private Bundle bundle;//a copy of original data

    public BackRecord(@IdRes int containerViewId, @NonNull Fragment fragment, Bundle bundle, boolean returnable) {
        this.containerViewId = containerViewId;
        if (returnable)
            this.fragmentWeakReference = new WeakReference<>(fragment);
        this.returnable = returnable;
        this.clzName = fragment.getClass().getName();
        if (bundle == null)
            return;

        //copy data. The original data will be recycled if fragment was recycled.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            this.bundle = bundle.deepCopy();
        } else {
            this.bundle = new Bundle(bundle);
        }
    }

containerViewId——Fragment容器Id
fragmentWeakReference——Fragment缓存管理。这里创建WeakReference实例,当触发系统内存回收时(内存不足或者System.gc()主动通知系统可清理内存),可以回收掉这些弱引用的Fragment实例。
clzName——Fragment的class name。用于创建被系统回收掉的Fragment实例。
returnable——true,Fragment实例被加入Fragment回退栈里。
bundle——创建Fragment实例时传入data的备份。用于创建被系统回收掉的Fragment实例。this.bundle = bundle.deepCopy();或者this.bundle = new Bundle(bundle);,v26Api以及之上支持深拷贝,低于v26Api用浅拷贝。如果不用copy的方式,bundle一直放在回退栈里,以至于bundle关联的但不显示的Fragment实例内存不能被回收掉,一直占用着内存。

    public void show(@IdRes int containerViewId, @NonNull Fragment fragment, Bundle bundle, boolean returnable) {
        //remember the current showing fragment.
        currentShowFragment = fragment;
        //bundle data
        if (bundle != null)
            fragment.setArguments(bundle);
        else
            bundle = fragment.getArguments();
        fragmentManager.beginTransaction().replace(containerViewId, fragment).commit();
        backRecordStack.push(new BackRecord(containerViewId, fragment, bundle, returnable));
    }

1、显示并存储当前显示的Fragment实例。这里我们可以看到用的是replace(int, Fragment)}方法。每显示Fragment都会创建一个回退记录,returnable标识这个回退记录是否是有效回退记录。

    @Nullable
    private BackRecord getLastReturnableStepRecord() {
        if (backRecordStack.isEmpty())
            return null;

        //get the top of stack
        BackRecord record = backRecordStack.pop();
        if (!record.isReturnable()) {
            return getLastReturnableStepRecord();
        }
        return backRecordStack.push(record);
    }

2、获取上一个可回退的Fragment。这里递归pop(),直到获取到可回退的Fragment。

public boolean back() {
        if (backRecordStack.empty())
            return false;

        //back current step record
        backRecordStack.pop();
        if (currentShowFragment != null) {
            fragmentManager.beginTransaction().remove(currentShowFragment).commit();
            currentShowFragment = null;
        }
        //get the last returnable step record
        BackRecord lastReturnableBackRecord = getLastReturnableStepRecord();
        if (lastReturnableBackRecord != null) {
            Fragment tempFragment = lastReturnableBackRecord.createInstanceIfNecessary();
            if (tempFragment != null) {
                currentShowFragment = tempFragment;
                fragmentManager.beginTransaction()
                        .replace(lastReturnableBackRecord.getContainerViewId(), currentShowFragment)
                        .commit();
            }
        }
        return true;
    }

3、回退并显示上一个Fragment。A、移除掉当前显示的Fragment。B、查找上一个可回退的Fragment(如果有缓存则取缓存,如果缓存已经被系统回收,则利用java反射机制创建一个Fragment实例并绑定bundle数据)。

BackRecord.javacreateInstanceIfNecessary():

    @Nullable
    public Fragment createInstanceIfNecessary() {
        Fragment fragment = fragmentWeakReference.get();
        if (fragment == null) {
            try {
                Log.i(TAG, "createInstanceIfNecessary: newInstance " + (bundle == null ? "" : bundle.getString("extra_content")));
                Object object = Class.forName(clzName).newInstance();
                if (object instanceof Fragment)
                    fragment = (Fragment) object;
                if (fragment != null)
                    fragment.setArguments(bundle);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return fragment;
    }

4、获取Fragment缓存实例,如果已经被系统回收,则通过Java反射创建一个新的实例并传入bundle数据。

如何使用?

FragmentBackHelper fragmentBackHelper = new FragmentBackHelper(getSupportFragmentManager());

    public void widgetClick(View v){
        switch (v.getId()){
            case R.id.btn_back:
                if (!fragmentBackHelper.back())
                    Toast.makeText(this, "can't back", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn_show_next:
                boolean returnable = new Random().nextBoolean();
                int index = new Random().nextInt(100);
                fragmentBackHelper.show(R.id.fragment_container, new DefaultFragment(), newBundle(index, returnable), returnable);
                break;
        }
    }

点击查看简单使用示例
  如此,很轻松能管理Fragment回退栈了,也不用担心会OutOfMemoryError了。童鞋们,给我点个💗吧!!!

篇尾

  Wechat:eoy9527

没有加倍的勤奋,就既没有才能,也没有天才。 —— 门捷列夫

上一篇下一篇

猜你喜欢

热点阅读