片段的布局字段在初始化时为空。

5
我遇到了传递数据给片段的问题。它在生产环境中0.1%的情况下会崩溃。假设在100k次活动打开中,它发生了100次。看起来并不是很频繁,但这让我非常困扰,我认为我在片段初始化数据方面做错了什么。问题是,我只创建一次片段,而在需要向它们传递数据的所有其他时间,我都是这样做的:myFragmentInstance.setData(Object someData); 崩溃发生是因为它告诉我的那些视图元素在片段中找不到,它们是NULL,但如果我没有重新创建它们,一切应该都没问题。我没有旋转手机,或者内存不足。它发生在网络重新连接时,因为在网络重新连接时,我会去服务器获取新鲜数据,然后将该新数据设置到我的片段中。我有两个片段字段的照片,也许你们中的一些人知道那些数据可以告诉关于崩溃时片段状态的信息。 我使用库ButterKnife来初始化片段和活动的字段,而不是使用findById进行初始化,也许它有一些影响或没有影响?

这是一个简单的项目链接(仅此问题在github上):https://github.com/yozhik/Reviews/tree/master/app/src/main/java/com/ylet/sr/review

描述:

CollapsingActivity - 带有Collapsing AppBarLayout的活动。它将一个或两个片段加载到“fragment_content_holder”中,并具有TabLayout以在视图分页器中切换片段。

在活动方法onCreate()中,我只是模拟对服务器的请求(loadData),当加载一些虚假数据时,我会在视图分页器中显示片段,在第一次调用时,我创建了新的TabMenuAdapter扩展FragmentPagerAdapter,用片段填充并保存实例链接。在下一次调用中,我不会从头开始创建片段,只是用新鲜的数据填充它们。

MenuFragment1, MenuFragment2 - 两个片段。 MenuFragment1 - 有一个方法public void setupData(SomeCustomData data),用于设置新数据,而不是在网络重新连接时重新创建片段。

NetworkStateReceiver - 监听网络变化并发送通知。

TabMenuAdapter - 只是一个简单的类来保存片段。

05-11 18:11:05.088 12279-12279/com.myProjectName E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.myProjectName, PID: 12279
    java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.
        at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:111)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5268)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
        at com.yozhik.myProjectName.view.fragments.MyFinalTermsFragment.setupMyInformation(MyFinalTermsFragment.java:145)
        at com.yozhik.myProjectName.view.fragments.MyFinalTermsFragment.setupWithData(MyFinalTermsFragment.java:133)
        at com.yozhik.myProjectName.view.activity.MyFinalActivity.onDataLoaded(MyFinalActivity.java:742)
        at com.yozhik.myProjectName.presenter.MyFinalPresenter$1.onNext(MyFinalPresenter.java:55)
        at com.yozhik.myProjectName.presenter.MyFinalPresenter$1.onNext(MyFinalPresenter.java:47)
        at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:200)
        at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:252)
        at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
        at android.os.Handler.handleCallback(Handler.java:739) 
        at android.os.Handler.dispatchMessage(Handler.java:95) 
        at android.os.Looper.loop(Looper.java:135) 
        at android.app.ActivityThread.main(ActivityThread.java:5268) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at java.lang.reflect.Method.invoke(Method.java:372) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:69705-11 18:11:07.953 2155-3877/? E/WifiStateMachine: Did not find remoteAddress {192.168.200.1} in /proc/net/arp
05-11 18:11:07.966 2155-3877/? E/WifiStateMachine: WifiStateMachine CMD_START_SCAN source -2 txSuccessRate=3800.62 rxSuccessRate=4732.06 targetRoamBSSID=any RSSI=-68
05-11 18:11:07.967 2155-3877/? E/WifiStateMachine: WifiStateMachine L2Connected CMD_START_SCAN source -2 2324, 2325 -> obsolete
05-11 18:11:08.021 2155-3896/? E/ConnectivityService: Unexpected mtu value: 0, wlan0
05-11 18:11:08.579 13514-13366/? E/WakeLock: release without a matched acquire!

enter image description here enter image description here enter image description here enter image description here

在方法setupData中崩溃的片段,因为data_1_txt有时为空。

public class MenuFragment1 extends Fragment {

    public SomeCustomData transferedDataFromActivity;
    private TextView data_1_txt;

    public static MenuFragment1 newInstance(SomeCustomData data) {
        Log.d("TEST", "MenuFragment1.newInstance");
        MenuFragment1 fragment = new MenuFragment1();

        Bundle args = new Bundle();
        args.putSerializable("DATA_FROM_ACTIVITY", data);
        fragment.setArguments(args);

        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("TEST", "MenuFragment1.onCreate");

        if (getArguments() != null) {
            this.transferedDataFromActivity = (SomeCustomData) getArguments().getSerializable("DATA_FROM_ACTIVITY");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d("TEST", "MenuFragment1.onCreateView");
        View v = inflater.inflate(R.layout.menu_fragment_1, container, false);
        data_1_txt = (TextView) v.findViewById(R.id.data_1_txt);

        setupInOnCreateView();

        return v;
    }

    protected void setupInOnCreateView() {
        Log.d("TEST", "MenuFragment1.setupInOnCreateView");
        //initialization of all view elements of layout with data is happens here.
        setupData(transferedDataFromActivity);
    }

    public void setupData(SomeCustomData data) {
        Log.d("TEST", "MenuFragment1.setupData");
        this.transferedDataFromActivity = data;
        if (transferedDataFromActivity != null) {
            data_1_txt.setText(transferedDataFromActivity.Name);
        }
    }
}

片段布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/green"
    android:orientation="vertical">

