【问题标题】:Fragment loses reference to activity without reason片段无故失去对活动的引用
【发布时间】:2018-05-15 11:36:12
【问题描述】:

在我的一个片段中,我有时会遇到一个异常,指出我的 Context 为空。

我目前无法重现该问题,因为我尝试以多种不同方式访问崩溃的代码,但没有一致的结果。

这是我得到的崩溃日志:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: eu.side.aurora, PID: 12940
    java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference
        at android.widget.Toast.<init>(Toast.java:101)
        at android.widget.Toast.makeText(Toast.java:258)
        at eu.side.aurora.views.fragments.StepStock.lambda$onCreateView$2$StepStock(StepStock.java:161)

以及引用的代码

    InterventionsViewModel interventionsViewmodel = ViewmodelProviders.of(getActivity()).get(InterventionsViewModel)
    interventionsViewmodel.getExceptions().observe(getActivity(), exception ->{
        if(exception instanceof QuantityOverflowException){
            Toast.makeText(getActivity(), "No you just can't add more stock than there is available!", Toast.LENGTH_SHORT).show();
        }
        if(exception instanceof ScannedArticleNotFoundException){
            Toast.makeText(getActivity(), "This article doesn't even exist! Get your shit together please!²", Toast.LENGTH_SHORT).show();
        }
     });

奇怪的是,我在片段的 onCreateView 中几乎所有地方都引用了 getActivty() ,但它崩溃了。

要使用下面的视图访问代码,我只需按 +- 按钮从我的列表中添加或删除文章。当它达到最大值时,它会发送一个Toast 以通知用户。

当点击加号减号按钮时,它们会通知ViewModel,这将改变我之前观察到的可观察对象。

有人知道这是从哪里来的吗?我假设它来自我的片段与我的Activity 分离,但我无法弄清楚它是如何做到这一点的,因为我在创建片段后再也没有接触过它。

提前致谢, 马修

编辑:根据要求,这是完整片段的代码 包eu.side.aurora.views.fragments;

public class StepStock extends Fragment {
    private INextStep nextStep;

    @BindView(R.id.articles_autocomplete)
    AutoCompleteTextView articlesAutocomplete;
    @BindView(R.id.articles_recycler)
    RecyclerView articleRecycler;


    private AccountManager accountManager;
    private Account account;
    private ArrayList<DetailedArticle> articles;
    private ArrayList<Article> stockedArticles;
    private StockRecyclerAdapter stockAdapter;
    private InterventionsViewModel interventionViewModel;

    public StepStock() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment StepStock.
     */
    // TODO: Rename and change types and number of parameters
    public static StepStock newInstance() {
        return new StepStock();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view =  inflater.inflate(R.layout.fragment_step_stock, container, false);
        ButterKnife.bind(this, view);

        interventionViewModel = ViewModelProviders.of(getActivity()).get(InterventionsViewModel.class);

        /* UI Intialization */
        articleRecycler.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));


        /**
         * Stock recycler view
         * Used to keep track of stock used in an intervention
         * OnPlus -> Add one from article
         * OnMinus -> Remove one from article
         * */
        stockedArticles = new ArrayList<>();
        stockAdapter = new StockRecyclerAdapter(stockedArticles, new StockRecyclerAdapter.OnArticleClickListener() {
            @Override
            public void onArticleClicked(int position) {
                //new intent
            }

            @Override
            public void onPlusClicked(int position) {
                interventionViewModel.addOneToStockAtArticle(stockAdapter.getData().get(position));
            }

            @Override
            public void onMinusClicked(int position) {
                interventionViewModel.minusOneOrRemoveAtArticle(stockAdapter.getData().get(position));
            }
        });

        interventionViewModel.getCurrentIntervention().observe(getActivity(), intervention -> {
            if(intervention!= null && intervention.getArticles() != null)
                stockAdapter.putData(intervention.getArticles());
        });

        interventionViewModel.getExceptions().observe(getActivity(), exception ->{
            if(exception instanceof QuantityOverflowException){
                Toast.makeText(getActivity(), "No you just can't add more stock than there is available!", Toast.LENGTH_SHORT).show();
            }
            if(exception instanceof ScannedArticleNotFoundException){
                Toast.makeText(getActivity(), "This article doesn't even exist! Get your shit together please!²", Toast.LENGTH_SHORT).show();
            }
        });
        return view;
    }


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if(context instanceof INextStep){
            nextStep = (INextStep) context;
        }
        else{
            throw new  RuntimeException(context.toString() + " must implement INextStep");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        nextStep = null;
    }
}

【问题讨论】:

  • 请张贴片段代码。
  • @SushobhNadiger 我刚刚相应地更新了帖子
  • 我认为你不应该在 onCreate 中调用 getActivity。在创建 Activity 之前,可能会创建您的视图。稍后再尝试。

标签: android android-fragments android-context


【解决方案1】:

您的代码崩溃是因为您的“异常观察者”忽略了片段的生命周期。

片段可以在许多不同的场景中附加/分离活动,例如配置更改或将应用程序移至后台等。分离片段时,getActivity() 方法返回 null,这就是你崩溃的原因。

另一方面,对象interventionViewModel.getExceptions() 的生命周期是完全独立的。每次对象更改时,都会调用观察者,无论片段处于什么状态。所以,如果 Fragment 被分离,应用就会崩溃。

要解决此问题 - 分离片段时不要使用上下文。通常,当片段与活动分离时,您不想听模型更改。如果您在 onDetach 中取消注册观察者,它将解决您的问题。

祝你好运!

【讨论】:

  • 我试过了,但我不知道它是否有效,因为我不知道如何重现我的错误
  • 有没有办法在应用处于后台时触发interventionViewModel.getExceptions() 更新?这可能是测试它的方法。另一个尝试的选项是在更新interventionViewModel.getExceptions() 时旋转设备。当您旋转设备时,片段也会分离,因此也可能会重现错误。
  • 我能想到的唯一方法是向 + 和 - 按钮发送垃圾邮件。我会尝试获得更可靠的解决方案
【解决方案2】:

我会删除这段代码

interventionViewModel.getExceptions().observe(this, exception ->{
        if(exception instanceof QuantityOverflowException){
            Toast.makeText(getActivity(), "No you just can't add more stock than there is available!", Toast.LENGTH_SHORT).show();
        }
        if(exception instanceof ScannedArticleNotFoundException){
            Toast.makeText(getActivity(), "This article doesn't even exist! Get your shit together please!²", Toast.LENGTH_SHORT).show();
        }
    });

并覆盖onactivitycreated 方法并将其放在那里。然后,您可以确定 getActivity() 不会返回 null。另请注意,在观察方法中,我传递的是片段的实例而不是活动,因此观察者将尊重片段的生命周期。

【讨论】:

  • 不幸的是,我认为这行不通。您将观察者附加在哪里并不重要。重要的是当模型更改时必须附加片段。如果片段分离后模型继续更改,则应忽略事件,因为此时没有上下文。因此,将代码移至onActivityCreated 很可能无法解决问题..
  • 我已经更新了答案。如果您认为它会起作用,请告诉我。
  • 那部分我不确定,取决于问题中未包含的观察者的实现。所以作者会让你知道:)
  • 我确实在开始时将片段的引用传递给了我的观察者,但在我的情况下,我在跨片段传递数据时遇到了问题。使用getActivity() 是可行的方法。
猜你喜欢
  • 1970-01-01
  • 2021-06-09
  • 2012-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多