IT_Programming/Android_Java

[펌] Android Support Library 를 안정감 있게 사용하는 3가지 방법.

JJun ™ 2014. 6. 18. 00:08


 출처

  : http://aroundck.tistory.com/2897


 

출처 : http://www.crashlytics.com/blog/3-key-ways-to-increase-android-support-library-stability/

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 로 소스는 아래와 같다.


https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/app/FragmentRetainInstance.java



/*

 * 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() 모두를 제거해주는 것이 좋다.