该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!


前言

上一篇文章中详细分析了Fragment相关知识,那么作为“小Activity”,Fragment能做什么呢,如何使用Fragment得到最佳实践呢。Fragment的设计最初也许是为了大屏幕平板设备的需求,不过现在Fragment已经广泛运用到我们普通的手机设备上。下图是我们几乎在主流App中都能发现的一个功能。

熟悉Android的朋友一定都会知道,很简单嘛,使用TabHost就OK了!但是殊不知,TabHost并非是那么的简单,它的可扩展性非常的差,不能随意地定制Tab项显示的内容,而且运行还要依赖于ActivityGroup。ActivityGroup原本主要是用于为每一个TabHost的子项管理一个单独的Activity,但目前已经被废弃了。为什么呢?当然就是因为Fragment的出现了!

好了,,下面我就来实现上图的效果,不过在开始之前,首先你必须已经了解Fragment的用法了,如果你对Fragment还比较陌生的话,建议先去阅读我前面的一篇文章Android开发之漫漫长途 XII——Fragment详解

先创建宿主Activity

新建BestFragmentActivity

  1. public class BestFragmentActivity extends AppCompatActivity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_best_fragment);
  6. //下面是LuseenBottomNavigation的使用
  7. BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation);
  8. BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
  9. ("首页", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
  10. BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
  11. ("分类", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp);
  12. BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
  13. ("任务", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
  14. BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
  15. ("购物车", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp);
  16. BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
  17. ("我的", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp);
  18. bottomNavigationView.addTab(bottomNavigationItem);
  19. bottomNavigationView.addTab(bottomNavigationItem1);
  20. bottomNavigationView.addTab(bottomNavigationItem2);
  21. bottomNavigationView.addTab(bottomNavigationItem3);
  22. bottomNavigationView.addTab(bottomNavigationItem4);
  23. }
  24. }

对应的布局文件activity_best_fragment

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:id="@+id/main_content"
  8. android:fitsSystemWindows="true"
  9. >
  10. <!--Fragment之后就动态的放在该布局文件下-->
  11. <FrameLayout
  12. android:id="@+id/frame_content"
  13. android:layout_width="match_parent"
  14. android:layout_height="match_parent"
  15. android:scrollbars="none"
  16. android:layout_above="@+id/bottomNavigation"
  17. />
  18. <!--关于底层布局我这里使用了Github上的开源项目-->
  19. <com.luseen.luseenbottomnavigation.BottomNavigation.BottomNavigationView
  20. android:id="@+id/bottomNavigation"
  21. android:layout_width="match_parent"
  22. android:layout_height="wrap_content"
  23. android:layout_alignParentBottom="true"
  24. app:bnv_colored_background="false"
  25. app:bnv_with_text="true"
  26. app:bnv_shadow="false"
  27. app:bnv_tablet="false"
  28. app:bnv_viewpager_slide="true"
  29. app:bnv_active_color="@color/colorPrimary"
  30. app:bnv_active_text_size="@dimen/bottom_navigation_text_size_active"
  31. app:bnv_inactive_text_size="@dimen/bottom_navigation_text_size_inactive"/>
  32. </RelativeLayout>

关于底层布局我这里使用了Github上的开源项目LuseenBottomNavigation,该项目地址是https://github.com/armcha/LuseenBottomNavigation读者可自行查看

接着创建Fragment

目前Fragment作为演示使用,可以看到布局内容都非常简单,我这里只给出其中一个Fragment的创建过程和源码,项目完整源码可见文末的源码地址。

