Nhiệm vụ nền, hộp thoại tiến trình, thay đổi định hướng - có giải pháp làm việc 100% nào không?


235

Tôi tải xuống một số dữ liệu từ internet trong luồng nền (tôi sử dụng AsyncTask) và hiển thị hộp thoại tiến trình trong khi tải xuống. Thay đổi định hướng, Hoạt động được khởi động lại và sau đó AsyncTask của tôi đã hoàn thành - Tôi muốn loại bỏ hộp thoại progess và bắt đầu một Hoạt động mới. Nhưng việc gọi notifyDialog đôi khi đưa ra một ngoại lệ (có thể do Hoạt động đã bị hủy và Hoạt động mới chưa được bắt đầu).

Cách tốt nhất để xử lý loại vấn đề này (cập nhật giao diện người dùng từ luồng nền hoạt động ngay cả khi người dùng thay đổi hướng) là gì? Có ai đó từ Google cung cấp một số "giải pháp chính thức" không?


4
Bài viết trên blog của tôi về chủ đề này có thể giúp đỡ. Đó là về việc giữ lại các tác vụ chạy dài qua các thay đổi cấu hình.
Alex Lockwood

1
Câu hỏi này có liên quan là tốt.
Alex Lockwood

Chỉ cần FTR có một bí ẩn liên quan ở đây .. stackoverflow.com/q/23742412/294884
Fattie

Câu trả lời:


336

Bước # 1: Tạo AsyncTaskmột staticlớp lồng nhau hoặc một lớp hoàn toàn riêng biệt, không phải là lớp bên trong (không tĩnh).

Bước # 2: AsyncTaskGiữ Activitythông qua một thành viên dữ liệu, được đặt qua hàm tạo và bộ cài đặt.

Bước # 3: Khi tạo AsyncTask, cung cấp dòng điện Activitycho hàm tạo.

Bước # 4: Vào onRetainNonConfigurationInstance(), trả lại AsyncTask, sau khi tách nó ra khỏi hoạt động ban đầu, bây giờ sẽ biến mất.

Bước # 5: onCreate()Nếu getLastNonConfigurationInstance()không null, hãy đặt nó vào AsyncTasklớp của bạn và gọi setter của bạn để liên kết hoạt động mới của bạn với tác vụ.

Bước # 6: Không tham khảo thành viên dữ liệu hoạt động từ doInBackground().

Nếu bạn làm theo công thức trên, tất cả sẽ hoạt động. onProgressUpdate()onPostExecute()bị đình chỉ giữa bắt đầu onRetainNonConfigurationInstance()và kết thúc tiếp theo onCreate().

Dưới đây là một dự án mẫu thể hiện kỹ thuật.

Một cách tiếp cận khác là bỏ qua AsyncTaskvà chuyển công việc của bạn thành một IntentService. Điều này đặc biệt hữu ích nếu công việc cần hoàn thành có thể kéo dài và sẽ tiếp tục bất kể người dùng làm gì về mặt hoạt động (ví dụ: tải xuống một tệp lớn). Bạn có thể sử dụng một chương trình phát theo thứ tự Intentđể có hoạt động đáp ứng với công việc đang được thực hiện (nếu nó vẫn ở phía trước) hoặc nâng cao Notificationđể cho người dùng biết nếu công việc đã được thực hiện. Đây là một bài viết blog với nhiều hơn về mô hình này.


8
Cảm ơn rất nhiều cho câu trả lời tuyệt vời của bạn cho vấn đề phổ biến này! Để được kỹ lưỡng, bạn có thể thêm vào bước 4 mà chúng ta phải tách (đặt thành null) hoạt động trong AsyncTask. Điều này cũng được minh họa trong dự án mẫu.
Kevin Gaudin

3
Nhưng nếu tôi cần có quyền truy cập vào các thành viên của Activity thì sao?
Eugene

3
@Andrew: Tạo một lớp bên trong tĩnh hoặc một cái gì đó giữ trên một số đối tượng và trả về nó.
CommonsWare

11
onRetainNonConfigurationInstance()không được dùng nữa và giải pháp thay thế được đề xuất là sử dụng setRetainInstance(), nhưng nó không trả về một đối tượng. Có thể xử lý asyncTaskthay đổi cấu hình với setRetainInstance()?
Indrek Kõue

