Phân mảnh Android. Giữ lại một AsyncTask trong khi xoay màn hình hoặc thay đổi cấu hình


86

Tôi đang làm việc trên ứng dụng Điện thoại thông minh / Máy tính bảng, chỉ sử dụng một APK và tải tài nguyên khi cần thiết tùy thuộc vào kích thước màn hình, lựa chọn thiết kế tốt nhất dường như là sử dụng Fragment qua ACL.

Ứng dụng này đã hoạt động tốt cho đến bây giờ chỉ dựa trên hoạt động. Đây là một lớp mô phỏng về cách tôi xử lý AsyncTasks và ProgressDialogs trong Hoạt động để chúng hoạt động ngay cả khi màn hình được xoay hoặc thay đổi cấu hình xảy ra giữa giao tiếp.

Tôi sẽ không thay đổi tệp kê khai để tránh tái tạo Hoạt động, có nhiều lý do khiến tôi không muốn làm điều đó, nhưng chủ yếu là do các tài liệu chính thức nói rằng nó không được khuyến khích và tôi đã quản lý mà không có nó cho đến nay, vì vậy vui lòng không khuyến nghị điều đó lộ trình.

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

Mã này đang hoạt động tốt, tôi có khoảng 10.000 người dùng mà không có khiếu nại, vì vậy có vẻ hợp lý khi chỉ sao chép logic này vào Thiết kế dựa trên phân mảnh mới, nhưng tất nhiên, nó không hoạt động.

Đây là 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
                }
            }
        }
    }
}

Tôi không thể sử dụng onRetainNonConfigurationInstance()vì nó phải được gọi từ Activity chứ không phải Fragment, điều tương tự cũng xảy ra getLastNonConfigurationInstance(). Tôi đã đọc một số câu hỏi tương tự ở đây mà không có câu trả lời.

Tôi hiểu rằng nó có thể yêu cầu một số hoạt động xung quanh để sắp xếp thứ này một cách hợp lý trong các phân đoạn, điều đó đang nói, tôi muốn duy trì cùng một logic thiết kế cơ bản.

Đâu sẽ là cách thích hợp để giữ lại AsyncTask trong quá trình thay đổi cấu hình và nếu nó vẫn chạy, hãy hiển thị một processDialog, lưu ý rằng AsyncTask là một lớp bên trong của Fragment và chính Fragment sẽ gọi AsyncTask.execute ()?


1
Có thể luồng về cách xử lý thay đổi cấu hình với AsyncTask có thể giúp ích
rds 19/12/11

AsyncTask liên kết với một ứng dụng cuộc sống cycle..thus có thể tiếp tục khi hoạt động tái tạo
Fred Grott

Kiểm tra bài viết của tôi về chủ đề này: Xử lý Configuration Changes với Fragments
Alex Lockwood

Câu trả lời:


75

Các mảnh vỡ thực sự có thể làm cho việc này dễ dàng hơn rất nhiều. Chỉ cần sử dụng phương thức Fragment.setRetainInstance (boolean) để giữ lại cá thể phân mảnh của bạn qua các lần thay đổi cấu hình. Lưu ý rằng đây là sự thay thế được khuyến nghị cho Activity.onRetainnonConfigurationInstance () trong tài liệu.

Nếu vì lý do nào đó mà bạn thực sự không muốn sử dụng một đoạn được giữ lại, bạn có thể thực hiện các cách tiếp cận khác. Lưu ý rằng mỗi đoạn có một mã định danh duy nhất được trả về bởi Fragment.getId () . Bạn cũng có thể tìm hiểu xem một phân đoạn có bị chia nhỏ để thay đổi cấu hình hay không thông qua Fragment.getActivity (). IsChangedConfigurations () . Vì vậy, tại thời điểm bạn quyết định dừng AsyncTask của mình (rất có thể là onStop () hoặc onDestroy ()), ví dụ: bạn có thể kiểm tra xem cấu hình có đang thay đổi hay không và nếu có thì dán nó vào SparseArray tĩnh bên dưới mã định danh của phân mảnh, và sau đó trong onCreate () hoặc onStart () của bạn, hãy xem liệu bạn có AsyncTask trong mảng thưa thớt có sẵn hay không.


