BaseActivity与BaseFragment的封装

来源:互联网 发布:知敬畏守规矩 编辑:程序博客网 时间:2024/05/22 10:57

源码地址AndroidStudio开发环境:源码下载

这篇博客主要是从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的封装:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public abstract class BaseActivity extends AppCompatActivity {  
  2.   
  3.     //布局文件ID  
  4.     protected abstract int getContentViewId();  
  5.   
  6.     //布局中Fragment的ID  
  7.     protected abstract int getFragmentContentId();  
  8.   
  9.     //添加fragment  
  10.     protected void addFragment(BaseFragment fragment) {  
  11.         if (fragment != null) {  
  12.             getSupportFragmentManager().beginTransaction()  
  13.                     .replace(getFragmentContentId(), fragment, fragment.getClass().getSimpleName())  
  14.                     .addToBackStack(fragment.getClass().getSimpleName())  
  15.                     .commitAllowingStateLoss();  
  16.         }  
  17.     }  
  18.   
  19.     //移除fragment  
  20.     protected void removeFragment() {  
  21.         if (getSupportFragmentManager().getBackStackEntryCount() > 1) {  
  22.             getSupportFragmentManager().popBackStack();  
  23.         } else {  
  24.             finish();  
  25.         }  
  26.     }  
  27.   
  28.     //返回键返回事件  
  29.     @Override  
  30.     public boolean onKeyDown(int keyCode, KeyEvent event) {  
  31.         if (KeyEvent.KEYCODE_BACK == keyCode) {  
  32.             if (getSupportFragmentManager().getBackStackEntryCount() == 1) {  
  33.                 finish();  
  34.                 return true;  
  35.             }  
  36.         }  
  37.         return super.onKeyDown(keyCode, event);  
  38.     }  
  39. }  

(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:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Created by tangyangkai on 16/5/4. 
  3.  */  
  4. public abstract class AppActivity extends BaseActivity {  
  5.   
  6.     //获取第一个fragment  
  7.     protected abstract BaseFragment getFirstFragment();  
  8.   
  9.     //获取Intent  
  10.     protected void handleIntent(Intent intent) {  
  11.   
  12.     }  
  13.   
  14.     @Override  
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(getContentViewId());  
  18.         if (null != getIntent()) {  
  19.             handleIntent(getIntent());  
  20.         }  
  21.         //避免重复添加Fragment  
  22.         if (null == getSupportFragmentManager().getFragments()) {  
  23.             BaseFragment firstFragment = getFirstFragment();  
  24.             if (null != firstFragment) {  
  25.                 addFragment(firstFragment);  
  26.             }  
  27.         }  
  28.   
  29.     }  
  30.   
  31.     @Override  
  32.     protected int getContentViewId() {  
  33.         return R.layout.activity_base;  
  34.     }  
  35.   
  36.     @Override  
  37.     protected int getFragmentContentId() {  
  38.         return R.id.fragment_container;  
  39.     }  
  40. }  

(1)一个必须实现的抽象方法来获取当前Activity应该显示的第一个Fragment 
(2)获取intent的方法,在需要传递或者接受数据的中Activity实现 
(3)在Activity的onCreate()方法中拿到intent,并且添加第一个fragment作为Activity的主界面进行显示 
最后贴一下activity_base.xml布局文件代码

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:id="@+id/fragment_container"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     tools:context=".BaseActivity">  
  8. </RelativeLayout>  

3.BaseFragment的封装:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Created by tangyangkai on 16/5/4. 
  3.  */  
  4. public abstract class BaseFragment extends Fragment {  
  5.   
  6.     protected BaseActivity mActivity;  
  7.   
  8.     protected abstract void initView(View view, Bundle savedInstanceState);  
  9.   
  10.     //获取布局文件ID  
  11.     protected abstract int getLayoutId();  
  12.   
  13.     //获取宿主Activity  
  14.     protected BaseActivity getHoldingActivity() {  
  15.         return mActivity;  
  16.     }  
  17.   
  18.       @Override  
  19.     public void onAttach(Context context) {//Modified 2016-06-01</span>  
  20.         super.onAttach(context);  
  21.         this.mActivity = (BaseActivity) context;  
  22.     }  
  23.   
  24.     //添加fragment  
  25.     protected void addFragment(BaseFragment fragment) {  
  26.         if (null != fragment) {  
  27.             getHoldingActivity().addFragment(fragment);  
  28.         }  
  29.     }  
  30.   
  31.     //移除fragment  
  32.     protected void removeFragment() {  
  33.         getHoldingActivity().removeFragment();  
  34.     }  
  35.   
  36.   
  37.     @Override  
  38.     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
  39.         View view = inflater.inflate(getLayoutId(), container, false);  
  40.         initView(view, savedInstanceState);  
  41.         return view;  
  42.     }  
  43. }  

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

 安卓有一种特殊情况,就是在APP运行在后台的时候,系统资源紧张的时候会把APP的资源全部回收(杀死APP的进程),这时候把APP再从后台返回到前台的时候,APP会重启。
