天天看點

Fragment總結(二)四、Fragment與Activity通信五、卡頓六、運作時配置變化

四、Fragment與Activity通信

1.在Fragment中可以通過getActivity得到目前綁定的Activity的執行個體,然後進行操作

2.Handler、EventBus

public class MainActivity extends FragmentActivity{
      //聲明一個Handler
      public Handler mHandler = new Handler(){
          @Override
           public void handleMessage(Message msg) {
                super.handleMessage(msg);
                 ...相應的處理代碼
           }
     }
     ...相應的處理代碼
}
           
public class MainFragment extends Fragment{
    //儲存Activity傳遞的handler
    private Handler mHandler;
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        //這個地方已經産生了耦合,若還有其他的activity,這個地方就得修改
        if(activity instance MainActivity){
            mHandler =  ((MainActivity)activity).mHandler;
        }
    }
           ...相應的處理代碼
}
           

該方案存在的缺點:

Fragment對具體的Activity存在耦合,不利于Fragment複用

不利于維護,若想删除相應的Activity,Fragment也得改動

沒法擷取Activity的傳回資料

EventBus使用類似,EventBus使用反射實作,性能會差一些

3.廣播

用廣播解決此問題有點大材小用了,廣播的意圖主要是用在一對多,接收廣播者是未知的情況

廣播性能肯定會差

傳播資料有限制(必須得實作序列化接口才可以)

4.通信優化

因為要考慮Fragment的重複使用,是以必須降低Fragment與Activity的耦合,而且Fragment更不應該直接操作别的Fragment,畢竟Fragment操作應該由它的管理者Activity來決定。

FragmentOne.java檔案

public class FragmentOne extends Fragment implements OnClickListener {

    private Button mBtn;

    //設定按鈕點選的回調
    public interface FOneBtnClickListener {
        void onFOneBtnClick();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one, container, false);
        mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
        mBtn.setOnClickListener(this);
        return view;
    }

    //交給宿主Activity處理,如果它希望處理
    @Override
    public void onClick(View v) {
        if (getActivity() instanceof FOneBtnClickListener) {
            ((FOneBtnClickListener) getActivity()).onFOneBtnClick();
        }
    }

}
           

可以看到,現在的FragmentOne不和任何Activity耦合,任何Activity都可以使用,并且我們聲明了一個接口,來回調其點選事件,想要重寫其點選事件的Activity實作此接口即可,可以看到我們在onClick中首先判斷了目前綁定的Activity是否實作了該接口,如果實作了則調用。

FragmentTwo.java檔案

public class FragmentTwo extends Fragment implements OnClickListener {

    private Button mBtn ;
    private FTwoBtnClickListener fTwoBtnClickListener ;

    public interface FTwoBtnClickListener {
        void onFTwoBtnClick();
    }

    //設定回調接口
    public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener) {
        this.fTwoBtnClickListener = fTwoBtnClickListener;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_two, container, false);
        mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
        mBtn.setOnClickListener(this);
        return view ;
    }

    @Override
    public void onClick(View v) {
        if(fTwoBtnClickListener != null) {
            fTwoBtnClickListener.onFTwoBtnClick();
        }
    }

}
           

與FragmentOne極其類似,但是我們提供了setListener這樣的方法,意味着Activity不僅需要實作該接口,還必須顯示調用mFTwo.setfTwoBtnClickListener(this)。

MainActivity.java檔案

public class MainActivity extends Activity implements FOneBtnClickListener,
        FTwoBtnClickListener {

    private FragmentOne mFOne;
    private FragmentTwo mFTwo;
    private FragmentThree mFThree; //FragmentThree代碼參考上一個例子中的代碼

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        mFOne = new FragmentOne();
        FragmentManager fm = getFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.add(R.id.id_content, mFOne, "ONE");
        tx.commit();
    }

    //FragmentOne 按鈕點選時的回調
    @Override
    public void onFOneBtnClick() {
        if (mFTwo == null) {
            mFTwo = new FragmentTwo();
            mFTwo.setfTwoBtnClickListener(this);
        }
        FragmentManager fm = getFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.replace(R.id.id_content, mFTwo, "TWO");
        tx.addToBackStack(null);
        tx.commit();
    }

    //FragmentTwo按鈕點選時的回調
    @Override
    public void onFTwoBtnClick() {
        if (mFThree == null) {
            mFThree = new FragmentThree();
        }
        FragmentManager fm = getFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.hide(mFTwo);
        tx.add(R.id.id_content, mFThree, "THREE");
        //tx.replace(R.id.id_content, fThree, "THREE");
        tx.addToBackStack(null);
        tx.commit();
    }

}
           

這種方案應該是既能達到複用,又能達到很好的可維護性,并且性能也是杠杠的。但是唯一的一個遺憾是假如項目很大了,Activity與Fragment的數量也會增加,這時候為每對Activity與Fragment互動定義互動接口就是一個很頭疼的問題( 包括為接口的命名,新定義的接口相應的Activity還得實作,相應的Fragment還得進行強制轉換 )。