Lưu ý rằng setRetainInstance chỉ có nếu bạn không sử dụng ngăn xếp trở lại.
Neil

4
Không có khả năng AsyncTask gửi lại kết quả của nó trước khi onCreateView của Fragment được giữ lại sẽ chạy?
jakk

6
@jakk Các phương thức vòng đời cho Hoạt động, Phân đoạn, v.v. được gọi tuần tự bởi hàng đợi thông báo của luồng GUI chính, vì vậy, ngay cả khi tác vụ hoàn thành đồng thời trong nền trước khi các phương thức vòng đời này hoàn thành (hoặc thậm chí được gọi), onPostExecutephương thức vẫn cần đợi trước khi cuối cùng được xử lý bởi hàng đợi tin nhắn của luồng chính.
Alex Lockwood

Cách tiếp cận này (RetainInstance = true) sẽ không hoạt động nếu bạn muốn tải các tệp bố cục khác nhau cho từng hướng.
Justin

Việc khởi động asynctask trong phương thức onCreate của MainActivity dường như chỉ hoạt động nếu asynctask - bên trong đoạn "worker" - được khởi động bằng hành động rõ ràng của người dùng. Bởi vì luồng chính và giao diện người dùng có sẵn. Tuy nhiên, khởi động asynctask ngay sau khi khởi chạy ứng dụng - không có hành động của người dùng như nhấp vào nút - sẽ đưa ra một ngoại lệ. Trong trường hợp đó, asynctask có thể được gọi trong phương thức onStart trên MainActivity, không phải trong phương thức onCreate.
ʕ ᵔᴥᵔ ʔ

66

Tôi nghĩ rằng bạn sẽ thích ví dụ cực kỳ toàn diện và làm việc của tôi được trình bày chi tiết dưới đây.

  1. Xoay hoạt động và hộp thoại vẫn tồn tại.
  2. Bạn có thể hủy tác vụ và hộp thoại bằng cách nhấn nút quay lại (nếu bạn muốn hành vi này).
  3. Nó sử dụng các mảnh vỡ.
  4. Bố cục của phân mảnh bên dưới hoạt động sẽ thay đổi đúng cách khi thiết bị xoay.
  5. Có một bản tải xuống mã nguồn hoàn chỉnh và một APK được biên dịch trước để bạn có thể xem hành vi có đúng như những gì bạn muốn hay không.

Biên tập

Theo yêu cầu của Brad Larson, tôi đã sao chép hầu hết các giải pháp được liên kết bên dưới. Ngoài ra kể từ khi tôi đăng nó, tôi đã được chỉ đến AsyncTaskLoader. Tôi không chắc nó hoàn toàn có thể áp dụng cho những vấn đề tương tự, nhưng dù sao thì bạn cũng nên kiểm tra nó.

Sử dụng AsyncTaskvới hộp thoại tiến trình và xoay thiết bị.

Một giải pháp hiệu quả!

Cuối cùng tôi đã có mọi thứ để làm việc. Mã của tôi có các tính năng sau:

  1. A Fragmentcó bố cục thay đổi theo hướng.
  2. Một AsyncTasktrong đó bạn có thể làm một số công việc.
  3. A DialogFragmenthiển thị tiến trình của nhiệm vụ trong thanh tiến trình (không chỉ là một con quay không xác định).
  4. Xoay hoạt động mà không làm gián đoạn tác vụ hoặc loại bỏ hộp thoại.
  5. Nút quay lại loại bỏ hộp thoại và hủy tác vụ (mặc dù vậy bạn có thể thay đổi hành vi này khá dễ dàng).

Tôi không nghĩ rằng sự kết hợp của tính làm việc có thể được tìm thấy ở bất kỳ nơi nào khác.

Ý tưởng cơ bản là như sau. Có một MainActivitylớp chứa một đoạn duy nhất - MainFragment. MainFragmentcó các bố cục khác nhau cho hướng ngang và dọc và setRetainInstance()sai để bố cục có thể thay đổi. Điều này có nghĩa là khi hướng thiết bị bị thay đổi, cả hai MainActivityMainFragmenthoàn toàn bị phá hủy và tái tạo.

