কিভাবে পিছনে স্ট্যাক মধ্যে টুকরা উদাহরণস্বরূপ অবস্থা সংরক্ষণ করুন?


488

আমি এসও তে অনুরূপ প্রশ্নের অনেকগুলি উদাহরণ খুঁজে পেয়েছি তবে দুর্ভাগ্যক্রমে কোনও উত্তর আমার প্রয়োজনীয়তা পূরণ করে না।

আমার প্রতিকৃতি এবং ল্যান্ডস্কেপ জন্য বিভিন্ন লেআউট আছে এবং আমি ব্যাক স্ট্যাক ব্যবহার করছি, যা উভয়ই আমাকে ব্যবহার করতে বাধা দেয় setRetainState()এবং কনফিগারেশন পরিবর্তনের রুটিন ব্যবহার করে কৌশলগুলি ricks

আমি টেক্সটভিউজে ব্যবহারকারীর কাছে নির্দিষ্ট তথ্য দেখাব, যা ডিফল্ট হ্যান্ডলারে সংরক্ষিত হয় না। ক্রমাগত ক্রিয়াকলাপগুলি ব্যবহার করে আমার অ্যাপ্লিকেশনটি লেখার সময়, নিম্নলিখিতগুলি ভালভাবে কাজ করেছে:

TextView vstup;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.whatever);
    vstup = (TextView)findViewById(R.id.whatever);
    /* (...) */
}

@Override
public void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    state.putCharSequence(App.VSTUP, vstup.getText());
}

@Override
public void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    vstup.setText(state.getCharSequence(App.VSTUP));
}

এর সাথে Fragment, এটি কেবল খুব নির্দিষ্ট পরিস্থিতিতে কাজ করে। বিশেষত, ভয়াবহভাবে ভাঙ্গার ফলে কোনও খণ্ডকে প্রতিস্থাপন করা হয়, এটি পিছনের স্ট্যাকের মধ্যে রাখা হয় এবং তারপরে নতুন খণ্ডটি প্রদর্শিত হওয়ার সময় স্ক্রিনটি ঘোরানো হয়। আমি যা বুঝতে পেরেছি তা থেকে, পুরানো খণ্ডটি onSaveInstanceState()প্রতিস্থাপনের সময় কোনও কল আসে না তবে Activityএটির সাথে কোনওভাবে যুক্ত থাকে এবং এই পদ্ধতিটি পরে কল করা হয় যখন এর Viewআর অস্তিত্ব নেই, সুতরাং আমার কোনও TextViewফলাফলের জন্য a এ সন্ধান করা NullPointerException

এছাড়াও, আমি দেখেছি যে আমার রেফারেন্স পালন TextViewsসঙ্গে একটি ভাল ধারণা নয় Fragmentগুলি, এমনকি যদি এটা দিয়ে ঠিকঠাক লেগেছে Activity'র। onSaveInstanceState()সেক্ষেত্রে , প্রকৃতপক্ষে রাষ্ট্রটিকে সংরক্ষণ করে তবে যদি টুকরাকটি লুকানো থাকে তখন আমি পর্দা দু'বার ঘোরালে সমস্যাটি আবার উপস্থিত হয়, কারণ onCreateView()এটি নতুন ক্ষেত্রে বলা হয় না।

আমি রাষ্ট্র সংরক্ষণ চিন্তা onDestroyView()কিছু মধ্যে Bundleটাইপ বর্গ সদস্য উপাদান (এটা আসলে আরো ডেটা, শুধু এক TextViewও সংরক্ষণ) যে মধ্যে onSaveInstanceState()তবে অন্যান্য অপূর্ণতা আছে। প্রাথমিকভাবে, যদি টুকরা করা হয় বর্তমানে দেখানো দুটি ফাংশন কলিং আদেশ, বিপরীত তাই আমি দুটি ভিন্ন পরিস্থিতিতে জন্য অ্যাকাউন্ট প্রয়োজন চাই করা হয়। একটি পরিষ্কার এবং সঠিক সমাধান থাকতে হবে!


1
এখানে বিস্তারিত ব্যাখ্যা সহ খুব ভাল উদাহরণ রয়েছে is emuneee.com/blog/2013/01/07/saving-fragment-states
Hesam

