从BaseActivity与BaseFragment的封装谈起

源至Hongyang微信公众号,博客源址从BaseActivity与BaseFragment的封装谈起

这篇博客主要是从BaseActivity与BaseFragment的封装开始,总结自己在实战开发中关于Fragment的注意事项以及心得体会。先看以下效果图:

这里模拟的是用户登录模块,你可能会说,很普通的效果嘛,这有啥。嘿嘿,那我要告诉你的是,这么多模块仅仅由两个Activity构成的。等你从头到尾看完这篇博客,你就会惊叹其中的奥秘了。废话不多说,开始。

多模块Activity+多Fragment 

开发APP非常适合的架构,相对于多Activity,这种架构APP占用内存降低,性能提升;相对于单Activity+多Fragment,这种开发起来逻辑相对简单,不容易出错。

对于多模块Activity+多Fragment,这里有两个概念需要我们了解一下

同级式Fragment: 比如QQ的主界面,消息,联系人,动态,这三个Fragment就属于同级关系,我们平时项目中主界面的Fragment也是属于同级Fragment

流程式Fragment: 比如我这个示例Demo,可以理解为用户账户流程,可以包括:登录/注册模块—-忘记/找回密码模块—-用户协议模块,这些Fragent就是属于流程式Fragment

我的示例Demo使用的是流程式Fragment,结合今天的主题—-BaseActivity与BaseFragment的封装,我们一探究竟。

1.BaseActivity的封装:

public abstract class BaseActivity extends AppCompatActivity {

    //布局文件ID
    protected abstract int getContentViewId();

    //布局中Fragment的ID
    protected abstract int getFragmentContentId();

    //添加fragment
    protected void addFragment(BaseFragment fragment) {
        if (fragment != null) {
            getSupportFragmentManager().beginTransaction()
                    .replace(getFragmentContentId(), fragment, fragment.getClass().getSimpleName())
                    .addToBackStack(fragment.getClass().getSimpleName())
                    .commitAllowingStateLoss();
        }
    }

    //移除fragment
    protected void removeFragment() {
        if (getSupportFragmentManager().getBackStackEntryCount() > 1) {
            getSupportFragmentManager().popBackStack();
        } else {
            finish();
        }
    }

    //返回键返回事件
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (KeyEvent.KEYCODE_BACK == keyCode) {
            if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
                finish();
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }
}

(1)两个必须实现的抽象方法,获取布局文件Layout的resource ID,获取布局文件中Fragment的ID

(2)添加fragment:开启一个事物,替换了当前layout容器中的由getFragmentContentId()标识的fragment。通过调用 addToBackStack(String tag), replace事务被保存到back stack, 因此用户可以回退事务,并通过按下BACK按键带回前一个fragment,如果没有调用 addToBackStack(String tag), 那么当事务提交后, 那个fragment会被销毁,并且用户不能导航回到它。其中参数tag将作为本次加入BackStack的Transaction的标志。commitAllowingStateLoss(),这种提交是允许发生异常时状态值丢失的情况下也能正常提交事物。

(3)移除fragment:与addToBackStack()相对应的接口方法是popBackStack(),调用该方法后会将事务操作插入到FragmentManager的操作队列,轮询到该事务时开始执行。这里进行了一下判断,获取回退栈中所有事务数量,大于1的时候,执行回退操作,等于1的时候,代表当前Activity只剩下一个Fragment,直接finish()当前Activity即可

(4)监听返回键的返回事件,当事务数量等于1的时候,直接finish()

2.BaseActivity的进一步封装—-AppActivity:

/**
 * Created by tangyangkai on 16/5/4.
 */
public abstract class AppActivity extends BaseActivity {

    //获取第一个fragment
    protected abstract BaseFragment getFirstFragment();

