android实例化,【Android面试】为什么要用newInstance来实例化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如何编写(请严格按照截图的来步步创建哦)

ecbac58107d1

Create BlankFragment.png

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.

*

* See the Android Training lesson

* "http://developer.android.com/training/basics/fragments/communicating.html"

* >Communicating with Other Fragments

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

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:id="@+id/layout_top"

android:layout_width="match_parent"

android:layout_height="0dp"

android:layout_weight="1"/>

android:id="@+id/layout_bottom"

android:layout_width="match_parent"

android:layout_height="0dp"

android:layout_weight="1"/>

ecbac58107d1

fragment_main.png

由图和代码可知,我们在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

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/text"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="center" />

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

ecbac58107d1

运行效果.png

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

ecbac58107d1

横屏时.png

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 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

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

* regardless 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方式无论从代码整洁之道还是程序规范的稳定性而言,都是每个程序员应该学习使用的方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值