【问题标题】:Fragment has target not in fragment Manager after rotation of screen with retained async task fragment旋转屏幕后,片段的目标不在片段管理器中,并保留了异步任务片段
【发布时间】:2014-09-07 12:23:53
【问题描述】:

我一直在关注 Alex Lockwood (2013) 的本教程,了解如何在配置更改后使线程报告回新的活动实例。

http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html

在我尝试在嵌套片段中执行此操作之前,一切都很好。基本上 Activity 添加了 Fragment A 并且 Fragment A 被 Fragment B 替换,并且在 Fragment B 内部启动了一个异步任务线程。

但是,如果我回到 Fragment A(通过 backstack)然后尝试旋转,我会得到标题中所述的以下异常。

这里是代码

主要活动

public class MainActivity extends ActionBarActivity {

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

        if(savedInstanceState == null)
        {
             FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
             ft.add(android.R.id.content, new FragmentA()).commit();
        }

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

片段 A

public class FragmentA extends Fragment implements OnClickListener
{

    private Button GoToFragmentB;
    private ViewGroup container;

    @Override
    public View onCreateView(LayoutInflater inflater,ViewGroup container,  Bundle savedInstanceState)
    {
    // TODO Auto-generated method stub

    View view = inflater.inflate(R.layout.fragment_a, container, false);
    this.container = container;
    GoToFragmentB = (Button)view.findViewById(R.id.bGoToFragmentB);
    GoToFragmentB.setOnClickListener(this);
    return view;
    }

    @Override
    public void onClick(View v) 
    {
        // TODO Auto-generated method stub
        FragmentManager fragmentManager = getFragmentManager();
        FragmentB fb = new FragmentB();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(container.getId(), fb, FragmentB.class.getName());
        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.commit();
    }

}

片段 B

public class FragmentB extends Fragment implements OnClickListener, ThreadFragment.AsyncTaskCallbacks{

    private ThreadFragment mThreadFragment;
    private ProgressBar progress_horizontal;
    private TextView percent_progress;
    private Button task_button;

    private static final String KEY_CURRENT_PROGRESS = "current_progress"; 
    private static final String KEY_PERCENT_PROGRESS = "percent_progress"; 


    @Override
    public View onCreateView(LayoutInflater inflater,ViewGroup container,  Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        View view = inflater.inflate(R.layout.fragment_b, container, false);

        progress_horizontal = (ProgressBar) view.findViewById(R.id.progress_horizontal);
        percent_progress = (TextView)view.findViewById(R.id.percent_progress);
        task_button = (Button)view.findViewById(R.id.task_button);
        task_button.setOnClickListener(this);



        return view;
    }


    @Override
    public void onActivityCreated(Bundle savedInstanceState) 
    {
        // TODO Auto-generated method stub
        super.onActivityCreated(savedInstanceState);

        if(savedInstanceState != null)
        {
            progress_horizontal.setProgress(savedInstanceState.getInt(KEY_CURRENT_PROGRESS)); 
            percent_progress.setText(savedInstanceState.getString(KEY_PERCENT_PROGRESS)); 

        }

        FragmentManager fm = getActivity().getSupportFragmentManager();
        mThreadFragment = (ThreadFragment) fm.findFragmentByTag(ThreadFragment.class.getName());

        if(mThreadFragment == null)
        {
            mThreadFragment = new ThreadFragment();
            mThreadFragment.setTargetFragment(this, 0);
            fm.beginTransaction().add(mThreadFragment, ThreadFragment.class.getName()).commit();
        }

        if(mThreadFragment.isRunning() == true)
        {
            task_button.setText(getString(R.string.cancel));
        }
        else
        {
            task_button.setText(getString(R.string.start));
        }

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        // TODO Auto-generated method stub
        super.onSaveInstanceState(outState);
        outState.putInt(KEY_CURRENT_PROGRESS, progress_horizontal.getProgress()); 
        outState.putString(KEY_PERCENT_PROGRESS, percent_progress.getText().toString()); 

    }

    @Override
    public void onClick(View v) 
    {
        // TODO Auto-generated method stub
        if(mThreadFragment.isRunning() == true)
        {
            mThreadFragment.cancel();
        }
        else
        {
            mThreadFragment.start();
        }
    }


    @Override
    public void onPreExecute() 
    {
        // TODO Auto-generated method stub
        task_button.setText(getString(R.string.cancel));
        Toast.makeText(getActivity(), R.string.task_started_msg, Toast.LENGTH_SHORT).show();
    }


    @Override
    public void onProgressUpdate(int percent)
    {
        // TODO Auto-generated method stub
        progress_horizontal.setProgress(percent * progress_horizontal.getMax() / 100);
        percent_progress.setText(percent + "%");
    }


