android - एंड्रॉइड फ्रैगमेंट्स स्क्रीन रोटेशन या कॉन्फ़िगरेशन परिवर्तन के दौरान AsyncTask को बनाए रखना




android-fragments progressdialog (8)

अगर किसी को इस धागे पर अपना रास्ता मिल जाए तो मुझे एक app.Service कार्य को app.Service से चलाने के लिए एक साफ दृष्टिकोण मिला। app.Service (START_STICKY के साथ शुरू हुई) और फिर यह पता लगाने के लिए कि क्या सेवा (और इसलिए async कार्य) ) अभी भी चल रहा है;

    public boolean isServiceRunning(String serviceClassName) {
    final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
    final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);

    for (RunningServiceInfo runningServiceInfo : services) {
        if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
            return true;
        }
    }
    return false;
 }

यदि ऐसा है, तो DialogFragment (या जो कुछ भी) दोबारा जोड़ें और यदि यह सुनिश्चित नहीं है कि संवाद को खारिज कर दिया गया है।

यह विशेष रूप से प्रासंगिक है यदि आप v4.support.* का उपयोग कर रहे हैं v4.support.* पुस्तकालयों के बाद से (लेखन के समय) उन्हें setRetainInstance विधि के साथ समस्याएं पता हैं और पेजिंग देखें। इसके अलावा, उदाहरण को बनाए रखने से आप संसाधनों के एक अलग सेट (यानी नए अभिविन्यास के लिए एक अलग दृश्य लेआउट) का उपयोग करके अपनी गतिविधि को फिर से बना सकते हैं।

मैं एक स्मार्टफोन / टैबलेट ऐप पर काम कर रहा हूं, केवल एक एपीके का उपयोग कर रहा हूं, और स्क्रीन आकार के आधार पर आवश्यक संसाधन लोड करना, सबसे अच्छा डिज़ाइन विकल्प एसीएल के माध्यम से फ्रैगमेंट्स का उपयोग करना प्रतीत होता है।

यह ऐप अभी तक ठीक काम कर रहा है जब तक कि केवल गतिविधि आधारित न हो। स्क्रीन पर घुमाए जाने पर या कॉन्फ़िगरेशन परिवर्तन मध्य संचार होने पर भी उन्हें कार्य करने के लिए क्रियाकलापों में AsyncTasks और ProgressDialogs को प्रबंधित करने का एक नकली वर्ग है।

मैं गतिविधि के मनोरंजन से बचने के लिए मैनिफेस्ट को नहीं बदलूंगा, ऐसे कई कारण हैं जिन्हें मैं नहीं करना चाहता हूं, लेकिन मुख्य रूप से क्योंकि आधिकारिक दस्तावेज़ों का कहना है कि इसकी अनुशंसा नहीं की गई है और मैंने इसके बिना अब तक प्रबंधित किया है, इसलिए कृपया इसकी सिफारिश न करें मार्ग।

public class Login extends Activity {

    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.login);
        //SETUP UI OBJECTS
        restoreAsyncTask();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (pd != null) pd.dismiss();
        if (asyncLoginThread != null) return (asyncLoginThread);
        return super.onRetainNonConfigurationInstance();
    }

    private void restoreAsyncTask();() {
        pd = new ProgressDialog(Login.this);
        if (getLastNonConfigurationInstance() != null) {
            asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
            if (asyncLoginThread != null) {
                if (!(asyncLoginThread.getStatus()
                        .equals(AsyncTask.Status.FINISHED))) {
                    showProgressDialog();
                }
            }
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... args) {
            try {
                //Connect to WS, recieve a JSON/XML Response
                //Place it somewhere I can use it.
            } catch (Exception e) {
                return true;
            }
            return true;
        }

        protected void onPostExecute(Boolean result) {
            if (result) {
                pd.dismiss();
                //Handle the response. Either deny entry or launch new Login Succesful Activity
            }
        }
    }
}

यह कोड ठीक काम कर रहा है, मेरे पास शिकायत के बिना लगभग 10.000 उपयोगकर्ता हैं, इसलिए यह तर्कसंगत लग रहा था कि इस तर्क को नए फ्रैगमेंट आधारित डिजाइन में कॉपी करें, लेकिन, ज़ाहिर है, यह काम नहीं कर रहा है।

लॉगिनफ्रैगमेंट यहां है:

public class LoginFragment extends Fragment {