    //获取Intent
    protected void handleIntent(Intent intent) {

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getContentViewId());
        if (null != getIntent()) {
            handleIntent(getIntent());
        }
        //避免重复添加Fragment
        if (null == getSupportFragmentManager().getFragments()) {
            BaseFragment firstFragment = getFirstFragment();
            if (null != firstFragment) {
                addFragment(firstFragment);
            }
        }

    }

    @Override
    protected int getContentViewId() {
        return R.layout.activity_base;
    }

    @Override
    protected int getFragmentContentId() {
        return R.id.fragment_container;
    }
}

(1)一个必须实现的抽象方法来获取当前Activity应该显示的第一个Fragment

(2)获取intent的方法,在需要传递或者接受数据的中Activity实现

(3)在Activity的onCreate()方法中拿到intent,并且添加第一个fragment作为Activity的主界面进行显示

最后贴一下activity_base.xml布局文件代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".BaseActivity">
</RelativeLayout>

3.BaseFragment的封装:

/**
 * Created by tangyangkai on 16/5/4.
 */
public abstract class BaseFragment extends Fragment {

    protected BaseActivity mActivity;

    protected abstract void initView(View view, Bundle savedInstanceState);

    //获取布局文件ID
    protected abstract int getLayoutId();

    //获取宿主Activity
    protected BaseActivity getHoldingActivity() {
        return mActivity;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        this.mActivity = (BaseActivity) activity;
    }

    //添加fragment
    protected void addFragment(BaseFragment fragment) {
        if (null != fragment) {
            getHoldingActivity().addFragment(fragment);
        }
    }

    //移除fragment
    protected void removeFragment() {
        getHoldingActivity().removeFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(getLayoutId(), container, false);
        initView(view, savedInstanceState);
        return view;
    }
}

为了方便后面文章的介绍,先补充一种情况:

安卓有一种特殊情况,就是在APP运行在后台的时候,系统资源紧张的时候会把APP的资源全部回收(杀死APP的进程),这时候把APP再从后台返回到前台的时候,APP会重启。

安卓有一种特殊情况,就是在APP运行在后台的时候,系统资源紧张的时候会把APP的资源全部回收(杀死APP的进程),这时候把APP再从后台返回到前台的时候,APP会重启。鸿洋大哥的博客有相关记录:Android
Fragment 你应该知道的一切

这种内存不足的情况会导致许多问题,其中之一就是Fragment调用getActivity()的地方却返回null,报了空指针异常。解决办法就是在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)里赋值,使用mActivity代替getActivity()。其他的代码注释很详细,大家一看便懂。

4.Activity与Fragment的使用: 

BaseActivity与BaseFragment的封装都已经完成,接下来就是具体在项目中的使用了,这里分两种情况。

第一种情况:不接收数据的Activity

/**
 * Created by tangyangkai on 16/5/10.
 */
public class MainActivity extends AppActivity {

    @Override
    protected BaseFragment getFirstFragment() {
        return MainFragment.newInstance();
    }
}

示例Demo中的主界面MainActivity,没有接收其他界面传递过来的数据。可以看到代码相当的精简,对应的MainFragment代码如下:

public class MainFragment extends BaseFragment {

    private Button mainBtn, mainSecondBtn;

    public static MainFragment newInstance() {
        return new MainFragment();
    }

    @Override
    protected void initView(View view, Bundle savedInstanceState) {
        mainBtn = (Button) view.findViewById(R.id.main_btn);
        mainBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle data = new Bundle();
                data.putString("username", "tangyankai");
                Intent intent = new Intent(getActivity(), LoginActivity.class);
                intent.putExtras(data);
                startActivity(intent);

            }
        });

        mainSecondBtn = (Button) view.findViewById(R.id.main_second_btn);
        mainSecondBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addFragment(SecondFragment.newInstance("从首界面跳转过来的"));
            }
        });
    }

    @Override
    protected int getLayoutId() {
        return R.layout.fragment_main;
    }
}