    @Override
    public void onCancelled() 
    {
        // TODO Auto-generated method stub
        task_button.setText("Start");
        progress_horizontal.setProgress(0);
        percent_progress.setText("0%");

    }


    @Override
    public void onPostExecute() 
    {
        // TODO Auto-generated method stub
        task_button.setText(getString(R.string.start));
        progress_horizontal.setProgress(progress_horizontal.getMax());
        percent_progress.setText(getString(R.string.one_hundred_percent));
        Toast.makeText(getActivity(), R.string.task_complete_msg, Toast.LENGTH_SHORT).show();

    }

    @Override
    public void onPause() {
        // TODO Auto-generated method stub
        mThreadFragment.pause();
        super.onPause();
    }

    @Override
    public void onResume() {
        // TODO Auto-generated method stub
        mThreadFragment.resume();
        super.onResume();
    }

}

线程片段

public class ThreadFragment extends Fragment {


    static interface AsyncTaskCallbacks
    {
        void onPreExecute();
        void onProgressUpdate(int percent);
        void onCancelled();
        void onPostExecute();
    }

    private AsyncTaskCallbacks mCallback;
    private boolean mRunning;
    private boolean isPause;

    private TestTask mTask;

    @Override
    public void onAttach(Activity activity) {
        // TODO Auto-generated method stub
        super.onAttach(activity);
        if(!(getTargetFragment() instanceof AsyncTaskCallbacks))
        {
            throw new IllegalStateException("Target fragment must implement the AsyncTaskCallbacks interface.");
        }


        if(getTargetFragment() != null)
        {
        mCallback = (AsyncTaskCallbacks) getTargetFragment();
        }


    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        cancel();

    }

    public void start()
    {
        if(mRunning == false)
        {
            mTask = new TestTask();
            mTask.execute();
            mRunning = true;
        }
    }

    public void cancel()
    {
        if(mRunning == true)
        {
            mTask.cancel(false);
            mTask = null;
            mRunning = false;
            isPause = false;
        }
    }

    public void pause()
    {
        if(mRunning == true)
        {
            isPause = true;
        }
    }

    public void resume()
    {
        isPause = false;
    }

    public boolean isRunning()
    {
        return mRunning;
    }

    private class TestTask extends AsyncTask<Void, Integer, Void>
    {

        @Override
        protected void onPreExecute() 
        {
            // TODO Auto-generated method stub
            mCallback.onPreExecute();
            mRunning = true;
        }

        @Override
        protected Void doInBackground(Void... params) 
        {


                // TODO Auto-generated method stub
                for(int i = 0; !isCancelled() && i < 100; i++)
                {
                    if(isPause == true)
                    {
                        sleep();
                    }

                    SystemClock.sleep(100);
                    publishProgress(i);
                }



            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            // TODO Auto-generated method stub
            mCallback.onProgressUpdate(values[0]);
        }

        @Override
        protected void onCancelled() {
            // TODO Auto-generated method stub
            mCallback.onCancelled();
            mRunning = false;
        }

        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            mCallback.onPostExecute();
            mRunning = false;
        }

        private void sleep()
        {
            try
            {
                while(isPause)
                {
                Thread.sleep(500);
                }
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }


    }



}

我的日志猫

09-06 19:49:31.068: E/AndroidRuntime(2402): FATAL EXCEPTION: main
09-06 19:49:31.068: E/AndroidRuntime(2402): java.lang.IllegalStateException: Failure saving state: ThreadFragment{40ce2700 #2 com.ersen.asynctaskpausetest.ThreadFragment} has target not in fragment manager: FragmentB{40d3a170}
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.support.v4.app.FragmentManagerImpl.saveAllState(FragmentManager.java:1699)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.java:547)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.app.Activity.performSaveInstanceState(Activity.java:1147)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1216)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3666)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.app.ActivityThread.access$700(ActivityThread.java:141)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1240)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.os.Handler.dispatchMessage(Handler.java:99)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.os.Looper.loop(Looper.java:137)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at android.app.ActivityThread.main(ActivityThread.java:5041)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at java.lang.reflect.Method.invokeNative(Native Method)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at java.lang.reflect.Method.invoke(Method.java:511)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
09-06 19:49:31.068: E/AndroidRuntime(2402):     at dalvik.system.NativeStart.main(Native Method)

------------编辑更新 2014 年 9 月 8 日--------------------

我在销毁方法上将此添加到我的片段 B

@Override
public void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    mThreadFragment.setTargetFragment(null , -1);

}

我对我的 Fragment B onActivityCreated 进行了以下调整。请注意,如果片段已被 fragmentManger.findFragmentByTag 找到,则调用 else

if(mThreadFragment == null)
    {
        mThreadFragment = new ThreadFragment();
        mThreadFragment.setTargetFragment(this, 0);
        fm.beginTransaction().add(mThreadFragment, ThreadFragment.class.getName()).commit();
    }
    else
    {
        mThreadFragment.setTargetFragment(this, 0);
    }

