Android 从 activity 访问片段时很少出现 NPE
Android rare NPE when accessing fragment from activity
我在 Android 中有一个 activity,其中包含框架布局。其中一个框架布局被一个片段夸大了。
在片段的 onResume()
中,调用了在 Activity 中实现的侦听器。
然后侦听器调用片段的方法。此时,在对片段的引用上发生了 NPE。
这种情况很少发生,但至少被转载了2次。
我怀疑问题与 activity 和片段的生命周期有关。
在 activity 仍处于生命周期的 onCreate()
阶段时对片段进行引用,这可能在片段初始化之前。
我的分析正确吗?如何防止 NPE?
这是代码(请记住,我重命名了很多代码并删除了看似无关的部分):
Activity:
FoodsActivity extends AppCompatActivity implements FruitStateFragment.OnAppleSelectedListener {
private Context mContext;
private FruitsManager mFruitsManagr;
private FruitStateFragment mFruitStateFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.foods_activity);
if (savedInstanceState != null) {
return;
}
if (mContext == null) {
mContext = getApplicationContext();
}
mFruitsManagr = FruitsManager.get(mContext);
if (findViewById(R.id.fl_fruits_status) != null) {
mFruitStateFragment = new FruitStateFragment();
mFruitStateFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_fruits_status, mFruitStateFragment, "FruitStateFragment").commit();
}
mFruitsManagr.setApple(0);
}
@Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof FruitStateFragment) {
FruitStateFragment fruitStateFragment = (FruitStateFragment) fragment;
fruitStateFragment.setOnAppleSelectedListener(this);
}
}
public void onAppleSelected(Integer appleNum) {
FruitsManager fManager = FruitsManager.get(mContext);
fManager.setApple(appleNum);
// NPE on mFruitStateFragment
mFruitStateFragment.updateBasketUi(fManager.getActiveBasketName());
}
}
片段:
FruitStateFragment extends Fragment {
private Context mContext;
FruitsManager mFruitsManagr;
OnAppleSelectedListener mAppleCallback;
public void setOnAppleSelectedListener(OnAppleSelectedListener callback) {
this.mAppleCallback = callback;
}
public interface OnAppleSelectedListener {
void onAppleSelected(Integer appleNum);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mContext == null) {
mContext = getActivity().getApplicationContext();
}
mFruitsManagr = FruitsManager.get(mContext);
}
@Override
public void onResume() {
super.onResume();
mAppleCallback.onAppleSelected(mFruitsManagr.getActiveApple());
}
void updateBasketUi(String basketName) {
}
}
经理:
public class FruitsManager {
private static FruitsManager sMe;
private Context mContext;
private static int mActiveApple = 0;
private static String mActiveBasketName = "";
private FruitsManager(Context context) {
mContext = context;
initInterfaces();
}
public int getActiveApple() {
return mActiveApple;
}
public int getActiveBasketName() {
return mActiveBasketName;
}
}
日志:
D FRUITS: 0001 onCreate (FoodsActivity%onCreate:)
D FRUITS: 0002 onCreate (FruitStateFragment%onCreate:)
D FRUITS: 0003 onCreateView (FruitStateFragment%onCreateView:)
D FRUITS: 0004 onActivityCreated (FruitStateFragment%onActivityCreated:)
D FRUITS: 0005 initViews (FruitStateFragment%initViews:)
D FRUITS: 0006 onResume (FruitStateFragment%onResume:)
E AndroidRuntime: java.lang.RuntimeException: Unable to resume activity {com.hi/FoodsActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.hi.FruitStateFragment.updateBasketUi(java.lang.String)' on a null object reference
这些行:
if (savedInstanceState != null) {
return;
}
意味着您永远不会在重新创建 activity 时设置 mFruitStateFragment
变量 - 您只在首次添加片段时设置它。这也意味着您的 mFruitsManagr
和 mContext
也没有设置,因为它也在该行之后。如果您希望这些在每次创建 activity 时都可用,则应删除该检查并仅包装应该只发生一次的事情。
这意味着您的 activity 应该看起来像
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.foods_activity);
if (mContext == null) {
mContext = getApplicationContext();
}
mFruitsManagr = FruitsManager.get(mContext);
if (savedInstanceState == null) {
mFruitStateFragment = new FruitStateFragment();
mFruitStateFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_fruits_status, mFruitStateFragment, "FruitStateFragment").commit();
// I assume this shouldn't be set every time the activity
// is recreated
mFruitsManagr.setApple(0);
} else {
// Get the Fragment that is already created from the FragmentManager
mFruitStateFragment = getSupportFragmentManager()
.findFragmentById(R.id.fl_fruits_status);
}
}
当然,由于您使用的是 onAttachFragment()
,您还可以从那里获得参考,因为每次重新创建 activity 时都会调用它:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.foods_activity);
if (mContext == null) {
mContext = getApplicationContext();
}
mFruitsManagr = FruitsManager.get(mContext);
if (savedInstanceState == null) {
// We'll store a reference to this in onAttachFragment()
FruitStateFragment fruitStateFragment = new FruitStateFragment();
fruitStateFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_fruits_status, fruitStateFragment, "FruitStateFragment").commit();
// I assume this shouldn't be set every time the activity
// is recreated
mFruitsManagr.setApple(0);
}
}
@Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof FruitStateFragment) {
// This get called every time the activity is created,
// ensuring that mFruitStateFragment is always set
mFruitStateFragment = (FruitStateFragment) fragment;
mFruitStateFragment.setOnSimNumSelectedListener(this);
}
}
我在 Android 中有一个 activity,其中包含框架布局。其中一个框架布局被一个片段夸大了。
在片段的 onResume()
中,调用了在 Activity 中实现的侦听器。
然后侦听器调用片段的方法。此时,在对片段的引用上发生了 NPE。
这种情况很少发生,但至少被转载了2次。
我怀疑问题与 activity 和片段的生命周期有关。
在 activity 仍处于生命周期的 onCreate()
阶段时对片段进行引用,这可能在片段初始化之前。
我的分析正确吗?如何防止 NPE?
这是代码(请记住,我重命名了很多代码并删除了看似无关的部分):
Activity:
FoodsActivity extends AppCompatActivity implements FruitStateFragment.OnAppleSelectedListener {
private Context mContext;
private FruitsManager mFruitsManagr;
private FruitStateFragment mFruitStateFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.foods_activity);
if (savedInstanceState != null) {
return;
}
if (mContext == null) {
mContext = getApplicationContext();
}
mFruitsManagr = FruitsManager.get(mContext);
if (findViewById(R.id.fl_fruits_status) != null) {
mFruitStateFragment = new FruitStateFragment();
mFruitStateFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_fruits_status, mFruitStateFragment, "FruitStateFragment").commit();
}
mFruitsManagr.setApple(0);
}
@Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof FruitStateFragment) {
FruitStateFragment fruitStateFragment = (FruitStateFragment) fragment;
fruitStateFragment.setOnAppleSelectedListener(this);
}
}
public void onAppleSelected(Integer appleNum) {
FruitsManager fManager = FruitsManager.get(mContext);
fManager.setApple(appleNum);
// NPE on mFruitStateFragment
mFruitStateFragment.updateBasketUi(fManager.getActiveBasketName());
}
}
片段:
FruitStateFragment extends Fragment {
private Context mContext;
FruitsManager mFruitsManagr;
OnAppleSelectedListener mAppleCallback;
public void setOnAppleSelectedListener(OnAppleSelectedListener callback) {
this.mAppleCallback = callback;
}
public interface OnAppleSelectedListener {
void onAppleSelected(Integer appleNum);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mContext == null) {
mContext = getActivity().getApplicationContext();
}
mFruitsManagr = FruitsManager.get(mContext);
}
@Override
public void onResume() {
super.onResume();
mAppleCallback.onAppleSelected(mFruitsManagr.getActiveApple());
}
void updateBasketUi(String basketName) {
}
}
经理:
public class FruitsManager {
private static FruitsManager sMe;
private Context mContext;
private static int mActiveApple = 0;
private static String mActiveBasketName = "";
private FruitsManager(Context context) {
mContext = context;
initInterfaces();
}
public int getActiveApple() {
return mActiveApple;
}
public int getActiveBasketName() {
return mActiveBasketName;
}
}
日志:
D FRUITS: 0001 onCreate (FoodsActivity%onCreate:)
D FRUITS: 0002 onCreate (FruitStateFragment%onCreate:)
D FRUITS: 0003 onCreateView (FruitStateFragment%onCreateView:)
D FRUITS: 0004 onActivityCreated (FruitStateFragment%onActivityCreated:)
D FRUITS: 0005 initViews (FruitStateFragment%initViews:)
D FRUITS: 0006 onResume (FruitStateFragment%onResume:)
E AndroidRuntime: java.lang.RuntimeException: Unable to resume activity {com.hi/FoodsActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.hi.FruitStateFragment.updateBasketUi(java.lang.String)' on a null object reference
这些行:
if (savedInstanceState != null) {
return;
}
意味着您永远不会在重新创建 activity 时设置 mFruitStateFragment
变量 - 您只在首次添加片段时设置它。这也意味着您的 mFruitsManagr
和 mContext
也没有设置,因为它也在该行之后。如果您希望这些在每次创建 activity 时都可用,则应删除该检查并仅包装应该只发生一次的事情。
这意味着您的 activity 应该看起来像
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.foods_activity);
if (mContext == null) {
mContext = getApplicationContext();
}
mFruitsManagr = FruitsManager.get(mContext);
if (savedInstanceState == null) {
mFruitStateFragment = new FruitStateFragment();
mFruitStateFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_fruits_status, mFruitStateFragment, "FruitStateFragment").commit();
// I assume this shouldn't be set every time the activity
// is recreated
mFruitsManagr.setApple(0);
} else {
// Get the Fragment that is already created from the FragmentManager
mFruitStateFragment = getSupportFragmentManager()
.findFragmentById(R.id.fl_fruits_status);
}
}
当然,由于您使用的是 onAttachFragment()
,您还可以从那里获得参考,因为每次重新创建 activity 时都会调用它:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.foods_activity);
if (mContext == null) {
mContext = getApplicationContext();
}
mFruitsManagr = FruitsManager.get(mContext);
if (savedInstanceState == null) {
// We'll store a reference to this in onAttachFragment()
FruitStateFragment fruitStateFragment = new FruitStateFragment();
fruitStateFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fl_fruits_status, fruitStateFragment, "FruitStateFragment").commit();
// I assume this shouldn't be set every time the activity
// is recreated
mFruitsManagr.setApple(0);
}
}
@Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof FruitStateFragment) {
// This get called every time the activity is created,
// ensuring that mFruitStateFragment is always set
mFruitStateFragment = (FruitStateFragment) fragment;
mFruitStateFragment.setOnSimNumSelectedListener(this);
}
}