출처
: http://aroundck.tistory.com/2897
1. AsyncTasks and Configuration Changes
AsyncTask 를 실행시킨 fragment 가 asyntask 완료 되는 시점에서 activity 로부터 detach 되어있으면 다음과 같은 에러가 발생한다. AsyncTask 가 activity 로부터 detach 되어 있는 상황은 보통 configuration change ( 대표적으로 화면 전환 ) 의 경우에 주로 발생한다.
java.lang.IllegalStateException: Fragment MyFragment not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:551)
at android.support.v4.app.Fragment.getString(Fragment.java:573)
이 에러를 막기 위한 한 방법으로는 AsyncTask 를 configuration change 에 대해 유지되도록 하는 것.
RetainedFragment 라는 class 를 이용하면 listener 를 통해 AsyncTask 의 상태를 listen 할 수 있다.
RetainedFragment 는 Build in class 가 아니고, thread 를 이용해서 config change 에 대해 destory 되지 않도록 하는 custom fragment 로 소스는 아래와 같다.
/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.apis.app; import com.example.android.apis.R; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; /** * This example shows how you can use a Fragment to easily propagate state * (such as threads) across activity instances when an activity needs to be * restarted due to, for example, a configuration change. This is a lot * easier than using the raw Activity.onRetainNonConfiguratinInstance() API. */ public class FragmentRetainInstance extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// First time init, create the UI. if (savedInstanceState == null) { getFragmentManager().beginTransaction().add(android.R.id.content, new UiFragment()).commit(); } } /** * This is a fragment showing UI that will be updated from work done * in the retained fragment. */ public static class UiFragment extends Fragment { RetainedFragment mWorkFragment; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_retain_instance, container, false); // Watch for button clicks. Button button = (Button)v.findViewById(R.id.restart); button.setOnClickListener(new onClickListener() { public void onClick(View v) { mWorkFragment.restart(); } }); return v; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); FragmentManager fm = getFragmentManager(); // Check to see if we have retained the worker fragment. mWorkFragment = (RetainedFragment)fm.findFragmentByTag("work"); // If not retained (or first time running), we need to create it. if (mWorkFragment == null) { mWorkFragment = new RetainedFragment(); // Tell it who it is working with. mWorkFragment.setTargetFragment(this, 0); fm.beginTransaction().add(mWorkFragment, "work").commit(); } } } /** * This is the Fragment implementation that will be retained across * activity instances. It represents some ongoing work, here a thread * we have that sits around incrementing a progress indicator. */ public static class RetainedFragment extends Fragment { ProgressBar mProgressBar; int mPosition; boolean mReady = false; boolean mQuiting = false; /** * This is the thread that will do our work. It sits in a loop running * the progress up until it has reached the top, then stops and waits. */ final Thread mThread = new Thread() { @Override public void run() { // We'll figure the real value out later. int max = 10000; // This thread runs almost forever. while (true) { // Update our shared state with the UI. synchronized (this) { // Our thread is stopped if the UI is not ready // or it has completed its work. while (!mReady || mPosition >= max) { if (mQuiting) { return; } try { wait(); } catch (InterruptedException e) { } } // Now update the progress. Note it is important that // we touch the progress bar with the lock held, so it // doesn't disappear on us. mPosition++; max = mProgressBar.getMax(); mProgressBar.setProgress(mPosition); } // Normally we would be doing some work, but put a kludge // here to pretend like we are. synchronized (this) { try { wait(50); } catch (InterruptedException e) { } } } } }; /** * Fragment initialization. We way we want to be retained and * start our thread. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// Tell the framework to try to keep this fragment around // during a configuration change. setRetainInstance(true);
// Start up the worker thread. mThread.start(); } /** * This is called when the Fragment's Activity is ready to go, after * its content view has been installed; it is called both after * the initial fragment creation and after the fragment is re-attached * to a new activity. */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState);
// Retrieve the progress bar from the target's view hierarchy. mProgressBar = (ProgressBar)getTargetFragment().getView().findViewById( R.id.progress_horizontal);
// We are ready for our thread to go. synchronized (mThread) { mReady = true; mThread.notify(); } } /** * This is called when the fragment is going away. It is NOT called * when the fragment is being propagated between activity instances. */ @Override public void onDestroy() { // Make the thread go away. synchronized (mThread) { mReady = false; mQuiting = true; mThread.notify(); }
super.onDestroy(); } /** * This is called right before the fragment is detached from its * current activity instance. */ @Override public void onDetach() { // This fragment is being detached from its activity. We need // to make sure its thread is not going to touch any activity // state after returning from this function. synchronized (mThread) { mProgressBar = null; mReady = false; mThread.notify(); }
super.onDetach(); } /** * API for our UI to restart the progress thread. */ public void restart() { synchronized (mThread) { mPosition = 0; mThread.notify(); } } } } |
2. Safely Performing Fragment Transactions.
Activity 가 fragment transaction 을 handle 할 수 없을 때 transaction 이 발생하면 다음과 같은 에러가 발생한다.
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord:574)
at android.support.v4.app.DialogFragment.show(DialogFragment:127)
FragmentActivity 가 background 가 되고, FragmentManigerImpl 의 mStateSaved flag 가 true 되었을 때 발생한다.
이 문제를 해결하기 위해서는 onSaveInstanceState() 가 불린 후에는 fragment transaction 이 수행되면 안 된다.
위와 같은 에러는 mStateSaved flag 가 false 로 바뀌기 전에 onResume 이 불릴 경우에 발생한다.
따라서 onResume() 에서의 fragment transaction 을 피하고, onResumeFragment() 에서 수행하는 것이 좋다.
3. Managing the Cursor Lifecycle.
CursorAdapter 를 쓰다 보면 다음과 같은 에러에 봉착하곤 한다.
java.lang.IllegalStateException: this should only be called when the cursor is valid
at android.support.v4.widget.CursorAdapter.getView(CursorAdapter.java:245)
at android.widget.HeaderViewListAdapter.getView(HeaderViewListAdapter.java:253)
이는 CursorAdapter 의 mDataValid 가 false 로 되었을 때 발생하며, 이는
1. cursor 가 null 일 경우
2. cursor의 requery 가 끝난 경우.
3. data 에 onInvalidated 가 호출된 경우.
위의 경우에 발생한다.
CursorLoader 와 startManagingCursor() 를 동시에 사용할 경우에 위의 경우가 주로 발생한다.
CursorLoader 가 생기면서 startManagingCursor 는 deprecated 되었다.
따라서 Fragment 와 CursorLoader 를 사용할 경우에는 startManaingCursor(), stopManagingCursor() 모두를 제거해주는 것이 좋다.
'IT_Programming > Android_Java' 카테고리의 다른 글
[Android] 정규식과 ImageSpan을 활용해 괄호 안 문자 이미지로 변경하기 (0) | 2014.07.16 |
---|---|
[펌] Android In-App Billing (IAB Version 3) 보안 완벽 정리 (0) | 2014.07.16 |
배터리를 절약하는 네트워크 어플리케이션 구현 (0) | 2014.06.18 |
Android Layout Tricks: Efficiency (0) | 2014.06.17 |
Faster screen orientation change (0) | 2014.06.17 |