Thực tiễn tốt nhất: AsyncTask trong khi thay đổi định hướng


151

AsyncTask là một điều tuyệt vời để chạy các tác vụ phức tạp trong một luồng khác.

Nhưng khi có sự thay đổi định hướng hoặc thay đổi cấu hình khác trong khi AsyncTaskvẫn đang chạy, dòng điện Activitysẽ bị hủy và khởi động lại. Và như thể hiện của AsyncTaskkết nối với hoạt động đó, nó không thành công và gây ra cửa sổ thông báo "buộc đóng".

Vì vậy, tôi đang tìm kiếm một số loại "thực hành tốt nhất" để tránh những lỗi này và ngăn AsyncTask không thành công.

Những gì tôi đã thấy cho đến nay là:

  • Vô hiệu hóa thay đổi hướng. (Chắc chắn không phải là cách bạn nên xử lý việc này.)
  • Để nhiệm vụ tồn tại và cập nhật nó với phiên bản hoạt động mới thông qua onRetainNonConfigurationInstance
  • Chỉ cần hủy tác vụ khi Activitybị hủy và khởi động lại khi nó Activityđược tạo lại.
  • Liên kết nhiệm vụ với lớp ứng dụng thay vì thể hiện hoạt động.
  • Một số phương pháp được sử dụng trong dự án "kệ" (thông qua onRestoreInstanceState)

Một số ví dụ mã:

Android AsyncT Nhiệm vụ trong khi xoay màn hình, Phần IPhần II

KệActivity.java

Bạn có thể giúp tôi tìm ra cách tiếp cận tốt nhất để giải quyết vấn đề tốt nhất và cũng dễ thực hiện không? Bản thân mã cũng rất quan trọng vì tôi không biết cách giải quyết vấn đề này một cách chính xác.


Có một bản sao, kiểm tra stackoverflow.com/questions/4584015/ này .
TeaCupApp

Đây là từ Blog của Mark Murphy ... AsyncTask và ScreenRotation có thể giúp ... liên kết
Gopal

Mặc dù đây là một bài viết cũ, nhưng IMO này , là một cách tiếp cận dễ dàng hơn (và tốt hơn?).
DroidDev

Tôi chỉ tự hỏi tại sao tài liệu không nói về những tình huống rất tầm thường như vậy.
Saletanth Karumanaghat

Câu trả lời:


140

Đừng KHÔNG sử dụng android:configChangesđể giải quyết vấn đề này. Đây là thực tế rất xấu.

Đừng KHÔNG sử dụng Activity#onRetainNonConfigurationInstance()một trong hai. Đây là mô-đun ít hơn và không phù hợp cho Fragmentcác ứng dụng dựa trên.

Bạn có thể đọc bài viết của tôi mô tả cách xử lý các thay đổi cấu hình bằng Fragments giữ lại . Nó giải quyết vấn đề giữ lại một AsyncTaskthay đổi xoay vòng độc đáo. Về cơ bản bạn cần phải lưu trữ 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ó Activitythông qua việc giữ lại Fragment.


26
Ý tưởng hay, nhưng không phải ai cũng sử dụng Fragment. Có rất nhiều mã kế thừa được viết từ lâu trước khi Fragment là một lựa chọn.
Scott Biggie

14
@ScottBiggs Các đoạn có sẵn thông qua thư viện hỗ trợ tất cả các cách trở lại Android 1.6. Và bạn có thể đưa ra một ví dụ về một số mã kế thừa vẫn đang được sử dụng tích cực sẽ gặp khó khăn khi sử dụng thư viện hỗ trợ Fragment không? Bởi vì tôi thực sự không nghĩ rằng đó là một vấn đề.
Alex Lockwood

4
@tactoth Tôi không cảm thấy cần phải giải quyết những vấn đề này trong câu trả lời của mình, vì 99,9% mọi người không còn sử dụng nữa TabActivity. Thành thật mà nói, tôi không chắc tại sao chúng ta thậm chí còn nói về điều này ... mọi người đều đồng ý rằng đó Fragmentlà con đường để đi. :)
Alex Lockwood