    <TextView
        android:id="@+id/data_1_txt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/yellow"
        android:text="Test"
        android:textSize="20sp" />

    <include layout="@layout/description_layout" />

</LinearLayout>

1
在你的片段中,你实际上是在onCreateView中执行findViewById。相反地,尝试在setupData方法中执行findViewById(而不是使用变量)。如果它仍然为空,则意味着你的片段已被销毁。顺便说一下,你需要特别小心地使用Thread,因为它不具备生命周期感知能力。 - Dennis K
你试过我的答案了吗?只需将 getMyData() 替换为你的 textview.setText("your string") - Pouya Danesh
@pouya 我还没有尝试你的答案-因为它看起来像是黑客攻击,而且我认为这个崩溃的原因是不同的,而且这个错误很难复现,所以我不能复现它并告诉你是否已经修复了。但无论如何还是谢谢。 - yozhik
@pouya 是的,简单项目链接在问题描述中:https://github.com/yozhik/Reviews/tree/master/app/src/main/java/com/ylet/sr/review - yozhik
在生产中的ViewPager中有多少个片段? - Harikumar Alangode
显示剩余5条评论
3个回答

2

根据我的经验,如果片段中出现错误,通常是由于在viewpagerTabMenu中预加载了片段,因此我建议您检查片段是否对用户可见,如果是,则获取数据和其他内容。以下是我的代码:

public class Fragment1 extends Fragment {
boolean visible = false;
public static Fragment1 newInstance() {
    return new Fragment1();
}
@Override
public View onCreateView(LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    if (visible && isResumed()) {
        onResume();
    } else if (visible) {
        getMyData();
    }
    return rootView;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    visible = isVisibleToUser;
    if (isVisibleToUser) {
        getMyData();
    }
    else {
    }
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
}
}

这样,如果视图尚未创建且对用户不可见,则片段将不执行任何操作。
希望这能有所帮助。

1
@azizbekian,我实际上搜索了很多方法来解决在ViewPager中预加载片段的问题,但这是我想到的最好的方法。如果你有更好的方法,请告诉我。在我的情况下,我的Web服务一起被调用。 - Pouya Danesh
@azizbekian,如果您有更好的解决方案,请与我们分享,谢谢。 - yozhik
setUserVisibleHint() 中,你应该进行额外的检查,即 getActivity() != null,因为如果你记录 setUserVisibleHint 的调用,你会发现它被多次调用并返回 true 值,但是如果你也记录 getActivity(),你会发现当 setUserVisibleHint 为 true 时,很多时候 getActivity() 可能会返回 null。 - Kruti Parekh
@parekhkruti26,有什么比实际操作更好的检查方式呢?我已经这样做了,它可以正常工作。而且 getMyData() 只被调用一次。这是因为我已经检查了用户的可见性,只有在活动不为空时才可见。此外,我正在使用这段代码。它可能是一个 hack,但对我来说很好用,没有错误。 - Pouya Danesh

2
我相信你的问题是由于在数组中保留片段引用所致。片段有生命周期,引用不能保证持久存在。正如你所说,很难复制和跟踪到具体出了什么问题,但也许你不需要这样做。 以下是一些解决方法:
  1. 不要存储片段的引用。基本上按照Google页面上的示例(https://developer.android.com/training/animation/screen-slide),每次请求时实例化一个新的片段。
  2. 如果你担心性能问题,缓存可以解决它,请尝试使用FragmentStatePagerAdapter-它缓存页面并管理片段的状态。
  3. 如果需要从主片段(或活动)访问页面片段,而不是存储引用,请使用`findFragmentByTag',它将始终返回片段的当前活动实例。

谢谢您的回答,我也有这个想法,但在stackoverflow上我也发现保留片段引用是可以的。因为我想知道为什么我必须始终使用“findFragmentByTag”来查找片段,这对我来说似乎是额外的负担。 - yozhik
顺便问一下,如果在FragmentStatePagerAdaper中我只能使用新的MyFragment提供片段,那么我该如何“findFragmentByTag”?我没有在那里提到任何标签,它是在适配器的核心中实现的。而且我需要以某种方式为这些片段设置新鲜数据,而不是重新创建它们。 - yozhik
1
这至少是问题的一部分,所以我支持它 - 如果您查看OP链接的Github存储库此处,似乎手动将Fragments缓存为Activity的字段。你几乎永远不应该这样做 - Android操作系统管理Fragments。自己为Fragments滚动适配器可能会像当前的适配器一样让你陷入麻烦;-) - David Rawson

1
我坚信事情并不像其他人解释的那样复杂。如果我理解正确,网络事务引起的延迟是罪魁祸首。

考虑以下情况。

  • 您正在发出网络请求,该请求在最后更改了视图。
  • 您切换了分页器。(片段被分离,视图被销毁)
  • 网络请求的回调来了。猜猜怎么着!崩溃了

因此,在处理片段的视图时,始终要更加小心。我通常会这样做。

//in the base class
protected boolean isSafe()
    {
        return !(this.isRemoving() || this.getActivity() == null || this.isDetached()
                || !this.isAdded() || this.getView() == null);
    }

//usage in derived classes
onNewtworkResult(Result result) {
    if(!isSafe())
        return;
    //rest of the code
}

另外,您也可以将潜在的代码放入try catch中。但这更像是一个盲目的尝试(至少在这种情况下)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接