鸿洋大哥的博客有相关记录: Android Fragment 你应该知道的一切 
这种内存不足的情况会导致许多问题,其中之一就是Fragment调用getActivity()的地方却返回null,报了空指针异常。解决办法就是在Fragment基类里设置一个Activity mActivity的全局变量,在onAttach(Activity activity)(使用onAttach(Context context))里赋值,使用mActivity代替getActivity()。其他的代码注释很详细,大家一看便懂。

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

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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Created by tangyangkai on 16/5/10. 
  3.  */  
  4. public class MainActivity extends AppActivity {  
  5.   
  6.     @Override  
  7.     protected BaseFragment getFirstFragment() {  
  8.         return MainFragment.newInstance();  
  9.     }  
  10. }  

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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MainFragment extends BaseFragment {  
  2.   
  3.     private Button mainBtn, mainSecondBtn;  
  4.   
  5.     public static MainFragment newInstance() {  
  6.         return new MainFragment();  
  7.     }  
  8.   
  9.   
  10.     @Override  
  11.     protected void initView(View view, Bundle savedInstanceState) {  
  12.         mainBtn = (Button) view.findViewById(R.id.main_btn);  
  13.         mainBtn.setOnClickListener(new View.OnClickListener() {  
  14.             @Override  
  15.             public void onClick(View v) {  
  16.                 Bundle data = new Bundle();  
  17.                 data.putString("username""tangyankai");  
  18.                 Intent intent = new Intent(getActivity(), LoginActivity.class);  
  19.                 intent.putExtras(data);  
  20.                 startActivity(intent);  
  21.   
  22.             }  
  23.         });  
  24.   
  25.         mainSecondBtn = (Button) view.findViewById(R.id.main_second_btn);  
  26.         mainSecondBtn.setOnClickListener(new View.OnClickListener() {  
  27.             @Override  
  28.             public void onClick(View v) {  
  29.                 addFragment(SecondFragment.newInstance("从首界面跳转过来的"));  
  30.             }  
  31.         });  
  32.     }  
  33.   
  34.     @Override  
  35.     protected int getLayoutId() {  
  36.         return R.layout.fragment_main;  
  37.     }  
  38. }  


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

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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Created by tangyangkai on 16/5/10. 
  3.  */  
  4. public class LoginActivity extends AppActivity {  
  5.   
  6.     private String username;  
  7.     @Override  
  8.     protected void handleIntent(Intent intent) {  
  9.         super.handleIntent(intent);  
  10.         Bundle bundle = intent.getExtras();  
  11.         if (null != bundle) {  
  12.             username = bundle.getString("username");  
  13.         }  
  14.     }  
  15.   
  16.     @Override  
  17.     protected BaseFragment getFirstFragment() {  
  18.         return FirstFragment.newInstance(username);  
  19.     }  
  20.   
  21. }  

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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Created by tangyangkai on 16/5/10. 
  3.  */  
  4. public class FirstFragment extends BaseFragment {  
  5.     @Override  
  6.     protected int getLayoutId() {  
  7.         return R.layout.fragment_first;  
  8.     }  
  9.   
  10.     public static String FIRST_FRAGMENT = "first_fragment";  
  11.     private String msg;  
  12.     private EditText usernameEdt;  
  13.     private TextView registerTxt, promiseTxt;  
  14.     private ImageView backImg;  
  15.   
  16.     public static FirstFragment newInstance(String msg) {  
  17.         FirstFragment fragment = new FirstFragment();  
  18.         Bundle bundle = new Bundle();  
  19.         bundle.putSerializable(FIRST_FRAGMENT, msg);  
  20.         fragment.setArguments(bundle);  
  21.         return fragment;  
  22.     }  
  23.   
  24.     @Override  
  25.     public void onCreate(Bundle savedInstanceState) {  
  26.         super.onCreate(savedInstanceState);  
  27.         if (null != getArguments()) {  
  28.             msg = (String) getArguments().getSerializable(FIRST_FRAGMENT);  
  29.         }  
  30.     }  
  31.   
  32.     @Override  
  33.     protected void initView(View view, Bundle savedInstanceState) {  
  34.         usernameEdt = (EditText) view.findViewById(R.id.username_edt);  
  35.         usernameEdt.setText(msg);  
  36.         registerTxt = (TextView) view.findViewById(R.id.register_txt);  
  37.         registerTxt.setOnClickListener(new View.OnClickListener() {  
  38.             @Override  
  39.             public void onClick(View v) {  
  40.                 addFragment(SecondFragment.newInstance("从登录界面跳转过来的"));  
  41.             }  
  42.         });  
  43.   
  44.         backImg = (ImageView) view.findViewById(R.id.first_back);  
  45.         backImg.setOnClickListener(new View.OnClickListener() {  
  46.             @Override  
  47.             public void onClick(View v) {  
  48.                 removeFragment();  
  49.             }  
  50.         });  
  51.   
  52.         promiseTxt = (TextView) view.findViewById(R.id.promise_txt);  
  53.         promiseTxt.setOnClickListener(new View.OnClickListener() {  
  54.             @Override  
  55.             public void onClick(View v) {  
  56.                 addFragment(ThirdFragment.newInstance());  
  57.             }  
  58.         });  
  59.   
  60.     }  
  61.   
  62.   
  63. }  

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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static FirstFragment newInstance(String msg) {  
  2.     FirstFragment fragment = new FirstFragment();  
  3.     Bundle bundle = new Bundle();  
  4.     bundle.putSerializable(FIRST_FRAGMENT, msg);  
  5.     fragment.setArguments(bundle);  
  6.     return fragment;  
  7. }  
  8.   
  9. @Override  
  10. public void onCreate(Bundle savedInstanceState) {  
  11.     super.onCreate(savedInstanceState);  
  12.     if (null != getArguments()) {  
  13.         msg = (String) getArguments().getSerializable(FIRST_FRAGMENT);  
  14.     }  
  15. }  