10
@SYLARRR: Hoàn toàn đúng. Có Fragmentgiữ các AsyncTask. Có Fragmentcuộc gọi setRetainInstance(true)theo chính nó. Có AsyncTaskchỉ nói chuyện với Fragment. Bây giờ, trên một thay đổi cấu hình, Fragmentkhông bị phá hủy và được tạo lại (mặc dù hoạt động là), và do đó, AsyncTaskđược giữ lại trong suốt thay đổi cấu hình.
CommonsWare

13

Câu trả lời được chấp nhận là rất hữu ích, nhưng nó không có hộp thoại tiến trình.

May mắn cho bạn, người đọc, tôi đã tạo ra một ví dụ cực kỳ toàn diện và hoạt động về AsyncTask với hộp thoại tiến trình !

  1. Xoay hoạt động, và hộp thoại 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 đoạn bên dưới hoạt động thay đổi đúng khi thiết bị quay.

Câu trả lời được chấp nhận là về các lớp tĩnh (không phải thành viên). Và những điều đó là cần thiết để tránh rằng AsyncTask có một con trỏ (ẩn) đến thể hiện của lớp bên ngoài, điều này sẽ trở thành rò rỉ bộ nhớ khi phá hủy hoạt động.
Tuneweizen

Vâng không chắc tại sao tôi lại nói về các thành viên tĩnh, vì tôi thực sự cũng đã sử dụng chúng ... kỳ lạ. Chỉnh sửa câu trả lời.
Timmmm

Bạn có thể vui lòng cập nhật liên kết của bạn? Tôi thực sự cần điều này.
Romain Pellerin

Xin lỗi, đã không có xung quanh để khôi phục trang web của tôi - Tôi sẽ làm điều đó sớm thôi! Nhưng về cơ bản, về cơ bản, nó giống như mã trong câu trả lời này: stackoverflow.com/questions/8417885/
Kẻ

1
Liên kết là không có thật; chỉ dẫn đến một chỉ mục vô dụng mà không có dấu hiệu cho biết mã ở đâu.
FractalBob

9

Tôi đã làm việc trong một tuần để tìm giải pháp cho vấn đề nan giải này mà không cần phải chỉnh sửa tệp kê khai. Các giả định cho giải pháp này là:

  1. Bạn luôn cần sử dụng hộp thoại tiến trình
  2. Mỗi lần chỉ có một nhiệm vụ được thực hiện
  3. Bạn cần duy trì tác vụ khi điện thoại được xoay và hộp thoại tiến trình sẽ tự động bị loại bỏ.

Thực hiện

Bạn sẽ cần sao chép hai tệp được tìm thấy ở dưới cùng của bài đăng này vào không gian làm việc của bạn. Chỉ cần chắc chắn rằng:

  1. Tất cả của bạn Activitynên mở rộngBaseActivity

  2. Trong onCreate(), super.onCreate()nên được gọi sau khi bạn khởi tạo bất kỳ thành viên nào cần được truy cập bởi ASyncTasks của bạn . Ngoài ra, ghi đè getContentViewId()để cung cấp id bố cục biểu mẫu.

  3. Ghi đè onCreateDialog() như bình thường để tạo các hộp thoại được quản lý bởi hoạt động.

  4. Xem mã bên dưới để biết một lớp bên trong tĩnh mẫu để tạo AsyncT task của bạn. Bạn có thể lưu trữ kết quả của mình trong mResult để truy cập sau.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

Và cuối cùng, để khởi động nhiệm vụ mới của bạn:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

Đó là nó!Tôi hy vọng giải pháp mạnh mẽ này sẽ giúp được ai đó.

BaseActivity.java (tự tổ chức nhập khẩu)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

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

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}

4

Có ai đó từ Google cung cấp một số "giải pháp chính thức" không?

Đúng.

Giải pháp không chỉ là đề xuất kiến ​​trúc ứng dụng chứ không phải chỉ là một số mã .

Họ đề xuất 3 mẫu thiết kế cho phép ứng dụng hoạt động đồng bộ với máy chủ, bất kể trạng thái ứng dụng (nó sẽ hoạt động ngay cả khi người dùng hoàn thành ứng dụng, người dùng thay đổi màn hình, ứng dụng bị chấm dứt, mọi trạng thái có thể khác một hoạt động dữ liệu nền có thể được xen vào, điều này bao gồm nó)