Riêng chúng tôi có MyTask(mở rộng từ AsyncTask) thực hiện tất cả các công việc. Chúng tôi không thể lưu trữ nó MainFragmentvì nó sẽ bị phá hủy và Google đã không dùng nữa setRetainNonInstanceConfiguration(). Điều đó không phải lúc nào cũng có sẵn và tốt nhất là một vụ hack xấu xí. Thay vào đó, chúng tôi sẽ lưu trữ MyTasktrong một phân mảnh khác, một DialogFragmentđược gọi TaskFragment. Đây fragment sẽ đã setRetainInstance()thiết lập là true, vì vậy như là thiết bị quay đoạn này không bị phá hủy, và MyTaskđược giữ lại.

Cuối cùng, chúng ta cần phải cho TaskFragmentai biết để thông báo khi nó kết thúc và chúng ta làm điều đó bằng cách sử dụng setTargetFragment(<the MainFragment>)khi chúng ta tạo nó. Khi thiết bị được xoay và thiết bị bị MainFragmentphá hủy và một phiên bản mới được tạo, chúng tôi sử dụng FragmentManagerđể tìm hộp thoại (dựa trên thẻ của nó) và thực hiện setTargetFragment(<the new MainFragment>). Nó khá là nhiều.

Có hai việc khác tôi cần làm: đầu tiên hủy tác vụ khi hộp thoại bị loại bỏ và thứ hai đặt thông báo loại bỏ thành null, nếu không hộp thoại sẽ bị loại bỏ một cách kỳ lạ khi thiết bị được xoay.

Mật mã

Tôi sẽ không liệt kê các bố cục, chúng khá rõ ràng và bạn có thể tìm thấy chúng trong phần tải xuống dự án bên dưới.

Hoạt động chủ yêu

Việc này thật thẳng thắn. Tôi đã thêm một lệnh gọi lại vào hoạt động này để nó biết khi nào nhiệm vụ kết thúc, nhưng bạn có thể không cần điều đó. Chủ yếu tôi chỉ muốn hiển thị cơ chế gọi lại hoạt động phân mảnh vì nó khá gọn gàng và bạn có thể chưa từng thấy nó trước đây.

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

Nó dài nhưng đáng giá!

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

Việc của tôi

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

Tải xuống dự án mẫu

Đây là mã nguồnAPK . Xin lỗi, ADT nhất quyết phải thêm thư viện hỗ trợ trước khi cho phép tôi thực hiện một dự án. Tôi chắc rằng bạn có thể xóa nó.


4
Tôi sẽ tránh giữ lại thanh tiến trình DialogFragment, bởi vì nó có các phần tử giao diện người dùng chứa các tham chiếu đến ngữ cảnh cũ. Thay vào đó, tôi sẽ lưu trữ AsyncTasktrong một phân đoạn trống khác và đặt DialogFragmentlàm mục tiêu của nó.
SD

Các tham chiếu đó sẽ bị xóa khi thiết bị được xoay và onCreateView()được gọi lại? Cái cũ mProgressBarít nhất sẽ bị ghi đè bằng cái mới.
Timmmm

Không rõ ràng, nhưng tôi khá chắc chắn về điều đó. Bạn có thể thêm mProgressBar = null;vào onDestroyView()nếu bạn muốn chắc chắn hơn. Phương pháp của Singularity có thể là một ý tưởng hay, nhưng nó sẽ làm tăng độ phức tạp của mã hơn nữa!
Timmmm 5/12/12

1
Tham chiếu mà bạn giữ trên asynctask là đoạn processdialog, phải không? vì vậy 2 câu hỏi: 1- điều gì sẽ xảy ra nếu tôi muốn thay đổi đoạn thực gọi là processdialog; 2- điều gì sẽ xảy ra nếu tôi muốn chuyển các thông số cho asynctask? Trân trọng,
Maxrunner

