总结
- Fragment初始化用newInstance
- viewPager开多Fragment, 考虑懒加载
- getActivity空指针,
onAttach()
拿到mActivity - 避免异步中进行
commit()
- 避免在
onCreate()
外commit()
- genymotion别乱拖,否则横竖屏切换不会重启activity
- fragment第二次进入显示白原因
- onBackPressedBUG
- FragmentStateLoss
生命周期
只有在activity处于onResume()
时,fragment的生命周期才会自由,否则,被activity控制
-
onAttach()
, 拿到activity, 添加Listener -
onCreate()
, ??? -
onCreateView()
, 创建视图 -
onActivityCreated()
, activity的onCreate 返回时调用 -
onDestroyView()
, Fragment视图被移除时调用 -
onDestroy()
, -
onDetach()
, Fragment 与 activity 取消关联时调用, 操作与onAttach()
相反
加载
静态加载,类似 view
- activity 的布局中
<fragment android:id="@+id/id_fragment_title" **必须包含ID** android:name="com.包名.路径.TitleFragment" android:layout_width="fill_parent" android:layout_height="45dp" />
动态加载
1.构造
Google规定 Fragment 需要提供一个 public 的无参构造函数,在 framework 状态恢复时使用
-
如果需要接受外部的参数创建Fragment的, 需保存参数到 bundle:
inflate(int resources, 当前布局D ViewGroup root, 根布局G boolean attachToRoot 是否依赖根布局G),
viewgroup的
addview()
不能添加一个已包含父控件的视图, 如果设置第三个参数为false, 则当前布局D 不依赖于根布局G, 返回的是xml为根布局的view- 如果不设置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导入, 直接报 OOM
-
这个参数是parentView, 当你创建子布局时, 如果希望这个布局依赖于第二
//FragmentOne中的newInstance函数 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.jianshu.com/p/caa5d6568faa 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2.替换
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(布局,Fragment 实例)
fragmentTransaction.commit();
3. 通信
-
activity传给fragment
activity 中通过getFragmentManager().findFragmentById(R.id.test_fragment);
获取 Fragment 实例,
也可以 -
Fragment传给activity
-
fragment内部创建接口
public interface FragmentOneBtnClickListener{ void onOneBtnClick(); }
-
fragment内按钮点击时调用
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实现接口及方法
public class MainActivity extends AppCompatActivity implements OneFragment.FragmentOneBtnClickListener{ @Override public void onOneBtnClick() { Toast.makeText(this, "one", Toast.LENGTH_SHORT).show(); } }
-
-
Fragment.startActivityForResult();
Activity 里的 FragmentOne 调用startActivityForResult
开启ActivityTwo, ActivityTwo里的FragmentTwo操作回调时调用getActivity().setResult(FragmentOne.REQUEST_DETAIL, intent);
FragmentManager
当activity被杀死重建时,fragment会被保存, 但会创建新的FragmentManager, 新FragmentManager会先去获取保存下来的fragment队列, 再去重建, 从而恢复之前的状态
FragmentTransaction (Google)
-
add()
; 添加 Fragment -
remove()
; 移除 Fragment, 若添加addToBackStack(null)
,则不会执行onDestroy()
和onDetach()
; -
replace()
; = remove + add -
hide()
; 隐藏 -
show()
; 显示隐藏的 -
detach(fragment)
; 移除 view, 但 fragment 依然被 FragmentManager 管理 -
attach()
; 重建视图 -
commit();
默认异步,并不立刻执行,而是加入 UI 线程的队列中,- 后接executePendingTransactions() 立即执行所有 pending在队列中的transaction
commitAllowStateLoss();
-
commitNow()^v24^
, 只同步执行此次 transaction(完善executePendingTransactions),不可与addToBackStack()共用 commitNowAllowStateLoss()
Back Stack
- Activity 的 BackStack,系统维护,每个 task 一个 BackStack
- Fragment 的 BackStack,宿主 Activity 维护,每个 activity 一个
- 通过 addToBackStack 调用,按 Back 键后执行 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
; - 某个事件触发下,FragmentB replace FragmentA,提交的是
commitAllowStateLoss
; - 此时可能会发生两种情况
- 第一种,系统杀死了activity,activity重建,使用步骤2的onSaveInstanceState恢复,A恢复,B没有
- 第二种,activity 没被杀死,FragmentB 显示,到下次 Activity stop时,这个包含了 B 的状态被保存了下来
例2 fragment中执行异步
- activity 执行AsyncTask, 同时打开
ProgressDialog(API26被弃用,推荐ProgressBar和Notification) - 执行过程中, 进行旋转屏幕 可能的情况如下:
- 上个线程还在执行, 又开一个线程, 可能操作一些已经被处理的控件, 报空
- 关闭dialog的代码在onPostExecute, 但是上个线程被杀死, 无法关闭
- 解决: DialogFragment
例3 内存泄漏 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占用的内存, 但因为sInstance 是静态的, 一直持有fragment的引用, 即使
destroy
也不行
- 解决:
- 用
getApplicationContext()
- 弱引用
- 把sInstance用WeakReference包起来, 需要的时候
wr.get()
;
- 把sInstance用WeakReference包起来, 需要的时候
- 用
例4 横竖屏切换fromHongYang
- 横竖屏切换时, activity重建, fragment生命周期跟着变, 同时因为activity的
onSaveInstanceState
, 之前的fragment们也被还原出来,会产生多个fragment -
第一次切换
-
第二次切换
- 解决: 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自己实现
-
Fragment 的 xml 里添加 toolBar
-
onCreate加入 setHasOptionsMenu(true)
@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); }
-
设置依赖的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; }
-
实现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实现
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: 调用depatch(), 只销毁视图, 适合主界面
- FragmentPagerAdapter: 销毁, 可存数据进bundle, 然后保存在onSaveInstanceState
DialogFragment 替代 Dialog和AlertDialog
Dialog和继承的AlertDialog无法在横竖屏切换时保存数据, DialogFragment依靠FragmentManager自动重建并恢复数据
- 自定义view 的 DialogFragment 创建
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 创建
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来保存数据
系统帮助数据恢复
-
少量数据
onSaveInstanceState
和onRestoreInstanceState
-
大量数据, 无法序列化, 如bitmap, 用Fragment存放, 但是切勿传递任何包含context的对象
- 创建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调用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()); } }
- 创建Fragment, 添加
自行处理数据变更(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
回调AsyncTask, 并在onCreate时调用 setData
- 创建Fragment, 不过setData保存的是AsyncTask
public class OtherRetainedFragment extends Fragment
{
// data object we want to retain
// 保存一个异步的任务
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;
}
}
- 创建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");
}
/**
* 加载数据
*/
@Override
protected Void doInBackground(Void... params) {
items = loadingData();
return null;
}
/**
* 加载完成回调当前的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保存大量数据",
"onSaveInstanceState保存数据",
"getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",
"Spark"));
}
/**
* 设置Activity,因为Activity会一直变化
*
* @param activity
*/
public void setActivity(FixProblemsActivity activity) {
// 如果上一个Activity销毁,将与上一个Activity绑定的DialogFragment销毁
if (activity == null) {
mLoadingDialog.dismiss();
}
// 设置为当前的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();
}
}
}
- 创建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();
}
/**
* 回调
*/
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单例和内存泄露
- 简书 | Android Fragment使用(一) 基础篇 温故知新
- CSDN | android.support.v4.app.Fragment和 android.app.Fragment的区别
- CSDN | Android Fragment 真正的完全解析(上)
- CSDN | Android Fragment 真正的完全解析(下)
- CSDN | Android Fragment 你应该知道的一切
- CSDN | Android 官方推荐 : DialogFragment 创建对话框
- Google | 处理运行时变更
- CSDN | Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案
- CSDN | android fragment事务的提交使用的时候出现的一些问题。。
- 简书 | Fragment进阶-commit使用细节及源码分析
建议阅读
网友评论