我们就拿第一个GoodsFragment举例把

  1. public class GoodsFragment extends Fragment {
  2. private static String TAG= GoodsFragment.class.getSimpleName();
  3. @Override
  4. public void onAttach(Context context) {
  5. super.onAttach(context);
  6. Log.d(TAG,"onAttach");
  7. }
  8. @Nullable
  9. @Override
  10. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  11. Log.d(TAG,"onCreateView");
  12. View view = inflater.inflate(R.layout.fragment_goods, null);
  13. return view;
  14. }
  15. @Override
  16. public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
  17. super.onViewCreated(view, savedInstanceState);
  18. Log.d(TAG,"onViewCreated");
  19. }
  20. @Override
  21. public void onActivityCreated(@Nullable Bundle savedInstanceState) {
  22. super.onActivityCreated(savedInstanceState);
  23. Log.d(TAG,"onActivityCreated");
  24. }
  25. @Override
  26. public void onCreate(@Nullable Bundle savedInstanceState) {
  27. super.onCreate(savedInstanceState);
  28. Log.d(TAG,"onCreate");
  29. }
  30. @Override
  31. public void onStart() {
  32. super.onStart();
  33. Log.d(TAG,"onStart");
  34. }
  35. @Override
  36. public void onResume() {
  37. super.onResume();
  38. Log.d(TAG,"onResume");
  39. }
  40. @Override
  41. public void onPause() {
  42. super.onPause();
  43. Log.d(TAG,"onPause");
  44. }
  45. @Override
  46. public void onStop() {
  47. super.onStop();
  48. Log.d(TAG,"onStop");
  49. }
  50. @Override
  51. public void onDestroyView() {
  52. super.onDestroyView();
  53. Log.d(TAG,"onDestroyView");
  54. }
  55. @Override
  56. public void onDestroy() {
  57. super.onDestroy();
  58. Log.d(TAG,"onDestroy");
  59. }
  60. @Override
  61. public void onDetach() {
  62. super.onDetach();
  63. Log.d(TAG,"onDetach");
  64. }
  65. }

源码非常的简单,在onCreateView中加载布局文件,该布局文件也非常简单,仅仅定义了一个帧布局,在帧布局中包含了一个TextView

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <FrameLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent">
  6. <TextView
  7. android:layout_width="wrap_content"
  8. android:layout_height="wrap_content"
  9. android:text="Goods"
  10. android:textStyle="bold"
  11. android:textSize="30sp"
  12. android:layout_gravity="center"/>
  13. </FrameLayout>

按照上面的流程我们建立了所需的Fragment,接着该更改BestFragmentActivity的代码,更改后的源码如下

  1. public class BestFragmentActivity extends AppCompatActivity{
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_best_fragment);
  6. //底部导航布局
  7. BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation);
  8. BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
  9. ("首页", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
  10. BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
  11. ("分类", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp);
  12. BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
  13. ("任务", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
  14. BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
  15. ("购物车", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp);
  16. BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
  17. ("我的", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp);
  18. bottomNavigationView.addTab(bottomNavigationItem);
  19. bottomNavigationView.addTab(bottomNavigationItem1);
  20. bottomNavigationView.addTab(bottomNavigationItem2);
  21. bottomNavigationView.addTab(bottomNavigationItem3);
  22. bottomNavigationView.addTab(bottomNavigationItem4);
  23. //为底部导航布局设置点击事件
  24. bottomNavigationView.setOnBottomNavigationItemClickListener(new OnBottomNavigationItemClickListener() {
  25. @Override
  26. public void onNavigationItemClick(int i) {
  27. switch (i){
  28. case 0:
  29. switchToHome();
  30. break;
  31. case 1:
  32. switchToCategory();
  33. break;
  34. case 2:
  35. switchToTask();
  36. break;
  37. case 3:
  38. switchToGoodCar();
  39. break;
  40. case 4:
  41. switchToAbout();
  42. break;
  43. }
  44. }
  45. });
  46. //初始加载首页,即GoodsFragment
  47. switchToHome();
  48. }
  49. private void switchToAbout() {
  50. getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new AboutFragment(),AboutFragment.class.getName()).commit();
  51. }
  52. private void switchToCategory() {
  53. getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new CategoryFragment(),CategoryFragment.class.getName()).commit();
  54. }
  55. private void switchToTask() {
  56. getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new TaskFragment(),TaskFragment.class.getName()).commit();
  57. }
  58. private void switchToGoodCar() {
  59. getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodCarFragment(),GoodCarFragment.class.getName()).commit();
  60. }
  61. private void switchToHome() {
  62. getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodsFragment(),GoodsFragment.class.getName()).commit();
  63. }
  64. }

