Android редкий NPE при доступе к фрагменту из активности - PullRequest
0 голосов
/ 31 марта 2020

У меня есть активность в Android, которая содержит макеты кадров. Один из макетов кадра раздувается фрагментом. В onResume() фрагмента вызывается слушатель, который реализован в Activity. Затем слушатель вызывает метод для фрагмента. В этот момент возникает NPE для ссылки на фрагмент.

Это происходит очень редко, но оно воспроизводилось как минимум 2 раза. Я подозреваю, что проблема связана с жизненным циклом действия и фрагмента. Ссылка на фрагмент делается, когда действие все еще находится на этапе 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 Ответ

0 голосов
/ 31 марта 2020

Эти строки:

if (savedInstanceState != null) {
    return;
}

Означают, что вы никогда не устанавливали переменную mFruitStateFragment, когда ваша деятельность воссоздается - вы устанавливаете ее только при первом добавлении фрагмента. Это также означает, что ваши mFruitsManagr и mContext также не установлены, так как это также после этой строки. Если вы хотите, чтобы они были доступны при каждом создании вашей деятельности, вы должны снять эту проверку и обернуть только то, что должно произойти только один раз.

Это означает, что ваша деятельность должна выглядеть как

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