Cách xử lý AsyncTask trong khi Xoay màn hình?


88

Tôi đã đọc rất nhiều về cách lưu trạng thái phiên bản của mình hoặc cách đối phó với việc hoạt động của tôi bị phá hủy trong quá trình xoay màn hình.

Dường như có rất nhiều khả năng nhưng tôi chưa tìm ra cách nào hoạt động tốt nhất để truy xuất kết quả của AsyncTask.

Tôi có một số AsyncTasks chỉ đơn giản là bắt đầu lại và gọi isFinishing()phương thức của hoạt động và nếu hoạt động kết thúc, họ sẽ không cập nhật bất kỳ thứ gì.

Vấn đề là tôi có một Nhiệm vụ thực hiện một yêu cầu đến một dịch vụ web có thể không thành công hoặc không thành công và việc khởi động lại tác vụ sẽ dẫn đến thiệt hại tài chính cho người dùng.

Bạn sẽ giải quyết điều này như thế nào? Ưu điểm hoặc nhược điểm của các giải pháp khả thi là gì?


1
Xem câu trả lời của tôi tại đây . Bạn cũng có thể tìm thấy thông tin này về những gì setRetainInstance(true)thực sự hữu ích.
Timmmm

những gì tôi sẽ làm chỉ đơn giản là triển khai một dịch vụ cục bộ thực hiện xử lý (trong một luồng) mà asyncTask của bạn đang thực hiện. Để hiển thị kết quả, hãy truyền dữ liệu cho hoạt động của bạn. Giờ đây, hoạt động chỉ có nhiệm vụ hiển thị dữ liệu và quá trình xử lý không bao giờ bị gián đoạn do xoay màn hình.
Ai đó ở đâu đó

Điều gì về việc sử dụng AsyncTaskLoader thay vì AsyncTask ??
Sourangshu Biswas

Câu trả lời:


6

Đề xuất đầu tiên của tôi là đảm bảo rằng bạn thực sự cần đặt lại hoạt động của mình khi xoay màn hình (hành vi mặc định). Mỗi khi gặp vấn đề với việc xoay vòng, tôi đã thêm thuộc tính này vào <activity>thẻ của mình trong AndroidManifest.xml và vẫn ổn.

android:configChanges="keyboardHidden|orientation"

Nó trông có vẻ kỳ lạ, nhưng nó có tác dụng gì đối với onConfigurationChanged()phương pháp của bạn , nếu bạn không cung cấp nó, nó chỉ không làm gì khác hơn là đo lại bố cục, có vẻ là một cách hoàn toàn phù hợp để xử lý xoay vòng hầu hết thời gian .


5
Nhưng điều đó sẽ ngăn Activity thay đổi bố cục. Và do đó, buộc người dùng phải sử dụng thiết bị của mình theo một hướng cụ thể do ứng dụng của bạn quy định chứ không phải theo nhu cầu của họ.
Janusz

77
Sử dụng kỹ thuật này ngăn bạn dễ dàng sử dụng các tài nguyên cụ thể của cấu hình. Ví dụ: nếu bạn muốn bố cục hoặc các bảng vẽ hoặc chuỗi của mình hoặc bất cứ thứ gì khác ở chế độ dọc và ngang, bạn sẽ muốn hành vi mặc định. Ghi đè thay đổi cấu hình chỉ nên được thực hiện trong các trường hợp rất cụ thể (trò chơi, trình duyệt web, v.v.) và không vì lười biếng hoặc tiện lợi vì bạn đang tự ràng buộc mình.
Romain Guy

38
Đó chỉ là nó, Romain. "nếu bạn muốn bố cục hoặc đồ có thể vẽ hoặc chuỗi hoặc bất cứ thứ gì khác biệt ở dạng dọc và dạng ngang, bạn sẽ muốn có hành vi mặc định", tôi tin rằng đó là trường hợp sử dụng hiếm hơn nhiều so với bạn tưởng tượng. Những gì bạn gọi là "trường hợp rất cụ thể" là hầu hết các nhà phát triển tôi tin. Sử dụng bố cục tương đối hoạt động trong mọi thứ nguyên là phương pháp hay nhất và nó không quá khó. Nói về sự lười biếng là rất sai lầm, những kỹ thuật này là để cải thiện trải nghiệm người dùng chứ không làm giảm thời gian phát triển.
Jim Blackler