    FragmentActivity parentActivity;
    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    public interface OnLoginSuccessfulListener {
        public void onLoginSuccessful(GlobalContainer globalContainer);
    }

    public void onSaveInstanceState(Bundle outState){
        super.onSaveInstanceState(outState);
        //Save some stuff for the UI State
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setRetainInstance(true);
        //If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
        parentActivity = getActivity();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
        return loginLayout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //SETUP UI OBJECTS
        if(savedInstanceState != null){
            //Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
            @Override
            protected Boolean doInBackground(String... args) {
                try {
                    //Connect to WS, recieve a JSON/XML Response
                    //Place it somewhere I can use it.
                } catch (Exception e) {
                    return true;
                }
                return true;
            }

            protected void onPostExecute(Boolean result) {
                if (result) {
                    pd.dismiss();
                    //Handle the response. Either deny entry or launch new Login Succesful Activity
                }
            }
        }
    }
}

मैं onRetainNonConfigurationInstance() उपयोग नहीं कर सकता क्योंकि इसे गतिविधि से बुलाया जाना चाहिए, न कि Fragment, getLastNonConfigurationInstance() साथ ही जाता है। मैंने बिना किसी जवाब के कुछ समान प्रश्न पढ़े हैं।

मैं समझता हूं कि इस सामान को टुकड़ों में व्यवस्थित तरीके से व्यवस्थित करने के लिए कुछ काम करने की आवश्यकता हो सकती है, कहा जा रहा है, मैं एक ही मूल डिजाइन तर्क को बनाए रखना चाहता हूं।

एक कॉन्फ़िगरेशन परिवर्तन के दौरान AsyncTask को बनाए रखने का उचित तरीका क्या होगा, और यदि यह अभी भी चल रहा है, तो प्रगति Dialog दिखाएं, इस बात को ध्यान में रखते हुए कि AsyncTask Fragment के लिए एक आंतरिक वर्ग है और यह Fragment स्वयं है जो AsyncTask.execute को आमंत्रित करता है ()?


टुकड़े वास्तव में इसे बहुत आसान बना सकते हैं। कॉन्फ़िगरेशन परिवर्तनों में अपना टुकड़ा उदाहरण बनाए रखने के लिए बस Fragment.setRetainInstance(boolean) विधि का उपयोग करें। ध्यान दें कि यह दस्तावेज़ों में Activity.onRetainnonConfigurationInstance() लिए अनुशंसित प्रतिस्थापन है।

अगर किसी कारण से आप वास्तव में एक बनाए गए टुकड़े का उपयोग नहीं करना चाहते हैं, तो आप अन्य दृष्टिकोण भी ले सकते हैं। ध्यान दें कि प्रत्येक खंड में Fragment.getId() द्वारा लौटा एक अद्वितीय पहचानकर्ता होता है। आप यह भी पता लगा सकते हैं कि Fragment.getActivity().isChangingConfigurations() माध्यम से एक कॉन्फ़िगरेशन परिवर्तन के लिए एक टुकड़ा फेंक दिया जा रहा है Fragment.getActivity().isChangingConfigurations() । तो, उस बिंदु पर जहां आप अपने AsyncTask (ऑनस्टॉप () या ऑनस्ट्रोय () में सबसे अधिक संभावना को रोकने का निर्णय लेते हैं), उदाहरण के लिए आप जांच सकते हैं कि कॉन्फ़िगरेशन बदल रहा है और यदि ऐसा है तो इसे खंड के पहचानकर्ता के तहत एक स्थिर स्पैरएरे में चिपकाएं, और फिर आपके ऑनक्रेट () या ऑनस्टार्ट () में यह देखने के लिए देखें कि क्या आपके पास स्पैस सरणी में एक AsyncTask है।


