ชิ้นส่วน Android การรักษา AsyncTask ระหว่างการหมุนหน้าจอหรือการเปลี่ยนแปลงการกำหนดค่า


86

ฉันกำลังทำงานกับแอปสมาร์ทโฟน / แท็บเล็ตโดยใช้ APK เพียงไฟล์เดียวและโหลดทรัพยากรตามความจำเป็นทั้งนี้ขึ้นอยู่กับขนาดหน้าจอตัวเลือกการออกแบบที่ดีที่สุดดูเหมือนจะใช้ Fragments ผ่าน ACL

แอปนี้ทำงานได้ดีจนถึงขณะนี้เป็นเพียงกิจกรรมเท่านั้น นี่เป็นคลาสจำลองของวิธีที่ฉันจัดการ AsyncTasks และ ProgressDialogs ใน Activities เพื่อให้ทำงานได้แม้จะหมุนหน้าจอหรือมีการเปลี่ยนแปลงการกำหนดค่าเกิดขึ้นระหว่างการสื่อสาร

ฉันจะไม่เปลี่ยนรายการเพื่อหลีกเลี่ยงการพักผ่อนหย่อนใจของกิจกรรมมีสาเหตุหลายประการที่ฉันไม่ต้องการทำ แต่ส่วนใหญ่เป็นเพราะเอกสารอย่างเป็นทางการบอกว่าไม่แนะนำและฉันได้จัดการโดยที่ยังไม่ได้ทำกิจกรรมนี้ดังนั้นโปรดอย่าแนะนำว่า เส้นทาง.

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 คนโดยไม่มีการร้องเรียนดังนั้นจึงดูเหมือนมีเหตุผลที่จะคัดลอกตรรกะนี้ไปยังการออกแบบตาม Fragment ใหม่ แต่แน่นอนว่ามันไม่ทำงาน

นี่คือ LoginFragment:

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 ในระหว่างการเปลี่ยนแปลงการกำหนดค่าและหากยังคงรันอยู่ให้แสดง progressDialog โดยคำนึงว่า AsyncTask เป็นคลาสภายในของ Fragment และเป็น Fragment เองที่เรียกใช้ AsyncTask.execute ()?


1
บางทีเธรดเกี่ยวกับวิธีจัดการกับการเปลี่ยนแปลงการกำหนดค่าด้วย AsyncTaskสามารถช่วยได้
rds

เชื่อมโยง AsyncTask กับวงจรชีวิตของแอปพลิเคชัน.. สามารถดำเนินการต่อได้เมื่อมีการสร้างกิจกรรมใหม่
Fred Grott

ตรวจสอบโพสต์ของฉันในหัวข้อนี้: การจัดการการเปลี่ยนแปลงการกำหนดค่าด้วยFragments
Alex Lockwood

คำตอบ:


75

ชิ้นส่วนสามารถทำให้ง่ายขึ้นมาก เพียงใช้เมธอดFragment.setRetainInstance (บูลีน)เพื่อเก็บอินสแตนซ์แฟรกเมนต์ของคุณไว้ในการเปลี่ยนแปลงคอนฟิกูเรชัน โปรดทราบว่านี่เป็นการแทนที่ที่แนะนำสำหรับActivity.onRetainnonConfigurationInstance ()ในเอกสาร

หากด้วยเหตุผลบางประการที่คุณไม่ต้องการใช้ชิ้นส่วนที่เก็บรักษาไว้มีวิธีอื่น ๆ ที่คุณสามารถทำได้ โปรดทราบว่าแต่ละส่วนมีตัวระบุที่ไม่ซ้ำกันกลับโดยFragment.getId () นอกจากนี้คุณยังจะพบว่าหากส่วนจะถูกดึงลงมาสำหรับการเปลี่ยนแปลงการตั้งค่าผ่านFragment.getActivity (). isChangingConfigurations () ดังนั้นเมื่อถึงจุดที่คุณจะตัดสินใจหยุด AsyncTask (ใน onStop () หรือ onDestroy () เป็นไปได้มากที่สุด) ตัวอย่างเช่นคุณสามารถตรวจสอบได้ว่าการกำหนดค่ามีการเปลี่ยนแปลงหรือไม่และหากเป็นเช่นนั้นให้ติดไว้ใน SparseArray แบบคงที่ภายใต้ตัวระบุของส่วน จากนั้นใน onCreate () หรือ onStart () ของคุณเพื่อดูว่าคุณมี AsyncTask ในอาร์เรย์กระจัดกระจายหรือไม่


โปรดสังเกตว่า setRetainInstance จะเกิดขึ้นต่อเมื่อคุณไม่ได้ใช้แบ็คสแตก
Neil

4
เป็นไปไม่ได้ที่ AsyncTask จะส่งผลลัพธ์กลับมาก่อนที่ onCreateView ของ Fragment ที่เก็บไว้จะทำงาน?
jakk

6
@jakk เมธอดอายุการใช้งานสำหรับ Activitys, Fragments และอื่น ๆ จะถูกเรียกตามลำดับโดยคิวข้อความของเธรด GUI หลักดังนั้นแม้ว่างานจะเสร็จสิ้นพร้อมกันในพื้นหลังก่อนที่วิธีการของวงจรชีวิตเหล่านี้จะเสร็จสมบูรณ์ (หรือแม้กระทั่งเรียกใช้) onPostExecuteวิธีนี้ก็ยังคงต้อง รอก่อนในที่สุดจะถูกประมวลผลโดยคิวข้อความของเธรดหลัก
Alex Lockwood