2
Điều gì xảy ra nếu AsyncTask phải được gọi từ một mảnh được lồng?
Eduardo Naveda

3
@AlexLockwood - "mọi người đồng ý rằng Mảnh vỡ là con đường để đi.". Các Dev ở tại Squared sẽ không đồng ý!
JBeckton

36

Tôi thường giải quyết vấn đề này bằng cách sử dụng các ý định phát sóng AsyncT task của tôi trong cuộc gọi lại .onPostExecute (), vì vậy họ không sửa đổi Hoạt động bắt đầu trực tiếp chúng. Các hoạt động nghe các chương trình phát sóng này với BroadcastReceivers động và hành động tương ứng.

Bằng cách này, AsyncT task không phải quan tâm đến cá thể Activity cụ thể xử lý kết quả của chúng. Họ chỉ "hét" khi họ kết thúc và nếu một Hoạt động diễn ra vào khoảng thời gian đó (đang hoạt động và tập trung / đang ở trạng thái tiếp tục), điều đó quan tâm đến kết quả của nhiệm vụ, thì nó sẽ được xử lý.

Điều này liên quan đến chi phí cao hơn một chút, vì thời gian chạy cần xử lý phát sóng, nhưng tôi thường không bận tâm. Tôi nghĩ rằng việc sử dụng LocalBroadcastManager thay vì hệ thống mặc định rộng hơn một chút sẽ tăng tốc mọi thứ lên một chút.


6
Nếu bạn có thể thêm một ví dụ cho câu trả lời thì nó sẽ hữu ích hơn
Sankar V

1
Tôi nghĩ rằng đây là giải pháp cung cấp ít khớp nối hơn giữa các hoạt động và các mảnh vỡ
Roger Garzon Nieto

7
Đây có thể là một phần của giải pháp nhưng có vẻ như nó sẽ không giải quyết được vấn đề AsyncTask được tạo lại sau khi thay đổi hướng.
miguel

4
Điều gì xảy ra nếu bạn không may mắn và không có hoạt động nào xung quanh trong khi phát sóng? (tức là bạn đang quay giữa)
Sam

24

Dưới đây là một ví dụ khác về AsyncTask sử dụng Fragmentđể xử lý các thay đổi cấu hình thời gian chạy (như khi người dùng xoay màn hình) với setRetainInstance(true). Một thanh tiến trình xác định (cập nhật thường xuyên) cũng được thể hiện.

Ví dụ này một phần dựa trên các tài liệu chính thức, Giữ lại một đối tượng trong khi thay đổi cấu hình .

Trong ví dụ này, công việc yêu cầu một luồng nền là việc tải một hình ảnh từ internet vào UI.

Alex Lockwood có vẻ đúng khi nói đến việc xử lý các thay đổi cấu hình thời gian chạy với AsyncT task bằng cách sử dụng "Mảnh vỡ được giữ lại" là cách tốt nhất. onRetainNonConfigurationInstance()bị phản đối trong Lint, trong Android Studio. Các tài liệu chính thức cảnh báo chúng tôi sử dụng android:configChanges, từ Xử lý thay đổi cấu hình , ...

Tự xử lý thay đổi cấu hình có thể khiến việc sử dụng các tài nguyên thay thế trở nên khó khăn hơn nhiều, vì hệ thống không tự động áp dụng chúng cho bạn. Kỹ thuật này nên được coi là giải pháp cuối cùng khi bạn phải tránh khởi động lại do thay đổi cấu hình và không được khuyến nghị cho hầu hết các ứng dụng.

Sau đó, có một vấn đề là liệu người ta có nên sử dụng AsyncTask cho chủ đề nền hay không.

Tài liệu tham khảo chính thức cho AsyncTask cảnh báo ...