五、卡頓

1.原因

首頁的ViewPager有十幾個Fragment,在快速切換的時候,容易産生卡頓現象

2.分析

當ViewPager切換到目前的Fragment時,Fragment會加載布局并顯示内容,如果使用者這時快速切換ViewPager,即Fragment需要加載UI内容,而又頻繁地切換Fragment,就容易産生卡頓現象(類似在ListView快速滑動的同時加載圖檔容易卡頓)。

3.處理方案

1.Fragment輕量化

如果ViewPager加載的Fragment都比較輕量,适當精簡Fragment的布局,可提高Fragment加載的速度,進而減緩卡頓現象。

2.防止Fragment被銷毀

ViewPager在切換的時候,如果頻繁銷毀和加載Fragment,就容易産生卡頓現象,阻止Fragment的銷毀可有效減緩卡頓現象。

(1) 在PagerAdapter裡覆寫destroyItem方法可阻止銷毀Fragment

@Override 
public void destroyItem(ViewGroup container, int position, Object object) {
    //super.destroyItem(container, position, object);                                                                                                                
}
           

(2) 通過PagerAdapter的setOffscreenPageLimit()方法可以設定保留幾個Fragment,适當增大參數可防止Fragment頻繁地被銷毀和建立。

風險:在Fragment比較多的情況下,部分低端機型容易産生OOM問題。

3.Fragment内容延遲加載

(1) 描述

在切換到目前Fragment的時候,并不立刻去加載Fragment的内容,而是先加載一個簡單的空布局,然後啟動一個延時任務,延時時長為T,當使用者在該Fragment停留時間超過T時,繼續執行加載任務;而當使用者切換到其他Fragment,停留時間低于T,則取消該延時任務。

(2) 具體操作

首先,設定延遲任務

private Runnable LOAD_DATA = new Runnable() {
    @Override 
    public void run() { 
        //在這裡講資料内容加載到Fragment上
    }
};
           

啟動任務

@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    //初始化視圖,這裡最好先設定一個進度對話框,提示使用者正在加載資料
    initView();
    //啟動任務,這裡設定500毫秒後開始加載資料 
    handler.postDelayed(LOAD_DATA,500);
    return view;
}
           

若使用者切換到其他Fragment則取消任務

//判斷Fragment是否可視的重載方法
@Override
public void setUserVisibleHint(boolean isVisibleToUser{
    super.setUserVisibleHint(isVisibleToUser); 
    if(!isVisibleToUser)
    mHandler.removeCallbacks(LOAD_DATA); 
}
           

(3) 注意

使用setUserVisibleHint判斷使用者是否切換到其他Fragment,這樣的做法有個缺陷,因為會在ViewPager開始滑動的時候取消延時任務,而在滑動偏移量不足的情況下,ViewPager會繼續復原到目前Fragment,導緻目前Fragment的加載任務被取消而又不會重新啟動加載任務。

可以給ViewPager增加一個OnPageChangeListener,該監聽器的onPageSelected(position)能監聽ViewPager目前切換到哪個Fragment,在這裡将其他Fragment的延遲加載任務取消掉。

六、運作時配置變化

在Activity的學習中我們都知道,當螢幕旋轉時,是對螢幕上的視圖進行了重新繪制。因為當螢幕發生旋轉,Activity發生重新啟動,預設的Activity中的Fragment也會跟着Activity重新建立,用腳趾頭都明白,橫屏和豎屏顯示的不一樣肯定是進行了重新繪制視圖的操作。是以,不斷的旋轉就不斷繪制,這是一種很耗費記憶體資源的操作,那麼如何來進行優化?

如果重新啟動你的Activity需要恢複大量的資料,重建立立網絡連接配接,或者執行其他的密集型操作,這樣因為配置發生變化而完全重新啟動可能會是一個慢的使用者體驗。并且,使用系統提供的onSaveIntanceState()的回調中,使用Bundle來完全恢複你Activity的狀态是可能是不現實的(Bundle不是設計用來攜帶大量資料的(例如bitmap),并且Bundle中的資料必須能夠被序列化和反序列化),這樣會消耗大量的記憶體和導緻配置變化緩慢。在這樣的情況下,當你的Activity因為配置發生改變而重新開機,你可以通過保持一個Fragment來緩解重新啟動帶來的負擔。這個Fragment可以包含你想要保持的有狀态的對象的引用。

當Android系統因為配置變化關閉你的Activity的時候,你的Activity中被辨別保持的fragments不會被銷毀。你可以在你的Activity中添加這樣的fragements來儲存有狀态的對象。

在運作時配置發生變化時,在Fragment中儲存有狀态的對象

a) 繼承Fragment,聲明引用指向你的有狀态的對象

b) 當Fragment建立時調用setRetainInstance(boolean)

c) 把Fragment執行個體添加到Activity中

d) 當Activity重新啟動後,使用FragmentManager對Fragment進行恢複

//在fragment中進行設定
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //在配置變化的時候将這個fragment儲存下來
    setRetainInstance(true);
}