วิธีนี้ (RetainInstance = true) จะใช้ไม่ได้หากคุณต้องการโหลดไฟล์เลย์เอาต์ที่แตกต่างกันสำหรับแต่ละแนว
จัสติน

การเริ่มต้น asynctask ภายในเมธอด onCreate ของ MainActivity ดูเหมือนจะใช้ได้ผลก็ต่อเมื่อ asynctask - ภายในส่วน "worker" - เริ่มต้นโดยการกระทำของผู้ใช้อย่างชัดเจน เนื่องจากมีเธรดหลักและส่วนติดต่อผู้ใช้ การเริ่มต้น asynctask ทันทีหลังจากเปิดตัวแอปอย่างไรก็ตามโดยไม่มีการดำเนินการของผู้ใช้เช่นการคลิกปุ่มจะให้ข้อยกเว้น ในกรณีนี้สามารถเรียก asynctask ในเมธอด onStart บน MainActivity ไม่ใช่ในเมธอด onCreate
ʕᵔᴥᵔʔ

66

ฉันคิดว่าคุณจะเพลิดเพลินไปกับตัวอย่างการทำงานที่ครอบคลุมและครอบคลุมมากตามรายละเอียดด้านล่าง

  1. การหมุนใช้งานได้และกล่องโต้ตอบยังคงอยู่
  2. คุณสามารถยกเลิกงานและกล่องโต้ตอบได้โดยกดปุ่มย้อนกลับ (หากคุณต้องการลักษณะนี้)
  3. มันใช้เศษ
  4. เค้าโครงของส่วนที่อยู่ใต้กิจกรรมจะเปลี่ยนแปลงอย่างถูกต้องเมื่ออุปกรณ์หมุน
  5. มีการดาวน์โหลดซอร์สโค้ดที่สมบูรณ์และAPK ที่คอมไพล์ไว้ล่วงหน้าเพื่อให้คุณสามารถดูว่าลักษณะการทำงานเป็นสิ่งที่คุณต้องการ

แก้ไข

ตามที่ Brad Larson ร้องขอฉันได้ทำซ้ำโซลูชันที่เชื่อมโยงส่วนใหญ่ด้านล่าง ตั้งแต่ที่ฉันโพสต์ฉันถูกชี้ไปที่AsyncTaskLoaderนอกจากนี้ตั้งแต่ผมโพสต์ไว้ฉันได้รับการชี้ไปที่ฉันไม่แน่ใจว่ามันสามารถใช้ได้กับปัญหาเดียวกันทั้งหมด แต่คุณควรตรวจสอบต่อไป

การใช้ AsyncTaskกับกล่องโต้ตอบความคืบหน้าและการหมุนอุปกรณ์

โซลูชันที่ใช้งานได้!

ในที่สุดฉันก็ได้ทำงานทุกอย่างแล้ว รหัสของฉันมีคุณสมบัติดังต่อไปนี้:

  1. Fragmentที่มีรูปแบบการเปลี่ยนแปลงด้วยการวาง
  2. อัน AsyncTaskซึ่งคุณสามารถทำงานบางอย่าง
  3. DialogFragmentซึ่งแสดงความคืบหน้าของงานในแถบความคืบหน้า (ไม่ใช่แค่ตัวหมุนที่ไม่แน่นอน)
  4. การหมุนทำงานโดยไม่ขัดจังหวะงานหรือปิดกล่องโต้ตอบ
  5. ปุ่มย้อนกลับจะปิดกล่องโต้ตอบและยกเลิกงาน (คุณสามารถปรับเปลี่ยนพฤติกรรมนี้ได้ค่อนข้างง่าย)

ฉันไม่คิดว่าการผสมผสานระหว่างความสามารถในการทำงานจะหาได้จากที่อื่น

แนวคิดพื้นฐานมีดังนี้ มีเป็นMainActivityระดับที่มีส่วนเดียว MainFragment- MainFragmentมีเค้าโครงที่แตกต่างกันสำหรับการวางแนวนอนและแนวตั้งและsetRetainInstance()เป็นเท็จเพื่อให้เค้าโครงสามารถเปลี่ยนแปลงได้ ซึ่งหมายความว่าเมื่อการวางแนวอุปกรณ์ที่มีการเปลี่ยนแปลงทั้งในMainActivityและMainFragmentจะถูกทำลายอย่างสมบูรณ์และสร้าง

เรามีMyTask(ขยายจากAsyncTask) ซึ่งทำงานทั้งหมด เราไม่สามารถเก็บไว้ในMainFragmentเพราะที่จะถูกทำลายและ Google setRetainNonInstanceConfiguration()ได้เลิกใช้อะไรเช่น นั่นไม่สามารถใช้ได้เสมอไปและเป็นการแฮ็กที่น่าเกลียดที่สุด แต่เราจะเก็บไว้MyTaskในส่วนอื่นที่DialogFragmentเรียกว่าTaskFragment. นี้ส่วนจะมีการsetRetainInstance()ตั้งค่าเป็นจริงเพื่อที่จะหมุนอุปกรณ์ชิ้นนี้จะไม่ถูกทำลายและMyTaskถูกเก็บไว้

