从活动访问片段时Android罕见的NPE

Posted

技术标签:

【中文标题】从活动访问片段时Android罕见的NPE【英文标题】:Android rare NPE when accessing fragment from activity 【发布时间】:2020-07-11 12:39:33 【问题描述】:

我在 android 中有一个包含框架布局的活动。其中一个框架布局是用片段膨胀的。 在片段的onResume() 中,调用了一个在Activity 中实现的监听器。 侦听器然后调用片段的方法。此时,片段的引用发生了 NPE。

这种情况很少发生,但至少被复制了 2 次。 我怀疑问题与活动和片段的生命周期有关。 当 Activity 仍处于生命周期的 onCreate() 步骤中时,就会对片段进行引用,这可能在片段初始化之前。

我的分析正确吗?如何预防 NPE?

这是代码(请记住,我已经重命名了很多代码并删除了看起来不相关的部分):

活动:

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

【问题讨论】:

【参考方案1】:

这些行:

if (savedInstanceState != null) 
    return;

意味着您在重新创建活动时从未设置您的mFruitStateFragment 变量 - 您仅在首次添加片段时设置它。这也意味着您的mFruitsManagrmContext 也没有设置,因为它也在该行之后。如果您希望每次创建活动时都可以使用这些检查,则应删除该检查并仅包装应该只发生一次的事情。

这意味着您的活动应该看起来像

@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(),因此您也可以从那里获得参考,因为每次重新创建活动时都会调用该参考:

@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);
    

【讨论】:

感谢您的清晰解释,以及不要每次都拨打.setApple(0)的建议。自从我提出这个问题以来,代码已经改变,因为我后来意识到监听器从来没有必要。我实施了你关于savedIstanceState != null 的建议,我想我不会再看到NPE了。

以上是关于从活动访问片段时Android罕见的NPE的主要内容,如果未能解决你的问题,请参考以下文章

Android:从片段调用时如何从活动中获取返回结果?

Android:从片段调用活动

从android中的活动调用片段

Android - 如何从 FragmentActivity 访问 Fragment

使后退按钮从活动返回到Android中的片段

从活动向上导航到片段打开相同的片段 - Android 导航组件