给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)点击返回按钮,一句话就帮你搞定,轻松返回上一个界面:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. removeFragment();  

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

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

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

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

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

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. transaction.add(R.id.content, IndexFragment,”fg1″);  
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void onCreate(Bundle savedInstanceState) {    
  2.     fManager = getFragmentManager();    
  3.     if (savedInstanceState != null) {    
  4.         fg1 = (AllOfficialAccountFragment) fManager.findFragmentByTag("fg1");    
  5.         fg2 = (MovieOfficialAccountFragment) fManager.findFragmentByTag("fg2");    
  6.         fg3 = (NewsOfficialAccountFragment) fManager.findFragmentByTag("fg3");    
  7.         fg4 = (OtherOfficialAccountFragment) fManager.findFragmentByTag("fg4");             
  8.     }    
  9.     super.onCreate(savedInstanceState);         
  10. }  
到这里,BaseActivity与BaseFragment的封装已经结束了,这只是最最最基础的封装,大家可以把一些常用的方法封装到基类当中,让基类Activity与Fragment发挥最大程度的作用。

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

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 手上长了个鸡眼怎么办 6岁儿童手指脱皮怎么办 手指骨折后关节僵硬怎么办 手指外伤后关节肿大僵硬怎么办 胳膊骨折了手肿怎么办 耳朵被肘了耳鸣怎么办 耳朵鼓膜外显的怎么办 耳膜破了怎么办为好 耳朵的鼓膜破了怎么办 被打耳鼓膜穿孔怎么办 两只耳朵嗡嗡响怎么办 耳朵长了个脓包怎么办 胸一个大一个小怎么办 把耳朵掏出血了怎么办 掏耳朵戳出血了怎么办 耳朵戳伤流血了怎么办 耳朵挖破出血了怎么办 耳朵让耳屎堵了怎么办 手被牙齿划破了怎么办 耳朵掏伤了很痛怎么办 掏伤耳朵发炎了怎么办 耳朵被掏发炎了怎么办 打的耳洞化脓了怎么办 打了耳洞流脓了怎么办 打了耳洞发炎怎么办 打了耳洞化脓了怎么办 3岁宝宝耳朵流脓怎么办 耳朵里面是湿的怎么办 耳朵里天天很痒怎么办 身上长湿疹很痒怎么办 嗓子干疼耳朵痒怎么办 上火了耳朵嗡嗡响怎么办 太阳凹颧骨外扩怎么办 4岁儿童脊柱侧弯怎么办 瘦的人得多囊怎么办 智齿刚长出来该怎么办 宝宝耳朵睡尖了怎么办 睡觉压的耳朵疼怎么办 月子里奶水越来越少怎么办 月子里生气回奶了怎么办 儿童疫苗本丢了怎么办