ในที่สุดเราต้องบอกว่าTaskFragmentใครจะแจ้งให้ทราบเมื่อเสร็จสิ้นและเราจะทำโดยใช้setTargetFragment(<the MainFragment>)เมื่อเราสร้างมันขึ้นมา เมื่ออุปกรณ์ถูกหมุนและMainFragmentถูกทำลายและมีการสร้างอินสแตนซ์ใหม่เราจะใช้FragmentManagerเพื่อค้นหากล่องโต้ตอบ (ตามแท็ก) และทำsetTargetFragment(<the new MainFragment>)และทำ มันสวยมาก

มีอีกสองสิ่งที่ฉันต้องทำ: อันดับแรกยกเลิกงานเมื่อกล่องโต้ตอบถูกปิดและอย่างที่สองตั้งค่าข้อความเลิกจ้างเป็นโมฆะมิฉะนั้นกล่องโต้ตอบจะถูกปิดอย่างแปลกประหลาดเมื่อหมุนอุปกรณ์

รหัส

ฉันจะไม่แสดงเลย์เอาต์พวกเขาค่อนข้างชัดเจนและคุณสามารถค้นหาได้ในการดาวน์โหลดโครงการด้านล่าง

กิจกรรมหลัก

นี่ค่อนข้างตรงไปตรงมา ฉันเพิ่มการโทรกลับเข้าไปในกิจกรรมนี้เพื่อให้มันรู้ว่างานเสร็จเมื่อใด แต่คุณอาจไม่ต้องการสิ่งนั้น โดยหลักแล้วฉันแค่อยากจะแสดงกลไกการเรียกกลับของ Fragment-activity เพราะมันค่อนข้างเรียบร้อยและคุณอาจไม่เคยเห็นมาก่อน

public class MainActivity extends Activity implements MainFragment.Callbacks
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public void onTaskFinished()
    {
        // Hooray. A toast to our success.
        Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
        // NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
        // the duration in milliseconds. ANDROID Y U NO ENUM? 
    }
}

MainFragment

ยาว แต่คุ้ม!

public class MainFragment extends Fragment implements OnClickListener
{
    // This code up to onDetach() is all to get easy callbacks to the Activity. 
    private Callbacks mCallbacks = sDummyCallbacks;

    public interface Callbacks
    {
        public void onTaskFinished();
    }
    private static Callbacks sDummyCallbacks = new Callbacks()
    {
        public void onTaskFinished() { }
    };

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        if (!(activity instanceof Callbacks))
        {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }
        mCallbacks = (Callbacks) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        mCallbacks = sDummyCallbacks;
    }

    // Save a reference to the fragment manager. This is initialised in onCreate().
    private FragmentManager mFM;

    // Code to identify the fragment that is calling onActivityResult(). We don't really need
    // this since we only have one fragment to deal with.
    static final int TASK_FRAGMENT = 0;

    // Tag so we can find the task fragment again, in another instance of this fragment after rotation.
    static final String TASK_FRAGMENT_TAG = "task";

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

        // At this point the fragment may have been recreated due to a rotation,
        // and there may be a TaskFragment lying around. So see if we can find it.
        mFM = getFragmentManager();
        // Check to see if we have retained the worker fragment.
        TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);

        if (taskFragment != null)
        {
            // Update the target fragment so it goes to this fragment instead of the old one.
            // This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
            // keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
            // use weak references. To be sure you aren't leaking, you may wish to make your own
            // setTargetFragment() which does.
            taskFragment.setTargetFragment(this, TASK_FRAGMENT);
        }
    }

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

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);

        // Callback for the "start task" button. I originally used the XML onClick()
        // but it goes to the Activity instead.
        view.findViewById(R.id.taskButton).setOnClickListener(this);
    }

    @Override
    public void onClick(View v)
    {
        // We only have one click listener so we know it is the "Start Task" button.

        // We will create a new TaskFragment.
        TaskFragment taskFragment = new TaskFragment();
        // And create a task for it to monitor. In this implementation the taskFragment
        // executes the task, but you could change it so that it is started here.
        taskFragment.setTask(new MyTask());
        // And tell it to call onActivityResult() on this fragment.
        taskFragment.setTargetFragment(this, TASK_FRAGMENT);

        // Show the fragment.
        // I'm not sure which of the following two lines is best to use but this one works well.
        taskFragment.show(mFM, TASK_FRAGMENT_TAG);
//      mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
        {
            // Inform the activity. 
            mCallbacks.onTaskFinished();
        }
    }