很简单的业务逻辑,点击第一个按钮,携带数据,跳转到LoginActivity;点击第二个按钮,跳转到注册模块,这里故意添加了一个参数,这里后面会说到。

第二种情况:接收数据的Activity

/**
 * Created by tangyangkai on 16/5/10.
 */
public class LoginActivity extends AppActivity {

    private String username;
    @Override
    protected void handleIntent(Intent intent) {
        super.handleIntent(intent);
        Bundle bundle = intent.getExtras();
        if (null != bundle) {
            username = bundle.getString("username");
        }
    }

    @Override
    protected BaseFragment getFirstFragment() {
        return FirstFragment.newInstance(username);
    }

}

可以看到,LoginActivity与MainActivity不一样的是,重写了handleIntent()这个方法来获取传递过来的数据,更加重要的一点,创建Fragment的时候传递了一个参数 这是为什么呢,先来看看fragment的代码你就知道了

/**
 * Created by tangyangkai on 16/5/10.
 */
public class FirstFragment extends BaseFragment {
    @Override
    protected int getLayoutId() {
        return R.layout.fragment_first;
    }

    public static String FIRST_FRAGMENT = "first_fragment";
    private String msg;
    private EditText usernameEdt;
    private TextView registerTxt, promiseTxt;
    private ImageView backImg;

    public static FirstFragment newInstance(String msg) {
        FirstFragment fragment = new FirstFragment();
        Bundle bundle = new Bundle();
        bundle.putSerializable(FIRST_FRAGMENT, msg);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (null != getArguments()) {
            msg = (String) getArguments().getSerializable(FIRST_FRAGMENT);
        }
    }

    @Override
    protected void initView(View view, Bundle savedInstanceState) {
        usernameEdt = (EditText) view.findViewById(R.id.username_edt);
        usernameEdt.setText(msg);
        registerTxt = (TextView) view.findViewById(R.id.register_txt);
        registerTxt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addFragment(SecondFragment.newInstance("从登录界面跳转过来的"));
            }
        });

        backImg = (ImageView) view.findViewById(R.id.first_back);
        backImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                removeFragment();
            }
        });

        promiseTxt = (TextView) view.findViewById(R.id.promise_txt);
        promiseTxt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addFragment(ThirdFragment.newInstance());
            }
        });

    }

}

代码不少,我们先挑重点讲:

    public static FirstFragment newInstance(String msg) {
        FirstFragment fragment = new FirstFragment();
        Bundle bundle = new Bundle();
        bundle.putSerializable(FIRST_FRAGMENT, msg);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (null != getArguments()) {
            msg = (String) getArguments().getSerializable(FIRST_FRAGMENT);
        }
    }

给Fragment添加newInstance方法,将需要的参数传入,设置到bundle中,然后setArguments(bundle),最后在onCreate中进行获取。 

这种使用arguments来创建Fragment的方法,强烈推荐使用:

(1)这样就完成了Fragment和Activity间的解耦,使用Fragment的一个很大的原因,就是为了复用。这一点在我主界面点击第二个按钮跳转到注册界面有所体现

(2)对Fragment传递数据,建议使用setArguments(Bundle args),而后在onCreate中使用getArguments()取出,在 内存不足导致异常时,系统会帮你保存数据,不会造成数据的丢失。和Activity的Intent原理一致。

(3)使用newInstance(参数) 创建Fragment对象,优点是调用者只需要关系传递的哪些数据,而无需关心传递数据的Key是什么。

参考资料:

鸿洋大哥: Android Fragment 你应该知道的一切

Fragment系列: Fragment全解析系列(二):正确的使用姿势

然后就是业务逻辑:

(1)点击注册按钮,跳转到注册模块,注意这里我传递了一个和主界面不一样的参数,为了区分,并且都在注册模块进行了显示。你会发现,示例Demo中,点击登录模块的注册按钮与点击首界面的注册按钮跳转到注册模块时候,显示的文字不一样。这里纯属演示,实际项目中,我们可以根据传递的不同参数,对Fragment进行不一样的操作,显示不一样的数据。达到最大程度的Fragment复用!