AsyncT task nên được sử dụng một cách lý tưởng cho các hoạt động ngắn (nhiều nhất là vài giây.) Nếu bạn cần giữ các luồng hoạt động trong thời gian dài, bạn nên sử dụng các API khác nhau được cung cấp bởi java.util.conc hiện nhịp Giám đốc điều hành, ThreadPoolExecutor và FutureTask.

Ngoài ra, người ta có thể sử dụng một dịch vụ, trình tải (sử dụng CoderLoader hoặc AsyncTaskLoader) hoặc nhà cung cấp nội dung để thực hiện các hoạt động không đồng bộ.

Tôi chia phần còn lại của bài viết thành:

  • Thủ tục; và
  • Tất cả các mã cho các thủ tục trên.

Thủ tục

  1. Bắt đầu với AsyncTask cơ bản như một lớp bên trong của một hoạt động (nó không cần phải là một lớp bên trong nhưng nó có thể sẽ thuận tiện để trở thành). Ở giai đoạn này, AsyncTask không xử lý các thay đổi cấu hình thời gian chạy.

    public class ThreadsActivity extends ActionBarActivity {
    
        private ImageView mPictureImageView;
    
        private class LoadImageFromNetworkAsyncTask
                              extends AsyncTask<String, Void, Bitmap> {
    
            @Override
            protected Bitmap doInBackground(String... urls) {
                return loadImageFromNetwork(urls[0]);
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                mPictureImageView.setImageBitmap(bitmap);
            }
        }
    
        /**
         * Requires in AndroidManifext.xml
         *  <uses-permission android:name="android.permission.INTERNET" />
         */
        private Bitmap loadImageFromNetwork(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeStream((InputStream)
                                              new URL(url).getContent());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            mPictureImageView =
                (ImageView) findViewById(R.id.imageView_picture);
        }
    
        public void getPicture(View view) {
            new LoadImageFromNetworkAsyncTask()
                .execute("http://i.imgur.com/SikTbWe.jpg");
        }
    
    }
  2. Thêm một lớp RetainedFragment lồng nhau mở rộng lớp Fragement và không có UI riêng. Thêm setRetainInstance (true) vào sự kiện onCreate của Fragment này. Cung cấp các thủ tục để thiết lập và nhận dữ liệu của bạn.

    public class ThreadsActivity extends Activity {
    
        private ImageView mPictureImageView;
        private RetainedFragment mRetainedFragment = null;
        ...
    
        public static class RetainedFragment extends Fragment {
    
            private Bitmap mBitmap;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
                // The key to making data survive
                // runtime configuration changes.
                setRetainInstance(true);
            }
    
            public Bitmap getData() {
                return this.mBitmap;
            }
    
            public void setData(Bitmap bitmapToRetain) {
                this.mBitmap = bitmapToRetain;
            }
        }
    
        private class LoadImageFromNetworkAsyncTask
                        extends AsyncTask<String, Integer,Bitmap> {
        ....
  3. Trong lớp Activity ngoài cùng, onCreate () xử lý RetainedFragment: Tham chiếu nó nếu nó đã tồn tại (trong trường hợp Activity đang khởi động lại); tạo và thêm nó nếu nó không tồn tại; Sau đó, nếu nó đã tồn tại, hãy lấy dữ liệu từ RetainedFragment và đặt UI của bạn với dữ liệu đó.

    public class ThreadsActivity extends Activity {
    
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            final String retainedFragmentTag = "RetainedFragmentTag";
    
            mPictureImageView =
                      (ImageView) findViewById(R.id.imageView_picture);
            mLoadingProgressBar =
                    (ProgressBar) findViewById(R.id.progressBar_loading);
    
            // Find the RetainedFragment on Activity restarts
            FragmentManager fm = getFragmentManager();
            // The RetainedFragment has no UI so we must
            // reference it with a tag.
            mRetainedFragment =
              (RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);
    
            // if Retained Fragment doesn't exist create and add it.
            if (mRetainedFragment == null) {
    
                // Add the fragment
                mRetainedFragment = new RetainedFragment();
                fm.beginTransaction()
                    .add(mRetainedFragment, retainedFragmentTag).commit();
    
            // The Retained Fragment exists
            } else {
    
                mPictureImageView
                    .setImageBitmap(mRetainedFragment.getData());
            }
        }
  4. Bắt đầu AsyncTask từ UI

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. Thêm và mã một thanh tiến trình xác định:

    • Thêm một thanh tiến trình vào bố trí UI;
    • Nhận một tham chiếu đến nó trong Activity oncreate ();
    • Làm cho nó hiển thị và bất khả xâm phạm khi bắt đầu và kết thúc quá trình;
    • Xác định tiến trình để báo cáo cho UI trong onProTHERUpdate.
    • Thay đổi tham số Chung thứ 2 của AsyncTask từ Void thành loại có thể xử lý các cập nhật tiến độ (ví dụ: Integer).
    • xuất bản Xuất phát tại các điểm thông thường trong doInBackground ().

Tất cả các mã cho thủ tục trên

Bố trí hoạt động.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.mysecondapp.ThreadsActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/imageView_picture"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:background="@android:color/black" />

        <Button
            android:id="@+id/button_get_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@id/imageView_picture"
            android:onClick="getPicture"
            android:text="Get Picture" />

        <Button
            android:id="@+id/button_clear_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/button_get_picture"
            android:layout_toEndOf="@id/button_get_picture"
            android:layout_toRightOf="@id/button_get_picture"
            android:onClick="clearPicture"
            android:text="Clear Picture" />

        <ProgressBar
            android:id="@+id/progressBar_loading"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/button_get_picture"
            android:progress="0"
            android:indeterminateOnly="false"
            android:visibility="invisible" />

    </RelativeLayout>
</ScrollView>

Hoạt động với: lớp bên trong AsyncTask; lớp bên trong RetainedFragment lớp bên trong xử lý các thay đổi cấu hình thời gian chạy (ví dụ: khi người dùng xoay màn hình); và một thanh tiến trình xác định cập nhật theo định kỳ. ...

public class ThreadsActivity extends Activity {

    private ImageView mPictureImageView;
    private RetainedFragment mRetainedFragment = null;
    private ProgressBar mLoadingProgressBar;

    public static class RetainedFragment extends Fragment {

        private Bitmap mBitmap;

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

            // The key to making data survive runtime configuration changes.
            setRetainInstance(true);
        }

        public Bitmap getData() {
            return this.mBitmap;
        }

        public void setData(Bitmap bitmapToRetain) {
            this.mBitmap = bitmapToRetain;
        }
    }

    private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
            Integer, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... urls) {
            // Simulate a burdensome load.
            int sleepSeconds = 4;
            for (int i = 1; i <= sleepSeconds; i++) {
                SystemClock.sleep(1000); // milliseconds
                publishProgress(i * 20); // Adjust for a scale to 100
            }

            return com.example.standardapplibrary.android.Network
                    .loadImageFromNetwork(
                    urls[0]);
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            mLoadingProgressBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            publishProgress(100);
            mRetainedFragment.setData(bitmap);
            mPictureImageView.setImageBitmap(bitmap);
            mLoadingProgressBar.setVisibility(View.INVISIBLE);
            publishProgress(0);
        }
    }

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

        final String retainedFragmentTag = "RetainedFragmentTag";

        mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
        mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);

        // Find the RetainedFragment on Activity restarts
        FragmentManager fm = getFragmentManager();
        // The RetainedFragment has no UI so we must reference it with a tag.
        mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
                retainedFragmentTag);

        // if Retained Fragment doesn't exist create and add it.
        if (mRetainedFragment == null) {

            // Add the fragment
            mRetainedFragment = new RetainedFragment();
            fm.beginTransaction().add(mRetainedFragment,
                                      retainedFragmentTag).commit();

            // The Retained Fragment exists
        } else {

            mPictureImageView.setImageBitmap(mRetainedFragment.getData());
        }
    }

    public void getPicture(View view) {
        mLoadingProgressBar.setVisibility(View.VISIBLE);
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }

    public void clearPicture(View view) {
        mRetainedFragment.setData(null);
        mPictureImageView.setImageBitmap(null);
    }
}