TaskFragment

    // This and the other inner class can be in separate files if you like.
    // There's no reason they need to be inner classes other than keeping everything together.
    public static class TaskFragment extends DialogFragment
    {
        // The task we are running.
        MyTask mTask;
        ProgressBar mProgressBar;

        public void setTask(MyTask task)
        {
            mTask = task;

            // Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment.
            mTask.setFragment(this);
        }

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

            // Retain this instance so it isn't destroyed when MainActivity and
            // MainFragment change configuration.
            setRetainInstance(true);

            // Start the task! You could move this outside this activity if you want.
            if (mTask != null)
                mTask.execute();
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState)
        {
            View view = inflater.inflate(R.layout.fragment_task, container);
            mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar);

            getDialog().setTitle("Progress Dialog");

            // If you're doing a long task, you probably don't want people to cancel
            // it just by tapping the screen!
            getDialog().setCanceledOnTouchOutside(false);

            return view;
        }

        // This is to work around what is apparently a bug. If you don't have it
        // here the dialog will be dismissed on rotation, so tell it not to dismiss.
        @Override
        public void onDestroyView()
        {
            if (getDialog() != null && getRetainInstance())
                getDialog().setDismissMessage(null);
            super.onDestroyView();
        }

        // Also when we are dismissed we need to cancel the task.
        @Override
        public void onDismiss(DialogInterface dialog)
        {
            super.onDismiss(dialog);
            // If true, the thread is interrupted immediately, which may do bad things.
            // If false, it guarantees a result is never returned (onPostExecute() isn't called)
            // but you have to repeatedly call isCancelled() in your doInBackground()
            // function to check if it should exit. For some tasks that might not be feasible.
            if (mTask != null) {
                mTask.cancel(false);
            }

            // You don't really need this if you don't want.
            if (getTargetFragment() != null)
                getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
        }

        @Override
        public void onResume()
        {
            super.onResume();
            // This is a little hacky, but we will see if the task has finished while we weren't
            // in this activity, and then we can dismiss ourselves.
            if (mTask == null)
                dismiss();
        }

        // This is called by the AsyncTask.
        public void updateProgress(int percent)
        {
            mProgressBar.setProgress(percent);
        }

        // This is also called by the AsyncTask.
        public void taskFinished()
        {
            // Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
            // after the user has switched to another app.
            if (isResumed())
                dismiss();

            // If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
            // onResume().
            mTask = null;

            // Tell the fragment that we are done.
            if (getTargetFragment() != null)
                getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null);
        }
    }

MyTask

    // This is a fairly standard AsyncTask that does some dummy work.
    public static class MyTask extends AsyncTask<Void, Void, Void>
    {
        TaskFragment mFragment;
        int mProgress = 0;

        void setFragment(TaskFragment fragment)
        {
            mFragment = fragment;
        }

        @Override
        protected Void doInBackground(Void... params)
        {
            // Do some longish task. This should be a task that we don't really
            // care about continuing
            // if the user exits the app.
            // Examples of these things:
            // * Logging in to an app.
            // * Downloading something for the user to view.
            // * Calculating something for the user to view.
            // Examples of where you should probably use a service instead:
            // * Downloading files for the user to save (like the browser does).
            // * Sending messages to people.
            // * Uploading data to a server.
            for (int i = 0; i < 10; i++)
            {
                // Check if this has been cancelled, e.g. when the dialog is dismissed.
                if (isCancelled())
                    return null;

                SystemClock.sleep(500);
                mProgress = i * 10;
                publishProgress();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Void... unused)
        {
            if (mFragment == null)
                return;
            mFragment.updateProgress(mProgress);
        }

        @Override
        protected void onPostExecute(Void unused)
        {
            if (mFragment == null)
                return;
            mFragment.taskFinished();
        }
    }
}

ดาวน์โหลดโครงการตัวอย่าง

นี่คือรหัสที่มาและเอพีเค ขออภัย ADT ยืนยันที่จะเพิ่มไลบรารีการสนับสนุนก่อนที่จะให้ฉันสร้างโครงการ ฉันแน่ใจว่าคุณสามารถลบออกได้


4
ฉันจะหลีกเลี่ยงการคงแถบความคืบหน้าDialogFragmentไว้เพราะมันมีองค์ประกอบ UI ที่อ้างอิงถึงบริบทเก่า แต่ฉันจะเก็บไว้AsyncTaskในส่วนที่ว่างเปล่าอื่นและตั้งDialogFragmentเป็นเป้าหมาย
SD

การอ้างอิงเหล่านั้นจะไม่ถูกล้างเมื่อหมุนอุปกรณ์และonCreateView()ถูกเรียกอีกครั้งหรือไม่? เก่าmProgressBarจะถูกเขียนทับด้วยใหม่เป็นอย่างน้อย
Timmmm

ไม่ชัดเจน แต่ฉันค่อนข้างแน่ใจเกี่ยวกับเรื่องนี้ คุณสามารถเพิ่มmProgressBar = null;ในonDestroyView()ถ้าคุณต้องการที่จะเพิ่มความแน่ใจ วิธีการของ Singularity อาจเป็นความคิดที่ดี แต่จะเพิ่มความซับซ้อนของโค้ดมากยิ่งขึ้น!
Timmmm

1
การอ้างอิงที่คุณถือไว้ใน asynctask คือส่วน progressdialog ใช่ไหม? ดังนั้น 2 คำถาม: 1- จะเกิดอะไรขึ้นถ้าฉันต้องการแก้ไขส่วนจริงที่เรียกว่า progressdialog; 2- จะเกิดอะไรขึ้นถ้าฉันต้องการส่งพารามิเตอร์ไปยัง asynctask? ขอแสดงความนับถือ
Maxrunner