मेरा दृष्टिकोण प्रतिनिधिमंडल डिज़ाइन पैटर्न का उपयोग करना है, सामान्य रूप से, हम आपके AysncTask.doInBackground () विधि में AsyncTask (प्रतिनिधिमंडल) से BusinessDAO (प्रतिनिधि) से वास्तविक व्यापार तर्क (इंटरनेट या डेटाबेस या डेटा से डेटा पढ़ सकते हैं) को अलग कर सकते हैं। , BusinessDAO को वास्तविक कार्य का प्रतिनिधि दें, फिर BusinessDAO में सिंगलटन प्रक्रिया तंत्र को कार्यान्वित करें, ताकि BusinessDAO.doSomething () को एकाधिक कॉल केवल एक वास्तविक कार्य को हर बार चलने और कार्य परिणाम की प्रतीक्षा करने के लिए ट्रिगर करेगा। विचार प्रतिनिधि (यानी AsyncTask) के बजाय, कॉन्फ़िगरेशन परिवर्तन के दौरान प्रतिनिधि (यानी BusinessDAO) को बनाए रखता है।

  1. अपना खुद का आवेदन बनाएं / कार्यान्वित करें, इसका उद्देश्य यहां बिजनेसडाओ को बनाना / प्रारंभ करना है, ताकि हमारा बिजनेसडाओ का जीवन चक्र एप्लिकेशन स्कॉप्ड हो, न कि गतिविधि स्कॉप्ड हो, ध्यान दें कि आपको MyApplication का उपयोग करने के लिए AndroidManifest.xml को बदलने की आवश्यकता है:

    public class MyApplication extends android.app.Application {
      private BusinessDAO businessDAO;
    
      @Override
      public void onCreate() {
        super.onCreate();
        businessDAO = new BusinessDAO();
      }
    
      pubilc BusinessDAO getBusinessDAO() {
        return businessDAO;
      }
    
    }
    
  2. हमारी मौजूदा गतिविधि / सगाई अधिकांशतः अपरिवर्तित है, फिर भी एसिंक टास्क को आंतरिक कक्षा के रूप में कार्यान्वित करती है और गतिविधि / फ्रेगमेंट से AsyncTask.execute () को शामिल करती है, अब अंतर यह है कि AsyncTask व्यवसाय कार्य को वास्तविक कार्य सौंपेगा, इसलिए कॉन्फ़िगरेशन परिवर्तन के दौरान, दूसरा AsyncTask शुरू किया जाएगा और निष्पादित किया जाएगा, और BusinessDAO.doSomething () दूसरी बार कॉल करें, हालांकि, BusinessDAO.doSomething () को दूसरी कॉल एक नया चलने वाला कार्य ट्रिगर नहीं करेगा, इसके बजाए, मौजूदा रनिंग कार्य को समाप्त करने की प्रतीक्षा कर रहा है:

    public class LoginFragment extends Fragment {
      ... ...
    
      public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> {
        // get a reference of BusinessDAO from application scope.
        BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO();
    
        @Override
        protected Boolean doInBackground(String... args) {
            businessDAO.doSomething();
            return true;
        }
    
        protected void onPostExecute(Boolean result) {
          //Handle task result and update UI stuff.
        }
      }
    
      ... ...
    }
    
  3. BusinessDAO के अंदर, सिंगलटन प्रक्रिया तंत्र को लागू करें, उदाहरण के लिए:

    public class BusinessDAO {
      ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1));
      Future<MyTask> myFutureTask = null;
    
      public void doSomething() {
        if (myFutureTask == null) {
          // nothing running at the moment, submit a new callable task to run.
          MyTask myTask = new MyTask();
          myFutureTask = completionExecutor.submit(myTask);
        }
        // Task already submitted and running, waiting for the running task to finish.
        myFutureTask.get();
      }
    
      // If you've never used this before, Callable is similar with Runnable, with ability to return result and throw exception.
      private class MyTask extends Callable<MyTask> {
        public MyAsyncTask call() {
          // do your job here.
          return this;
        }
      }
    
    }
    

मैं 100% निश्चित नहीं हूं कि यह काम करेगा, इसके अलावा, नमूना कोड स्निपेट को स्यूडोकोड के रूप में माना जाना चाहिए। मैं बस आपको डिजाइन स्तर से कुछ सुराग देने की कोशिश कर रहा हूं। कोई प्रतिक्रिया या सुझाव स्वागत है और सराहना की है।


मेरा पहला सुझाव आंतरिक AsyncTasks से बचने के लिए है, आप एक प्रश्न पढ़ सकते हैं जिसे मैंने इसके बारे में पूछा और जवाब: एंड्रॉइड: AsyncTask सिफारिशें: निजी वर्ग या सार्वजनिक कक्षा?

इसके बाद मैंने गैर-आंतरिक का उपयोग शुरू किया और ... अब मुझे बहुत सारे लाभ दिखाई देते हैं।