Trong ví dụ này, hàm thư viện (được tham chiếu ở trên với tiền tố gói rõ ràng com.example.st Chuẩnappl Library.android.Network) hoạt động thực sự ...

public static Bitmap loadImageFromNetwork(String url) {
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
                .getContent());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bitmap;
}

Thêm bất kỳ quyền nào mà tác vụ nền của bạn yêu cầu vào AndroidManifest.xml ...

<manifest>
...
    <uses-permission android:name="android.permission.INTERNET" />

Thêm hoạt động của bạn vào AndroidManifest.xml ...

<manifest>
...
    <application>
        <activity
            android:name=".ThreadsActivity"
            android:label="@string/title_activity_threads"
            android:parentActivityName=".MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.mysecondapp.MainActivity" />
        </activity>

Tuyệt quá. Bạn nên viết một blog về điều này.
Akh

2
@AKh. Bạn có nghĩa là đề nghị câu trả lời của tôi chiếm quá nhiều chỗ trên Stackoverflow?
John Bentley

1
Tôi nghĩ rằng anh ta chỉ có nghĩa là bạn có một câu trả lời tuyệt vời và bạn nên viết một blog! =) @JohnBentley
Sandy D.

@ SandyD.yday Cảm ơn bạn đã giải thích tích cực. Tôi hy vọng cô ấy hoặc anh ấy dự định điều đó.
John Bentley

