[Android开发]你所忽略过的Fragment的坑

WiKi

本文从几个实验开始,到无UI的Fragment,详解Fragment使用过程中可能忽略的相关用法和各种坑。

参考

Fragment全解析系列(一):那些年踩过的坑
Fragment全解析系列(二):正确的使用姿势
Android中保存和恢复Fragment状态的最好方法

Replace、Add、Hide\Show的区别

Hide\Show只影响可见性,并不会影响生命周期。

**
 * Hides an existing fragment.  This is only relevant for fragments whose
 * views have been added to a container, as this will cause the view to
 * be hidden.
 *
 * @param fragment The fragment to be hidden.
 *
 * @return Returns the same FragmentTransaction instance.
 */
public abstract FragmentTransaction hide(Fragment fragment);

/**
 * Shows a previously hidden fragment.  This is only relevant for fragments whose
 * views have been added to a container, as this will cause the view to
 * be shown.
 *
 * @param fragment The fragment to be shown.
 *
 * @return Returns the same FragmentTransaction instance.
 */
public abstract FragmentTransaction show(Fragment fragment);

add和replace的区别在哪里?,下面做几个实验,从生命周期的角度来分析。
假如Activity中的FrameLayout布局R.id.container是Fragment的容器。现在替换为FragmentA,然后从FragmentA中打开FragmentB,过程即:
FrameLayout→FragmentA→FragmentB

实验1:FrameLayout→[add(),FragmentA]→[add(),FragmentB]

第一阶段FrameLayout→[add(),FragmentA]的log:

11-06 20:30:29.497 21080-21080/name.free.fragmentdemo D/AAAAA: onStart
11-06 20:30:29.498 21080-21080/name.free.fragmentdemo D/AAAAA: onCreateView
11-06 20:30:29.501 21080-21080/name.free.fragmentdemo D/AAAAA: onViewCreated
11-06 20:30:29.501 21080-21080/name.free.fragmentdemo D/AAAAA: onResume

第二阶段[add(),FragmentA]→[add(),FragmentB]的log:

11-06 20:31:13.707 21080-21080/name.free.fragmentdemo D/BBBBB: onStart
11-06 20:31:13.707 21080-21080/name.free.fragmentdemo D/BBBBB: onCreateView
11-06 20:31:13.720 21080-21080/name.free.fragmentdemo D/BBBBB: onViewCreated
11-06 20:31:13.721 21080-21080/name.free.fragmentdemo D/BBBBB: onResume

结果是FragmentA仍然停留在onResume()阶段,两个Fragment叠加在一起。
如果此时熄灭屏幕,FragmentA和FragmentB都会先后进入到onPause()和onStop()的阶段。

11-06 20:32:57.127 21080-21080/name.free.fragmentdemo D/AAAAA: onPause
11-06 20:32:57.127 21080-21080/name.free.fragmentdemo D/BBBBB: onPause
11-06 20:32:57.203 21080-21080/name.free.fragmentdemo D/AAAAA: onStop
11-06 20:32:57.203 21080-21080/name.free.fragmentdemo D/BBBBB: onStop

亮屏后FragmentA和FragmentB又分别恢复到onResume()的阶段。

11-06 20:34:04.212 21080-21080/name.free.fragmentdemo D/AAAAA: onResume
11-06 20:34:04.212 21080-21080/name.free.fragmentdemo D/BBBBB: onResume

这个时候,按下一次Back键,FragmentA和FragmentB都交替进入销毁阶段,之后完全退出了该Activity。

11-06 20:35:16.876 21080-21080/name.free.fragmentdemo D/AAAAA: onPause
11-06 20:35:16.876 21080-21080/name.free.fragmentdemo D/BBBBB: onPause
11-06 20:35:17.247 21080-21080/name.free.fragmentdemo D/AAAAA: onStop
11-06 20:35:17.247 21080-21080/name.free.fragmentdemo D/BBBBB: onStop
11-06 20:35:17.248 21080-21080/name.free.fragmentdemo D/AAAAA: onDestroyView
11-06 20:35:17.248 21080-21080/name.free.fragmentdemo D/AAAAA: onDestroy
11-06 20:35:17.248 21080-21080/name.free.fragmentdemo D/BBBBB: onDestroyView
11-06 20:35:17.248 21080-21080/name.free.fragmentdemo D/BBBBB: onDestroy

实验2:FrameLayout→[add(),FragmentA]→[replace(),FragmentB]