दूसरा, Application क्लास में अपने चल रहे AsyncTask का संदर्भ रखें - http://developer.android.com/reference/android/app/Application.html

हर बार जब आप एक AsyncTask शुरू करते हैं, इसे एप्लिकेशन पर सेट करें और जब यह समाप्त हो जाए तो इसे शून्य पर सेट करें।

जब कोई टुकड़ा / गतिविधि शुरू होती है तो आप यह जांच सकते हैं कि कोई भी AsyncTask चल रहा है (यह जांच कर रहा है कि यह शून्य पर है या नहीं) और उसके बाद संदर्भ को जो कुछ भी आप चाहते हैं (गतिविधि, खंड आदि ताकि आप कॉलबैक कर सकें)।

यह आपकी समस्या का समाधान करेगा: यदि आपके पास किसी भी निर्धारित समय पर केवल 1 AsyncTask चल रहा है तो आप एक साधारण संदर्भ जोड़ सकते हैं:

AsyncTask<?,?,?> asyncTask = null;

अन्यथा, एपिकेशन में हैश मैप उनके संदर्भ के साथ है।

प्रगति संवाद सटीक उसी सिद्धांत का पालन कर सकता है।


मैंने एक बहुत ही छोटी ओपन-सोर्स पृष्ठभूमि कार्य लाइब्रेरी बनाई जो कि AsyncTask पर आधारित है लेकिन अतिरिक्त कार्यक्षमता के साथ:

  1. कॉन्फ़िगरेशन परिवर्तनों में कार्यों को स्वचालित रूप से बनाए रखना;
  2. यूआई कॉलबैक (श्रोताओं);
  3. डिवाइस घुमाने पर कार्य को पुनरारंभ या रद्द नहीं करता है (जैसे लोडर करेंगे);

लाइब्रेरी आंतरिक रूप से किसी भी उपयोगकर्ता इंटरफ़ेस के बिना एक Fragment का उपयोग करती है, जिसे कॉन्फ़िगरेशन परिवर्तनों में सेट किया जाता है ( setRetainInstance(true) )।

आप इसे गिटहब पर पा सकते हैं: https://github.com/NeoTech-Software/Android-Retainable-Tasks

सबसे बुनियादी उदाहरण (संस्करण 0.2.0):

यह उदाहरण बहुत सीमित मात्रा में कोड का उपयोग करके पूरी तरह से कार्य को बरकरार रखता है।

कार्य:

private class ExampleTask extends Task<Integer, String> {

    public ExampleTask(String tag){
        super(tag);
    }

    protected String doInBackground() {
        for(int i = 0; i < 100; i++) {
            if(isCancelled()){
                break;
            }
            SystemClock.sleep(50);
            publishProgress(i);
        }
        return "Result";
    }
}

गतिविधि:

public class Main extends TaskActivityCompat implements Task.Callback {

    @Override
    public void onClick(View view){
        ExampleTask task = new ExampleTask("activity-unique-tag");
        getTaskManager().execute(task, this);
    }

    @Override
    public Task.Callback onPreAttach(Task<?, ?> task) {
        //Restore the user-interface based on the tasks state
        return this; //This Activity implements Task.Callback
    }

    @Override
    public void onPreExecute(Task<?, ?> task) {
        //Task started
    }

    @Override
    public void onPostExecute(Task<?, ?> task) {
        //Task finished
        Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show();
    }
}

मैंने हाल ही में एक लेख पोस्ट किया है जिसमें वर्णित Fragment एस का उपयोग करके कॉन्फ़िगरेशन परिवर्तनों को कैसे प्रबंधित किया जाए। यह एक रोटेशन परिवर्तन में अच्छी तरह से एक AsyncTask को बनाए रखने की समस्या हल करता है।

टीएल; डीआर एक Fragment अंदर अपने AsyncTask को होस्ट करने के लिए है, Fragment पर setRetainInstance(true) को कॉल करें, और AsyncTask की प्रगति / परिणाम को इसकी Activity पर वापस रिपोर्ट करें (या यह लक्ष्य Fragment , यदि आप वर्णित दृष्टिकोण का उपयोग करना चुनते हैं @Timmmm द्वारा) बनाए रखा Fragment माध्यम से।


Have a look at below example , how to use retained fragment to retain background task:

public class NetworkRequestFragment extends Fragment {