上面的代码可以根据上一篇文章比较容易的写出来,而且正常运行,可是在实际开发过程中我们不得不考虑代码的性能问题。其实上面的代码存在性能问题,尤其是在底部导航这种场景中,Fragment之间的来回切换,这里使用的replace方法。关于这个方法带来的问题以及如何进行优化,将在下一节详细说明。

Fragment 性能优化问题#

FragmentTransaction##

谈到Fragment的性能优化问题,就不得不对FragmentTransaction进行深入的研究以及探讨,上面使用了getSupportFragmentManager().beginTransaction()得到了FragmentTransaction对象,并依次调用其replace方法和commit方法。

  • replace(int containerViewId, Fragment fragment)、replace(int containerViewId, Fragment fragment, String tag)

该方法的作用是,类似于先remove掉视图容器所有的Fragment,再add方法参数中的fragment,并为该Fragment设置标签tag。

  1. getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new AboutFragment(),AboutFragment.class.getName()).commit();
  2. getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new CategoryFragment(),CategoryFragment.class.getName()).commit();
  3. getSupportFragmentManager().beginTransaction().add(R.id.frame_content,new TaskFragment(),TaskFragment.class.getName()).commit();
  4. getSupportFragmentManager().beginTransaction().replace(R.id.frame_content,new GoodsFragment(),GoodsFragment.class.getName()).commit();

如上面所示代码块中,我们先进行了3次添加操作,之后的replace操作会移出前面添加的Fragment,再添加方法参数中指定的Frament。

  • add(int containerViewId, Fragment fragment, String tag)、 remove(Fragment fragment)

    FragmentTransaction的Add()操作是维持着一个队列的,在这个队列中,根据ADD进去的先后顺序形成了一个链表,我们上面的操作在这个列表中的形式变化如下图所示:

  • remove(Fragment fragment) : 移除一个已经存在的Fragment.

  • show(Fragment fragment): 显示一个以前被隐藏过的Fragment

  • hide(Fragment fragment) : 隐藏一个存在的Fragment

    注:①Fragment被hide/show,仅仅是隐藏/显示Fragment的视图,不会有任何生命周期方法的调用。

    ②在Fragment中重写onHiddenChanged方法可以对Fragment的hide和show状态进行监听。

还有一些其他的方法这里就不一一列举了,有了上面所列出的方法,我们就能对Fragment有个很不错的优化了。

Fragment性能问题分析与解决##

Fragment性能问题分析###

我们上面是使用replace来切换页面,那么在每次切换的时候,Fragment都会重新实例化,重新加载一边数据,这样非常消耗性能和用户的数据流量。这是因为replace操作,每次都会把container中的现有的fragment实例清空,然后再把指定的fragment添加进去,就就造成了在切换到以前的fragment时,就会重新实例会fragment。

Fragment性能问题解决###