2
Tôi nhận thấy rằng điều này hoạt động hoàn hảo cho LinearLayout, nhưng khi sử dụng RelativeLayout, nó không vẽ lại bố cục một cách chính xác khi chuyển sang chế độ ngang (ít nhất là không phải trên N1). Xem câu hỏi này: stackoverflow.com/questions/2987049/…
JohnRock

9
Tôi đồng ý với Romain (anh ấy biết mình đang nói gì, anh ấy phát triển hệ điều hành). Điều gì xảy ra khi bạn muốn chuyển ứng dụng của mình sang máy tính bảng và giao diện người dùng của bạn trông rất kinh khủng khi kéo dài? Nếu bạn thực hiện phương pháp của câu trả lời này, bạn sẽ cần phải mã hóa lại toàn bộ giải pháp của mình vì bạn đã thực hiện với cách hack lười biếng này.
Austyn Mahoney

46

Bạn có thể xem cách tôi xử lý AsyncTaskvà thay đổi hướng tại code.google.com/p/shelves . Có nhiều cách khác nhau để thực hiện, cách tôi chọn trong ứng dụng này là hủy bất kỳ tác vụ nào hiện đang chạy, lưu trạng thái của nó và bắt đầu một tác vụ mới với trạng thái đã lưu khi tác vụ mới Activityđược tạo. Thật dễ dàng để thực hiện, nó hoạt động tốt và một phần thưởng nữa là nó có nhiệm vụ dừng các tác vụ của bạn khi người dùng rời khỏi ứng dụng.

Bạn cũng có thể sử dụng onRetainNonConfigurationInstance()để chuyển AsyncTasksang cái mới Activity(hãy cẩn thận để không làm rò rỉ cái trước Activitytheo cách này.)


1
tôi đã cố gắng nó và quay trong cuốn sách ngắt tìm kiếm và mang lại cho tôi kết quả ít hơn khi không quay, quá xấu
max4ever

1
Tôi không thể tìm thấy một cách sử dụng AsyncTask nào trong mã đó. Có một lớp UserTask trông tương tự. Dự án này có trước AsyncTask không?
devconsole

7
AsyncTask đến từ UserTask. Ban đầu tôi viết UserTask cho các ứng dụng của riêng mình và sau đó chuyển nó thành AsyncTask. Xin lỗi về điều đó, tôi quên nó đã được đổi tên.
Romain Guy

@RomainGuy Chào bạn, hy vọng bạn khỏe. Theo mã của bạn, 2 yêu cầu được gửi đến máy chủ mặc dù lúc đầu tác vụ bị hủy nhưng nó không được hủy thành công. Tôi không biết tại sao. Bạn vui lòng cho tôi biết có cách nào để giải quyết vấn đề này không.
iamcrypticcoder

10

Đây là câu hỏi thú vị nhất mà tôi đã thấy liên quan đến Android !!! Trên thực tế, tôi đã tìm kiếm giải pháp trong suốt những tháng qua. Vẫn chưa giải quyết được.

Hãy cẩn thận, chỉ cần ghi đè

android:configChanges="keyboardHidden|orientation"

công cụ là không đủ.

Hãy xem xét trường hợp người dùng nhận được cuộc gọi điện thoại trong khi AsyncTask của bạn đang chạy. Yêu cầu của bạn đã được máy chủ xử lý, vì vậy AsyncTask đang chờ phản hồi. Trong thời điểm này, ứng dụng của bạn ở chế độ nền, vì ứng dụng Điện thoại vừa xuất hiện ở phía trước. Hệ điều hành có thể giết hoạt động của bạn vì nó ở chế độ nền.


6

Tại sao bạn không luôn giữ tham chiếu đến AsyncTask hiện tại trên Singleton do Android cung cấp?

Bất cứ khi nào một tác vụ bắt đầu, trên PreExecute hoặc trên trình tạo, bạn xác định:

((Application) getApplication()).setCurrentTask(asyncTask);

Bất cứ khi nào nó kết thúc, bạn đặt nó thành null.

Bằng cách đó, bạn luôn có một tham chiếu cho phép bạn thực hiện một số việc như onCreate hoặc onResume được sử dụng cho logic cụ thể của bạn:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

Nếu nó rỗng, bạn biết rằng hiện tại không có gì đang chạy!

:-)