11-06 20:40:50.849 28011-28011/name.free.fragmentdemo D/AAAAA: onPause
11-06 20:40:50.849 28011-28011/name.free.fragmentdemo D/AAAAA: onStop
11-06 20:40:50.849 28011-28011/name.free.fragmentdemo D/AAAAA: onDestroyView
11-06 20:40:50.850 28011-28011/name.free.fragmentdemo D/BBBBB: onStart
11-06 20:40:50.850 28011-28011/name.free.fragmentdemo D/BBBBB: onCreateView
11-06 20:40:50.852 28011-28011/name.free.fragmentdemo D/BBBBB: onViewCreated
11-06 20:40:50.852 28011-28011/name.free.fragmentdemo D/BBBBB: onResume
11-06 20:40:50.991 28011-28011/name.free.fragmentdemo D/AAAAA: onDestroy

可以看出,FragmentA在FragmentB启动前进入到onDestroyView(),在FragmentB进入onResume()后,FragmentA进入到onDestroy(),完全销毁。
这个时候,按下一次Back键,FragmentB进入销毁阶段,之后完全退出了该Activity。

实验3:FrameLayout→[replace(),FragmentA]→[replace(),FragmentB]

与FrameLayout→add()→FragmentA→replace()→FragmentB相同。

实验4:FrameLayout→[replace(),FragmentA]→[add,FragmentB]

与FrameLayout→add()→FragmentA→add()→FragmentB相同。

结论

  • FrameLayout→[replace(),FragmentA]与FrameLayout→[add(),FragmentA]的效果是一样的,两者都是replace的效果;
  • FragmentA→[add,FragmentB]的过程中,FragmentA其实处于Resume()阶段,两个Fragment的UI处于叠加状态;
  • 按Back键,不论是在FragmentA还是FragmentB,都会直接退出整个Activity。
    #Fragment栈结构

实验5:FrameLayout→[add(),FragmentA,addToBackStack()]→[add(),FragmentB]

实验室1不同的地方是,第二阶段还是第三阶段,按back键时,会先执行出栈操作,销毁FragmentA。

11-13 19:42:38.840 28278-28278/name.free.fragmentdemo D/AAAAA: onPause
11-13 19:42:38.840 28278-28278/name.free.fragmentdemo D/AAAAA: onStop
11-13 19:42:38.840 28278-28278/name.free.fragmentdemo D/AAAAA: onDestroyView
11-13 19:42:39.009 28278-28278/name.free.fragmentdemo D/AAAAA: onDestroy

实验6:FrameLayout→[add(),FragmentA,addToBackStack()]→[replace(),FragmentB]

与实验室1不同的地方是,replace()执行后,FragmentA没有执行onDestory(),因为FragmentA在栈中被保存。

11-13 20:24:13.757 11013-11013/name.free.fragmentdemo D/AAAAA: onStart
11-13 20:24:13.757 11013-11013/name.free.fragmentdemo D/AAAAA: onCreateView
11-13 20:24:13.766 11013-11013/name.free.fragmentdemo D/AAAAA: onViewCreated
11-13 20:24:13.766 11013-11013/name.free.fragmentdemo D/AAAAA: onResume
11-13 20:24:17.477 11013-11013/name.free.fragmentdemo D/AAAAA: onPause
11-13 20:24:17.477 11013-11013/name.free.fragmentdemo D/AAAAA: onStop
11-13 20:24:17.477 11013-11013/name.free.fragmentdemo D/AAAAA: onDestroyView
11-13 20:24:17.479 11013-11013/name.free.fragmentdemo D/BBBBB: onStart
11-13 20:24:17.479 11013-11013/name.free.fragmentdemo D/BBBBB: onCreateView
11-13 20:24:17.481 11013-11013/name.free.fragmentdemo D/BBBBB: onViewCreated
11-13 20:24:17.481 11013-11013/name.free.fragmentdemo D/BBBBB: onResume

当执行back键后,如同实验5,FragmentA出栈,执行了onDestroy()函数。

实验7:FrameLayout→[add(),FragmentA,addToBackStack()]→[add(),FragmentB,addToBackStack()]

第1阶段和第2阶段与实验1相同,不同指出是FragmentA与FragmentB先后入栈。
执行完毕后按Back键,FragmentB先出栈,同时被销毁。
再按Back键,FragmentA出栈被销毁,同时退出Activity。

实验8:FrameLayout→[replace(),FragmentA,addToBackStack()]→[replace(),FragmentB()]

这里FragmentA、FragmentB先后入栈,FragmentA执行到onDestoryView阶段。
按Back键后,FragmentB会先出栈,并执行到onDestoryView阶段,FragmentA会从onViewCreated阶段恢复到onResume(),然后FragmentB会执行onDestroy()。
再按Back键,FragmentA会出栈,然后执行到onDestory。整个过程如下:

11-13 20:37:58.568 20671-20671/name.free.fragmentdemo D/AAAAA: onStart
11-13 20:37:58.569 20671-20671/name.free.fragmentdemo D/AAAAA: onCreateView
11-13 20:37:58.571 20671-20671/name.free.fragmentdemo D/AAAAA: onViewCreated
11-13 20:37:58.572 20671-20671/name.free.fragmentdemo D/AAAAA: onResume
11-13 20:38:00.428 20671-20671/name.free.fragmentdemo D/AAAAA: onPause
11-13 20:38:00.428 20671-20671/name.free.fragmentdemo D/AAAAA: onStop
11-13 20:38:00.428 20671-20671/name.free.fragmentdemo D/AAAAA: onDestroyView
11-13 20:38:00.429 20671-20671/name.free.fragmentdemo D/BBBBB: onStart
11-13 20:38:00.429 20671-20671/name.free.fragmentdemo D/BBBBB: onCreateView
11-13 20:38:00.434 20671-20671/name.free.fragmentdemo D/BBBBB: onViewCreated
11-13 20:38:00.434 20671-20671/name.free.fragmentdemo D/BBBBB: onResume
11-13 20:38:15.976 20671-20671/name.free.fragmentdemo D/BBBBB: onPause
11-13 20:38:15.976 20671-20671/name.free.fragmentdemo D/BBBBB: onStop
11-13 20:38:15.976 20671-20671/name.free.fragmentdemo D/BBBBB: onDestroyView
11-13 20:38:15.977 20671-20671/name.free.fragmentdemo D/AAAAA: onCreateView
11-13 20:38:15.981 20671-20671/name.free.fragmentdemo D/AAAAA: onViewCreated
11-13 20:38:15.981 20671-20671/name.free.fragmentdemo D/AAAAA: onResume
11-13 20:38:16.160 20671-20671/name.free.fragmentdemo D/BBBBB: onDestroy
11-13 20:38:39.930 20671-20671/name.free.fragmentdemo D/AAAAA: onPause
11-13 20:38:39.930 20671-20671/name.free.fragmentdemo D/AAAAA: onStop
11-13 20:38:39.930 20671-20671/name.free.fragmentdemo D/AAAAA: onDestroyView
11-13 20:38:40.083 20671-20671/name.free.fragmentdemo D/AAAAA: onDestroy

总结

  • Fragment加入栈中,这会导致该Fragment不会被destory,而是处于onResume或onDestoryView阶段,这两个阶段要根据后续是add还是replace来决定。
  • 按Back键会执行一次出栈任务。
    #Back事件
    在兼容模式下Fragment的容器是v4包里的FragmentActivity:
    /**
     * Take care of popping the fragment back stack or finishing the activity
     * as appropriate.
     */
    @Override
    public void onBackPressed() {
        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
            super.onBackPressed();
        }
    }

在非兼容模式下,是Activity:

    /**
     * Called when the activity has detected the user's press of the back
     * key.  The default implementation simply finishes the current activity,
     * but you can override this to do whatever you want.
     */
    public void onBackPressed() {
        if (mActionBar != null && mActionBar.collapseActionView()) {
            return;
        }

        if (!mFragments.getFragmentManager().popBackStackImmediate()) {
            finishAfterTransition();
        }
    }

可见,按下Back键等同于执行了一次Fragment出栈操作。这里就引出了出栈的几个方法:

 /**
     * Pop the top state off the back stack.  This function is asynchronous -- it
     * enqueues the request to pop, but the action will not be performed until the
     * application returns to its event loop.
     */
    public abstract void popBackStack();

    /**
     * Like {@link #popBackStack()}, but performs the operation immediately
     * inside of the call.  This is like calling {@link #executePendingTransactions()}
     * afterwards.
     * @return Returns true if there was something popped, else false.
     */
    public abstract boolean popBackStackImmediate();

    /**
     * Pop the last fragment transition from the manager's fragment
     * back stack.  If there is nothing to pop, false is returned.
     * This function is asynchronous -- it enqueues the
     * request to pop, but the action will not be performed until the application
     * returns to its event loop.
     * 
     * @param name If non-null, this is the name of a previous back state
     * to look for; if found, all states up to that state will be popped.  The
     * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
     * the named state itself is popped. If null, only the top state is popped.
     * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
     */
    public abstract void popBackStack(String name, int flags);

    /**
     * Like {@link #popBackStack(String, int)}, but performs the operation immediately
     * inside of the call.  This is like calling {@link #executePendingTransactions()}
     * afterwards.
     * @return Returns true if there was something popped, else false.
     */
    public abstract boolean popBackStackImmediate(String name, int flags);

    /**
     * Pop all back stack states up to the one with the given identifier.
     * This function is asynchronous -- it enqueues the
     * request to pop, but the action will not be performed until the application
     * returns to its event loop.
     * 
     * @param id Identifier of the stated to be popped. If no identifier exists,
     * false is returned.
     * The identifier is the number returned by
     * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.  The
     * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
     * the named state itself is popped.
     * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
     */
    public abstract void popBackStack(int id, int flags);

    /**
     * Like {@link #popBackStack(int, int)}, but performs the operation immediately
     * inside of the call.  This is like calling {@link #executePendingTransactions()}
     * afterwards.
     * @return Returns true if there was something popped, else false.
     */
    public abstract boolean popBackStackImmediate(int id, int flags);