知道了问题的根源所在,那么解决的办法也呼之欲出了。我们不能使用replace来进行页面的切换,那么可使用的方法貌似只有add了,我们可以在加载的时候判断Fragment是不是已经被添加到队列中,如果已添加,我们就显示(show)该Fragment,隐藏(hide)其他,如果没有添加过呢,就添加。这样就能做到多个Fragment切换不重新实例化。具体到代码中就是这样的

  1. public class BestFragmentActivity extends AppCompatActivity{
  2. //当前的Fragment
  3. private Fragment mCurFragment = new Fragment();
  4. //初始化其他的Fragment
  5. private GoodsFragment mGoodsFragment = new GoodsFragment();
  6. private GoodCarFragment mGoodCarFragment = new GoodCarFragment();
  7. private TaskFragment mTaskFragment = new TaskFragment();
  8. private AboutFragment mAboutFragment = new AboutFragment();
  9. private CategoryFragment mCategoryFragment = new CategoryFragment();
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_best_fragment);
  14. BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation);
  15. BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
  16. ("首页", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_account_balance_white_48dp);
  17. BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
  18. ("分类", ContextCompat.getColor(this, R.color.secondColor), R.mipmap.ic_list_white_48dp);
  19. BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
  20. ("任务", ContextCompat.getColor(this, R.color.firstColor), R.mipmap.ic_add_circle_outline_white_48dp);
  21. BottomNavigationItem bottomNavigationItem3 = new BottomNavigationItem
  22. ("购物车", ContextCompat.getColor(this, R.color.thirdColor), R.mipmap.ic_add_shopping_cart_white_48dp);
  23. BottomNavigationItem bottomNavigationItem4 = new BottomNavigationItem
  24. ("我的", ContextCompat.getColor(this, R.color.colorAccent), R.mipmap.ic_account_box_white_48dp);
  25. bottomNavigationView.addTab(bottomNavigationItem);
  26. bottomNavigationView.addTab(bottomNavigationItem1);
  27. bottomNavigationView.addTab(bottomNavigationItem2);
  28. bottomNavigationView.addTab(bottomNavigationItem3);
  29. bottomNavigationView.addTab(bottomNavigationItem4);
  30. bottomNavigationView.setOnBottomNavigationItemClickListener(new OnBottomNavigationItemClickListener() {
  31. @Override
  32. public void onNavigationItemClick(int i) {
  33. switch (i){
  34. case 0:
  35. switchToHome();
  36. break;
  37. case 1:
  38. switchToCategory();
  39. break;
  40. case 2:
  41. switchToTask();
  42. break;
  43. case 3:
  44. switchToGoodCar();
  45. break;
  46. case 4:
  47. switchToAbout();
  48. break;
  49. }
  50. }
  51. });
  52. switchToHome();
  53. }
  54. private void switchFragment(Fragment targetFragment){
  55. FragmentTransaction transaction = getSupportFragmentManager()
  56. .beginTransaction();
  57. if (!targetFragment.isAdded()) {//如果要显示的targetFragment没有添加过
  58. transaction
  59. .hide(mCurFragment)//隐藏当前Fragment
  60. .add(R.id.frame_content, targetFragment,targetFragment.getClass().getName())//添加targetFragment
  61. .commit();
  62. } else {//如果要显示的targetFragment已经添加过
  63. transaction//隐藏当前Fragment
  64. .hide(mCurFragment)
  65. .show(targetFragment)//显示targetFragment
  66. .commit();
  67. }
  68. //更新当前Fragment为targetFragment
  69. mCurFragment = targetFragment;
  70. }
  71. private void switchToAbout() {
  72. switchFragment(mAboutFragment);
  73. }
  74. private void switchToCategory() {
  75. switchFragment(mCategoryFragment);
  76. }
  77. private void switchToTask() {
  78. switchFragment(mTaskFragment);
  79. }
  80. private void switchToGoodCar() {
  81. switchFragment(mGoodCarFragment);
  82. }
  83. private void switchToHome() {
  84. switchFragment(mGoodsFragment);
  85. }
  86. }

这样就达到了我们的目的,我们在来回切换的操作中,Fragment只实例一次,少了销毁又重新创建等带来的性能消耗,另我们想要在Fragment中更新数据时,我们可以在自定义Fragment中重写其onHiddenChanged方法

  1. @Override
  2. public void onHiddenChanged(boolean hidden) {
  3.    super.onHiddenChanged(hidden);
  4.    if (hidden){
  5.       //Fragment隐藏时调用
  6.    }else {
  7.        //Fragment显示时调用
  8.    }
  9. }