1
@Maxrunner สำหรับการส่งผ่านพารามิเตอร์สิ่งที่ง่ายที่สุดน่าจะเป็นที่จะย้ายไปmTask.execute() MainFragment.onClick()หรือคุณสามารถอนุญาตให้ส่งผ่านพารามิเตอร์setTask()หรือแม้แต่เก็บไว้ในMyTaskตัวเอง ฉันไม่แน่ใจว่าคำถามแรกของคุณหมายถึงอะไร แต่คุณอาจต้องการใช้TaskFragment.getTargetFragment()? ฉันค่อนข้างมั่นใจว่ามันจะใช้งานได้โดยใช้ไฟล์ViewPager. แต่ViewPagersไม่ค่อยเข้าใจหรือจัดทำเป็นเอกสารขอให้โชคดี! จำส่วนของคุณไม่ได้ถูกสร้างขึ้นจนกว่าจะมองเห็นได้ในครั้งแรก
Timmmm

16

ฉันเพิ่งโพสต์บทความที่อธิบายวิธีจัดการกับการเปลี่ยนแปลงการกำหนดค่าโดยใช้Fragments. ช่วยแก้ปัญหาในการรักษาไฟล์AsyncTaskเปลี่ยนแปลงการหมุนเวียนได้ดี

สินค้า TL; DR คือการใช้โฮสต์ของคุณAsyncTaskภายในFragmentโทรsetRetainInstance(true)ในFragmentและรายงานAsyncTaskของความคืบหน้า / ผลการกลับไปของมันActivity(หรือมันเป้าหมายFragmentถ้าคุณเลือกที่จะใช้วิธีการอธิบายโดย @Timmmm) Fragmentผ่านการเก็บรักษาไว้


5
คุณจะไปเกี่ยวกับ Fragments ที่ซ้อนกันได้อย่างไร? เช่นเดียวกับ AsyncTask ที่เริ่มต้นจาก RetainedFragment ภายใน Fragment (Tab) อื่น
Rekin

หากส่วนที่ถูกเก็บรักษาไว้แล้วทำไมไม่เพียงดำเนินการงาน async จากภายในส่วนที่เก็บไว้นั้น หากยังคงไว้อยู่แล้วภารกิจ async จะสามารถรายงานกลับไปได้แม้ว่าจะมีการเปลี่ยนแปลงการกำหนดค่า
Alex Lockwood

@AlexLockwood ขอบคุณสำหรับบล็อก แทนที่จะต้องจัดการonAttachและonDetachจะดีกว่าไหมถ้าข้างในTaskFragmentเราโทรหาgetActivityทุกครั้งที่ต้องการให้โทรกลับ (โดยตรวจสอบ instaceof TaskCallbacks)
Cheok Yan Cheng

1
คุณสามารถทำได้ไม่ว่าจะด้วยวิธีใดก็ตาม ฉันเพิ่งทำในนั้นonAttach()และonDetach()เพื่อที่ฉันจะได้หลีกเลี่ยงการแคสต์กิจกรรมไปTaskCallbacksเรื่อย ๆ ทุกครั้งที่ฉันต้องการใช้
Alex Lockwood

1
@AlexLockwood หากแอปพลิเคชันของฉันเป็นไปตามกิจกรรมเดียว - การออกแบบชิ้นส่วนหลายชิ้นฉันควรมีส่วนงานแยกต่างหากสำหรับชิ้นส่วน UI แต่ละชิ้นของฉันหรือไม่ ดังนั้นโดยพื้นฐานแล้ววงจรชีวิตของแต่ละส่วนของงานจะถูกจัดการโดยชิ้นส่วนเป้าหมายและจะไม่มีการสื่อสารกับกิจกรรม
Manas Bajaj

13

คำแนะนำแรกของฉันคือหลีกเลี่ยง AsyncTasks ภายในคุณสามารถอ่านคำถามที่ฉันถามเกี่ยวกับเรื่องนี้และคำตอบ: Android: คำแนะนำของ AsyncTask: คลาสส่วนตัวหรือคลาสสาธารณะ?

หลังจากนั้นฉันก็เริ่มใช้ non-inner และ ... ตอนนี้ฉันเห็นประโยชน์มากมาย

อย่างที่สองคือเก็บข้อมูลอ้างอิงของ AsyncTask ที่กำลังทำงานอยู่ในApplicationคลาส - http://developer.android.com/reference/android/app/Application.html

ทุกครั้งที่คุณเริ่ม AsyncTask ให้ตั้งค่าบนแอปพลิเคชันและเมื่อเสร็จสิ้นการตั้งค่าเป็น null

เมื่อแฟรกเมนต์ / กิจกรรมเริ่มต้นขึ้นคุณสามารถตรวจสอบว่า AsyncTask กำลังทำงานอยู่หรือไม่ (โดยตรวจสอบว่าเป็นโมฆะหรือไม่บนแอปพลิเคชัน) จากนั้นตั้งค่าการอ้างอิงภายในเป็นสิ่งที่คุณต้องการ (กิจกรรมแฟรกเมนต์ ฯลฯ เพื่อให้คุณสามารถโทรกลับได้)

วิธีนี้จะช่วยแก้ปัญหาของคุณ: หากคุณมี AsyncTask เพียง 1 รายการที่ทำงานในเวลาที่กำหนดคุณสามารถเพิ่มข้อมูลอ้างอิงง่ายๆ:

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