1
আমি emunee.com লিঙ্ক দ্বিতীয়। এটি আমার জন্য একটি কাঠির ইউআই সমস্যা সমাধান করেছে!
রেইনেক্টর রব

উত্তর:


541

উদাহরণস্বরূপ অবস্থাটি সঠিকভাবে সংরক্ষণ Fragmentকরতে আপনার নিম্নলিখিতটি করা উচিত:

1. খণ্ডগুলিতে, ওভাররাইড করে onSaveInstanceState()পুনরায় পুনঃস্থাপনের মাধ্যমে দৃষ্টান্ত সংরক্ষণ করুন onActivityCreated():

class MyFragment extends Fragment {

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's state here
        }
    }
    ...
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        //Save the fragment's state here
    }

}

২. এবং গুরুত্বপূর্ণ বিষয় , ক্রিয়াকলাপে আপনাকে খণ্ডটির উদাহরণটি সংরক্ষণ করতে হবে onSaveInstanceState()এবং পুনরুদ্ধার করতে হবে onCreate()

class MyActivity extends Activity {

    private MyFragment 

    public void onCreate(Bundle savedInstanceState) {
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's instance
            mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
            ...
        }
        ...
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        //Save the fragment's instance
        getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
    }

}

আশাকরি এটা সাহায্য করবে.


7
এটি আমার জন্য পুরোপুরি কাজ করেন! কোনও কর্মক্ষেত্র নেই, হ্যাক নেই, কেবল এইভাবে বোঝা যায়। এর জন্য আপনাকে ধন্যবাদ, কয়েক ঘন্টা অনুসন্ধান সফল হয়েছে। খণ্ডে আপনার মানগুলির SaveInstanceState () দিন, তারপরে ক্রিয়াকলাপটি খণ্ডটি ধারণ করে সংরক্ষণ করুন, তারপরে পুনরুদ্ধার করুন :)
ম্যাটম্যাট

77
এমসন্টেন্ট কী?
উইজার্ড

14
@ উইজুর্ড এমকন্টেন্ট হ'ল একটি খণ্ডন, এটি ক্রিয়াকলাপের বর্তমান খণ্ডের উদাহরণ হিসাবে উল্লেখ করে।
থানহ এইচএইচএইচ

13
আপনি কীভাবে ব্যাখ্যা করতে পারেন যে এটি কীভাবে পিছনের স্ট্যাকের একটি খণ্ডের উদাহরণস্বরূপ সংরক্ষণ করবে? ওপি এমনটাই বলেছিল।
হিটম্যানিডোস

51
এটি প্রশ্নের সাথে সম্পর্কিত নয়, টুকরোটি ব্যাকস্ট্যাকে রাখলে onSaveInstance বলা হয় না
তাদাস

87

আমি এই মুহুর্তে এইভাবেই ব্যবহার করছি ... এটি খুব জটিল তবে কমপক্ষে এটি সমস্ত সম্ভাব্য পরিস্থিতি পরিচালনা করে। যদি কেউ আগ্রহী হয়।

public final class MyFragment extends Fragment {
    private TextView vstup;
    private Bundle savedState = null;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.whatever, null);
        vstup = (TextView)v.findViewById(R.id.whatever);

        /* (...) */

        /* If the Fragment was destroyed inbetween (screen rotation), we need to recover the savedState first */
        /* However, if it was not, it stays in the instance from the last onDestroyView() and we don't want to overwrite it */
        if(savedInstanceState != null && savedState == null) {
            savedState = savedInstanceState.getBundle(App.STAV);
        }
        if(savedState != null) {
            vstup.setText(savedState.getCharSequence(App.VSTUP));
        }
        savedState = null;

        return v;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        savedState = saveState(); /* vstup defined here for sure */
        vstup = null;
    }

    private Bundle saveState() { /* called either from onDestroyView() or onSaveInstanceState() */
        Bundle state = new Bundle();
        state.putCharSequence(App.VSTUP, vstup.getText());
        return state;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        /* If onDestroyView() is called first, we can use the previously savedState but we can't call saveState() anymore */
        /* If onSaveInstanceState() is called first, we don't have savedState, so we need to call saveState() */
        /* => (?:) operator inevitable! */
        outState.putBundle(App.STAV, (savedState != null) ? savedState : saveState());
    }

    /* (...) */

}