其中popBackStack()是异步方法,只是把出栈这个动作加到了消息队列的顶部;而popBackStackImmediate()是同步方法,立即执行出栈动作。

无UI的Fragment

在实验8中,FragmentA的视图会销毁,进入回退栈。第一次按Back键,FragmentB出栈并被销毁,而后FragmentA重建视图,并进入到onResume()阶段。这种情况下,没有调用onSaveInstanceState(Bundle outState)函数,那么FragmentA如何保存临时数据呢?
更常见的场景是:Fragment中有一个后台进程,当横竖屏切换时,Activityhe和其中的Fragment会销毁重建,后台进程不容易在横竖屏切换时不受影响。
这里引出无UI的Fragment的用法。无UI的Fragment主要用于保存后台数据和进程,需要为Fragment设置一个属性:

    /**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated:
     * 
    *
  • {@link #onDestroy()} will not be called (but {@link #onDetach()} still * will be, because the fragment is being detached from its current activity). *
  • {@link #onCreate(Bundle)} will not be called since the fragment * is not being re-created. *
  • {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} will * still be called. *
*/ public void setRetainInstance(boolean retain) { mRetainInstance = retain; }

如果设置为true,则当Activity重建时该Fragment并不会销毁。这里以一个实例来讲解,该实例是Google官方提供的,(地址:FragmentRetainInstance.java)。