1
@Maxrunner, Để chuyển các tham số, điều dễ dàng nhất có lẽ là chuyển mTask.execute()đến MainFragment.onClick(). Ngoài ra, bạn có thể cho phép các tham số được truyền vào setTask()hoặc thậm chí lưu trữ chúng trong MyTaskchính nó. Tôi không chắc chính xác câu hỏi đầu tiên của bạn có nghĩa là gì, nhưng có thể bạn muốn sử dụng TaskFragment.getTargetFragment()? Tôi khá chắc chắn rằng nó sẽ hoạt động bằng cách sử dụng a ViewPager. Nhưng ViewPagerskhông được hiểu rõ hoặc được ghi chép lại, chúc bạn may mắn! Hãy nhớ rằng phân đoạn của bạn không được tạo cho đến khi nó được hiển thị lần đầu tiên.
Timmmm 06/12/12

16

Gần đây tôi đã đăng một bài viết mô tả cách xử lý các thay đổi cấu hình bằng cách sử dụng Fragmentcác s được giữ lại . Nó giải quyết vấn đề duy trì sự AsyncTaskthay đổi trên một vòng quay một cách độc đáo.

TL; DR là chủ sử dụng của bạn AsyncTaskbên trong một Fragment, cuộc gọi setRetainInstance(true)trên Fragment, và báo cáo AsyncTaskcủa tiến độ / kết quả lại cho nó Activity(hoặc nó mục tiêu Fragment, nếu bạn chọn để sử dụng phương pháp mô tả bởi @Timmmm) thông qua việc giữ lại Fragment.


5
Bạn sẽ làm thế nào về các Phân mảnh lồng nhau? Giống như một AsyncTask bắt đầu từ một RetainedFragment bên trong một Fragment (Tab) khác.
Rekin

Nếu phân đoạn đã được giữ lại, thì tại sao không chỉ thực hiện tác vụ không đồng bộ từ bên trong phân đoạn được giữ lại đó? Nếu nó đã được giữ lại thì tác vụ không đồng bộ sẽ có thể báo cáo lại nó ngay cả khi xảy ra thay đổi cấu hình.
Alex Lockwood,

@AlexLockwood Cảm ơn blog. Thay vì phải xử lý onAttachonDetach, sẽ tốt hơn nếu bên trong TaskFragment, chúng ta chỉ gọi getActivitybất cứ khi nào chúng ta cần gọi lại. (Bằng cách kiểm tra instaceof TaskCallbacks)
Cheok Yan Cheng

1
Bạn có thể làm điều đó theo một trong hai cách. Tôi chỉ làm điều đó trong onAttach()onDetach()vì vậy tôi có thể tránh liên tục truyền hoạt động cho TaskCallbacksmỗi lần tôi muốn sử dụng nó.
Alex Lockwood

1
@AlexLockwood Nếu ứng dụng của tôi đang tuân theo một hoạt động đơn lẻ - thiết kế nhiều phân đoạn, tôi có nên có một phân đoạn tác vụ riêng cho từng phân đoạn giao diện người dùng của mình không? Vì vậy, về cơ bản vòng đời của mỗi phân đoạn nhiệm vụ sẽ được quản lý bởi phân đoạn mục tiêu của nó và sẽ không có giao tiếp với hoạt động.
Manas Bajaj

13

Đề xuất đầu tiên của tôi là tránh các AsyncTasks bên trong , bạn có thể đọc một câu hỏi mà tôi đã hỏi về điều này và câu trả lời: Android: Đề xuất AsyncTask: lớp riêng hay lớp chung?

Sau đó, tôi bắt đầu sử dụng không bên trong và ... bây giờ tôi thấy RẤT NHIỀU lợi ích.

Thứ hai là, giữ một tham chiếu về AsyncTask đang chạy của bạn trong ApplicationLớp - http://developer.android.com/reference/android/app/Application.html

Mỗi khi bạn khởi động AsyncTask, hãy đặt nó trên Ứng dụng và khi nó hoàn tất, hãy đặt nó thành null.

Khi một phân đoạn / hoạt động bắt đầu, bạn có thể kiểm tra xem có AsyncTask nào đang chạy hay không (bằng cách kiểm tra xem nó có rỗng hay không trên Ứng dụng) và sau đó đặt tham chiếu bên trong thành bất cứ thứ gì bạn muốn (hoạt động, phân đoạn, v.v. để bạn có thể thực hiện gọi lại).

Điều này sẽ giải quyết vấn đề của bạn: Nếu bạn chỉ có 1 AsyncTask chạy vào bất kỳ thời điểm xác định nào, bạn có thể thêm một tham chiếu đơn giản:

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