বিকল্পভাবে , প্যাসিভ এসগুলিতে ডেটাটি Viewভেরিয়েবলগুলিতে Viewপ্রদর্শন করা এবং দুটি ব্যবহার করে দুটি জিনিস সিঙ্ক করে রেখে কেবল সেগুলি প্রদর্শন করার জন্য সেগুলি ব্যবহার করা সর্বদা সম্ভাবনা । যদিও আমি শেষ অংশটি খুব পরিষ্কার বিবেচনা করি না।


68
এটি আমি এখনও অবধি খুঁজে পাওয়া সেরা সমাধান তবে এখনও একটি (কিছুটা বহিরাগত) সমস্যা বাকি রয়েছে: যদি আপনার দুটি টুকরোগুলি থাকে Aএবং Bযেখানে Aবর্তমানে ব্যাকস্ট্যাকে রয়েছে এবং Bদৃশ্যমান হয়, তবে আপনি A(অদৃশ্য) অবস্থা হারাবেন এক) আপনি যদি ডিসপ্লেটি দু'বার ঘোরান । সমস্যাটি কেবল এই দৃশ্যে onCreateView()ডাকা হয় না onCreate()। সুতরাং পরে, এ onSaveInstanceState()থেকে রাষ্ট্রকে রক্ষা করার কোনও মতামত নেই। একজনকে সংরক্ষণ করতে হবে এবং তারপরে রাষ্ট্রটি প্রবেশ করানো হবে onCreate()
ডেভনসোলে

7
@ দেবকনসোল আমি আশা করি এই মন্তব্যের জন্য আমি আপনাকে পাঁচটি ভোট দিতে পারি! এই ঘূর্ণনটি দু'দিন ধরে আমাকে হত্যা করছে।
DroidT

মহান উত্তরের জন্য আপনাকে ধন্যবাদ! যদিও আমার একটা প্রশ্ন আছে। এই খণ্ডে মডেল অবজেক্ট (POJO) ইনস্ট্যান্ট করার জন্য সেরা জায়গাটি কোথায়?
রেনজিথ

7
অন্যের জন্য সময় বাঁচাতে সহায়তা করার জন্য App.VSTUPএবং App.STAVউভয় স্ট্রিং ট্যাগ হ'ল তারা যা চেষ্টা করছে তা উপস্থাপন করে। উদাহরণ: savedState = savedInstanceState.getBundle(savedGamePlayString);বাsavedState.getDouble("averageTime")
ট্যানার হলম্যান

1
এটি সৌন্দর্যের জিনিস।
ইভান

61

সর্বশেষতম সমর্থন লাইব্রেরিতে এখানে আলোচিত সমাধানগুলির কোনওটিরই দরকার নেই। আপনি Activityযেমন ব্যবহার করতে চান তেমন আপনার টুকরো টুকরো দিয়ে খেলতে পারেন FragmentTransaction। কেবল নিশ্চিত করুন যে আপনার টুকরোগুলি আইডি বা ট্যাগ দিয়ে চিহ্নিত করা যায়।

যতক্ষণ না আপনি প্রতিটি কল করে সেগুলি পুনরায় তৈরি করার চেষ্টা না করেন ততক্ষণ এই টুকরোগুলি স্বয়ংক্রিয়ভাবে পুনরুদ্ধার হবে onCreate()। পরিবর্তে, আপনার savedInstanceStateখালি নয় কিনা তা পরীক্ষা করা উচিত এবং এই ক্ষেত্রে তৈরি করা টুকরাগুলির পুরানো রেফারেন্সগুলি খুঁজে পাওয়া উচিত ।

এখানে একটি উদাহরণ:

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

    if (savedInstanceState == null) {
        myFragment = MyFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.my_container, myFragment, MY_FRAGMENT_TAG)
                .commit();
    } else {
        myFragment = (MyFragment) getSupportFragmentManager()
                .findFragmentByTag(MY_FRAGMENT_TAG);
    }
...
}