Điều này sẽ hoạt động? Có ai đã kiểm tra điều này? Hệ thống sẽ tiếp tục thực hiện nhiệm vụ nếu một cuộc gọi điện thoại bị gián đoạn hoặc nếu chúng tôi điều hướng đến một hoạt động mới và sau đó quay lại?
Robert

6
Applicationinstance có vòng đời riêng của nó - 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ó tái tạo.
Vit Khudenko

7
Tôi nghĩ: nếu Ứng dụng bị giết, toàn bộ ứng dụng sẽ bị giết (và do đó, tất cả các AsyncTasks cũng vậy)?
manmal

Tôi nghĩ rằng ứng dụng đó có thể bị giết mà không cần tất cả các asynctasks (rất hiếm). Nhưng @Arhimed với một số xác minh dễ thực hiện khi bắt đầu và kết thúc mỗi asynctask, bạn có thể tránh được các lỗi.
neteinstein

5

Cách thích hợp nhất để làm điều này là sử dụng một phân đoạn để giữ lại phiên bản của tác vụ không đồng bộ, qua các phép quay.

Đây là một liên kết đến ví dụ rất đơn giản giúp bạn dễ dàng thực hiện tích hợp kỹ thuật này vào ứng dụng của mình.

https://gist.github.com/daichan4649/2480065


Đây là một hướng dẫn khác sử dụng các phân đoạn được giữ lại: blogactivity.wordpress.com/2011/09/01/proper-use-of-asynctask
devconsole


3

Theo quan điểm của tôi, tốt hơn nên lưu trữ asynctask thông qua onRetainNonConfigurationInstancetách nó khỏi đối tượng Activity hiện tại và liên kết nó với một đối tượng Activity mới sau khi thay đổi hướng. Ở đây tôi đã tìm thấy một ví dụ rất hay về cách làm việc với AsyncTask và ProgressDialog.


2

Android: xử lý nền / Hoạt động không đồng bộ với thay đổi cấu hình

Để duy trì trạng thái hoạt động không đồng bộ trong quá trình chạy nền: bạn có thể nhờ sự trợ giúp của các phân đoạn.

Xem các bước sau:

Bước 1: Tạo một phân đoạn không có tiêu đề, giả sử tác vụ nền và thêm một lớp tác vụ không đồng bộ riêng với nó.

Bước 2 (Bước tùy chọn): nếu bạn muốn đặt con trỏ tải lên trên hoạt động của mình, hãy sử dụng mã dưới đây:

Bước 3: Trong Hoạt động chính của bạn, triển khai giao diện BackgroundTaskCallbacks được xác định ở bước 1

class BackgroundTask extends Fragment {
public BackgroundTask() {

}

// Add a static interface 

static interface BackgroundTaskCallbacks {
    void onPreExecute();

    void onCancelled();

    void onPostExecute();

    void doInBackground();
}

private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();

/**
 * Start the async operation.
 */
public void start() {
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
    if (!isRunning) {
        asyncOperation = new PerformAsyncOpeation();
        asyncOperation.execute();
        isRunning = true;
    }
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}

/**
 * Cancel the background task.
 */
public void cancel() {
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
    if (isRunning) {
        asyncOperation.cancel(false);
        asyncOperation = null;
        isRunning = false;
    }
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}

/**
 * Returns the current state of the background task.
 */
public boolean isRunning() {
    return isRunning;
}

/**
 * Android passes us a reference to the newly created Activity by calling
 * this method after each configuration change.
 */
public void onAttach(Activity activity) {
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
    super.onAttach(activity);
    if (!(activity instanceof BackgroundTaskCallbacks)) {
        throw new IllegalStateException(
                "Activity must implement the LoginCallbacks interface.");
    }

    // Hold a reference to the parent Activity so we can report back the
    // task's
    // current progress and results.
    callbacks = (BackgroundTaskCallbacks) activity;
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}

public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}

public void onDetach() {
    super.onDetach();
    callbacks = null;
}

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
    protected void onPreExecute() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPreExecute();
        }
        isRunning = true;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
    }

    protected Void doInBackground(Void... params) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
        if (callbacks != null) {
            callbacks.doInBackground();
        }
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
        return null;
    }

    protected void onCancelled() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
        if (callbacks != null) {
            callbacks.onCancelled();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
    }

    protected void onPostExecute(Void ignore) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPostExecute();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
    }
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
}

public void onStart() {
    super.onStart();
}

public void onResume() {
    super.onResume();
}

public void onPause() {
    super.onPause();
}

