用Fragment实现Tab页面切换效果初步总结
前言:
最近在Android项目中需要在活动中实现多页面切换的功能,第一次是实现的过程中,是让Activity同时去加载三个界面的,通过点击tab按钮对页面设置隐藏和显示,实现页面切换效果,但是后面发现这种实现方式其实存在很多问题:
- 首先,同时去加载三个页面,切换Activity的速度会变慢,尤其是布局中如果有很多ImageView,ImageButton等使用到图片资源的控件时,切换效果非常不好;
- 其次,由于使用了很多图片资源,在退出Activity的时候,像Drawable,Bitmap等一些资源并不会随着Activity的销毁而立即回收,导致手机占用很多内存空间,导致ANR(应用程序无响应)或者OOM(内存溢出)的Bug。然而,很多经验不足的Android开发人员,包括我自己,每次写Activity的时候,并没有去考虑这些问题,导致项目后面出现一系列的问题,还好没到那种无可挽回的地步,知道这点以后,一定要养成好的代码习惯。
- 然后,一个Activity里面会有大量的初始化控件的代码,还有其他的一些方法等等,是的Activity变的非常臃肿,难以维护,有时候出现一个错误,都不太好找。
然后网上找到一些资料,使用Fragment去实现Tab切换,是一个很不错的方式,基本可以解决以上问题。
我对Fragment的理解:中文翻译就叫碎片,它可以最大限度的利用手机和平板的显示空间,让开发者可以活用布局,实现很多不错的布局效果,当然要学会使用它,需要了解它的一些基本特性,方法,以及生命周期等。
关于Fragment的详细介绍,在这里推荐鸿洋的博客http://blog.csdn.net/lmj623565791/article/details/37970961,里面分了三篇将Fragment的使用以及一些需要注意的地方讲的非常详细,篇幅比较长,所以需要拿出点耐心来看。
第一部分:
在开展项目的这段时间中,将遇到的一些Bug贴出来,网上并不是那么容易就能找到答案的Bug,同时也是困扰了很久才解决的
Bug1:
描述:(java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.zaina/wayto.com.cn.task.TaskInfoActivity}: android.view.InflateException: Binary XML file line #17: Error inflating class <unknown>)
思路分析:起初认为是布局文件的错误,然而并没有找到答案,之后再网上搜寻答案,大部分都是说由于使用了自定义控件导致的,其他的原因也有,无奈的是,我并没有使用自定义控件,只是简单的不了个局,为控件使用了统一的Style样式而已。但是这个Bug有点奇怪的是,程序刚运行的时候,进入多页面的Activity,退出再进,没问题,随着次数的增多,程序就报这个错误。Bug的出现并没有固定的一个时机,于是,我就想到,可能是手机内存不足导致,后面用了eclipse里面一个强大的插件内存分析工具MAT(Memory Analyzer Tool) ,果然不出所料,找到了内存泄露的地方,正是布局里面使用了过多的图片资源而没有手动回收导致异常。
解决方案:1,在Activity中使用了图片资源,一定要记得在onStop()方法里面去手动释放资源,这里贴一段代码,也是网上找到的,仅供参考。
1 try { 2 for (ImageView image : imageList) { 3 if (image!=null) { 4 // Drawable d=image.getDrawable(); 5 // if (d!=null) d.setCallback(null); 6 // image.setImageDrawable(null); 7 BitmapDrawable bd=(BitmapDrawable) image.getBackground(); 8 if (bd!=null) { 9 System.gc(); 10 //释放的大小 11 long size=Runtime.getRuntime().totalMemory(); 12 bd.setCallback(null); 13 bd.getBitmap().recycle(); 14 bd=null; 15 System.gc(); 16 Log.i("TaskInfo", "释放"+size); 17 } 18 } 19 image=null; 20 } 21 imageList=null; 22 } catch (Exception e) { 23 e.printStackTrace(); 24 Log.i("TaskInfo", "错误"+e.getMessage()); 25 }
2,如果程序中有多次重复使用图片资源的情况,可以使用SoftReference或者LruCache一些缓存机制去处理,而不是每次去重新加载图片,相关内容可以参考http://blog.csdn.net/xiaanming/article/details/9825113,也是写的非常好的一篇博客,里面主要是讲到使用LruCache+线程池的方式异步下载图片,设计到的技术点也很多了。
相关参考:http://www.2cto.com/kf/201408/326452.html
http://stackoverflow.com/questions/7536988/android-app-out-of-memory-issues-tried-everything-and-still-at-a-loss/7576275
Bug2:
描述:Android FragmentManager BackStackRecord.run throwing NullPointerException
思路分析:这是我后面采用Fragment实现多页面效果的过程中遇到的一个错误,也是困扰了我一段时间,其实后面想想,根本原因还是自己对Fragment还不够了解,使用过程中遇到的一些错误就不知道该从哪里入手分析,后面还是借助百度找到了答案。其实是使用Fragment切换页面过程中,之前用每次都是用FragmentTransaction的replace()方法去实现,没有问题,但是每次都是销毁上一个Fragment,然后重新加载新的Fragment,这样也可以,然是效率不高,因为每次切换都要重新new 一个Fragment,重新createView(),切换页面过程中会有卡顿。看了弘扬的博客里面写到,如果想要保留前一个Fragment的参数,最好使用FragmentTransaction的hide()和show()方法。
第一次的写法:
//Fragment 声明三个Fragment为全局变量 private TaskDetailFragment detailFragment; private TaskTimeLineFragment timeLineFragment; private TaskAttachFragment attachFragment;
private void ChangeFragment(int tabId){ FragmentManager fm=getFragmentManager(); //开发Fragment事务 FragmentTransaction transaction = fm.beginTransaction(); String tag=""; switch (tabId) { case R.id.btn_detail: //详情 if (detailFragment==null) { detailFragment=TaskDetailFragment.newInstance(ticket); transaction.add(R.id.taskinfo_content,detailFragment,"DETAIL"); break; } transaction.show(detailFragment);//隐藏 transaction.hide(timeLineFragment);//显示 transaction.hide(attachFragment);//显示 break; /后面代码类似... }
依次按顺序,从第一个Fragment切换第二的Fragment,然后切换到第三个,都没问题,但是,从第一个切换到第二个Fragment,然后再切换回第一个Fragment时,第三个Fragment并没有加载,也就是为null,这也就是为什么会出现这个空指针的Bug。
解决:在执行FragmentTransaction的hide(),show(),replace(),add(),detach()等方法时,首先要判断Frangment是否为null,这里就直接贴代码了,判断为空的方式在代码中体现出来了
首先在transaction的add()方法中为Fragment制定Tag,然后用FragmentManager的findFragmentByTag("DETAIL")方法,判断返回值是否为空。
//开始Fragment事务 FragmentTransaction transaction = fm.beginTransaction(); // String tag=""; switch (tabId) { case R.id.btn_detail: //详情 if (detailFragment==null) { detailFragment=TaskDetailFragment.newInstance(ticket); transaction.add(R.id.taskinfo_content,detailFragment,"DETAIL"); break; } if(fm.findFragmentByTag("DETAIL")!=null)transaction.show(detailFragment); if(fm.findFragmentByTag("TIME")!=null)transaction.hide(timeLineFragment); if(fm.findFragmentByTag("ATTACH")!=null)transaction.hide(attachFragment); break;
相关参考:http://stackoverflow.com/questions/13393693/android-fragmentmanager-backstackrecord-run-throwing-nullpointerexception
第三部分:
最后,总结以上几点,发现自己对于Android的学习还是存在很多需要认真钻研的地方,对于自己不熟悉的技术和知识,一定要知其根源,多加练习,才能熟练运用,被自己所掌握,然后,分析问题的时候,思维不能形成固式,多发散,多参考资料,最终要的还是要多分析自己的代码,不要盲目修改,这样只会把Bug越改越多。
ps:项目还剩不到两周时间就要交差了,自己一定要好好努力,争取把自己第一个项目做好,做到不让自己留下遗憾!