源码地址:源码传送门

本篇总结

我们在本篇博客中比较详细的给出了一个Fragment的最佳实践,我们在许多主流App中都能看到这种顶部、底部导航的效果,并且在此基础上我们探讨了使用Fragment不当的存在性能问题及优化。


下篇预告

下篇打算往Fragment中加点东西,ListView


此致,敬礼

Android开发之漫漫长途 XIII——Fragment最佳实践的更多相关文章

  1. Android开发之漫漫长途 XII——Fragment详解

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  2. Android开发之漫漫长途 Fragment番外篇——TabLayout+ViewPager+Fragment

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  3. Android开发之漫漫长途 番外篇——自定义View的各种姿势2

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  4. Android开发之漫漫长途 XI——从I到X的小结

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  5. Android开发之漫漫长途 XVI——ListView与RecyclerView项目实战

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  6. Android开发之漫漫长途 Ⅰ——Android系统的创世之初以及Activity的生命周期

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>中的相关知识,再次表示该书 ...

  7. Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(2)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  8. Android开发之漫漫长途 Ⅱ——Activity的显示之Window和View(1)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  9. Android开发之漫漫长途 Ⅳ——Activity的显示之ViewRootImpl初探

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

随机推荐

  1. jQuery 遍历函数(八)

    函数 描述 .add() 将元素添加到匹配元素的集合中. .andSelf() 把堆栈中之前的元素集添加到当前集合中. .children() 获得匹配元素集合中每个元素的所有子元素. .closes ...

  2. 对比Tornado和Twisted两种异步Python框架

    做Python的人,一定知道两个性能优秀的异步网络框架:tornado,和twisted. 那么,这两个著名的框架,又有什么异同呢?tornado和twisted,我都用在几个游戏项目中,做过后端,觉 ...

  3. golang 类型断言的学习

    在php中有一个 serialize() 函数 可以把数组序列化成字符串进行存储和传输 如果想反序列化这种字符串,在php中只需要一个简单的unserialize() 函数就可以完成了.但是在gola ...

  4. eslint 的基本配置介绍

    eslint 这个代码规则,是在用webpack +vue-cli这个脚手架时候接触的,默认的规则可能不太习惯我们日常平时的代码开发,需要对这个规则稍加改造. 下面的是 eslintrc.js的基本规 ...

  5. 登录模块的进化史,带大家回顾java学习历程(二)

    接着前面的登录模块的进化史,带大家回顾java学习历程(一) 继续往下面讲 前面我们去实现登录功能,都是想着要完成这个功能,直接在处理实际业务的类中去开始写具体的代码一步步实现,也就是面向过程的编程. ...

  6. java.lang.Class类中的某些方法

    反射的代码会经常遇到,Class类中方法真的多,且用的少,大多用在底层源码这块,既然看到了,就记录一下吧,说不定以后厉害了,自己封装框架,haha getComponentType()方法: Syst ...

  7. equals和hashcode重写的问题

    public static void main(String[] args) { Set<Test> set = new HashSet<>(); Test t1 = new ...

  8. 透过一道面试题来探探JavaScript中执行上下文和变量对象的底

    在做面试题之前,我们先搞清楚两个概念 执行上下文(execution context) 变量对象(variable object) 执行上下文 我们都知道JavaScript的作用域一共分三种 全局作 ...

  9. Java反射-高级知识掌握

    PS:本文就Java反射的高级知识做下汇总,理清在什么情况下,我们应该去使用反射,提供框架的健壮性,ps:xieyang@163.com/xieyang@163.com

  10. java.io.FileNotFoundException class path resource [xxx.xml] cannot be opened

    没有找到xxx.xml,首先确定你项目里有这个文件吗,如果没有请添加,或者你已经存在配置文件,只是名字不是xxx.xml,请改正名字.此外还要注意最好把xxx.xml加入到classpath里,就是放 ...