public void onStop() {
    super.onStop();
}

public class ProgressIndicator extends Dialog {

public ProgressIndicator(Context context, int theme) {
    super(context, theme);
}

private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.progress_indicator);
    this.setCancelable(false);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}

@Override
public void show() {
    super.show();
}

@Override
public void dismiss() {
    super.dismiss();
}

@Override
public void cancel() {
    super.cancel();
}

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{

private static final String KEY_CURRENT_PROGRESS = "current_progress";

ProgressIndicator progressIndicator = null;

private final static String TAG = MyActivity.class.getSimpleName();

private BackgroundTask task = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(//"set your layout here");
    initialize your views and widget here .............



    FragmentManager fm = getSupportFragmentManager();
    task = (BackgroundTask) fm.findFragmentByTag("login");

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (task == null) {
        task = new BackgroundTask();
        fm.beginTransaction().add(task, "login").commit();
    }

    // Restore saved state
    if (savedInstanceState != null) {
        Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
                + task.isRunning());
        if (task.isRunning()) {
            progressIndicator = new ProgressIndicator(this,
                    R.style.TransparentDialog);
            if (progressIndicator != null) {
                progressIndicator.show();
            }
        }
    }
}

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState);
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
            + task.isRunning());
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}


private void performOperation() {

            if (!task.isRunning() && progressIndicator == null) {
                progressIndicator = new ProgressIndicator(this,
                        R.style.TransparentDialog);
                progressIndicator.show();
            }
            if (task.isRunning()) {
                task.cancel();
            } else {
                task.start();
            }
        }


@Override
protected void onDestroy() {
    super.onDestroy();
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}

@Override
public void onPreExecute() {
    Log.i(TAG, "CALLING ON PRE EXECUTE");
}

@Override
public void onCancelled() {
    Log.i(TAG, "CALLING ON CANCELLED");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();

}

public void onPostExecute() {
    Log.i(TAG, "CALLING ON POST EXECUTE");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
        progressIndicator = null;
    }
}

@Override
public void doInBackground() {
    // put your code here for background operation
}

}


1

Một điều cần xem xét là liệu kết quả của AsyncTask có nên chỉ dành cho hoạt động đã bắt đầu tác vụ hay không. Nếu có, thì câu trả lời của Romain Guy là tốt nhất. Nếu nó có sẵn cho các hoạt động khác của ứng dụng của bạn, thì onPostExecutebạn có thể sử dụng LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

Bạn cũng sẽ cần đảm bảo rằng hoạt động xử lý chính xác tình huống khi chương trình phát sóng được gửi đi trong khi hoạt động bị tạm dừng.


1

Có một cái nhìn lúc này bài . Bài đăng này liên quan đến việc AsyncTask thực hiện hoạt động chạy lâu và rò rỉ bộ nhớ khi xoay màn hình xảy ra cả trong một ứng dụng mẫu. Ứng dụng mẫu có sẵn trên nguồn giả mạo


0

Giải pháp của tôi.

Trong trường hợp của tôi, tôi có một chuỗi AsyncTasks có cùng ngữ cảnh. Hoạt động chỉ có quyền truy cập vào cái đầu tiên. Để hủy bất kỳ tác vụ đang chạy nào, tôi đã làm như sau:

public final class TaskLoader {

private static AsyncTask task;

     private TaskLoader() {
         throw new UnsupportedOperationException();
     }

     public static void setTask(AsyncTask task) {
         TaskLoader.task = task;
     }

    public static void cancel() {
         TaskLoader.task.cancel(true);
     }
}

Nhiệm vụ doInBackground():

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

Hoạt động onStop()hoặc onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}

0
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    final AddTask task = mAddTask;
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
        final String bookId = task.getBookId();
        task.cancel(true);

        if (bookId != null) {
            outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
            outState.putString(STATE_ADD_BOOK, bookId);
        }

        mAddTask = null;
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
        final String id = savedInstanceState.getString(STATE_ADD_BOOK);
        if (!BooksManager.bookExists(getContentResolver(), id)) {
            mAddTask = (AddTask) new AddTask().execute(id);
        }
    }
}

0

bạn cũng có thể thêm android: configChanges = "keyboardHidden | orient | screenSize"

với ví dụ kê khai của bạn, tôi hy vọng nó sẽ giúp

 <application
    android:name=".AppController"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:theme="@style/AppTheme">
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.