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

Android 片段的布局字段在初始化时为空,android,android-fragments,android-tablayout,fragmentpageradapter,butterknife,Android,Android Fragments,Android Tablayout,Fragmentpageradapter,Butterknife,我对向片段传递数据有异议。它在生产过程中的崩溃率为0.1%。让我们假设在活动的100k开始时,它会发生100次。看起来不太常见,但这让我非常困扰,我认为我在使用数据初始化片段时出错了。问题是,我只创建了一次片段,而所有其他需要向它们传递数据的时候,我都在以下一种方式进行:myFragmentInstance.setData(objectsomedata)和崩溃的发生是因为它告诉我们在片段中找不到这些视图元素,并且它们为NULL,但如果我没有重新创建它们,一切都应该正常。我没有转动手机,或者手机内

我对向片段传递数据有异议。它在生产过程中的崩溃率为0.1%。让我们假设在活动的100k开始时,它会发生100次。看起来不太常见,但这让我非常困扰,我认为我在使用数据初始化片段时出错了。问题是,我只创建了一次片段,而所有其他需要向它们传递数据的时候,我都在以下一种方式进行:
myFragmentInstance.setData(objectsomedata)和崩溃的发生是因为它告诉我们在片段中找不到这些视图元素,并且它们为NULL,但如果我没有重新创建它们,一切都应该正常。我没有转动手机,或者手机内存不足。它发生在网络重新连接上,因为在网络重新连接上,我将到服务器获取新数据,然后将新数据设置为我的片段。我有我使用的两个片段的字段的照片,也许你们中的一些人知道这些数据可以告诉我们崩溃时刻片段的状态。
我使用库
ButterKnife
初始化片段和活动的字段,而不是使用
findById
初始化它,可能它有一些影响还是没有

这里是指向简单项目的链接(仅在github上有此问题):

说明:

折叠活动
-带有
折叠AppBarLayout的活动
。它将一个或两个片段加载到“
fragment\u content\u holder
”中,并具有
TabLayout
在视图寻呼机中切换片段

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

菜单片段1,菜单片段1
-两个片段
MenuFragment1
-具有方法
公共无效设置数据(SomeCustomData数据)
,用于设置新数据,而不是在网络重新连接时重新创建片段

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:697) 
05-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!

方法setupData中崩溃的片段,因为数据\u 1\u 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>

根据我的经验,如果片段中出现错误,通常是因为在
查看页面
选项卡菜单
中预加载了片段,因此我所做的和建议您做的是检查片段是否对用户可见,如果是,则获取数据和其他内容,因此我的代码如下:

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);
}
}
这样,如果视图还没有创建,并且对用户不可见,那么片段将不会做任何事情


希望这有帮助。

我很确定您的问题是由于将对片段的引用保留在数组中。片段有生命周期,引用不能保证持久。正如你所说的,很难重现和追踪到底出了什么问题,但也许你不需要这样做。 关于如何解决此问题的一些建议:

  • 不要存储对片段的引用。基本上遵循Google页面()上的示例,并在每次请求时实例化一个新片段

  • 如果您担心性能,而缓存正在解决问题,请尝试使用FragmentStatePagerAdapter-它缓存页面并管理片段的状态

  • 如果需要从主片段(或活动)访问页面片段,而不是存储引用,请使用“findFragmentByTag”,它将始终返回片段的当前活动实例


  • 我坚信事情并不像其他人解释的那样复杂。如果我理解正确,网络交易造成的延迟是罪魁祸首。

    考虑一下这个场景。
    • 您正在发出网络请求,该请求会在视图中进行一些更改 最后
    • 你换传呼机。(片段被分离,视图被分离。) 销毁)
    • 下面是来自网络请求的回调。猜猜看崩溃
    因此,在处理片段视图时,最好更加小心。我通常这样做

    //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中。但这更像是一个盲击(至少在本例中是这样)。

    在片段中,您实际上是在
    onCreateView
    中执行
    findViewById
    。尝试在
    setupData
    方法中执行
    findViewById
    (而不是使用变量)。如果仍然为空,则表示您的片段已被销毁。顺便说一句,你需要特别小心使用
    线程
    ,因为这个确切的原因-它不是生命周期意识-你试过我的答案了吗?只要用你的
    textview.setText(“你的字符串”)
    @pouya替换
    getMyData()
    ,我还没有尝试你的答案-因为它对我来说像黑客,我认为这次崩溃的原因是不同的,而且原因是这个bug很难重现,所以我不能复制它并告诉你们它是固定的还是不固定的。但无论如何,谢谢。@DennisK使用线程是什么意思?我现在没有在代码中使用它。关于“findViewById”,根据文档,onCreateView是查找视图并在变量中记住它们的地方。而且很难复制这个bug。@yozhik是的,事实上这是一个黑客,在这方面是一个相当好的黑客。但是你能把你的
    片段管理器的代码和你在
    活动中调用所有这些代码的地方发布出来吗?@azizbekian我实际上搜索了很多,以克服在viewpager中预加载片段的问题,但是