Tôi cũng nghĩ rằng đó là một câu trả lời tuyệt vời và tôi cũng giải thích nó như vậy. Câu trả lời rất đầy đủ như thế này là tuyệt vời !!
LeonardoSibela

3

Gần đây, tôi đã tìm thấy một giải pháp tốt ở đây . Nó dựa trên việc lưu một đối tượng tác vụ thông qua RetainConfiguration. Theo quan điểm của tôi, giải pháp rất thanh lịch và đối với tôi, tôi đã bắt đầu sử dụng nó. Bạn chỉ cần lồng cái asynctask của bạn từ basetask và thế thôi.


Cảm ơn bạn rất nhiều vì câu trả lời thú vị này. Đó là một giải pháp tốt ngoài những giải pháp được đề cập trong câu hỏi liên quan.
caw

5
Thật không may, giải pháp này sử dụng các phương pháp không dùng nữa.
Damien

3

Dựa trên câu trả lời @Alex Lockwood và trên @William & @quickdraw mcgraw câu trả lời trên bài đăng này: Cách xử lý tin nhắn Handler khi hoạt động / đoạn bị tạm dừng , tôi đã viết một giải pháp chung.

Bằng cách này, việc xoay vòng được xử lý và nếu hoạt động chuyển sang nền trong quá trình thực thi tác vụ không đồng bộ, thì hoạt động sẽ nhận được các cuộc gọi lại (onPreExecute, onProTHERUpdate, onPostExecute & onCancelling) một lần nữa, vì vậy sẽ không xử lý IllegalStateException tin nhắn khi hoạt động / đoạn bị tạm dừng ).

Sẽ rất tuyệt nếu có cùng loại nhưng với các loại đối số chung, như AsyncTask (ví dụ: AsyncTaskFragment <Params, Progress, result>), nhưng tôi đã không quản lý để thực hiện nhanh chóng và không có thời gian. Nếu bất cứ ai muốn làm cải tiến, xin vui lòng!