Đề xuất này được giải thích trong bài phát biểu về ứng dụng khách Android REST trong Google I / O 2010 của Virgil Dobjanschi. Nó dài 1 giờ, nhưng nó là cực kỳ đáng xem.

Cơ sở của nó là trừu tượng hóa các hoạt động mạng thành một Servicehoạt động độc lập với bất kỳ Activitytrong ứng dụng. Nếu bạn đang làm việc với cơ sở dữ liệu, việc sử dụng ContentResolverCursorsẽ cung cấp cho bạn mẫu Trình quan sát ngoài luồng thuận tiện để cập nhật giao diện người dùng mà không cần bất kỳ logic quảng cáo nào, khi bạn cập nhật cơ sở dữ liệu cục bộ của mình với dữ liệu từ xa được tìm nạp. Bất kỳ mã sau hoạt động nào khác sẽ được chạy thông qua một cuộc gọi lại được chuyển đến Service(Tôi sử dụng một ResultReceiverlớp con cho việc này).

Dù sao, lời giải thích của tôi thực sự khá mơ hồ, bạn chắc chắn nên xem bài phát biểu.


2

Mặc dù câu trả lời của Mark (CommonsWare) thực sự có tác dụng đối với thay đổi định hướng, nhưng nó không thành công nếu Hoạt động bị hủy trực tiếp (như trong trường hợp gọi điện thoại).

Bạn có thể xử lý các thay đổi hướng VÀ các sự kiện Hoạt động bị hủy hiếm gặp bằng cách sử dụng đối tượng Ứng dụng để tham chiếu ASyncTask của bạn.

Có một lời giải thích tuyệt vời về vấn đề và giải pháp ở đây :

Tín dụng hoàn toàn thuộc về Ryan vì đã tìm ra điều này.


1

Sau 4 năm, Google đã giải quyết vấn đề chỉ bằng cách gọi setRetainInstance (true) trong Activity onCreate. Nó sẽ bảo vệ trường hợp hoạt động của bạn trong quá trình xoay thiết bị. Tôi cũng có một giải pháp đơn giản cho Android cũ.


1
Vấn đề mà người quan sát xảy ra là do Android phá hủy một lớp hoạt động khi xoay, mở rộng bàn phím và các sự kiện khác, nhưng một tác vụ không đồng bộ vẫn giữ một tham chiếu cho trường hợp bị phá hủy và cố gắng sử dụng nó để cập nhật giao diện người dùng. Bạn có thể hướng dẫn Android không phá hủy hoạt động trong biểu hiện hoặc thực tế. Trong trường hợp này, tham chiếu tác vụ không đồng bộ vẫn hợp lệ và không có vấn đề nào được quan sát. Vì xoay vòng có thể yêu cầu một số công việc bổ sung như tải lại các chế độ xem, v.v., Google không khuyến nghị duy trì hoạt động. Vì vậy, bạn quyết định.
Singagirl 8/2/2015

Cảm ơn, tôi đã biết về tình huống này nhưng không phải về setRetainInstance (). Điều tôi không hiểu là tuyên bố của bạn rằng Google đã sử dụng điều này để giải quyết các vấn đề được hỏi trong câu hỏi. Bạn có thể liên kết nguồn thông tin? Cảm ơn.
jj_


onRetainNonConfigurationInstance () Hàm này được gọi hoàn toàn là một tối ưu hóa và bạn không được dựa vào nó được gọi. <Từ cùng một nguồn: developer.android.com/reference/android/app/ từ
Dhananjay M

0

bạn nên gọi tất cả các hành động hoạt động bằng cách sử dụng trình xử lý hoạt động. Vì vậy, nếu bạn đang ở trong một số chủ đề, bạn nên tạo Runnable và đăng bằng Trình xử lý của Activitie. Nếu không, ứng dụng của bạn sẽ bị sập đôi khi có ngoại lệ gây tử vong.


0

Đây là giải pháp của tôi: https://github.com/Gotchamoh/Android-AsyncTask-ProTHERDialog

Về cơ bản các bước là:

  1. tôi sử dụng onSaveInstanceState để lưu nhiệm vụ nếu nó vẫn đang xử lý.
  2. Trong onCreate tôi nhận nhiệm vụ nếu nó đã được lưu.
  3. Trong onPausetôi loại bỏProgressDialog nếu nó được hiển thị.
  4. Trong onResumetôi hiển thị ProgressDialognếu tác vụ vẫn đang xử lý.
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.