อื่น ๆ มีใน Aplication a HashMap ที่มีการอ้างอิงถึงพวกเขา

กล่องโต้ตอบความคืบหน้าสามารถเป็นไปตามหลักการเดียวกัน


2
ฉันเห็นด้วยตราบใดที่คุณผูกวงจรชีวิตของ AsyncTask กับ Parent (โดยกำหนด AsyncTask เป็นคลาสภายในของ Activity / Fragment) มันค่อนข้างยากที่จะทำให้ AsyncTask ของคุณหลุดพ้นจากวงจรชีวิตของผู้ปกครองอย่างไรก็ตามฉันไม่ชอบของคุณ วิธีแก้ปัญหามันดูแฮ็คมาก
ยอร์กว

คำถามคือ .. คุณมีทางออกที่ดีกว่านี้หรือไม่?
neteinstein

1
ฉันจะต้องเห็นด้วยกับ @yorkw ที่นี่โซลูชันนี้นำเสนอให้ฉันเมื่อไม่นานมานี้เมื่อฉันจัดการกับปัญหานี้โดยไม่ใช้ส่วนย่อย (แอปที่ใช้กิจกรรม) คำถามนี้: stackoverflow.com/questions/2620917/…มีคำตอบเหมือนกันและฉันเห็นด้วยกับหนึ่งในความคิดเห็นที่ระบุว่า "อินสแตนซ์แอปพลิเคชันมีวงจรชีวิตของตัวเอง - OS ก็สามารถฆ่าได้เช่นกันดังนั้นวิธีนี้อาจทำให้เกิด ข้อผิดพลาดที่ยากต่อการทำซ้ำ "
blindstuff

1
ฉันยังไม่เห็นวิธีอื่นที่ "แฮ็ก" น้อยกว่าอย่างที่ @yorkw กล่าว ฉันใช้มันในหลาย ๆ แอพและด้วยความใส่ใจกับปัญหาที่อาจเกิดขึ้นทั้งหมดนี้ใช้งานได้ดี
neteinstein

บางทีโซลูชัน @hackbod จะช่วยคุณได้มากขึ้น
neteinstein

4

ฉันคิดวิธีการใช้ AsyncTaskLoaders สำหรับสิ่งนี้ ใช้งานง่ายและต้องการ IMO เหนือศีรษะน้อยกว่า ..

โดยพื้นฐานแล้วคุณสร้าง AsyncTaskLoader ดังนี้:

public class MyAsyncTaskLoader extends AsyncTaskLoader {
    Result mResult;
    public HttpAsyncTaskLoader(Context context) {
        super(context);
    }

    protected void onStartLoading() {
        super.onStartLoading();
        if (mResult != null) {
            deliverResult(mResult);
        }
        if (takeContentChanged() ||  mResult == null) {
            forceLoad();
        }
    }

    @Override
    public Result loadInBackground() {
        SystemClock.sleep(500);
        mResult = new Result();
        return mResult;
    }
}

จากนั้นในกิจกรรมของคุณที่ใช้ AsyncTaskLoader ด้านบนเมื่อคลิกปุ่ม:

public class MyActivityWithBackgroundWork extends FragmentActivity implements LoaderManager.LoaderCallbacks<Result> {

    private String username,password;       
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mylayout);
        //this is only used to reconnect to the loader if it already started
        //before the orientation changed
        Loader loader = getSupportLoaderManager().getLoader(0);
        if (loader != null) {
            getSupportLoaderManager().initLoader(0, null, this);
        }
    }

    public void doBackgroundWorkOnClick(View button) {
        //might want to disable the button while you are doing work
        //to prevent user from pressing it again.

        //Call resetLoader because calling initLoader will return
        //the previous result if there was one and we may want to do new work
        //each time
        getSupportLoaderManager().resetLoader(0, null, this);
    }   


    @Override
    public Loader<Result> onCreateLoader(int i, Bundle bundle) {
        //might want to start a progress bar
        return new MyAsyncTaskLoader(this);
    }


    @Override
    public void onLoadFinished(Loader<LoginResponse> loginLoader,
                               LoginResponse loginResponse)
    {
        //handle result
    }

    @Override
    public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader)
    {
        //remove references to previous loader resources

    }
}

ดูเหมือนว่าจะจัดการกับการเปลี่ยนแปลงการวางแนวได้ดีและงานเบื้องหลังของคุณจะดำเนินต่อไปในระหว่างการหมุน

สิ่งที่ควรทราบ:

  1. หากใน onCreate คุณแนบอีกครั้งกับ asynctaskloader คุณจะถูกเรียกกลับใน onLoadFinished () พร้อมกับผลลัพธ์ก่อนหน้า (แม้ว่าคุณจะได้รับแจ้งว่าคำขอเสร็จสมบูรณ์แล้วก็ตาม) นี่เป็นพฤติกรรมที่ดีเกือบตลอดเวลา แต่บางครั้งก็อาจเป็นเรื่องยุ่งยาก ในขณะที่ฉันคิดว่ามีหลายวิธีในการจัดการกับสิ่งที่ฉันทำคือฉันเรียกว่า loader.abandon () ใน onLoadFinished จากนั้นฉันเพิ่มการเช็คอิน onCreate เพื่อแนบกลับไปที่ตัวโหลดเท่านั้นหากยังไม่ได้ละทิ้ง หากคุณต้องการข้อมูลที่เป็นผลลัพธ์อีกครั้งคุณจะไม่ต้องการทำเช่นนั้น ในกรณีส่วนใหญ่คุณต้องการข้อมูล