    // Declare some sort of interface that your AsyncTask will use to communicate with the Activity
    public interface NetworkRequestListener {
        void onRequestStarted();
        void onRequestProgressUpdate(int progress);
        void onRequestFinished(SomeObject result);
    }

    private NetworkTask mTask;
    private NetworkRequestListener mListener;

    private SomeObject mResult;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Try to use the Activity as a listener
        if (activity instanceof NetworkRequestListener) {
            mListener = (NetworkRequestListener) activity;
        } else {
            // You can decide if you want to mandate that the Activity implements your callback interface
            // in which case you should throw an exception if it doesn't:
            throw new IllegalStateException("Parent activity must implement NetworkRequestListener");
            // or you could just swallow it and allow a state where nobody is listening
        }
    }

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

        // Retain this Fragment so that it will not be destroyed when an orientation
        // change happens and we can keep our AsyncTask running
        setRetainInstance(true);
    }

    /**
     * The Activity can call this when it wants to start the task
     */
    public void startTask(String url) {
        mTask = new NetworkTask(url);
        mTask.execute();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // If the AsyncTask finished when we didn't have a listener we can
        // deliver the result here
        if ((mResult != null) && (mListener != null)) {
            mListener.onRequestFinished(mResult);
            mResult = null;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        // We still have to cancel the task in onDestroy because if the user exits the app or
        // finishes the Activity, we don't want the task to keep running
        // Since we are retaining the Fragment, onDestroy won't be called for an orientation change
        // so this won't affect our ability to keep the task running when the user rotates the device
        if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) {
            mTask.cancel(true);
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();

        // This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity)
        // When the orientation change occurs, onDetach will be called and since the Activity is being destroyed
        // we don't want to keep any references to it
        // When the Activity is being re-created, onAttach will be called and we will get our listener back
        mListener = null;
    }

    private class NetworkTask extends AsyncTask<String, Integer, SomeObject> {

        @Override
        protected void onPreExecute() {
            if (mListener != null) {
                mListener.onRequestStarted();
            }
        }

        @Override
        protected SomeObject doInBackground(String... urls) {
           // Make the network request
           ...
           // Whenever we want to update our progress:
           publishProgress(progress);
           ...
           return result;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            if (mListener != null) {
                mListener.onRequestProgressUpdate(progress[0]);
            }
        }

        @Override
        protected void onPostExecute(SomeObject result) {
            if (mListener != null) {
                mListener.onRequestFinished(result);
            } else {
                // If the task finishes while the orientation change is happening and while
                // the Fragment is not attached to an Activity, our mListener might be null
                // If you need to make sure that the result eventually gets to the Activity
                // you could save the result here, then in onActivityCreated you can pass it back
                // to the Activity
                mResult = result;
            }
        }

    }
}

I write samepl code to solve this problem

First step is make Application class:

public class TheApp extends Application {

private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();

@Override
public void onCreate() {
    super.onCreate();
    sTheApp = this;
}

public static TheApp get() {
    return sTheApp;
}

public void registerTask(String tag, AsyncTask<?,?,?> task) {
    tasks.put(tag, task);
}

public void unregisterTask(String tag) {
    tasks.remove(tag);
}

public AsyncTask<?,?,?> getTask(String tag) {
    return tasks.get(tag);
}
}

In AndroidManifest.xml

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:name="com.example.tasktest.TheApp">

Code in activity:

public class MainActivity extends Activity {

private Task1 mTask1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mTask1 = (Task1)TheApp.get().getTask("task1");

}

/*
 * start task is not running jet
 */
public void handletask1(View v) {
    if (mTask1 == null) {
        mTask1 = new Task1();
        TheApp.get().registerTask("task1", mTask1);
        mTask1.execute();
    } else
        Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show();

}

/*
 * cancel task if is not finished
 */
public void handelCancel(View v) {
    if (mTask1 != null)
        mTask1.cancel(false);
}

public class Task1 extends AsyncTask<Void, Void, Void>{

    @Override
    protected Void doInBackground(Void... params) {
        try {
            for(int i=0; i<120; i++) {
                Thread.sleep(1000);
                Log.i("tests", "loop=" + i);
                if (this.isCancelled()) {
                    Log.e("tests", "tssk cancelled");
                    break;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onCancelled(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }

    @Override
    protected void onPostExecute(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }
}

}

When activity orientation changes variable mTask is inited from app context. When task is finished variable is set to null and remove from memory.

For me its enough.





android-support-library