তবে দ্রষ্টব্য যে বর্তমানে কোনও খণ্ডের গোপনীয় অবস্থা পুনরুদ্ধার করার সময় একটি ত্রুটি রয়েছে । আপনি যদি আপনার ক্রিয়াকলাপে টুকরোগুলি গোপন করে থাকেন তবে আপনাকে এই ক্ষেত্রে ম্যানুয়ালি এই রাষ্ট্রটি পুনরুদ্ধার করতে হবে।


2
সমর্থন লাইব্রেরি ব্যবহার করার সময় আপনি কী লক্ষ্য করেছেন এটি ঠিক করা বা আপনি কোথাও এটি পড়েছেন? আপনি এটি সম্পর্কে আরও কিছু তথ্য সরবরাহ করতে পারেন? ধন্যবাদ!
পাইওয়েজান

1
@ পাইভজান এটি ডক্স থেকে পরিষ্কারভাবে অনুমান করা যেতে পারে। উদাহরণস্বরূপ, সূচনা - ট্রান্সজেকশন () ডকটি অনুসরণ করে পড়েন: "এটি কারণ ফ্রেমওয়ার্কটি আপনার বর্তমান খণ্ডগুলিকে রাজ্যে সংরক্ষণ করার যত্ন নেয় (...)"। আমি বেশ কিছুদিন ধরেই আমার প্রত্যাশিত আচরণটি দিয়ে আমার অ্যাপসকে কোড করে চলেছি ing
রিকার্ডো

1
ভিউ পেজার ব্যবহার করা হলে @ রিকার্ডো এটি প্রয়োগ করে?
ডেরেক বিটি

1
সাধারণত হ্যাঁ, যদি না আপনি FragmentPagerAdapterবা প্রয়োগের ক্ষেত্রে ডিফল্ট আচরণ পরিবর্তন করেন FragmentStatePagerAdapterFragmentStatePagerAdapterউদাহরণস্বরূপ, আপনি restoreState()কোডটির দিকে নজর দিলে আপনি দেখতে পাবেন যে FragmentManagerঅ্যাডাপ্টার তৈরি করার সময় প্যারামিটার হিসাবে পাস হওয়া পদ্ধতিটি সেই টুকরো পুনরুদ্ধার করে ।
রিকার্ডো

4
আমি মনে করি এই অবদানটি মূল প্রশ্নের সেরা উত্তর। এটি আমার মতে - অ্যান্ড্রয়েড প্ল্যাটফর্মটি কীভাবে কাজ করে তার সাথে সর্বোত্তমভাবে একত্রিত। ভবিষ্যতের পাঠকদের আরও ভালভাবে সহায়তা করার জন্য আমি এই উত্তরটিকে "স্বীকৃত" হিসাবে চিহ্নিত করার সুপারিশ করব ।
ডিবিএম

17

আমি কেবল সমাধানটি দিতে চাই যে আমি ভ্যাসেক এবং ডেভনসোল থেকে প্রাপ্ত এই পোস্টে উপস্থাপিত সমস্ত মামলা পরিচালনা করে that এই দ্রবণটি যখন বিশেষাধিক ক্ষেত্রে ফোনটি একবারে একাধিক ঘোরানো হয় তখনও খণ্ডগুলি দৃশ্যমান হয় না।

এখানে আমি ব্যবহারের জন্য বান্ডিলটি সংরক্ষণ করেছি যেহেতু অনক্রিট এবং অন সেভইন্সট্যান্সস্টেট কেবলমাত্র কলগুলি যখন কলটি দৃশ্যমান হয় না তখন করা হয়

MyObject myObject;
private Bundle savedState = null;
private boolean createdStateInDestroyView;
private static final String SAVED_BUNDLE_TAG = "saved_bundle";

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        savedState = savedInstanceState.getBundle(SAVED_BUNDLE_TAG);
    }
}

যেহেতু ডিস্ট্রভভিউকে বিশেষ আবর্তনের পরিস্থিতিতে বলা হয় না আমরা নিশ্চিত হতে পারি যে এটি যদি রাজ্য তৈরি করে তবে আমাদের এটি ব্যবহার করা উচিত।

@Override
public void onDestroyView() {
    super.onDestroyView();
    savedState = saveState();
    createdStateInDestroyView = true;
    myObject = null;
}

এই অংশটি একই হবে।

private Bundle saveState() { 
    Bundle state = new Bundle();
    state.putSerializable(SAVED_BUNDLE_TAG, myObject);
    return state;
}