ฉันมีรายละเอียดเพิ่มเติมเกี่ยวกับการใช้สิ่งนี้สำหรับการโทร http ที่นี่


แน่ใจว่าgetSupportLoaderManager().getLoader(0);จะไม่คืนค่า null (เนื่องจากตัวโหลดที่มี id นั้น 0 ยังไม่มีอยู่)?
EmmanuelMess

1
ใช่มันจะเป็นโมฆะเว้นแต่ว่าการเปลี่ยนแปลงการกำหนดค่าทำให้กิจกรรมเริ่มต้นใหม่ในขณะที่ตัวโหลดกำลังดำเนินการอยู่ .. นั่นคือเหตุผลที่ฉันตรวจสอบค่าว่าง
Matt Wolfe

3

ฉันสร้างไลบรารีงานพื้นหลังโอเพ่นซอร์สขนาดเล็กมากซึ่งใช้ Marshmallow เป็นหลักAsyncTaskแต่มีฟังก์ชันเพิ่มเติมเช่น:

  1. รักษางานโดยอัตโนมัติในการเปลี่ยนแปลงการกำหนดค่า
  2. การโทรกลับ UI (ผู้ฟัง);
  3. ไม่รีสตาร์ทหรือยกเลิกงานเมื่ออุปกรณ์หมุน (เช่นเดียวกับ Loaders จะทำ)

ไลบรารีใช้ภายในFragmentโดยไม่มีอินเทอร์เฟซผู้ใช้ซึ่งจะยังคงอยู่ตลอดการเปลี่ยนแปลงคอนฟิกูเรชัน (setRetainInstance(true) )

คุณสามารถค้นหาได้ใน GitHub: 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();
    }
}

1

แนวทางของฉันคือใช้รูปแบบการออกแบบการมอบหมายโดยทั่วไปเราสามารถแยกตรรกะทางธุรกิจจริง (อ่านข้อมูลจากอินเทอร์เน็ตหรือฐานข้อมูลหรืออะไรก็ได้) จาก AsyncTask (ผู้รับมอบสิทธิ์) ไปยัง BusinessDAO (ผู้รับมอบสิทธิ์) ในวิธี AysncTask.doInBackground () ของคุณ มอบหมายงานจริงให้กับ BusinessDAO จากนั้นใช้กลไกกระบวนการเดี่ยวใน BusinessDAO ดังนั้นการเรียก BusinessDAO.doSomething () หลายครั้งจะทริกเกอร์งานจริงหนึ่งงานที่ทำงานในแต่ละครั้งและรอผลของงาน แนวคิดนี้จะเก็บผู้ร่วมประชุมไว้ (เช่น BusinessDAO) ในระหว่างการเปลี่ยนแปลงการกำหนดค่าแทนที่จะเป็นผู้รับมอบสิทธิ์ (เช่น AsyncTask)

  1. สร้าง / ใช้งานแอปพลิเคชันของเราเองโดยมีวัตถุประสงค์เพื่อสร้าง / เริ่มต้น BusinessDAO ที่นี่เพื่อให้วงจรชีวิตของ BusinessDAO ของเรามีขอบเขตการใช้งานไม่ใช่ขอบเขตของกิจกรรมโปรดทราบว่าคุณต้องเปลี่ยน AndroidManifest.xml เพื่อใช้ MyApplication:

    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. กิจกรรม / Fragement ที่มีอยู่ของเราส่วนใหญ่ไม่มีการเปลี่ยนแปลงยังคงใช้ AsyncTask เป็นคลาสภายในและเกี่ยวข้องกับ AsyncTask.execute () จาก Activity / Fragement ความแตกต่างในขณะนี้คือ AsyncTask จะมอบหมายงานจริงให้กับ BusinessDAO ดังนั้นในระหว่างการเปลี่ยนแปลงการกำหนดค่า 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% ว่าจะได้ผลหรือไม่นอกจากนี้ควรพิจารณาข้อมูลโค้ดตัวอย่างเป็นรหัสเทียม ฉันแค่พยายามบอกเบาะแสบางอย่างจากระดับการออกแบบ ข้อเสนอแนะหรือข้อเสนอแนะใด ๆ ยินดีต้อนรับและชื่นชม


ดูเหมือนทางออกที่ดีมาก เมื่อคุณตอบคำถามนี้เมื่อ 2 ปีครึ่งที่แล้วคุณได้ทำการทดสอบหรือไม่! คุณกำลังบอกว่าฉันไม่แน่ใจว่ามันใช้งานได้สิ่งนั้นก็ไม่ใช่ฉัน !! ฉันกำลังล็อกหาวิธีแก้ปัญหาที่ผ่านการทดสอบมาเป็นอย่างดีสำหรับปัญหานี้ คุณมีข้อเสนอแนะใด?
Alireza A. Ahmadi

1