Mật mã:

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class AsyncTaskFragment extends Fragment {

    /* ------------------------------------------------------------------------------------------ */
    // region Classes & Interfaces

    public static abstract class Task extends AsyncTask<Object, Object, Object> {

        private AsyncTaskFragment _fragment;

        private void setFragment(AsyncTaskFragment fragment) {

            _fragment = fragment;
        }

        @Override
        protected final void onPreExecute() {

            // Save the state :
            _fragment.setRunning(true);

            // Send a message :
            sendMessage(ON_PRE_EXECUTE_MESSAGE, null);
        }

        @Override
        protected final void onPostExecute(Object result) {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_POST_EXECUTE_MESSAGE, result);
        }

        @Override
        protected final void onProgressUpdate(Object... values) {

            // Send a message :
            sendMessage(ON_PROGRESS_UPDATE_MESSAGE, values);
        }

        @Override
        protected final void onCancelled() {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_CANCELLED_MESSAGE, null);
        }

        private void sendMessage(int what, Object obj) {

            Message message = new Message();
            message.what = what;
            message.obj = obj;

            Bundle data = new Bundle(1);
            data.putString(EXTRA_FRAGMENT_TAG, _fragment.getTag());
            message.setData(data);

            _fragment.handler.sendMessage(message);
        }
    }

    public interface AsyncTaskFragmentListener {

        void onPreExecute(String fragmentTag);
        void onProgressUpdate(String fragmentTag, Object... progress);
        void onCancelled(String fragmentTag);
        void onPostExecute(String fragmentTag, Object result);
    }

    private static class AsyncTaskFragmentPauseHandler extends PauseHandler {

        @Override
        final protected void processMessage(Activity activity, Message message) {

            switch (message.what) {

                case ON_PRE_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPreExecute(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
                case ON_POST_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPostExecute(message.getData().getString(EXTRA_FRAGMENT_TAG), message.obj); break; }
                case ON_PROGRESS_UPDATE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onProgressUpdate(message.getData().getString(EXTRA_FRAGMENT_TAG), ((Object[])message.obj)); break; }
                case ON_CANCELLED_MESSAGE : { ((AsyncTaskFragmentListener)activity).onCancelled(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
            }
        }
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Attributes

    private Task _task;
    private AsyncTaskFragmentListener _listener;
    private boolean _running = false;

    private static final String EXTRA_FRAGMENT_TAG = "EXTRA_FRAGMENT_TAG";
    private static final int ON_PRE_EXECUTE_MESSAGE = 0;
    private static final int ON_POST_EXECUTE_MESSAGE = 1;
    private static final int ON_PROGRESS_UPDATE_MESSAGE = 2;
    private static final int ON_CANCELLED_MESSAGE = 3;

    private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Getters

    public AsyncTaskFragmentListener getListener() { return _listener; }
    public boolean isRunning() { return _running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Setters

    public void setTask(Task task) {

        _task = task;
        _task.setFragment(this);
    }

    public void setListener(AsyncTaskFragmentListener listener) { _listener = listener; }
    private void setRunning(boolean running) { _running = running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Fragment lifecycle

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onResume() {

        super.onResume();
        handler.resume(getActivity());
    }

    @Override
    public void onPause() {

        super.onPause();
        handler.pause();
    }

    @Override
    public void onAttach(Activity activity) {

        super.onAttach(activity);
        _listener = (AsyncTaskFragmentListener) activity;
    }

    @Override
    public void onDetach() {

        super.onDetach();
        _listener = null;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Utils

    public void execute(Object... params) {

        _task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }

    public void cancel(boolean mayInterruptIfRunning) {

        _task.cancel(mayInterruptIfRunning);
    }

    public static AsyncTaskFragment getRetainedOrNewFragment(AppCompatActivity activity, String fragmentTag) {

        FragmentManager fm = activity.getSupportFragmentManager();
        AsyncTaskFragment fragment = (AsyncTaskFragment) fm.findFragmentByTag(fragmentTag);

        if (fragment == null) {

            fragment = new AsyncTaskFragment();
            fragment.setListener( (AsyncTaskFragmentListener) activity);
            fm.beginTransaction().add(fragment, fragmentTag).commit();
        }

        return fragment;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */
}

Bạn sẽ cần PauseHandler:

import android.app.Activity;
import android.os.Handler;
import android.os.Message;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Message Handler class that supports buffering up of messages when the activity is paused i.e. in the background.
 *
 * /programming/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
 */
public abstract class PauseHandler extends Handler {

    /**
     * Message Queue Buffer
     */
    private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());

    /**
     * Flag indicating the pause state
     */
    private Activity activity;

    /**
     * Resume the handler.
     */
    public final synchronized void resume(Activity activity) {
        this.activity = activity;

        while (messageQueueBuffer.size() > 0) {
            final Message msg = messageQueueBuffer.get(0);
            messageQueueBuffer.remove(0);
            sendMessage(msg);
        }
    }

    /**
     * Pause the handler.
     */
    public final synchronized void pause() {
        activity = null;
    }

    /**
     * Store the message if we have been paused, otherwise handle it now.
     *
     * @param msg   Message to handle.
     */
    @Override
    public final synchronized void handleMessage(Message msg) {
        if (activity == null) {
            final Message msgCopy = new Message();
            msgCopy.copyFrom(msg);
            messageQueueBuffer.add(msgCopy);
        } else {
            processMessage(activity, msg);
        }
    }

    /**
     * Notification message to be processed. This will either be directly from
     * handleMessage or played back from a saved message when the activity was
     * paused.
     *
     * @param activity  Activity owning this Handler that isn't currently paused.
     * @param message   Message to be handled
     */
    protected abstract void processMessage(Activity activity, Message message);
}

Sử dụng mẫu:

public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {

    private final static String ASYNC_TASK_FRAGMENT_A = "ASYNC_TASK_FRAGMENT_A";
    private final static String ASYNC_TASK_FRAGMENT_B = "ASYNC_TASK_FRAGMENT_B";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        Button testButton = (Button) findViewById(R.id.test_button);
        final AsyncTaskFragment fragment = AsyncTaskFragment.getRetainedOrNewFragment(TestActivity.this, ASYNC_TASK_FRAGMENT_A);

        testButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(!fragment.isRunning()) {

                    fragment.setTask(new Task() {

                        @Override
                        protected Object doInBackground(Object... objects) {

                            // Do your async stuff

                            return null;
                        }
                    });

                    fragment.execute();
                }
            }
        });
    }

    @Override
    public void onPreExecute(String fragmentTag) {}

    @Override
    public void onProgressUpdate(String fragmentTag, Float percent) {}

    @Override
    public void onCancelled(String fragmentTag) {}

    @Override
    public void onPostExecute(String fragmentTag, Object result) {

        switch (fragmentTag) {

            case ASYNC_TASK_FRAGMENT_A: {

                // Handle ASYNC_TASK_FRAGMENT_A
                break;
            }
            case ASYNC_TASK_FRAGMENT_B: {

                // Handle ASYNC_TASK_FRAGMENT_B
                break;
            }
        }
    }
}

