【发布时间】: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 目录中的代码,因为这与我正在做的事情密切相关。