关于 Android,用多个 activity,还是单 activity 配合 fragment?

7,544 阅读8分钟
原文链接: www.zhihu.com
目录
  • 封装BaseFragment基类
  • 使用静态工厂方法newInstance(...)来获取Fragment实例
  • Fragment状态保存/现场恢复
  • 避免错误操作导致Fragment的视图重叠
  • 避免异步操作导致Fragment崩溃(明天1月22日再补充,这是我在项目中适配Tablet时遇到过的坑)
  • Fragment里监听虚拟按键和实体按键的返回事件
  • 使用最大化的DialogFragment来实现浮动层级视图
  • 判断一个页面该使用Fragment还是Activity
------------------------------
2015-01-22 21:40
更新了一个如何在Fragment中监听返回键事件的小技巧,是我目前认为最好的方式。

我回答的都是一些比较基础的,常用的,所以没有涉及到一些深层次的问题。可以看看其他的回答,有些写的很不错。

在业务场景下到底是使用Fragment还是Activity我在答案最后也有提及到,我所参与的项目的三个子项目(包括我没参与过的N多项目,之前做code review有看过源码的那部分),基本上都是大量使用Fragment来做视图部分,组装更加灵活。

我们的视频文档组件开发也是以Fragment为基础来做的,我们接入项目的时候只需要预留Layout,之后组件会用Fragment的形式来填充Layout。

Fragment+Activity并不意味着一定只有一个Activity,我们项目就分LoginActivity,SplashActivity,HomeActivity,参见我最后的回答。

所以我觉得到底用不用是肯定毋庸置疑的,我们不能因为一个东西学习成本高,需要注意的点多,就否认它来带的便利。

Fragment还存在一些坑,是我在项目中遇到过的,明天会补充一下。

感谢 @碗盆 补充

补充一下 @面条的回答视图重叠是因为fragment已经发生了内存泄漏。
activity的创建、销毁完全托管到systemserver(ams),而fragment一般是手动new一个对象再add到systemserver,所以这里就有问题了。
fragment生命周期开始于add,结束于remove。不管app中间怎么变化,崩溃、进程被回收,只要没有remove,android框架都会自动创建、恢复fragment的状态。

---------------------------------------------------
2015-01-22 15:20
正准备出门去接女友回她家,发现居然破200个赞了。

评论里问打包好没有的,我人都在重庆了,顺丰小哥很快的好吗?!ヽ(・ω・。)ノ ,今天正在家里做了一天清洁,拖地,擦地,擦桌子,收拾我从福州带回来的一推破烂,去楼下收了一个快递,又下楼去物业拿了一桶送的油,然后各种洗衣服,下周开始每天要给女友做晚饭了(要是一个人我可以叫外卖)。

