该篇博客是以记录Android开发中Fragment的使用技巧。作为开发中的大头,这一块还是有必要好好了解一下的。包括其生命周期包括其重要特性等,主要研究了fragment的懒加载以及fragment生命周期的使用和调用过程。
开发也有一段时间了,不能说一直忽略懒加载,但是由于之前一直没有很好的掌握,故而希望借这次机会真真正正的把懒加载做好,因为不然性能真的很差,真的很难忍受初始化的时候明明只是进入了Fragment A 而 Fragment B C D 确得到了加载。这样真的非常的不好,因为每一个Fragment主页可能都会有很多的资源和网络请求需要进行加载,可想而知是非常的缓慢的,而且很容易造成内存不够的问题。说了这么多,有吐槽自己的不足点,也阐明了懒加载的必要性,下面就有有必要整理下实际项目中的懒加载应该如何做。OK,阐述一下懒加载主要存在于类似于TabLayout+ViewPager这种包含有缓存中,普通的Fragment切换是不需要懒加载的。
相信对于Fragment的生命周期大家都有所了解吧,下面是图例。
值得留意的是不同于Activity被操作系统管理,Fragment是依附于Activity的同样其也是托管于Activity的。注意这里能发现Activity的方法类似,是的,对于Fragement而言,其调用时机也是和活动相同步的。
案例1展示:活动-fragment-fragment切换生命周期
如下方log所展示,详细的记录了生命周期的切换。
//启动活动并且显示add FragmentA
Activity onCreate
FragmentA onAttach
onCreate
onCreateView
onStart
Activity onStart
onResume
FragmentA onResume
//先隐藏FragmentA 然后再add FragmentB
FragmentB onAttach
onCreate
onCreateView
onStart
onResume
// 退出活动
FragmentA onPause
FragmentB onPause
Activity onPause
FragmentA onStop
FragmentB onStop
Activity onStop
FragmentA onDestroyView
onDestroy
onDetach
FragmentB onDestroyView
onDestroy
onDetach
Activity onDestroy
案例展示2:验证setUserVisibleHint的调用时机
// 活动中展示Fragment
private void showFragment() {
// 开启事务 ------
FragmentTransaction fragmentTransaction =
getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment,new FragmentA());
// 进行提交 -------
fragmentTransaction.commit();
}
// Fragment中打印Log日志
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.e(TAG, "onAttach: ");
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
Log.e(TAG, "setUserVisibleHint: "+ isVisibleToUser);
super.setUserVisibleHint(isVisibleToUser);
}
@Override
public void onDetach() {
super.onDetach();
Log.e(TAG, "onDetach: ");
}
观察启动界面的日志:
09-12 11:29:06.173 3577-3577/com.example.mydairytestproject E/FragmentA: onAttach:
09-12 11:29:08.585 3577-3577/com.example.mydairytestproject E/FragmentA: onDetach:
值得注意的是,当仅仅是启动或者关闭一个Fragment时,其实并不会调用setUserVisibleHint()方法的。 该方法调用的时机是在:不同fragment切换时才会调用。
那我们接下来完成一个ViewPager+Fragment的案例(FragmentA + FragmentB),看看其生命周期调用的情况吧。
这次我们打印Fragment的生命周期方法调用吧,说明一下,我是这样操作的: 首先自然是打开Activity显示的是FragmentA,然后切换到FragmentB 之后再切换回 FragmentA.
16467-16467/com.example.mydairytestproject E/FragmentA: setUserVisibleHint: false
16467-16467/com.example.mydairytestproject E/FragmentB: setUserVisibleHint: false
16467-16467/com.example.mydairytestproject E/FragmentA: setUserVisibleHint: true
16467-16467/com.example.mydairytestproject E/FragmentA: onCreate:
16467-16467/com.example.mydairytestproject E/FragmentB: onCreate:
16467-16467/com.example.mydairytestproject E/FragmentA: onCreateView:
16467-16467/com.example.mydairytestproject E/FragmentB: onCreateView:
6467-16467/com.example.mydairytestproject E/FragmentA: setUserVisibleHint: false
16467-16467/com.example.mydairytestproject E/FragmentB: setUserVisibleHint: true
16467-16467/com.example.mydairytestproject E/FragmentB: setUserVisibleHint: false
6467-16467/com.example.mydairytestproject E/FragmentA: setUserVisibleHint: true
以上案例很重要,对于我们后面鉴定判别懒加载活动很重要,我们执行一些规避判断等也是基于以上的。
关于fragment生命˙周期这里说明下:
只有viewpager+fragment形式方会执行setUserVisibleHint()
且使用fragment hide()和show() 才会调用onHideChanged()
暂时对于fragment可见生命周期比较难把控。
a.未做处理的运行效果
我在每个加载的Fragmet中的initVIew( ) 和 initData( ) 中都加入了log。
可以发现,只要一进入,那么就会加载全部的四个Fragment的VIew 和 数据,假设每个界面都需要进行大额的网络请求,可想而知那肯定是灾难的啊,卡顿不说,性能还不好,用户明明没点进去啊,怎么数据就加载了呢,非常非常的不好。
08-17 17:20:36.410 8621-8621/com.example.myapplication22 E/initView: ---- 第三个Fragment-----
08-17 17:20:36.410 8621-8621/com.example.myapplication22 E/initData: ---- 第三个Fragment-----
08-17 17:20:36.412 8621-8621/com.example.myapplication22 E/initView: ---- 第二个Fragment-----
08-17 17:20:36.412 8621-8621/com.example.myapplication22 E/initData: ---- 第二个Fragment-----
08-17 17:20:36.413 8621-8621/com.example.myapplication22 E/initView: ---- 第四个Fragment-----
08-17 17:20:36.413 8621-8621/com.example.myapplication22 E/initData: ---- 第四个Fragment-----
08-17 17:20:36.415 8621-8621/com.example.myapplication22 E/initView: ---- 第一个Fragment-----
08-17 17:20:36.415 8621-8621/com.example.myapplication22 E/initData: ---- 第一个Fragment-----
b. 进行改进
优秀的开源框架,含有懒加载 Fragmentation
08-17 17:44:24.014 15253-15253/com.example.myapplication22 E/initView: ---- 第二个Fragment-----
08-17 17:44:24.017 15253-15253/com.example.myapplication22 E/initView: ---- 第三个Fragment-----
08-17 17:44:24.018 15253-15253/com.example.myapplication22 E/initView: ---- 第四个Fragment-----
08-17 17:44:24.019 15253-15253/com.example.myapplication22 E/initView: ---- 第一个Fragment-----
08-17 17:44:24.062 15253-15253/com.example.myapplication22 E/initData: ---- 第一个Fragment-----
08-17 17:44:25.232 15253-15253/com.example.myapplication22 E/initData: ---- 第二个Fragment-----
08-17 17:44:26.519 15253-15253/com.example.myapplication22 E/initData: ---- 第三个Fragment-----
08-17 17:44:27.255 15253-15253/com.example.myapplication22 E/initData: ---- 第四个Fragment-----
好了这样明显是在显示界面完了之后执行了initData()方法的。真的是完美!
只需要将您的activity基类继承自Fragmentation 实现的SupportActivty然后重写 onLazyInitView() 在这里面进行数据的加载即可。
@Override
public void onLazyInitView(@Nullable Bundle savedInstanceState) {
super.onLazyInitView(savedInstanceState);
initData();
initListener();
}
添加Fragment供FragmentManager管理时,onAttach(Activity)、onCreate(Bundle)以及onCreateView(…)方法会被调用。
public abstract class LazyFragment extends Fragment {
/**
* 标记已加载完成,保证懒加载只能加载一次
*/
private boolean hasLoaded = false;
/**
* 标记Fragment是否已经onCreate
*/
private boolean isCreated = false;
/**
* 界面对于用户是否可见
*/
private boolean isVisibleToUser = false;
private View view;
public LazyFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
init(getView(inflater, getLayoutId(), container), savedInstanceState);
return view;
}
private View getView(LayoutInflater inflater, int layoutId, ViewGroup container) {
return inflater.inflate(layoutId, container, false);
}
public void init(View view, Bundle savedInstanceState) {
isCreated = true;
this.view = view;
initViews(this.view,savedInstanceState);
lazyLoad(this.view,savedInstanceState);
}
/**
* 监听界面是否展示给用户,实现懒加载
* 这个方法也是网上的一些方法用的最多的一个,我的思路也是这个,不过把整体思路完善了一下
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
Log.i("TAG", "setUserVisibleHint: ");
//注:关键步骤
this.isVisibleToUser = isVisibleToUser;
lazyLoad(view, null);
}
public abstract void initViews(View view,Bundle savedInstanceState);
/**
* 懒加载方法,获取数据什么的放到这边来使用,在切换到这个界面时才进行网络请求
*/
private void lazyLoad(View view, Bundle savedInstanceState) {
//如果该界面不对用户显示、已经加载、fragment还没有创建,
//三种情况任意一种,不获取数据
if (!isVisibleToUser || hasLoaded || !isCreated) {
return;
}
lazyInit(view, savedInstanceState);
//注:关键步骤,确保数据只加载一次
hasLoaded = true;
}
/**
* 子类必须实现的方法,这个方法里面的操作都是需要懒加载的
*/
public abstract void lazyInit(View view, Bundle savedInstanceState);
public abstract int getLayoutId();
@Override
public void onDestroyView() {
super.onDestroyView();
isCreated = false;
hasLoaded = false;
}
}
运行结果如下图所示:经测试是确实有效果的。
下面的代码介绍了,合理的通过使用fragmentTransaction实现Fragement的添加与切换的效果,故而记录下
这里我要说明一下,这里我们不推荐用单例方式获取Fragment,这里会产生一些问题的
private void showWhichFragment(int index){
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
hideAllFragment(fragmentTransaction);
switch (index){
case 0:
if (mDetFragment == null){
mDetFragment = LaunchFgFactory.getFactoryInstance().getDetFgInstance();
fragmentTransaction.add(R.id.fgMain,mDetFragment);
}else {
fragmentTransaction.show(mDetFragment);
}
break;
case 1:
if (mNewsFragment == null){
mNewsFragment = LaunchFgFactory.getFactoryInstance().getNewFgInstance();
fragmentTransaction.add(R.id.fgMain,mNewsFragment);
}else {
fragmentTransaction.show(mNewsFragment);
}
break;
case 2:
if (mMsgFragment == null){
mMsgFragment = LaunchFgFactory.getFactoryInstance().getMsgInstance();
fragmentTransaction.add(R.id.fgMain,mMsgFragment);
}else {
fragmentTransaction.show(mMsgFragment);
}
break;
case 3:
if (mMeFragment == null){
mMeFragment = LaunchFgFactory.getFactoryInstance().getMeFgInstacne();
fragmentTransaction.add(R.id.fgMain,mMeFragment);
}else {
fragmentTransaction.show(mMeFragment);
}
break;
default:
break;
}
fragmentTransaction.commit();
}
private void hideAllFragment(FragmentTransaction fragmentTransaction){
if (mDetFragment != null){
fragmentTransaction.hide(mDetFragment);
}
if (mNewsFragment != null){
fragmentTransaction.hide(mNewsFragment);
}
if (mMsgFragment != null){
fragmentTransaction.hide(mMsgFragment);
}
if (mMeFragment != null){
fragmentTransaction.hide(mMeFragment);
}
}
@Override
protected void initListener() {
mBottomBar.setOnTabSelectListener(tabId -> {
switch (tabId){
case R.id.tabDet:
showWhichFragment(0);
break;
case R.id.tabNews:
showWhichFragment(1);
break;
case R.id.tabMsg:
showWhichFragment(2);
break;
case R.id.tabMe:
showWhichFragment(3);
break;
default:
break;
}
});
}
增加isAdded()判断
private void showWhichDialog(int index){
if (mFragmentManager == null){
mFragmentManager = getSupportFragmentManager();
}
FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
if (mCategoryFragment != null){
mFragmentTransaction.hide(mCategoryFragment);
}
if (mCenterFragment != null){
mFragmentTransaction.hide(mCenterFragment);
}
if (mHomeFragment != null){
mFragmentTransaction.hide(mHomeFragment);
}
switch (index){
case 0:
if (mHomeFragment == null){
mHomeFragment = new HomeFragment();
mFragmentTransaction.add(R.id.container,mHomeFragment);
}else {
if (mHomeFragment.isAdded()){
if (mHomeFragment.isHidden()){
mFragmentTransaction.show(mHomeFragment);
}
}else {
mFragmentTransaction.add(R.id.container,mHomeFragment);
}
}
break;
case 1:
if (mCategoryFragment == null){
mCategoryFragment = new CategoryFragment();
mFragmentTransaction.add(R.id.container,mCategoryFragment);
}else {
if (mCategoryFragment.isAdded()){
if (mCategoryFragment.isHidden()){
mFragmentTransaction.show(mCategoryFragment);
}
}else {
mFragmentTransaction.add(R.id.container,mCategoryFragment);
}
}
break;
case 2:
if (mCenterFragment == null){
mCenterFragment = new CenterFragment();
mFragmentTransaction.add(R.id.container,mCenterFragment);
}else {
if (mCenterFragment.isAdded()){
if (mCenterFragment.isHidden()){
mFragmentTransaction.show(mCenterFragment);
}
}else {
mFragmentTransaction.add(R.id.container,mCenterFragment);
}
}
break;
}
mFragmentTransaction.commit();
}
// 清除老的Fragment
private void clearOldFragment() {
if (mSupportFragmentManager == null){
mSupportFragmentManager = getSupportFragmentManager();
}
FragmentTransaction transaction = mSupportFragmentManager.beginTransaction();
List fragments = mSupportFragmentManager.getFragments();
if (transaction == null || fragments == null || fragments.size() == 0) {
return;
}
boolean doCommit = false;
for (Fragment fragment : fragments) {
if (fragment != null) {
transaction.remove(fragment);
doCommit = true;
}
}
if (doCommit) {
transaction.commitNow();
}
}
1. Fragment嵌套Fragment当使用TabLayout+ViewPager构建界面时需要使用:getChildFragmentManager(),让当前的Fragemnt去管理,如果越界给Activity去管理会出现加载不全的问题。
2. Fragment之间的传值问题
明确: Fragment之间的桥梁就是Activity,也就是说可以通过Activity这个载体去传递值。
样例: (xxxActivity)getActivity.getFragemtManager().findFragmentBuTag("tag");
这样就能获得设置了具体Tag的Fragment的实例,那么也就能够通过实例方法进行传值刷新UI了。
当然:像rxbus eventBus 等组件间通信咱们就不提了,也是基本功之一。
3. activity中创建fragment时,传递数值
如下所示,当该Fragment时存在多种类型,进而去做不同的网络请求时可以通过该方式传递数值。
public static DetFragment newInstance(String type) {
DetFragment fragment = new DetFragment();
Bundle args = new Bundle();
args.putString(TYPE, type);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (null != getArguments()){
type = getArguments().getString(TYPE);
Log.e(TAG, "TYPE = "+type);
}
}
4. activity 与 DialogFragment传值问题,通过回调函数方式
// activity中
// 显示fragment
ShowEnjoyDialogFragment.showFragment(MainActivity.this,mEnjoyBeanList,this);
// 接收fragment传值
@Override
public void onEnsureData(List mList) {
mEnjoyBeanList.clear();
mEnjoyBeanList.addAll(mList);
}
// dialogFragment中
public static void showFragment(Activity activity,List enjoys,EnsureListener ensureListener){
ShowEnjoyDialogFragment enjoyDialogFragment = new ShowEnjoyDialogFragment();
enjoyDialogFragment.setEnsureListener(ensureListener);
Bundle bundle = new Bundle();
bundle.putSerializable("list", (Serializable) enjoys);
enjoyDialogFragment.setArguments(bundle);
enjoyDialogFragment.show(activity.getFragmentManager(),"enjoy");
}
// 接收数值
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置数据...
_initData();
Bundle arguments = getArguments();
if (arguments != null) {
List enjoyBeans = (List) arguments.getSerializable("list");
if (enjoyBeans.size() == 0){
return;
}
for (int i=0;i
5. fragment初始化
fragment切换保存崩溃时的状态,注意点,添加时附带名称并且注意使用onSaveInstanceState()保存崩溃前的状态。
private void initFragments(Bundle savedInstanceState) {
if (savedInstanceState != null){
mDetFragment = (DetFragment) getSupportFragmentManager().findFragmentByTag("det");
mMsgFragment = (MsgFragment) getSupportFragmentManager().findFragmentByTag("msg");
mNewsFragment = (NewsFragment) getSupportFragmentManager().findFragmentByTag("news");
mMeFragment = (MeFragment) getSupportFragmentManager().findFragmentByTag("me");
// 假如异常结束了,那么可以从原来中获取到
currentTabPosition = savedInstanceState.getInt(HOME_CURRENT_TAB_POSITION);
}
showSwitchFragment(currentTabPosition);
// 设置默认
mBottomBar.setDefaultTab(R.id.tabDet);
}
/**
* fragment崩溃时会进行调用
* @param outState
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//奔溃前保存位置
if (mBottomBar != null) {
outState.putInt(HOME_CURRENT_TAB_POSITION, mBottomBar.getCurrentTabPosition());
}
}
if (null == mDetFragment){
mDetFragment = DetFragment.newInstance("android");
fragmentTransaction.add(R.id.fg_main,mDetFragment,"det");
}else {
fragmentTransaction.show(mDetFragment);
}
6. addToBackStack
官方的说法是取决于你是否要在回退的时候显示上一个Fragment,当添加该方法时按返回键会返回到上一个页面。