Khác, có trong Aplication một HashMap với các tham chiếu đến chúng.

Hộp thoại tiến trình có thể tuân theo cùng một nguyên tắc.


2
Tôi đã đồng ý miễn là bạn ràng buộc vòng đời của AsyncTask với Lớp cha của nó (bằng cách xác định AsyncTask là một lớp bên trong của Activity / Fragment), sẽ khá khó để làm cho AsyncTask của bạn thoát khỏi việc tái tạo vòng đời của cha nó, tuy nhiên, tôi không thích giải pháp, nó trông rất khó hiểu.
yorkw

Câu hỏi là .. bạn có giải pháp tốt hơn không?
neteinstein

1
Tôi sẽ phải đồng ý với @yorkw ở đây, giải pháp này đã được trình bày cho tôi cách đây một lúc khi tôi giải quyết vấn đề này mà không sử dụng các mảnh (ứng dụng dựa trên Hoạt động). Câu hỏi này: stackoverflow.com/questions/2620917/… có cùng một câu trả lời và tôi đồng ý với một trong những nhận xét cho biết " Phiên bản ứng dụng có vòng đời riêng - nó cũng có thể bị giết bởi hệ điều hành, vì vậy giải pháp này có thể gây ra lỗi khó sinh sản "
Blindstuff

1
Tôi vẫn không thấy có cách nào khác ít "hacky" hơn như @yorkw đã nói. Tôi đang sử dụng nó trong một số ứng dụng và với một số chú ý đến các vấn đề có thể xảy ra, tất cả đều hoạt động tốt.
neteinstein

Có thể giải pháp @hackbod phù hợp với bạn hơn.
neteinstein 19/12/11

4

Tôi đã nghĩ ra một phương pháp sử dụng AsyncTaskLoaders cho việc này. Nó khá dễ sử dụng và ít yêu cầu IMO hơn ..

Về cơ bản, bạn tạo một AsyncTaskLoader như thế này:

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

Sau đó, trong hoạt động của bạn, sử dụng AsyncTaskLoader ở trên khi một nút được nhấp:

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

    }
}

Điều này dường như xử lý tốt các thay đổi hướng và tác vụ nền của bạn sẽ tiếp tục trong quá trình xoay.

Một số điều cần lưu ý:

  1. Nếu trong onCreate bạn gắn lại với asynctaskloader, bạn sẽ được gọi lại trong onLoadFinishing () với kết quả trước đó (ngay cả khi bạn đã được thông báo rằng yêu cầu đã hoàn tất). Đây thực sự là hành vi tốt hầu hết thời gian nhưng đôi khi nó có thể khó xử lý. Trong khi tôi tưởng tượng có rất nhiều cách để xử lý điều này, những gì tôi đã làm được gọi là loader.abandon () trong onLoadFinishing. Sau đó, tôi đã thêm kiểm tra onCreate để chỉ gắn lại vào trình tải nếu nó chưa bị bỏ rơi. Nếu bạn cần dữ liệu kết quả một lần nữa, bạn sẽ không muốn làm điều đó. Trong hầu hết các trường hợp bạn muốn dữ liệu.

Tôi có thêm chi tiết về cách sử dụng cái này cho các cuộc gọi http tại đây


Chắc chắn điều đó getSupportLoaderManager().getLoader(0);sẽ không trả về null (vì trình tải với id đó, 0, chưa tồn tại)?
EmmanuelMess

1
Vâng, nó sẽ là null trừ khi thay đổi cấu hình khiến hoạt động khởi động lại trong khi trình tải đang diễn ra .. Đó là lý do tại sao tôi đã kiểm tra null.
Matt Wolfe,

3

Tôi đã tạo một thư viện nhiệm vụ nền mã nguồn mở rất nhỏ dựa trên Marshmallow AsyncTasknhưng có thêm chức năng như:

  1. Tự động giữ lại các tác vụ khi thay đổi cấu hình;
  2. Gọi lại giao diện người dùng (người nghe);
  3. Không khởi động lại hoặc hủy tác vụ khi thiết bị xoay (như Loaders sẽ làm);