คุณสามารถทำให้ AsyncTask เป็นฟิลด์แบบคงที่ หากคุณต้องการบริบทคุณควรจัดส่งบริบทแอปพลิเคชันของคุณ วิธีนี้จะหลีกเลี่ยงการรั่วไหลของหน่วยความจำมิฉะนั้นคุณจะต้องอ้างอิงถึงกิจกรรมทั้งหมดของคุณ


1

หากใครพบวิธีการของพวกเขาในเธรดนี้ฉันพบวิธีที่สะอาดคือการเรียกใช้งาน Async จาก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.*ไลบรารีเนื่องจาก (ในขณะที่เขียน) พวกเขาทราบปัญหาเกี่ยวกับsetRetainInstanceวิธีการและดูการเพจ นอกจากนี้การไม่รักษาอินสแตนซ์ไว้คุณสามารถสร้างกิจกรรมของคุณใหม่โดยใช้ชุดทรัพยากรอื่น (เช่นเค้าโครงมุมมองที่แตกต่างกันสำหรับการวางแนวใหม่)


การเรียกใช้บริการเพียงเพื่อรักษา AsyncTask ไม่ได้ใช้งานมากเกินไปใช่หรือไม่? บริการทำงานในกระบวนการของตนเองและไม่ได้มาโดยไม่มีค่าใช้จ่ายเพิ่มเติม
WeNeigh

Vinay ที่น่าสนใจ ไม่ได้สังเกตว่าแอปมีทรัพยากรมากขึ้น (ในขณะนี้มีน้ำหนักเบาอยู่แล้ว) เจออะไรมาบ้าง? ฉันมองว่าบริการเป็นสภาพแวดล้อมที่คาดเดาได้เพื่อให้ระบบทำงานได้โดยมีการยกของหนักหรือ I / O โดยไม่คำนึงถึงสถานะ UI การสื่อสารกับบริการเพื่อดูว่าเมื่อใดที่ดูเหมือนว่า 'ถูกต้อง' บริการที่ฉันเริ่มดำเนินการหยุดงานเล็กน้อยเมื่องานเสร็จสิ้นดังนั้นโดยทั่วไปจะอยู่รอดประมาณ 10-30 วินาที
BrantApps

คำตอบของ Commonsware ที่นี่ดูเหมือนจะแนะนำบริการเป็นความคิดที่ไม่ดี ตอนนี้ฉันกำลังพิจารณา AsyncTaskLoaders แต่ดูเหมือนว่าจะมาพร้อมกับปัญหาของตัวเอง (ความยืดหยุ่นเฉพาะสำหรับการโหลดข้อมูล ฯลฯ )
WeNeigh

1
ฉันเห็น. สังเกตเห็นได้ชัดว่าบริการนี้ที่คุณเชื่อมโยงได้รับการตั้งค่าอย่างชัดเจนเพื่อให้ทำงานในกระบวนการของตนเอง ต้นแบบดูเหมือนจะไม่ชอบรูปแบบนี้ที่ใช้บ่อย ฉันไม่ได้ระบุคุณสมบัติ "ดำเนินการในกระบวนการใหม่ทุกครั้ง" อย่างชัดเจนดังนั้นหวังว่าฉันจะได้รับการป้องกันจากคำวิจารณ์ส่วนนั้น ฉันจะพยายามหาปริมาณผลกระทบ แน่นอนว่าบริการตามแนวคิดไม่ใช่ 'ความคิดที่ไม่ดี' และเป็นพื้นฐานสำหรับทุกแอปที่ทำอะไรที่น่าสนใจจากระยะไกลโดยไม่ต้องเล่นสำนวน JDoc ของพวกเขาให้คำแนะนำเพิ่มเติมเกี่ยวกับการใช้งานหากคุณยังไม่แน่ใจ
BrantApps

0

ฉันเขียนโค้ด samepl เพื่อแก้ปัญหานี้

ขั้นแรกคือสร้างคลาส Application:

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);
}
}

ใน 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">

รหัสในกิจกรรม:

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;
    }
}

}

เมื่อการวางแนวกิจกรรมเปลี่ยนตัวแปร mTask ถูก inited จากบริบทของแอป เมื่องานเสร็จสิ้นตัวแปรถูกตั้งค่าเป็น null และลบออกจากหน่วยความจำ

สำหรับฉันมันเพียงพอแล้ว


0

ดูตัวอย่างด้านล่างวิธีใช้ส่วนที่เก็บไว้เพื่อเก็บงานพื้นหลัง:

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;
            }
        }

    }
}

-1

ลองดูที่นี่ที่นี่

มีวิธีแก้ปัญหาตามTimmmmวิธีแก้ปัญหา

แต่ฉันปรับปรุงแล้ว:

  • ตอนนี้วิธีแก้ปัญหาสามารถขยายได้ - คุณต้องขยายเท่านั้น FragmentAbleToStartTask

  • คุณสามารถทำงานหลายอย่างได้ในเวลาเดียวกัน

    และในความคิดของฉันมันง่ายเหมือน startActivityForResult และรับผลลัพธ์

  • คุณยังสามารถหยุดงานที่กำลังทำงานอยู่และตรวจสอบว่างานนั้น ๆ กำลังทำงานอยู่หรือไม่

ขออภัยสำหรับภาษาอังกฤษของฉัน


ลิงค์แรกเสีย
Tejzeratul
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.