先说结论:

1. 当需要给Fragment传值时,使用newInstance()方式来实例化一个Fragment,能够更好的将该Fragment使用的参数捆绑起来,不必每次实例化时都写下面的代码:

Bundle args = new Bundle();

2. 由于是在工厂方法内部封装了传参的方法,所以传入的参数将会得到保留,即使Fragment旋转重建也能够重新获取到这些传入参数。


Android日常研发中不可避免的肯定要用到Fragment,你如何使用的呢?Compare the two methods of use,是否觉得第二种更加简洁。
这时很多人肯定提出疑问:这两种使用方式有何区别,我的代码中到底使用哪种方式更好一些,以及为什么要使用这种方式 and so on,各位看官稍安勿躁,且听老衲娓娓道来。

Usage 1:

@Override
public void initView(Bundle savedInstanceState) {
BlankFragment mFragment = new BlankFragment();
Bundle bundle = new Bundle();
bundle.putString("arg1", "a");
bundle.putString("arg2", "b");
bundle.putString("arg3", "c");
mFragment.setArguments(bundle);
getFragmentManager().beginTransaction().replace(R.id.frame, mFragment).commit();
}

Usage 2:

@Override
public void initView(Bundle savedInstanceState) {
getFragmentManager().beginTransaction().replace(R.id.frame, BlankFragment.newInstance("a", "b")).commit();
}

首先我们新建一个fragment,我们一起来看一下android建议的fragment如何编写(请严格按照截图的来步步创建哦)

package com.itbird.utils;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import com.itbird.base.R; /**
* A simple {@link Fragment} subclass.
* Activities that contain this fragment must implement the
* {@link BlankFragment.OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link BlankFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class BlankFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2"; // TODO: Rename and change types of parameters
private String mParam1;
private String mParam2; private OnFragmentInteractionListener mListener; public BlankFragment() {
// Required empty public constructor
} /**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment BlankFragment.
*/
// TODO: Rename and change types and number of parameters
public static BlankFragment newInstance(String param1, String param2) {
BlankFragment fragment = new BlankFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
} @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
} @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false);
} // TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
if (mListener != null) {
mListener.onFragmentInteraction(uri);
}
} @Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
} @Override
public void onDetach() {
super.onDetach();
mListener = null;
} /**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
}

上述代码其实就是在一个Fragment的newInstance方法中传递两个参数,并且通过fragment.setArgument保存在它自己身上,而后通过onCreate()调用的时候将这些参数取出来。这样写没什么特殊的啊,不就是用静态工厂方法传个参数么,用构造器传参数不一样处理么?No,No,No,如果仅仅是个静态工厂而已,又怎么能成为谷歌推荐呢。

实践是检验真理的唯一标准,我们一起通过一个样例来实际操作一番
fragment_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <framelayout
android:id="@+id/layout_top"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/> <framelayout
android:id="@+id/layout_bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>

由图和代码可知,我们在xml中定义两个FrameLayout,平分整个屏幕高度

MainActivity.java

package com.itbird.myapplication;

import android.os.Bundle;
import android.support.v4.app.FragmentTransaction; public class MainActivity extends BaseActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_main);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.layout_top, new BlankFragment("顶部的Fragment", "test"));
transaction.add(R.id.layout_bottom, BlankFragment.newInstance("底部的Fragment", "test"));
transaction.commit();
}
}
}

BlankFragment.java

package com.itbird.myapplication;

import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; public class BlankFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2"; // TODO: Rename and change types of parameters
private String mParam1;
private String mParam2; public BlankFragment() {
// Required empty public constructor
} @SuppressLint("ValidFragment")
public BlankFragment(String mParam1, String mParam2) {
this.mParam1 = mParam1;
this.mParam2 = mParam2;
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment BlankFragment.
*/
// TODO: Rename and change types and number of parameters
public static BlankFragment newInstance(String param1, String param2) {
BlankFragment fragment = new BlankFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
} @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
} @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_blank, container, false);
TextView textView = view.findViewById(R.id.text);
textView.setText(mParam1 + mParam2);
return view;
}
}

fragment_blank.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" /> </FrameLayout>

通过阅读代码可知,我们通过两种不同的方式创建fragment,同样在其中心textview中展示相应拼接字段。

嗯,效果如预期的一样完美,此时,我们把屏幕横过来,看看会出现怎样的状况

My god,顶部的fragment 文本内容咋都变成null了。。。

我们来分析一下产生上述情况的原因:当我们横竖屏切换的时候,activity会重建,相应的,依附于它上面的Fragment也会重新创建。好,顺着这个思路,进activity的onCreate方法中看看:
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
if (mActivityInfo.parentActivityName != null) {
if (mActionBar == null) {
mEnableDefaultActionBarUp = true;
} else {
mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
}
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
}
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
if (mVoiceInteractor != null) {
mVoiceInteractor.attachActivity(this);
}
mCalled = true;
}