Thư viện sử dụng nội bộ Fragmentmà không có bất kỳ giao diện người dùng nào, giao diện này được giữ lại qua các lần thay đổi cấu hình ( setRetainInstance(true)).

Bạn có thể tìm thấy nó trên GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks

Ví dụ cơ bản nhất (phiên bản 0.2.0):

Ví dụ này giữ lại hoàn toàn nhiệm vụ, sử dụng một lượng mã rất hạn chế.

Bài tập:

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

Hoạt động:

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

Cách tiếp cận của tôi là sử dụng mẫu thiết kế ủy quyền, nói chung, chúng tôi có thể cô lập logic nghiệp vụ thực tế (đọc dữ liệu từ internet hoặc cơ sở dữ liệu hoặc bất kỳ thứ gì) từ AsyncTask (người ủy quyền) đến BusinessDAO (người ủy quyền), trong phương thức AysncTask.doInBackground () của bạn , ủy quyền nhiệm vụ thực tế cho BusinessDAO, sau đó triển khai cơ chế quy trình singleton trong BusinessDAO, để nhiều lệnh gọi đến BusinessDAO.doSomething () sẽ chỉ kích hoạt một tác vụ thực tế đang chạy mỗi lần và chờ kết quả tác vụ. Ý tưởng là giữ lại người đại diện (tức là BusinessDAO) trong quá trình thay đổi cấu hình, thay vì người ủy quyền (tức là AsyncTask).

  1. Tạo / Triển khai Ứng dụng của riêng chúng tôi, mục đích là tạo / khởi tạo BusinessDAO tại đây, để vòng đời của BusinessDAO của chúng tôi là phạm vi ứng dụng, không phải phạm vi hoạt động, lưu ý rằng bạn cần thay đổi AndroidManifest.xml để sử dụng 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. Activity / Fragement hiện tại của chúng tôi hầu như không thay đổi, vẫn triển khai AsyncTask như một lớp bên trong và liên quan đến AsyncTask.execute () từ Activity / Fragement, sự khác biệt bây giờ là AsyncTask sẽ ủy quyền nhiệm vụ thực tế cho BusinessDAO, vì vậy trong quá trình thay đổi cấu hình, AsyncTask thứ hai sẽ được khởi tạo và thực thi, và gọi BusinessDAO.doSomething () lần thứ hai, tuy nhiên, lần gọi thứ hai tới BusinessDAO.doSomething () sẽ không kích hoạt một tác vụ đang chạy mới, thay vào đó, chờ tác vụ đang chạy hiện tại kết thúc:

    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. Bên trong BusinessDAO, triển khai cơ chế quy trình singleton, ví dụ:

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

Tôi không chắc chắn 100% liệu điều này có hoạt động hay không, hơn nữa, đoạn mã mẫu nên được coi là mã giả. Tôi chỉ đang cố gắng cung cấp cho bạn một số manh mối từ cấp độ thiết kế. Mọi phản hồi hoặc đề xuất đều được hoan nghênh và đánh giá cao.


Có vẻ như giải pháp rất tốt đẹp. Kể từ khi bạn trả lời câu hỏi này khoảng 2 năm rưỡi trước, bạn đã thử nghiệm nó từ khi nào ?! Bạn đang nói rằng tôi không chắc nó hoạt động, điều không phải là tôi !! Tôi đang khóa một giải pháp đã được thử nghiệm tốt cho vấn đề này. Bạn có đề nghị nào không?
Alireza A. Ahmadi

1

Bạn có thể đặt AsyncTask thành một trường tĩnh. Nếu bạn cần một ngữ cảnh, bạn nên gửi ngữ cảnh ứng dụng của mình. Điều này sẽ tránh rò rỉ bộ nhớ, nếu không, bạn sẽ giữ một tham chiếu đến toàn bộ hoạt động của mình.


1

Nếu bất kỳ ai tìm thấy đường đến chuỗi này thì tôi thấy một cách tiếp cận rõ ràng là chạy tác vụ Async từ một app.Service(bắt đầu bằng START_STICKY) và sau đó tái tạo lặp lại các dịch vụ đang chạy để tìm hiểu xem dịch vụ (và do đó tác vụ async) vẫn còn đang chạy;

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