我对线程片段内的 OnAttach 进行了以下更改

if(getTargetFragment() != null)
{

    if((getTargetFragment().isVisible()) || getTargetFragment().isAdded())
    {
           mCallback = (AsyncTaskCallbacks) getTargetFragment();

    }
    else
    {
        mCallback = null;
    }
}

现在的区别是我不再收到上面提到的错误。我可以开始一个任务。销毁片段 b 和旋转(配置更改)没有任何问题。但是,如果我回到片段 b (自销毁后创建一个新实例),然后开始一个新任务。我收到这个新错误,表示片段 b 未附加。

09-08 14:07:28.495: E/AndroidRuntime(1650): java.lang.IllegalStateException: Fragment FragmentB{40cea860} not attached to Activity
09-08 14:07:28.495: E/AndroidRuntime(1650):     at android.support.v4.app.Fragment.getResources(Fragment.java:603)
09-08 14:07:28.495: E/AndroidRuntime(1650):     at android.support.v4.app.Fragment.getString(Fragment.java:625)
09-08 14:07:28.495: E/AndroidRuntime(1650):     at com.ersen.asynctaskpausetest.FragmentB.onPreExecute(FragmentB.java:119)
09-08 14:07:28.495: E/AndroidRuntime(1650):     at com.ersen.asynctaskpausetest.ThreadFragment$TestTask.onPreExecute(ThreadFragment.java:114)

它的意思是片段b不附加到活动。这些是有问题的罪魁祸首。

@Override
public void onPreExecute() 
{
    // TODO Auto-generated method stub
    task_button.setText(getString(R.string.cancel));
    Toast.makeText(getActivity(), R.string.task_started_msg, Toast.LENGTH_SHORT).show();
}

因为访问字符串资源需要一个活动,但这很奇怪,因为如果我制作新的片段 B,它不应该已经附加到活动了吗?

最终更新:固定版本:使用 Activity 处理回调,而不是片段 B

以下是更改的代码。

ThreadFragment:OnAttach

@Override
    public void onAttach(Activity activity)
    {
        // TODO Auto-generated method stub
        super.onAttach(activity);
        if(!(activity instanceof AsyncTaskCallbacks))
        {
            throw new IllegalStateException("Target fragment must implement the AsyncTaskCallbacks interface.");
        }



        mCallback = (AsyncTaskCallbacks) activity;

    }

我删除了 getTargetFragment 并使用了活动参考

片段 B

不再实现 ThreadFragment.AsyncTaskCallbacks 接口 删除了所有与 setTargetFragment 相关的代码。 由 ThreadFragment.AsyncTaskCallbacks 实现的方法被保留,但不再被覆盖,因此活动可以调用这些方法来做通常的事情。