所以各位有女友还没结婚的想裸辞朋友可要考虑清楚了,我女友给我岳父说我裸辞了,岳父当时就不说话了(。・`ω´・)。

晚上估计会补一点Fragment的其他使用小技巧,都是我工作中遇到过的,有用的东西,比较适合新手。

争取把这个回答写好,不辜负这200个赞。

------------------------------------------------------
谢邀,我正在打包行李中,看到这个问题来答一答。

首先Fragment带来的便利以及足以让我们无视它可能会导致的麻烦了,而且很多麻烦都是自己使用方法不正确导致的。

毕竟相比于Activity来说,创建一个Fragment所需系统资源相比Activity来说更少,然而控制却更为灵活。

我所参与的项目基本上不用Activity来做UI展示,这部分职责都移交给fragment来实现。

Fragment一般分为两类,一类是有UI的Fragment,可以作为页面,作为View来展示,另一类是用没有UI的Fragment,一般用作保存数据。

至于题主所说的重叠,以及创建多个的问题,都是自己使用不当导致的,你需要get正确的fragment使用方法。

好了,不说了,我要打包显示器了,晚了快递就要下班了。

有时间在补充。

------------------分割线-----------------------
打包完了,快递小哥还没来,又开始下雨了。。。我先写着。

例如为了实例化View,抽象一个getLayoutId方法,子类无需关心具体的创建操作,父类来做View的创建处理。同时可以提供一个afterCreate抽象函数,在初始化完成之后调用,子类可以做一些初始化的操作,你也可以添加一些常用的方法在基类,例如ShowToast().
 public abstract class BaseFragment extends Fragment {
    protected View mRootView;

@Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(null == mRootView){
           mRootView = inflater.inflate(getLayoutId(), container, false);
        }
        return mRootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        afterCreate(savedInstanceState);
    }

protected abstract int getLayoutId();

    protected abstract void afterCreate(Bundle savedInstanceState);
}

感谢 @xiaoxiao Mi 补充,已修改
不过有一点,public View onCreateView 这个方法中返回之前判断一下你的mRootView是null再inflate,这样会比较好,在ViewPager中随着页面滑动这个方法会调用多次,inflate过了之后就直接用就好了。

  • 使用静态工厂方法newInstance(...)来获取Fragment实例
可以在Google的代码中发现这种写法,好处是接收确切的参数,返回一个Fragment实例,避免了在创建Fragment的时候无法在类外部知道所需参数的问题,在合作开发的时候特别有用。

还有就是Fragment推荐使用setArguments来传递参数,避免在横竖屏切换的时候Fragment自动调用自己的无参构造函数,导致数据丢失。

public static WeatherFragment newInstance(String cityName) {
    Bundle args = new Bundle();
    args.putString(cityName,"cityName");
    WeatherFragment fragment = new WeatherFragment();
    fragment.setArguments(args);
    return fragment;
}

不要在Fragment里面保存ViewState!
不要在Fragment里面保存ViewState!
不要在Fragment里面保存ViewState!

为了让你的代码更加清晰和稳定,最好区分清楚fragment状态保存和view状态保存,
如果某个属性属于View,则不要在Fragment中做它的状态保存,除非属性属于Fragment。

每一个自定义View都有义务实现状态的保存,可以像EditText一样,设置一个开关来选择是否保存
比如说:android:freezeText="true/false"。

public class CustomView extends View {
 
    ...
 
    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        // 在这里保存当前状态
        return bundle;
    }
 
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        // 恢复保存的状态
    }
 
    ...
 
}

处理fragment状态保存,例如保存从服务器获取的数据。

    private String serverData;
     
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("data", serverData);
    }
 
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        serverData = savedInstanceState.getString("data");
    }

这个问题很简单,在add或者replace的时候,调用含有TAG参数的那个方法,之后再add相同TAG的Fragment的话,之前的会被替换掉,也就不会同时出现多个相同的Fragment了。
public class WeatherFragment extends Fragment {
    //TAG
    public static final String TAG = WeatherFragment.class.getSimpleName();

不过为了最大限度的重用,可以在Activity的onCreate(Bundle savedInstanceState)中判断savedInstanceState是否不为空;

不为空的话,先用getSupportFragmentManager(). findFragmentByTag()找一下,找到实例就不用再次创建。

WeatherFragment fragment = null;

if(savedInstanceState!=null){
fragment = getSupportFragmentManager().findFragmentByTag(WeatherFragment.TAG);
}

if(fragment == null){
   fragment = WeatherFragment.newInstance(...);
}

  • Fragment里监听虚拟按键和实体按键的返回事件
我见过很多方法,这个方法是最好的,给rootView设置一个OnKeyListener来监听key事件
mRootView.setFocusable(true);
mRootView.setFocusableInTouchMode(true);
mRootView.setOnKeyListener(new View.OnKeyListener() {
    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            //不一定是要触发返回栈,可以做一些其他的事情,我只是举个栗子。
            getActivity().onBackPressed();
            return true;
        }
        return false;
    }
});

  • 使用最大化的DialogFragment来实现浮动层级视图
如果你有一个列表页面,例如知乎,点击Item打开详情页,你可以使用最大化的DialogFragment来显示详情页,此时并不需要提供一个具体的ContainerId即可显示,因为详情页一般情况下在Phone上都是占据满屏幕的,用DialogFragment即可。

不过这并不是最好的做法,在要考虑到Tablet适配的情况下,如下图

Tablet上是嵌入的,而手机上是占据全部空间。

此时可以把详情页单纯用Fragment实现,满足Tablet设备嵌入的需要,在手机上可以使用全屏的DialogFragment来包裹Fragment,之后只需要DialogFragment.show(...)即可。

从这里就可以看出,Fragment的使用其实是非常灵活的。

  • 判断一个页面该使用Fragment还是Activity
如果后一个页面不需要用到前一个页面的太多数据,推荐用Activity展示,否则最好用Fragment( 当然这也不是绝对的)。

例如SplashActivity和MainActivity没有太多的耦合度,此时可以分成两个Activity。

上面所说的也只是Fragment的一部分使用方法,不过已经可以满足常用的开发需求了。

这些坑其实我在项目里面呆的时候都没遇到,自然而然的就照着同事的方式处理了,GET到很多之前不知道的东西,不过你要是遇到了,用百度一搜,一大堆出来,真正有用的没几个,而且都是你copy我,我copy你的。

还有一个原因是国内大部分人都没精力和时间或者心思来做原创分享,也没这个风气。

Fragment应该算是很基础的东西了,而且3.0以后就有了,你说这个东西难吗?也不难,就是要注意的点很多,用多了自然也就知道了。