Nếu có, hãy thêm lại DialogFragment (hoặc bất cứ thứ gì) và nếu không đảm bảo hộp thoại đã bị loại bỏ.

Điều này đặc biệt thích hợp nếu bạn đang sử dụng các v4.support.*thư viện vì (tại thời điểm viết bài) họ đã biết các vấn đề với setRetainInstancephương pháp và xem phân trang. Hơn nữa, bằng cách không giữ lại phiên bản, bạn có thể tạo lại hoạt động của mình bằng cách sử dụng một nhóm tài nguyên khác (tức là một bố cục chế độ xem khác cho hướng mới)


Không phải là quá mức cần thiết khi chạy một Dịch vụ chỉ để giữ lại một AsyncTask? Dịch vụ chạy trong quy trình của riêng nó và điều đó không đi kèm với chi phí bổ sung.
WeNeigh,

Vinay thú vị. Tôi không nhận thấy rằng ứng dụng đang nặng hơn bất kỳ tài nguyên nào (dù sao thì nó cũng khá nhẹ vào lúc này). Bạn đã tìm thấy gì? Tôi xem một dịch vụ như một môi trường có thể dự đoán được để cho phép hệ thống hoạt động với một số công việc nặng nhọc hoặc I / O bất kể trạng thái giao diện người dùng. Liên lạc với dịch vụ để xem khi nào điều gì đó đã hoàn thành có vẻ là 'đúng'. Các dịch vụ tôi bắt đầu thực hiện một chút ngừng công việc khi hoàn thành nhiệm vụ nên thường tồn tại trong khoảng 10-30 giây.
BrantApps

Câu trả lời của Commonsware ở đây dường như cho thấy Dịch vụ là một ý tưởng tồi. Tôi bây giờ xem xét AsyncTaskLoaders nhưng họ dường như đi kèm với những vấn đề của riêng mình (cứng nhắc, chỉ dành cho dữ liệu tải vv)
WeNeigh

1
Tôi hiểu rồi. Đáng chú ý, dịch vụ mà bạn liên kết đến này rõ ràng đã được thiết lập để chạy trong quy trình riêng của nó. Ông chủ có vẻ không thích mẫu này được dùng thường xuyên. Tôi đã không cung cấp rõ ràng các thuộc tính "chạy trong một quy trình mới mỗi lần", vì vậy hy vọng tôi không bị loại khỏi phần bị chỉ trích đó. Tôi sẽ cố gắng định lượng các hiệu ứng. Dịch vụ như một khái niệm tất nhiên không phải là 'ý tưởng tồi' và là nền tảng cho mọi ứng dụng làm bất cứ điều gì thú vị từ xa, không nhằm mục đích chơi chữ. JDoc của họ cung cấp thêm hướng dẫn về cách sử dụng chúng nếu bạn vẫn không chắc chắn.
BrantApps

0

Tôi viết mã samepl để giải quyết vấn đề này

Bước đầu tiên là tạo lớp Ứng dụng:

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

Trong 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">

Mã trong hoạt động:

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

}

Khi hướng hoạt động thay đổi, biến mTask sẽ được kích hoạt từ ngữ cảnh ứng dụng. Khi tác vụ hoàn thành, biến được đặt thành null và xóa khỏi bộ nhớ.

Đối với tôi, nó là đủ.


0

Hãy xem ví dụ dưới đây, cách sử dụng phân đoạn được giữ lại để giữ lại tác vụ nền:

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

Có một cái nhìn ở đây .

Có một giải pháp dựa trên giải pháp của Timmmm .

Nhưng tôi đã cải thiện nó:

  • Giờ đây, giải pháp có thể mở rộng - bạn chỉ cần mở rộng FragmentAbleToStartTask

  • Bạn có thể tiếp tục chạy nhiều tác vụ cùng lúc.

    Và theo tôi, nó dễ dàng như startActivityForResult và nhận kết quả

  • Bạn cũng có thể dừng một tác vụ đang chạy và kiểm tra xem tác vụ cụ thể có đang chạy hay không

Xin lỗi vì tiếng Anh của tôi


liên kết đầu tiên bị phá vỡ
Tejzeratul
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.