public void onPreExecute() 
    {
        // TODO Auto-generated method stub
        task_button.setText(getString(R.string.cancel));
        Toast.makeText(getActivity(), R.string.task_started_msg, Toast.LENGTH_SHORT).show();
    }



    public void onProgressUpdate(int percent)
    {
        // TODO Auto-generated method stub
        progress_horizontal.setProgress(percent * progress_horizontal.getMax() / 100);
        percent_progress.setText(percent + "%");
    }



    public void onCancelled() 
    {
        // TODO Auto-generated method stub
        task_button.setText("Start");
        progress_horizontal.setProgress(0);
        percent_progress.setText("0%");

    }



    public void onPostExecute() 
    {
        // TODO Auto-generated method stub
        task_button.setText(getString(R.string.start));
        progress_horizontal.setProgress(progress_horizontal.getMax());
        percent_progress.setText(getString(R.string.one_hundred_percent));
        Toast.makeText(getActivity(), R.string.task_complete_msg, Toast.LENGTH_SHORT).show();

主要活动

实现 ThreadFragment.AsyncTaskCallbacks 添加了 3 个实例变量 FragmentB、FragmentManger 和一个布尔值。

public class MainActivity extends ActionBarActivity implements ThreadFragment.AsyncTaskCallbacks{

FragmentB FB;
FragmentManager fragmentManager;
boolean didFragmentBStartATask;

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

    fragmentManager = getSupportFragmentManager();

    if(savedInstanceState == null)
    {
         FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
         ft.add(android.R.id.content, new FragmentA()).commit();
    }
    else
    {
        didFragmentBStartATask = savedInstanceState.getBoolean("didFragmentBStartATask");
        if(didFragmentBStartATask)
        {
            FB = (FragmentB)fragmentManager.findFragmentByTag(FragmentB.class.getName());
        }

    }

}

在创建时将初始化片段管理器。 如果它是一个恢复的实例,则获取布尔值以检查片段 b 是否执行了任务。 如果为真,则按标签查找片段B。

在保存实例状态时,只需将布尔值放入包中

@Override
    protected void onSaveInstanceState(Bundle outState) {
        // TODO Auto-generated method stub
        super.onSaveInstanceState(outState);
        outState.putBoolean("didFragmentBStartATask", didFragmentBStartATask);
    }

通过接口实现的方法

@Override
    public void onPreExecute() 
    {
        didFragmentBStartATask = true;
        if(FB == null)
        {
            FB = (FragmentB)fragmentManager.findFragmentByTag(FragmentB.class.getName());
        }

        FB.onPreExecute();

        // TODO Auto-generated method stub

    }

    @Override
    public void onProgressUpdate(int percent) {
        // TODO Auto-generated method stub
        FB.onProgressUpdate(percent);

    }

    @Override
    public void onCancelled() {
        // TODO Auto-generated method stub
        FB.onCancelled();

    }

    @Override
    public void onPostExecute() {
        // TODO Auto-generated method stub
        FB.onPostExecute();
        didFragmentBStartATask = false;

    }

在预执行时,将布尔值设置为 true 我们必须检查 FB 是否为 null 以防止出现空指针。如果为null,则查找片段(首次启动需要)

之后,只需使用 FB 实例调用 中的方法即可完成与之前相同的操作。

在执行后将布尔值设置为 false,因为它已完成

【问题讨论】:

  • 您可以通过让您保留的工作片段(负责创建和管理Thread/AsyncTask)直接与活动通信而不是直接与活动通信来避免此问题最初启动它的嵌套片段。片段到片段的通信有点棘手,因为其中一个片段可以在活动实际将其添加到其视图之前与另一个片段进行通信。我想backstack和嵌套片段的使用只会让这更加微妙。
  • 换句话说,嵌套片段可以在它希望线程片段开始/停止工作时向活动发送通知,然后活动执行持有对@的引用的实际工作987654340@并直接与它通信。
  • 理论上使用setTargetFragment 也应该可以工作(我在sample code 中也写了一个使用它的例子)。
  • 感谢您的建议并感谢您制作本教程。我将尝试使用该活动来实现回调。
  • 是的,我最初遵循位于 extras 目录中的代码,因为这与我正在做的事情密切相关。

标签: android android-fragments


【解决方案1】:

有点晚了,但您应该考虑为您的“子”片段删除对“setTargetFragment(this, 0)”的调用。相反,当您想从 Fragment B 创建“子片段”时,您应该使用 getChildFragmentManager() 而不是 getFragmentManager()。

然后在您的“子片段”中,您将使用 getParentFragment() 而不是 getTargetFragment() 来查找您的封闭片段。这两部分代码将如下所示:

threadFragment = new ThreadFragment();
FragmentManager fm = getChildFragmentManager();
fm.beginTransaction().add(threadFragment, ThreadFragment.class.getName()).commit();

然后在您的 ThreadFragment 中,您将不再使用 getTargetFragment() 来查找您的 Fragment B。相反,您将调用 getParentFragment()。

if(!(getParentFragment() instanceof AsyncTaskCallbacks))
    {
        throw new IllegalStateException("Target fragment must implement the AsyncTaskCallbacks interface.");
    }

    if(getParentFragment() != null)
    {
    mCallback = (AsyncTaskCallbacks) getParentFragment();
    }

最后,在您的 Fragment B 中,您需要进行一些更改才能找到您的 ThreadFragment。您需要将其更改为使用 ChildFragmentManager

FragmentManager fm = getChildFragmentManager();
mThreadFragment = (ThreadFragment) fm.findFragmentByTag(ThreadFragment.class.getName());

【讨论】:

    【解决方案2】:

    来自setTargetFragment的文档

    public void setTargetFragment (Fragment fragment, int requestCode) 此片段的可选目标。例如,如果此片段正在由另一个片段启动,并且完成后希望将结果返回给第一个片段,则可以使用此方法。

    尝试这样做。

    把它放在导致问题的片段中(在这种情况下可能在你的Fragment A):

    @Override
    public void onSaveInstanceState(final Bundle outState) {
    setTargetFragment(null, -1);
            ...
    }
    

    并记得在需要时将其设置为真正的目标片段。

    编辑

    您可以在Fragment B 中尝试这样做

    @Override
    public void onSaveInstanceState(final Bundle outState) {
    Fragment threadFrag = new ThreadFrag();
    threadFrag.setTargetFragment(this , -1);
            ...
    }
    

    【讨论】:

    • 请查看编辑后的答案,让我知道这是否解决了您的问题。
    • 嗨,谢谢,抱歉耽搁了。除了我添加的一些代码外,我还实现了您所说的解决方案。请参阅上面我编辑的答案。另一个问题正在发生,但它不再是与没有目标片段有关的问题 blah blah
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多