(2)点击返回按钮,一句话就帮你搞定,轻松返回上一个界面:

 removeFragment();

当然,点击手机返回键效果也是一样的

(3)点击用户协议按钮,跳转到用户协议模块。

至于其他的界面大同小异,你可以加上 忘记密码/修改密码 等模块,完全没问题。关于流程式Fragment,就先到这里,看看同级式Fragment应该注意的问题。

5.hide()与show()导致的Fragment重叠: 

同级式Fragment在内存不足导致的异常情况下,会出现重叠现象,处理方法是在基类Activity的onCreate函数,先去判断savedInstanceState是否为null,如果不为null,则表示里面有保存这个fragment。则不再重新去add这个fragment,而是通过Tag从前保存的数据中直接去读取,看一下代码:

在add的时候,加上一个tab参数

transaction.add(R.id.content, IndexFragment,”fg1″);
public void onCreate(Bundle savedInstanceState) {
    fManager = getFragmentManager();
    if (savedInstanceState != null) {
        fg1 = (AllOfficialAccountFragment) fManager.findFragmentByTag("fg1");
        fg2 = (MovieOfficialAccountFragment) fManager.findFragmentByTag("fg2");
        fg3 = (NewsOfficialAccountFragment) fManager.findFragmentByTag("fg3");
        fg4 = (OtherOfficialAccountFragment) fManager.findFragmentByTag("fg4");
    }
    super.onCreate(savedInstanceState);
}

到这里,BaseActivity与BaseFragment的封装已经结束了,这只是最最最基础的封装,大家可以把一些常用的方法封装到基类当中,让基类Activity与Fragment发挥最大程度的作用。

当然,业务逻辑简单的界面,一个Activity就可以搞定的那种,那就没必要使用这种方法了。这里把自己封装过程中关于Fragment的一些心得记录下来。关于Fragment的深度解析与其他注意事项,大家可以参考刚才给出的资料。

时间: 2024-10-12 22:28:27

从BaseActivity与BaseFragment的封装谈起的相关文章

Android 谈谈封装那些事 --BaseActivity 和 BaseFragment(二)

1.前言 昨天谈了BaseActivity的封装,Android谈谈封装那些事--BaseActivity和BaseFragment(一)有很多小伙伴提了很多建议,比如: 通用标题栏可以自定义View而不放在Base里面,代码更统一 BaseEventActivity里面应该留出开关保证不需要Bus的Activity使用 BaseStatusActivity里面就一个方法没必要新建一个 还有一些小的细节 在这里感谢大家的建议了啊.我修改了一部分,后面会慢慢优化,最后在HLibrary里面贴出最优

从变化逻辑的封装谈设计模式