显而易见,fragment的重建是在restoreAllState方法中,跟进
FragmentController.java

 /**
* Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment
* instances retained across configuration changes, including nested fragments
*
* @see #retainNestedNonConfig()
*/
public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
mHost.mFragmentManager.restoreAllState(state, nonConfig);
}

继续跟进
FragmentManager.java

 void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
// If there is no saved state at all, then there can not be
// any nonConfig fragments either, so that is that.
if (state == null) return;
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return; List<FragmentManagerNonConfig> childNonConfigs = null; // First re-attach any non-config instances we are retaining back
// to their saved state, so we don't try to instantiate them again.
... // Build the full list of active fragments, instantiating them from
// their saved state.
mActive = new ArrayList<>(fms.mActive.length);
if (mAvailIndices != null) {
mAvailIndices.clear();
}
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
FragmentManagerNonConfig childNonConfig = null;
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.add(f);
// Now that the fragment is instantiated (or came from being
// retained above), clear mInstance in case we end up re-restoring
// from this FragmentState again.
fs.mInstance = null;
} else {
mActive.add(null);
if (mAvailIndices == null) {
mAvailIndices = new ArrayList<>();
}
if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
mAvailIndices.add(i);
}
} // Update the target of all retained fragments.
... // Build the list of currently added fragments.
... // Build the back stack.
...
}

通过阅读, 找到关键代码

 Fragment f = fs.instantiate(mHost, mParent, childNonConfig);

然后锲而不舍跟进
FragmentManager.java

public Fragment instantiate(FragmentHostCallback host, Fragment parent,
FragmentManagerNonConfig childNonConfig) {
if (mInstance == null) {
final Context context = host.getContext();
if (mArguments != null) {
mArguments.setClassLoader(context.getClassLoader());
} mInstance = Fragment.instantiate(context, mClassName, mArguments); if (mSavedFragmentState != null) {
mSavedFragmentState.setClassLoader(context.getClassLoader());
mInstance.mSavedFragmentState = mSavedFragmentState;
}
mInstance.setIndex(mIndex, parent);
mInstance.mFromLayout = mFromLayout;
mInstance.mRestored = true;
mInstance.mFragmentId = mFragmentId;
mInstance.mContainerId = mContainerId;
mInstance.mTag = mTag;
mInstance.mRetainInstance = mRetainInstance;
mInstance.mDetached = mDetached;
mInstance.mHidden = mHidden;
mInstance.mFragmentManager = host.mFragmentManager;
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
"Instantiated fragment " + mInstance);
}
mInstance.mChildNonConfig = childNonConfig;
return mInstance;
}