এখন এখানে জটিল অংশ। আমারঅ্যাক্টিভিটি ক্রিয়েটেড পদ্ধতিতে আমি "মাইবজেক্ট" ভেরিয়েবল ইনস্ট্যান্ট করি তবে আবর্তনটিঅ্যাক্টিভিটিতে ঘটে এবং অনক্রিটভিউ কল হয় না। অতএব, যখন ওরিয়েন্টেশন একাধিকবার ঘোরে তখন মাইওবজেক্ট এই পরিস্থিতিতে শূন্য হবে। আমি ওঠার মতো বান্ডিলের মতো অনক্রিটে সংরক্ষিত একই বান্ডিলটি পুনরায় ব্যবহার করে এটি পেতে পারি get

    @Override
public void onSaveInstanceState(Bundle outState) {

    if (myObject == null) {
        outState.putBundle(SAVED_BUNDLE_TAG, savedState);
    } else {
        outState.putBundle(SAVED_BUNDLE_TAG, createdStateInDestroyView ? savedState : saveState());
    }
    createdStateInDestroyView = false;
    super.onSaveInstanceState(outState);
}

এখন আপনি যেখানেই রাজ্য পুনরুদ্ধার করতে চান কেবল সেভস্টেট বান্ডিলটি ব্যবহার করুন

  @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    if(savedState != null) {
        myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG);
    }
    ...
}

আপনি কি আমাকে বলতে পারবেন ... এখানে "মাইবজেক্ট" কী?
কাভি

2
আপনি এটি কিছু হতে চান। এটি এমন কিছু উপস্থাপনের একটি উদাহরণ যা বান্ডলে সংরক্ষণ করা হবে।
DroidT

3

DroidT কে ধন্যবাদ , আমি এটি তৈরি করেছি:

আমি বুঝতে পারি যে টুকরাটি যদি অনক্রিটভিউ () চালায় না, তবে তার দৃষ্টিভঙ্গি তাত্ক্ষণিক নয়। সুতরাং, যদি ব্যাক স্ট্যাকের টুকরাটি তার দৃষ্টিভঙ্গি তৈরি না করে, তবে আমি সর্বশেষ সঞ্চিত রাষ্ট্রটি সংরক্ষণ করি, অন্যথায় আমি যে ডেটা সংরক্ষণ করতে / পুনরুদ্ধার করতে চাই তার সাথে আমার নিজস্ব বান্ডিলটি তৈরি করি।

1) এই শ্রেণিটি প্রসারিত করুন:

import android.os.Bundle;
import android.support.v4.app.Fragment;

public abstract class StatefulFragment extends Fragment {

    private Bundle savedState;
    private boolean saved;
    private static final String _FRAGMENT_STATE = "FRAGMENT_STATE";

    @Override
    public void onSaveInstanceState(Bundle state) {
        if (getView() == null) {
            state.putBundle(_FRAGMENT_STATE, savedState);
        } else {
            Bundle bundle = saved ? savedState : getStateToSave();

            state.putBundle(_FRAGMENT_STATE, bundle);
        }

        saved = false;

        super.onSaveInstanceState(state);
    }

    @Override
    public void onCreate(Bundle state) {
        super.onCreate(state);

        if (state != null) {
            savedState = state.getBundle(_FRAGMENT_STATE);
        }
    }

    @Override
    public void onDestroyView() {
        savedState = getStateToSave();
        saved = true;

        super.onDestroyView();
    }

    protected Bundle getSavedState() {
        return savedState;
    }

    protected abstract boolean hasSavedState();

    protected abstract Bundle getStateToSave();

}

2) আপনার টুকরাটিতে আপনার অবশ্যই এটি থাকতে হবে:

@Override
protected boolean hasSavedState() {
    Bundle state = getSavedState();

    if (state == null) {
        return false;
    }

    //restore your data here

    return true;
}

3) উদাহরণস্বরূপ, আপনি অ্যাক্টিভিটিস ক্রিয়েটেডে হ্যাভিস্টস্টেটকে কল করতে পারেন:

@Override
public void onActivityCreated(Bundle state) {
    super.onActivityCreated(state);

    if (hasSavedState()) {
        return;
    }

    //your code here
}

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.