3

Bạn có thể sử dụng Trình tải cho việc này. Kiểm tra tài liệu ở đây


2
Các trình tải hiện không được chấp nhận kể từ API Android 28 (vì liên kết sẽ cho bạn biết).
Sumit

Các trình tải không bị phản đối, đó chỉ là cách bạn gọi chúng đã thay đổi
EdgeDev

2

Đối với những người muốn tránh các mảnh vỡ, bạn có thể giữ AsyncTask chạy trên các thay đổi hướng bằng cách sử dụng onRetainCustomNonConfigurationInstance () và một số hệ thống dây.

(Lưu ý rằng phương thức này là phương pháp thay thế cho onRetainNonConfigurationInstance ()) không dùng nữa .

Có vẻ như giải pháp này không được đề cập thường xuyên mặc dù. Tôi đã viết một ví dụ chạy đơn giản để minh họa.

Chúc mừng!

public class MainActivity extends AppCompatActivity {

private TextView result;
private Button run;
private AsyncTaskHolder asyncTaskHolder;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    result = (TextView) findViewById(R.id.textView_result);
    run = (Button) findViewById(R.id.button_run);
    asyncTaskHolder = getAsyncTaskHolder();
    run.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            asyncTaskHolder.execute();
        }
    });
}

private AsyncTaskHolder getAsyncTaskHolder() {
    if (this.asyncTaskHolder != null) {
        return asyncTaskHolder;
    }
    //Not deprecated. Get the same instance back.
    Object instance = getLastCustomNonConfigurationInstance();

    if (instance == null) {
        instance = new AsyncTaskHolder();
    }
    if (!(instance instanceof ActivityDependant)) {
        Log.e("", instance.getClass().getName() + " must implement ActivityDependant");
    }
    return (AsyncTaskHolder) instance;
}