public class FragmentRetainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FragmentManager fragmentManager=getFragmentManager();

        if (savedInstanceState == null) {
            fragmentManager.beginTransaction().add(android.R.id.content, new UiFragment()).commit();
        }
    }


    public static class UiFragment extends Fragment implements TaskFragment.TaskCallbacks{
        RetainedFragment mWorkFragment;

        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View v = inflater.inflate(R.layout.fragment_retain_instance, container, false);

            // Watch for button clicks.
            Button button = (Button)v.findViewById(R.id.restart);
            button.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    mWorkFragment.restart();
                }
            });

            return v;
        }

        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            FragmentManager fm = getFragmentManager();
            mWorkFragment = (RetainedFragment)fm.findFragmentByTag("work");

            // If not retained (or first time running), we need to create it.
            if (mWorkFragment == null) {
                mWorkFragment = new RetainedFragment();
                // Tell it who it is working with.
                //设置TargetFragment!
                mWorkFragment.setTargetFragment(this, 0);
                fm.beginTransaction().add(mWorkFragment, "work").commit();
            }

            TaskFragment taskFragment=(TaskFragment)fm.findFragmentByTag("work2");
            if (taskFragment==null){
                taskFragment=new TaskFragment();
                taskFragment.setTargetFragment(this,1);
                fm.beginTransaction().add(taskFragment,"work2").commit();
            }
        }
        @Override
        public void onPostExecute() {
            Toast.makeText(getActivity(),"From TaskFragment!",Toast.LENGTH_SHORT).show();
        }
    }

    public static class RetainedFragment extends Fragment {
        ProgressBar mProgressBar;
        int mPosition;
        boolean mReady;
        boolean mQuiting;
        final Thread mThread = new Thread() {
            @Override
            public void run() {
                int max = 10000;
                while (true) {

                    // Update our shared state with the UI.
                    synchronized (this) {
                        // Our thread is stopped if the UI is not ready
                        // or it has completed its work.
                        while (!mReady || mPosition >= max) {
                            if (mQuiting) {
                                return;
                            }

                            try {
                                wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                        }

                        // Now update the progress.  Note it is important that
                        // we touch the progress bar with the lock held, so it
                        // doesn't disappear on us.
                        mPosition++;
                        max = mProgressBar.getMax();
                        mProgressBar.setProgress(mPosition);
                    }

                    // Normally we would be doing some work, but put a kludge
                    // here to pretend like we are.
                    synchronized (this) {
                        try {
                            wait(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        };

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);//关键语句
            mThread.start();
        }

        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            mProgressBar = (ProgressBar)getTargetFragment().getView().findViewById(
                    R.id.progress_horizontal);

            // We are ready for our thread to go.
            synchronized (mThread) {
                mReady = true;
                mThread.notify();
            }

        }
        @Override
        public void onDestroy() {
            // Make the thread go away.
            synchronized (mThread) {
                mReady = false;
                mQuiting = true;
                mThread.notify();
            }

            super.onDestroy();
        }
        @Override
        public void onDetach() {
            // This fragment is being detached from its activity.  We need
            // to make sure its thread is not going to touch any activity
            // state after returning from this function.
            synchronized (mThread) {
                mProgressBar = null;
                mReady = false;
                mThread.notify();
            }

            super.onDetach();
        }
        public void restart() {
            synchronized (mThread) {
                mPosition = 0;
                mThread.notify();
            }
        }
    }
}

该实例是在UiFragment中添加了一个无UI的RetainedFragment,该RetainedFragment中开启了一个耗时的线程,并设置setRetainInstance(true),表示不随着Activity、UiFragment的重建而销毁。这样,当UiFragment重建时,就能保持ProgressBar的进度。
最后总结下,使用无UI的Fragment的情况如下:

  • 实验8,如果Fragment通过销毁视图的方式重建,且onSaveInstanceState(Bundle outState)函数不会执行,无法保存临时数据;
  • 如果有后台线程,受Fragment销毁重建影响。

Fragment的作为Fragment的容器

在API超过17之后,Fragment可以作为其他Fragment的容器。举个例子:

/**
 * Created by mi on 16-11-13.
 * 使用getChildFragmentManager()
 */
public class ChildFragmentActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FragmentManager fragmentManager=getFragmentManager();

        if (savedInstanceState == null) {
            fragmentManager.beginTransaction().add(android.R.id.content, new ContainerFragment()).commit();
        }
    }
    public static class ContainerFragment extends Fragment{
        private FragmentManager childFragmentManager;
        private static final String TAG="Child";

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            childFragmentManager=getChildFragmentManager();//注意这里
        }

        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View v = inflater.inflate(R.layout.layout_container_activity, container, false);

           Button button= (Button) v.findViewById(R.id.change);
            button.setText("Child Fragment");
            button.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   FragmentTransaction fragmentTransaction = childFragmentManager.beginTransaction();
                   fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                   Fragment childFragment=childFragmentManager.findFragmentByTag(TAG);
                   if (childFragment==null){
                       childFragment=new ChildFragment();
                       Bundle bundle = new Bundle();
                       bundle.putString("Child", "Child");
                       childFragment.setArguments(bundle );
                       fragmentTransaction.add(R.id.container, childFragment,TAG);
                   }
                  fragmentTransaction.commit();
               }
           });


            return v;
        }
    }
    public static class ChildFragment extends Fragment{
        private String tag;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Bundle bundle=getArguments();
            tag=bundle.getString(ContainerFragment.TAG);
            Log.d(tag,"onStart");
        }
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View v = inflater.inflate(R.layout.layout_fragment, container, false);
            Log.d(tag,"onViewCreated");
            TextView textView=(TextView)v.findViewById(R.id.text);
            textView.setText(tag);
            return v;
        }
    }
}

如果Fragment作为容器时,Activity可以不需要布局文件,而是直接替换android.R.id.content,它是Activity的根布局。

fragmentManager.beginTransaction().add(android.R.id.content, new ContainerFragment()).commit();

其他

Fragment的切换动画

FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
//transaction.setCustomAnimations可以设置自定义动画。

fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                Log.d(TAG, "onBackStackChanged()");
            }
        });

Fragment中追加Menu

    /**
     * Second fragment with a menu.
     */
    public static class Menu2Fragment extends Fragment {

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setHasOptionsMenu(true);//注意这里
        }

        @Override
        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            menu.add("Menu 2").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        }
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // TODO Auto-generated method stub
            Toast.makeText(getActivity(), "Menu ", Toast.LENGTH_SHORT).show();
            return super.onOptionsItemSelected(item);
        }
    }

请我喝咖啡

如果觉得写得不错,可以扫描我的微信二维码请我喝咖啡哦~

在这里插入图片描述
或者点击 打赏地址 请我喝杯茶~

你可能感兴趣的:(Android开发)