通常来说,对于某个满足了我们大部分需要的类,可以创建一个它的子类,并只改变其中我们不期望的部分(需要变化部分).只是继承一个类,就可以重用该类的代码,这是一件多美好的事情啊!不过,像大多数美好的事情一样,过度使用往往会变得不美好.根据可替换原则(LSP), public 继承具有概念上的现实意义,它代表的是一种is-a关系.使用继承之前一定要问问是否真的属于is-a的关系,否则继承非常容易被过度使用.基于此,建议优选使用对象组合(object composition)而不是类继承(class i

android手机安全卫士、Kotlin漫画、支付宝动画、沉浸状态栏等源码

Android精选源码 轻量级底部导航栏 android手机卫士源码 android实现高仿今日头条源码 一个用Kotlin写的简单漫画App源码 android吐槽项目完整源码 实现可以滑动文字逐渐变色的TabLayout android实现将app隐藏加密功能的源码 android实现横向滚动的卡片堆叠布局 android仿支付宝的咻咻动画源码 android状态栏和沉浸式导航栏管理源码 Android优质博客 从BaseActivity与BaseFragment的封装谈起 这篇博客主要是从

Android安卓开发知识库汇总

初级 Android 面试知识库 Android 面试题总结之Android 进阶(二) - fuchenxuan blog - 博客频道 - CSDN.NET 如何成为一名优秀的程序员 | Mystra 2016Android某公司面试题 | yuweiguo's blog 我面试到底问什么? - AndroidDeveloper - 知乎专栏 扫清Android面试障碍 [Android基础]Android总结篇 - 陶程的博客 - 博客频道 - CSDN.NET AndroidStudyD

完整Android项目搭建全过程

这篇博客也算是本人从事开发以来的一个总结,以前写博客是为了装逼,现在是为了成长,一个项目如果刚开始的框架没有搭建好,接下来的维护工作将变得异常困难,我们公司的按项目就是因为一开始的框架没有搭建好,只迭代了两个版本便维护不下去了,只能是请高人重新设计的框架,一切重新来过. 不同类型的项目对框架的要求自然不同,但是有一点是相同的,那就是,首先对基础语法进行封装,相应工具类.方法样式的封装,前期的封装可以避免后期项目无休止的重构代码,也就不会出现因频繁的改动需求导致代码大量冗余. 废话不多说,进入正题

Android校招:一位双非本科拿到了头条,小米,京东的offer

今天分享一位应届毕业生的校招面经,大家可以看看查漏补缺. 谢谢那些曾经帮助过我的人. 很多忘记了,能回忆起来的就尽量写多一些. 小米 一面(电话) Android: 1.内存泄漏的场景,Handler机制2.四大组件3.进程间通信4.自定义view5.Binder的使用6.性能优化7.fragment的生命周期8.事件分发机制,以及涉及到的设计模式 Java: 1.接口和抽象类区别2.继承和重写3.ArrayList和LinkedList区别4.死锁产生条件和应用场景5.Java的修饰符的使用,

MVP+Dagger2+Rxjava+Retrofit+GreenDao 开发的小应用,包含新闻、图片、视频3个大模块,代码封装良好

练习MVP架构开发的App,算是对自己学过的知识做一个总结,做了有一段时间,界面还算挺多的,代码量还是有的,里面做了大量封装,整体代码整理得很干净,这个我已经尽力整理了.不管是文件(java.xml.资源文件)命名,还是布局设计尽量简单简洁,我对自己写代码的规范还是有信心的- -.代码不会写的很复杂,整个代码结构有很高的统一度,结构也比较简单清晰,方便理解.里面做了大量的封装,包括基类的构建和工具类的封装,再配合Dagger2的使用可以极大地减轻V层(Activity和Fragment)的代码,

MVP+Dagger2+Rxjava+Retrofit+GreenDao 开发的小应用,包括新闻、图片、视频3个大模块,代码封装良好

练习MVP架构开发的App,算是对自己学过的知识做一个总结,做了有一段时间,界面还算挺多的.代码量还是有的,里面做了大量封装,总体代码整理得非常干净,这个我已经尽力整理了. 不管是文件(java.xml.资源文件)命名.还是布局设计尽量简单简洁,代码不会写的非常复杂.整个代码结构有非常高的统一度,结构也比較简单清晰,方便理解.里面做了大量的封装,包含基类的构建和工具类的封装.再配合Dagger2的使用能够极大地减轻V层(Activity和Fragment)的代码,假设你有看源代码的话你会发现大部

Android使用Fragment打造万能页面切换框架

首先我们来回忆一下传统用Activity进行的页面切换,activity之间切换,首先需要新建intent对象,给该对象设置一些必须的参数,然后调用startActivity方法进行页面跳转.如果需要activity返回结果,则调用startActivityForResult方法,在onActivityResult方法中获得返回结果.此外,每一个要展示的activity需要在AndroidManifest.xml文件中注册.而且,如果在某些特定的情况下(比如65536方法数爆炸)要动态加载dex