跟进到这里,终于有点头绪了,至少看到fragment实例化的地方了,迫不及待的再次点击去view一下下
Fragment.java

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
try {
Class<?> clazz = sClassMap.get(fname);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
if (!Fragment.class.isAssignableFrom(clazz)) {
throw new InstantiationException("Trying to instantiate a class " + fname
+ " that is not a Fragment", new ClassCastException());
}
sClassMap.put(fname, clazz);
}
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.mArguments = args;
}
return f;
} catch (ClassNotFoundException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (java.lang.InstantiationException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (IllegalAccessException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
}
}

山重水复疑无路,柳暗花明又一村
原来Fragment对象被反射创建之后,会调用这么一句代码
f.mArguments = args;
哦,なるほど(原来如此),Fragment在重新创建的时候只会调用无参的构造方法,并且如果之前通过fragment.setArguments(bundle)这种方式设置过参数的话,Fragment重建时会得到这些参数,所以,在onCreate中我们可以通过getArguments()的方式拿到我们之前设置的参数。同时由于Fragment在重建时并不会调用我们自定义的带参数的构造方法,所以我们传递的参数它也就获取不到了。

也许有网友依然会继续追问,重新set时,mArguments确定不会为空吗?Fragment销毁时,这个变量不会置空吗?我们通过源码看一下:
Fragment.java

 /**
* Called when the view previously created by {@link #onCreateView} has
* been detached from the fragment. The next time the fragment needs
* to be displayed, a new view will be created. This is called
* after {@link #onStop()} and before {@link #onDestroy()}. It is called
* <em>regardless</em> of whether {@link #onCreateView} returned a
* non-null view. Internally it is called after the view's state has
* been saved but before it has been removed from its parent.
*/
@CallSuper
public void onDestroyView() {
mCalled = true;
} /**
* Called when the fragment is no longer in use. This is called
* after {@link #onStop()} and before {@link #onDetach()}.
*/
@CallSuper
public void onDestroy() {
mCalled = true;
//Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager
// + " mLoaderManager=" + mLoaderManager);
if (!mCheckedForLoaderManager) {
mCheckedForLoaderManager = true;
mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
}
if (mLoaderManager != null) {
mLoaderManager.doDestroy();
}
}

看到此处,相信各位看官已经有“了然大明白”的感觉了,我就不再多说了。

总结

1.通过对比两种使用方式,我们知道两种方式别无其他,只是事关风格而已(代码”整”“洁”之道)
2.使用Fragment过程中在涉及到传参时,千万不要通过构造方法或者setParam方式直接赋值传入参数,必须使用setArguments来传参,否则程序在某些应用情景下,会丢参
强烈建议:两者虽无严格的对错之分,都可以使用,但是newInstance方式无论从代码整洁之道还是程序规范的稳定性而言,都是每个程序员应该学习使用的方式。

本文转自:【Android面试】为什么要用newInstance来实例化Fragment

Fragment: 使用newInstance()来实例化fragment(转)的更多相关文章

  1. 【译】使用newInstance()来实例化fragment

    我最近读到StackOverflow上面关于Fragment实例化的一个问题,觉得挺有趣的. new MyFragment()和MyFragment.newInstance()之间的差别是什么?应该用 ...

  2. (转载)解决切换Fragment时布局重新实例化

    解决切换Fragment时布局重新实例化 作者 instanceof 关注 2015.12.30 17:25* 字数 628 阅读 7616评论 17喜欢 23 关于Fragment 在这简单说一下F ...

  3. Fragment详解之三——管理Fragment(1)

    相关文章: 1.<Fragment详解之一--概述>2.<Fragment详解之二--基本使用方法>3.<Fragment详解之三--管理Fragment(1)>4 ...

  4. 利用Fragment创建动态UI 之 Fragment之间的通信

    为了可以复用一个fragment,所以在定义fragment的时候,要把它定义为一个完全独立和模块化,它有它自己的layout和行为.当你定义好了这些可复用的fragment,可以把他们和activi ...

  5. Android Fragment详解(二):Fragment创建及其生命周期

    Fragments的生命周期 每一个fragments 都有自己的一套生命周期回调方法和处理自己的用户输入事件. 对应生命周期可参考下图: 创建片元(Creating a Fragment) To c ...

  6. 清空Fragment回退栈中某个Fragment

    出栈的方法:getActivity().getSupportFragmentManager().popBackStackImmediate(CreateOneFragment.class.getNam ...

  7. Android Fragment详解(五):Fragment与Activity通讯

    与activity通讯 尽管fragment的实现是独立于activity的,可以被用于多个activity,但是每个activity所包含的是同一个fragment的不同的实例. Fragment可 ...

  8. android fragment 跳到另一个fragment

    一共有4个fragment,分别是contact(联系人),friends(朋友),search(查找),more(更多).使用的都是同一个布局,每个fragment中都有四个内部按钮,可以切换到其他 ...

  9. Android - Fragment (三)不同Fragment之间的通信

    在Fragment的java文件中,可以使用getActivity()来获得调用它的activity, 然后再找到另一个Fragment,进行通信 getActivity().getFragmentM ...

随机推荐

  1. 升级.net core 3.x 后mvc项目调试状态编辑view代码不能实时预览

    https://stackoverflow.com/a/58126955 简单来说 需要在nuget上安装Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilati ...

  2. JavaSE理论篇

    将已学过的知识记录在此,既能便于以后温习又能方便知识共享,做到共同成长. 计算机语言发展简史 主要分为三个阶段 机器语言:打点机,有点表示1,没有表示0,打点计时器 低级语言:汇编语言 高级语言:Ja ...

  3. SaltStack--使用salt-ssh

    SaltStack使用salt-ssh模式 salt-ssh 介绍 参考官档 salt-ssh是 0.17.0 新引入的一个功能,不需要minion对客户端进行管理,也可以不需要master:salt ...

  4. mysql免安装版配置和一些常见问题

    博客原文点我 今天在Windows Server 2003下安装mysql,遇到不少问题.之前在另外两台服务器安装的时候也遇到了几个问题,最后找到不少文章解决了,记录一下. 所有版本都是免安装的32和 ...

  5. Spring Boot 之:Spring Boot Admin

    client 连接都 admin 时报错: 2019-08-22 11:58:37.695 ERROR 55095 --- [nio-8000-exec-1] o.a.catalina.connect ...

  6. Linux中的CentOS 7克隆之后修改

    1.VMware Workstation软件查看克隆完成后的虚拟机网卡mac地址,记录下来 2.输入[cd /etc/sysconfig/network-scripts/]命令后,再执行[ip add ...

  7. mySql 常用命令 | 珠峰培训例子

    show databases; use mhxy; select database(); show tables; desc account_list_175; ),(); select from_u ...

  8. Go语言 - 包(package)

    在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基础之上的.本文介绍了Go语言中如何定义包.如何导出包的内容及如何导入其他包. Go语言的包(package) 包介绍 包( ...

  9. Game Based Learning: Why Does it Work?

    Forty years of research[i] says yes, games are effective learning tools. People learn from games, an ...

  10. 在swift项目中若要通过pod引入第三方的swift项目,必须加上use_frameworks!

    因为swift没法打.a https://www.jianshu.com/p/ac629a1cb8f5