@Override
//Not deprecated. Save the object containing the running task.
public Object onRetainCustomNonConfigurationInstance() {
    return asyncTaskHolder;
}

@Override
protected void onStart() {
    super.onStart();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.attach(this);
    }
}

@Override
protected void onStop() {
    super.onStop();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.detach();
    }
}

void updateUI(String value) {
    this.result.setText(value);
}

interface ActivityDependant {

    void attach(Activity activity);

    void detach();
}

class AsyncTaskHolder implements ActivityDependant {

    private Activity parentActivity;
    private boolean isRunning;
    private boolean isUpdateOnAttach;

    @Override
    public synchronized void attach(Activity activity) {
        this.parentActivity = activity;
        if (isUpdateOnAttach) {
            ((MainActivity) parentActivity).updateUI("done");
            isUpdateOnAttach = false;
        }
    }

    @Override
    public synchronized void detach() {
        this.parentActivity = null;
    }

    public synchronized void execute() {
        if (isRunning) {
            Toast.makeText(parentActivity, "Already running", Toast.LENGTH_SHORT).show();
            return;
        }
        isRunning = true;
        new AsyncTask<Void, Integer, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                for (int i = 0; i < 100; i += 10) {
                    try {
                        Thread.sleep(500);
                        publishProgress(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI(String.valueOf(values[0]));
                }
            }

            @Override
            protected synchronized void onPostExecute(Void aVoid) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI("done");
                } else {
                    isUpdateOnAttach = true;
                }
                isRunning = false;
            }
        }.execute();
    }
}

0

Tôi đã triển khai thư viện có thể giải quyết các vấn đề với tạm dừng hoạt động và giải trí trong khi nhiệm vụ của bạn đang thực thi.

Bạn nên thực hiện AsmykPleaseWaitTaskAsmykBasicPleaseWaitActivity. Hoạt động và tác vụ nền của bạn sẽ hoạt động tốt ngay cả khi bạn sẽ xoay màn hình và chuyển đổi giữa các ứng dụng


-9

CÔNG VIỆC NHANH CHÓNG (không được đề xuất)

Để tránh một Hoạt động tự hủy và tạo chính là khai báo hoạt động của bạn trong tệp kê khai: android: configChanges = "direction | keyboardHidden | screenSize

  <activity
        android:name=".ui.activity.MyActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:label="@string/app_name">

Như đã đề cập trong tài liệu

Hướng màn hình đã thay đổi - người dùng đã xoay thiết bị.

Lưu ý: Nếu ứng dụng của bạn nhắm mục tiêu API cấp 13 trở lên (như được khai báo bởi các thuộc tính minSdkVersion và targetSdkVersion), thì bạn cũng nên khai báo cấu hình "screenSize", bởi vì nó cũng thay đổi khi thiết bị chuyển đổi giữa hướng dọc và hướng ngang.


1
Điều này tốt nhất nên tránh. developer.android.com/guide/topics/resource/ Mạnh "Lưu ý: Việc tự xử lý thay đổi cấu hình có thể khiến việc sử dụng tài nguyên thay thế trở nên khó khăn hơn nhiều, vì hệ thống không tự động áp dụng chúng cho bạn. Kỹ thuật này nên được coi là lần cuối nghỉ dưỡng khi bạn phải tránh khởi động lại do thay đổi cấu hình và không được khuyến nghị cho hầu hết các ứng dụng. "
David
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.