總結(jié)
- Fragment初始化用newInstance
- viewPager開多Fragment, 考慮懶加載
- getActivity空指針,
onAttach()拿到mActivity - 避免異步中進行
commit() - 避免在
onCreate()外commit() - genymotion別亂拖,否則橫豎屏切換不會重啟activity
- fragment第二次進入顯示白原因
- onBackPressedBUG
- FragmentStateLoss
生命周期
只有在activity處于onResume()時,fragment的生命周期才會自由,否則,被activity控制
-
onAttach(), 拿到activity, 添加Listener -
onCreate(), ??? -
onCreateView(), 創(chuàng)建視圖 -
onActivityCreated(), activity的onCreate 返回時調(diào)用 -
onDestroyView(), Fragment視圖被移除時調(diào)用 -
onDestroy(), -
onDetach(), Fragment 與 activity 取消關(guān)聯(lián)時調(diào)用, 操作與onAttach()相反
加載
靜態(tài)加載,類似 view
- activity 的布局中
<fragment android:id="@+id/id_fragment_title" **必須包含ID** android:name="com.包名.路徑.TitleFragment" android:layout_width="fill_parent" android:layout_height="45dp" />
動態(tài)加載
1.構(gòu)造
Google規(guī)定 Fragment 需要提供一個 public 的無參構(gòu)造函數(shù),在 framework 狀態(tài)恢復(fù)時使用
-
如果需要接受外部的參數(shù)創(chuàng)建Fragment的, 需保存參數(shù)到 bundle:
inflate(int resources, 當前布局D ViewGroup root, 根布局G boolean attachToRoot 是否依賴根布局G),viewgroup的
addview()不能添加一個已包含父控件的視圖, 如果設(shè)置第三個參數(shù)為false, 則當前布局D 不依賴于根布局G, 返回的是xml為根布局的view- 如果不設(shè)置attachToRoot的false,
- 當fragmentTransaction add 時,
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. - 當viewpager導(dǎo)入, 直接報 OOM
-
這個參數(shù)是parentView, 當你創(chuàng)建子布局時, 如果希望這個布局依賴于第二
//FragmentOne中的newInstance函數(shù) public static FragmentOne newInstance(String text){ FragmentOne fragmentOne = new FragmentOne(); Bundle bundle = new Bundle(); bundle.putString("name", text);//傳參 fragmentOne.setArguments(bundle); return fragmentOne; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); text = getArguments() != null ? getArguments().getString("name") : ""; } @Override public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ View view = inflater.inflate(R.layout.fragmentm, container, false); return view; } 作者:DrunkPian0 鏈接:http://www.itdecent.cn/p/caa5d6568faa 來源:簡書 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
2.替換
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(布局,F(xiàn)ragment 實例)
fragmentTransaction.commit();
3. 通信
activity傳給fragment
activity 中通過getFragmentManager().findFragmentById(R.id.test_fragment);獲取 Fragment 實例,
也可以-
Fragment傳給activity
-
fragment內(nèi)部創(chuàng)建接口
public interface FragmentOneBtnClickListener{ void onOneBtnClick(); } -
fragment內(nèi)按鈕點擊時調(diào)用
Button button = (android.widget.Button) view.findViewById(R.id.btn_frag_one); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (getActivity() instanceof FragmentOneBtnClickListener){ ((FragmentOneBtnClickListener)getActivity()).onOneBtnClick(); } } }); -
activity實現(xiàn)接口及方法
public class MainActivity extends AppCompatActivity implements OneFragment.FragmentOneBtnClickListener{ @Override public void onOneBtnClick() { Toast.makeText(this, "one", Toast.LENGTH_SHORT).show(); } }
-
Fragment.startActivityForResult();
Activity 里的 FragmentOne 調(diào)用startActivityForResult開啟ActivityTwo, ActivityTwo里的FragmentTwo操作回調(diào)時調(diào)用getActivity().setResult(FragmentOne.REQUEST_DETAIL, intent);
FragmentManager
當activity被殺死重建時,fragment會被保存, 但會創(chuàng)建新的FragmentManager, 新FragmentManager會先去獲取保存下來的fragment隊列, 再去重建, 從而恢復(fù)之前的狀態(tài)
FragmentTransaction (Google)
-
add(); 添加 Fragment -
remove(); 移除 Fragment, 若添加addToBackStack(null),則不會執(zhí)行onDestroy()和onDetach(); -
replace(); = remove + add -
hide(); 隱藏 -
show(); 顯示隱藏的 -
detach(fragment); 移除 view, 但 fragment 依然被 FragmentManager 管理 -
attach(); 重建視圖 -
commit();默認異步,并不立刻執(zhí)行,而是加入 UI 線程的隊列中,- 后接executePendingTransactions() 立即執(zhí)行所有 pending在隊列中的transaction
commitAllowStateLoss();-
commitNow()^v24^, 只同步執(zhí)行此次 transaction(完善executePendingTransactions),不可與addToBackStack()共用 commitNowAllowStateLoss()
Back Stack
- Activity 的 BackStack,系統(tǒng)維護,每個 task 一個 BackStack
- Fragment 的 BackStack,宿主 Activity 維護,每個 activity 一個
- 通過 addToBackStack 調(diào)用,按 Back 鍵后執(zhí)行 commit 進去的 transaction 的逆操作
v4.fragment 和 app.fragment(以下簡稱v4和app)
- 支持版本不同, v4支持到4, app只支持11及以上
- v4需要jar包
- 獲取 FragmentManager
- v4:
getSupportFragmentManager - app:
getFragmentManager
- v4:
- 包含 v4 的 Activity 需要繼承 FragmentActivity
- v4的不支持 objectAnimator, Animator, 即不支持屬性動畫,只支持位移動畫。參考
- mStateSaved 何時置為true
- v4在
onSaveInstanceState和onStop - app在
onSaveInstanceState
- v4在
舉例
例1 commitAllowStateLoss
- acticity 中放入 FragmentA;
- activity 被后臺,運行
onStop和onSaveInstanceState; - 某個事件觸發(fā)下,F(xiàn)ragmentB replace FragmentA,提交的是
commitAllowStateLoss; - 此時可能會發(fā)生兩種情況
- 第一種,系統(tǒng)殺死了activity,activity重建,使用步驟2的onSaveInstanceState恢復(fù),A恢復(fù),B沒有
- 第二種,activity 沒被殺死,F(xiàn)ragmentB 顯示,到下次 Activity stop時,這個包含了 B 的狀態(tài)被保存了下來
例2 fragment中執(zhí)行異步
- activity 執(zhí)行AsyncTask, 同時打開
ProgressDialog(API26被棄用,推薦ProgressBar和Notification) - 執(zhí)行過程中, 進行旋轉(zhuǎn)屏幕 可能的情況如下:
- 上個線程還在執(zhí)行, 又開一個線程, 可能操作一些已經(jīng)被處理的控件, 報空
- 關(guān)閉dialog的代碼在onPostExecute, 但是上個線程被殺死, 無法關(guān)閉
- 解決: DialogFragment
例3 內(nèi)存泄漏 from簡書
- util.class
public class Util {
private Context mContext;
private static Util sInstance;
private Util(Context context) {
this.mContext = context;
}
public static Util getInstance(Context context) {
if (sInstance == null) {
sInstance = new Util(context);
}
return sInstance;
}
//other methods
}
- Fragment用了上面的類
- Fragment被干掉, GC想回收Fragment占用的內(nèi)存, 但因為sInstance 是靜態(tài)的, 一直持有fragment的引用, 即使
destroy也不行
- 解決:
- 用
getApplicationContext() - 弱引用
- 把sInstance用WeakReference包起來, 需要的時候
wr.get();
- 把sInstance用WeakReference包起來, 需要的時候
- 用
例4 橫豎屏切換fromHongYang
- 橫豎屏切換時, activity重建, fragment生命周期跟著變, 同時因為activity的
onSaveInstanceState, 之前的fragment們也被還原出來,會產(chǎn)生多個fragment -
第一次切換
-
第二次切換
- 按下Home鍵后的生命周期
- 解決: activity 的
onCreate方法中添加bundle非空后再進行transaction和commit,@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... if (savedInstanceState == null) { FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(R.id.layout, oneFragment, "one"); fragmentTransaction.commit(); } ... }
Fragment+ActionBar(ToolBar)
Fragment自己實現(xiàn)
Fragment 的 xml 里添加 toolBar
-
onCreate加入 setHasOptionsMenu(true)
@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } -
設(shè)置依賴的activity.setSupportActionBar
@Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_one, container, false); Toolbar toolbar = (Toolbar) view.findViewById(R.id.fragment_toolbar); mActivity.setSupportActionBar(toolbar); return view; } -
實現(xiàn)onCreateOptionsMenu
@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.toolbar, menu); } -
點擊事件onOptionsItemSelected
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.id_menu_fra_test: Toast.makeText(getActivity(), "FragmentMenuItem1", Toast.LENGTH_SHORT).show(); break; } return true; }
Activity實現(xiàn)
PS: Activity自身ToolBar
manifest 把 theme 的 parent 改成 NoActionBar
activity布局里添加ToolBar
-
activity的onCreate方法setSupportActionBar();
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); -
activity 重寫 onCreateOptionsMenu 添加布局
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar, menu); return true; }重寫 onOptionsItemSelected 添加點擊事件
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.backup: Toast.makeText(this, "backup", Toast.LENGTH_SHORT).show(); break; case R.id.delete: Toast.makeText(this, "del", Toast.LENGTH_SHORT).show(); break; case R.id.settings: Toast.makeText(this, "set", Toast.LENGTH_SHORT).show(); break; } return true; }
FragmentPagerAdapter 和 FragmentStatePagerAdapter
- FragmentPagerAdapter: 調(diào)用depatch(), 只銷毀視圖, 適合主界面
- FragmentPagerAdapter: 銷毀, 可存數(shù)據(jù)進bundle, 然后保存在onSaveInstanceState
DialogFragment 替代 Dialog和AlertDialog
Dialog和繼承的AlertDialog無法在橫豎屏切換時保存數(shù)據(jù), DialogFragment依靠FragmentManager自動重建并恢復(fù)數(shù)據(jù)
- 自定義view 的 DialogFragment 創(chuàng)建
public class EditDialogFragment extends DialogFragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { View inflate = inflater.inflate(R.layout.fragment_dialog, container); getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); return inflate; } - AlertDialog 的 DialogFragment 創(chuàng)建
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); LayoutInflater layoutInflater = getActivity().getLayoutInflater(); View view = layoutInflater.inflate(R.layout.fragment_dialog, null); // editName = (EditText) view.findViewById(R.id.id_txt_your_name); builder.setView(view) .setNegativeButton("sign", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Toast.makeText(getContext(),"test",Toast.LENGTH_SHORT).show(); // LoginCompleteListener loginCompleteListener = (LoginCompleteListener) getActivity(); // loginCompleteListener.onLoginComplete(editName.getText().toString(), "123"); } }).setPositiveButton("Cancel", null); return builder.create();
Fragment來保存數(shù)據(jù)
系統(tǒng)幫助數(shù)據(jù)恢復(fù)
少量數(shù)據(jù)
onSaveInstanceState和onRestoreInstanceState-
大量數(shù)據(jù), 無法序列化, 如bitmap, 用Fragment存放, 但是切勿傳遞任何包含context的對象
- 創(chuàng)建Fragment, 添加
setRetainInstance
public class RetainedFragment extends Fragment { // data object we want to retain private MyDataObject data; // this method is only called once for this fragment @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // retain this fragment setRetainInstance(true); } public void setData(MyDataObject data) { this.data = data; } public MyDataObject getData() { return data; } }- activity調(diào)用fragment保存
public class MyActivity extends Activity { private RetainedFragment dataFragment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // find the retained fragment on activity restarts FragmentManager fm = getFragmentManager(); dataFragment = (DataFragment) fm.findFragmentByTag(“data”); // create the fragment and data the first time if (dataFragment == null) { // add the fragment dataFragment = new DataFragment(); fm.beginTransaction().add(dataFragment, “data”).commit(); // load the data from the web dataFragment.setData(loadMyData()); } // the data is available in dataFragment.getData() ... } @Override public void onDestroy() { super.onDestroy(); // store the data in the fragment dataFragment.setData(collectMyLoadedData()); } } - 創(chuàng)建Fragment, 添加
自行處理數(shù)據(jù)變更(activity不重走生命周期, 不推薦)
- 在manifest的<Activity>里添加屬性
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
- 屏幕切換時可通過
onConfigurationChanged自行處理
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}
進階-Fragment保存AsyncTask
activity在 onCreate 和 onSaveInstanceState 回調(diào)AsyncTask, 并在onCreate時調(diào)用 setData
- 創(chuàng)建Fragment, 不過setData保存的是AsyncTask
public class OtherRetainedFragment extends Fragment
{
// data object we want to retain
// 保存一個異步的任務(wù)
private MyAsyncTask data;
// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setData(MyAsyncTask data)
{
this.data = data;
}
public MyAsyncTask getData()
{
return data;
}
}
- 創(chuàng)建AsyncTask
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private FixProblemsActivity activity;
/**
* 是否完成
*/
private boolean isCompleted;
/**
* 進度框
*/
private LoadingDialog mLoadingDialog;
private List<String> items;
public MyAsyncTask(FixProblemsActivity activity) {
this.activity = activity;
}
/**
* 開始時,顯示加載框
*/
@Override
protected void onPreExecute() {
mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
}
/**
* 加載數(shù)據(jù)
*/
@Override
protected Void doInBackground(Void... params) {
items = loadingData();
return null;
}
/**
* 加載完成回調(diào)當前的Activity
*/
@Override
protected void onPostExecute(Void unused) {
isCompleted = true;
notifyActivityTaskCompleted();
if (mLoadingDialog != null)
mLoadingDialog.dismiss();
}
public List<String> getItems() {
return items;
}
private List<String> loadingData() {
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
}
return new ArrayList<String>(Arrays.asList("通過Fragment保存大量數(shù)據(jù)",
"onSaveInstanceState保存數(shù)據(jù)",
"getLastNonConfigurationInstance已經(jīng)被棄用", "RabbitMQ", "Hadoop",
"Spark"));
}
/**
* 設(shè)置Activity,因為Activity會一直變化
*
* @param activity
*/
public void setActivity(FixProblemsActivity activity) {
// 如果上一個Activity銷毀,將與上一個Activity綁定的DialogFragment銷毀
if (activity == null) {
mLoadingDialog.dismiss();
}
// 設(shè)置為當前的Activity
this.activity = activity;
// 開啟一個與當前Activity綁定的等待框
if (activity != null && !isCompleted) {
mLoadingDialog = new LoadingDialog();
mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
}
// 如果完成,通知Activity
if (isCompleted) {
notifyActivityTaskCompleted();
}
}
private void notifyActivityTaskCompleted() {
if (null != activity) {
activity.onTaskCompleted();
}
}
}
- 創(chuàng)建activity
public class FixProblemsActivity extends ListActivity {
private static final String TAG = "MainActivity";
private ListAdapter mAdapter;
private List<String> mDatas;
private OtherRetainedFragment dataFragment;
private MyAsyncTask mMyTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (OtherRetainedFragment) fm.findFragmentByTag("data");
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new OtherRetainedFragment();
fm.beginTransaction().add(dataFragment, "data").commit();
}
mMyTask = dataFragment.getData();
if (mMyTask != null) {
mMyTask.setActivity(this);
} else {
mMyTask = new MyAsyncTask(this);
dataFragment.setData(mMyTask);
mMyTask.execute();
}
// the data is available in dataFragment.getData()
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
Log.e(TAG, "onRestoreInstanceState");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
mMyTask.setActivity(null);
super.onSaveInstanceState(outState);
Log.e(TAG, "onSaveInstanceState");
}
@Override
protected void onDestroy() {
Log.e(TAG, "onDestroy");
super.onDestroy();
}
/**
* 回調(diào)
*/
public void onTaskCompleted() {
mDatas = mMyTask.getItems();
mAdapter = new ArrayAdapter<String>(FixProblemsActivity.this,
android.R.layout.simple_list_item_1, mDatas);
setListAdapter(mAdapter);
}
}
BUGS
- findFragmentByTag 查不到
- 解決: 在 onCreate() 里提交commit()后, 在 onStart() 里find, 不能直接在commit()之后
嵌套
WTFs/min = 2^fragment count
參考
- 簡書 | EP38-Fragment單例和內(nèi)存泄露
- 簡書 | Android Fragment使用(一) 基礎(chǔ)篇 溫故知新
- CSDN | android.support.v4.app.Fragment和 android.app.Fragment的區(qū)別
- CSDN | Android Fragment 真正的完全解析(上)
- CSDN | Android Fragment 真正的完全解析(下)
- CSDN | Android Fragment 你應(yīng)該知道的一切
- CSDN | Android 官方推薦 : DialogFragment 創(chuàng)建對話框
- Google | 處理運行時變更
- CSDN | Android 屏幕旋轉(zhuǎn) 處理 AsyncTask 和 ProgressDialog 的最佳方案
- CSDN | android fragment事務(wù)的提交使用的時候出現(xiàn)的一些問題。。
- 簡書 | Fragment進階-commit使用細節(jié)及源碼分析
建議閱讀


