Làm cách nào để xử lý thay đổi hướng màn hình khi hộp thoại tiến trình và luồng nền hoạt động?


524

Chương trình của tôi thực hiện một số hoạt động mạng trong một luồng nền. Trước khi bắt đầu, nó bật lên một hộp thoại tiến trình. Hộp thoại bị loại bỏ trên trình xử lý. Tất cả đều hoạt động tốt, ngoại trừ khi hướng màn hình thay đổi trong khi hộp thoại bật lên (và luồng nền đang diễn ra). Tại thời điểm này, ứng dụng sẽ gặp sự cố hoặc bị khóa hoặc rơi vào giai đoạn kỳ lạ khi ứng dụng hoàn toàn không hoạt động cho đến khi tất cả các luồng đã bị giết.

Làm thế nào tôi có thể xử lý thay đổi hướng màn hình duyên dáng?

Mã mẫu dưới đây phù hợp với những gì chương trình thực sự của tôi làm:

public class MyAct extends Activity implements Runnable {
    public ProgressDialog mProgress;

    // UI has a button that when pressed calls send

    public void send() {
         mProgress = ProgressDialog.show(this, "Please wait", 
                      "Please wait", 
                      true, true);
        Thread thread = new Thread(this);
        thread.start();
    }

    public void run() {
        Thread.sleep(10000);
        Message msg = new Message();
        mHandler.sendMessage(msg);
    }

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mProgress.dismiss();
        }
    };
}

Cây rơm:

E/WindowManager(  244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244):     at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager(  244):     at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager(  244):     at android.app.Dialog.show(Dialog.java:212)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager(  244):     at MyAct.send(MyAct.java:294)
E/WindowManager(  244):     at MyAct$4.onClick(MyAct.java:174)
E/WindowManager(  244):     at android.view.View.performClick(View.java:2129)
E/WindowManager(  244):     at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager(  244):     at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager(  244):     at android.view.View.dispatchTouchEvent(View.java:3198)

Tôi đã cố gắng bỏ qua hộp thoại tiến trình trong onSaveInstanceState, nhưng điều đó chỉ ngăn chặn sự cố ngay lập tức. Chủ đề nền vẫn đang diễn ra và giao diện người dùng ở trạng thái được vẽ một phần. Cần phải giết toàn bộ ứng dụng trước khi nó bắt đầu hoạt động trở lại.


1
Xem xét các câu trả lời bạn đã nhận được, bạn nên thay đổi câu trả lời được chấp nhận theo hướng tốt nhất, phải không?
rds

Xem thêm một câu hỏi cũ stackoverflow.com/questions/456211/ từ
rds 21/03

3
Tất cả, Có một lời giải thích thực sự tuyệt vời và giải pháp có thể cho vấn đề này. Đi qua http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ Lemme biết nếu điều này giúp đỡ.
arcamax

2
Có một lời giải thích khá đầy đủ về cách giữ lại các tác vụ nền không đồng bộ trên các định hướng màn hình trong bài đăng trên blog này . Kiểm tra nó ra!
sư Adrian

Đơn giản chỉ cần đặt android: configChanges = "direction | screenSize" thành Activity in manifest. Nó sẽ dừng android để tạo lại hoạt động của nó
Jawad Zeb

Câu trả lời:


155

Khi bạn chuyển hướng, Android sẽ tạo Chế độ xem mới. Bạn có thể gặp sự cố vì luồng nền của bạn đang cố gắng thay đổi trạng thái trên trạng thái cũ. (Nó cũng có thể gặp sự cố vì luồng nền của bạn không nằm trên luồng UI)

Tôi khuyên bạn nên làm cho mHandler biến động và cập nhật nó khi định hướng thay đổi.


14
Bạn có thể đã xác định chính xác lý do cho vụ tai nạn. Tôi đã thoát khỏi sự cố, nhưng tôi vẫn chưa tìm ra cách khôi phục giao diện người dùng về trạng thái trước khi thay đổi định hướng một cách đáng tin cậy. Nhưng câu trả lời của bạn đã đưa tôi về phía trước, vì vậy trao giải nó như là câu trả lời.
Heikki Toivonen

4
Bạn sẽ nhận được một OnStart trong hoạt động của mình khi định hướng thay đổi. Về cơ bản, bạn phải cấu hình lại chế độ xem bằng dữ liệu cũ. Vì vậy, tôi khuyên bạn nên yêu cầu trạng thái số xuất phát từ thanh tiến trình và xây dựng lại chế độ xem mới khi bạn nhận được 'onStart' mới đó. Tôi không thể nhớ được nếu bạn có một hoạt động mới nhưng một số hoạt động tìm kiếm tài liệu sẽ giúp ích.
haseman

6
Đã chơi với nó gần đây, tôi có thể tin rằng bạn sẽ có một hoạt động mới khi ứng dụng của bạn thay đổi hướng. (Bạn cũng có được chế độ xem mới) Nếu bạn cố cập nhật chế độ xem cũ, bạn sẽ có một ngoại lệ vì chế độ xem cũ có bối cảnh ứng dụng không hợp lệ (hoạt động cũ của bạn) Bạn có thể khắc phục điều này bằng cách chuyển vào myActivity.getApplicationContext () thay vì một con trỏ đến chính hoạt động.
haseman

1
Ai đó có thể giải thích việc sử dụng / lợi ích của sự biến động trong bối cảnh này không
Jawad Zeb

2
@Nepster Vâng, tôi cũng đã tự hỏi về điều đó. Sẽ thật tuyệt nếu ai đó giải thích về sự biến động.
RestInPeace

261

Chỉnh sửa: Các kỹ sư của Google không đề xuất phương pháp này, như được mô tả bởi Dianne Hackborn (còn gọi là hackbod ) trong bài đăng StackOverflow này . Kiểm tra bài viết trên blog này để biết thêm thông tin.


Bạn phải thêm phần này vào phần khai báo hoạt động trong tệp kê khai:

android:configChanges="orientation|screenSize"

nó trông giống như

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

Vấn đề là hệ thống sẽ phá hủy hoạt động khi có sự thay đổi trong cấu hình. Xem Cấu hình thay đổi .

Vì vậy, đặt nó trong tập tin cấu hình sẽ tránh hệ thống phá hủy hoạt động của bạn. Thay vào đó, nó gọi onConfigurationChanged(Configuration)phương thức.


21
Đây chắc chắn là giải pháp tốt nhất; vì nó chỉ đơn giản là xoay bố cục (hành vi bạn mong đợi ở vị trí đầu tiên). Chỉ cần chắc chắn đặt android: configChanges = "direction | keyboardHidden" (vì điện thoại có bàn phím nằm ngang)
nikib3ro

24
Đây dường như là hành vi tôi mong đợi. Tuy nhiên, tài liệu chỉ ra rằng hoạt động bị hủy "vì mọi tài nguyên ứng dụng, bao gồm các tệp bố cục, có thể thay đổi dựa trên bất kỳ giá trị cấu hình nào. Do đó, cách an toàn duy nhất để xử lý thay đổi cấu hình là truy xuất lại tất cả tài nguyên". Và bên cạnh đó orientation, còn nhiều lý do nữa để cấu hình thay đổi: keyboardHidden(Tôi đã chỉnh sửa câu trả lời wiki rồi), uiMode(Ví dụ: đi vào hoặc ra khỏi chế độ xe hơi, thay đổi chế độ ban đêm), v.v ... Bây giờ tôi tự hỏi liệu đây có thực sự là một câu trả lời tốt
rds

116
Đây không phải là một giải pháp chấp nhận được. Nó chỉ che giấu vấn đề thực sự.
rf43

18
Hoạt động, nhưng không được Google khuyến nghị.
Ed Burnette

21
Xin vui lòng không làm theo phương pháp này ở đây. DDosAttack là hoàn toàn đúng. Hãy tưởng tượng bạn đang tạo một hộp thoại tiến trình để tải xuống hoặc một cái gì đó mất nhiều thời gian. Là người dùng, bạn sẽ không ở lại hoạt động đó và nhìn chằm chằm vào nó. Bạn sẽ chuyển sang màn hình chính hoặc sang một ứng dụng khác như trò chơi hoặc một cuộc gọi điện thoại có thể đến hoặc thứ gì đó đói tài nguyên khác cuối cùng sẽ phá hủy hoạt động của bạn. Và sau đó thì sao? Bạn đang đối mặt với cùng một vấn đề cũ mà KHÔNG được giải quyết bằng thủ thuật nhỏ gọn đó. Các hoạt động sẽ được tạo lại tất cả một lần nữa khi người dùng quay trở lại.
tiguchi

68

Tôi đã đưa ra một giải pháp vững chắc cho những vấn đề phù hợp với 'Cách thức Android'. Tôi có tất cả các hoạt động dài hạn của mình bằng cách sử dụng mẫu IntentService.

Đó là, các hoạt động của tôi phát ra ý định, IntentService thực hiện công việc, lưu dữ liệu trong DB và sau đó phát các ý định dính . Phần dính rất quan trọng, sao cho ngay cả khi Hoạt động bị tạm dừng trong suốt thời gian sau khi người dùng bắt đầu công việc và bỏ lỡ thời gian thực phát từ IntentService, chúng tôi vẫn có thể trả lời và nhận dữ liệu từ Hoạt động gọi. ProgressDialogs có thể làm việc với mô hình này khá độc đáo với onSaveInstanceState().

Về cơ bản, bạn cần lưu một cờ mà bạn có hộp thoại tiến trình đang chạy trong gói đối tượng đã lưu. Không lưu đối tượng hộp thoại tiến trình vì điều này sẽ rò rỉ toàn bộ Hoạt động. Để có một xử lý liên tục cho hộp thoại tiến trình, tôi lưu trữ nó như một tài liệu tham khảo yếu trong đối tượng ứng dụng. Khi thay đổi hướng hoặc bất cứ điều gì khác khiến Activity tạm dừng (cuộc gọi điện thoại, người dùng về nhà, v.v.) rồi tiếp tục, tôi bỏ qua hộp thoại cũ và tạo lại hộp thoại mới trong Activity mới được tạo.

Đối với các hộp thoại tiến trình không xác định, điều này là dễ dàng. Đối với kiểu thanh tiến trình, bạn phải đặt tiến trình đã biết cuối cùng trong gói và bất kỳ thông tin nào bạn đang sử dụng cục bộ trong hoạt động để theo dõi tiến trình. Khi khôi phục tiến trình, bạn sẽ sử dụng thông tin này để tạo lại thanh tiến trình ở trạng thái như trước và sau đó cập nhật dựa trên trạng thái hiện tại.

Vì vậy, để tóm tắt, việc đưa các tác vụ chạy dài vào IntentService kết hợp với việc sử dụng hợp lý onSaveInstanceState()cho phép bạn theo dõi hiệu quả các hộp thoại và khôi phục sau đó trong các sự kiện vòng đời Hoạt động. Dưới đây là các bit có liên quan của mã Activity. Bạn cũng sẽ cần logic trong BroadcastReceiver để xử lý các ý định dính một cách thích hợp, nhưng điều đó nằm ngoài phạm vi của điều này.

public void doSignIn(View view) {
    waiting=true;
    AppClass app=(AppClass) getApplication();
    String logingon=getString(R.string.signon);
    app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
    ...
}

@Override
protected void onSaveInstanceState(Bundle saveState) {
    super.onSaveInstanceState(saveState);
    saveState.putBoolean("waiting",waiting);
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState!=null) {
        restoreProgress(savedInstanceState);    
    }
    ...
}

private void restoreProgress(Bundle savedInstanceState) {
    waiting=savedInstanceState.getBoolean("waiting");
    if (waiting) {
        AppClass app=(AppClass) getApplication();
        ProgressDialog refresher=(ProgressDialog) app.Dialog.get();
        refresher.dismiss();
        String logingon=getString(R.string.signon);
        app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
    }
}

có vẻ là một giải pháp tốt
Derekyy

"Tôi có tất cả các hoạt động dài hạn của mình bằng cách sử dụng mẫu IntentService." Đây không phải là một giải pháp hoàn hảo vì nó giống như bắn ra đại bác vào chim sẻ và rất nhiều mã
nồi hơi

28

Tôi đã gặp vấn đề tương tự. Hoạt động của tôi cần phân tích một số dữ liệu từ một URL và nó chậm. Vì vậy, tôi tạo một chủ đề để làm như vậy, sau đó hiển thị một hộp thoại tiến trình. Tôi để chủ đề gửi một tin nhắn trở lại chủ đề UI thông qua Handlerkhi nó kết thúc. Trong Handler.handleMessage, tôi lấy đối tượng dữ liệu (sẵn sàng ngay bây giờ) từ luồng và đưa nó vào UI. Vì vậy, nó rất giống với ví dụ của bạn.

Sau rất nhiều thử nghiệm và lỗi, có vẻ như tôi đã tìm ra giải pháp. Ít nhất bây giờ tôi có thể xoay màn hình bất cứ lúc nào, trước hoặc sau khi hoàn thành chuỗi. Trong tất cả các thử nghiệm, hộp thoại được đóng đúng cách và tất cả các hành vi đều như mong đợi.

Những gì tôi đã làm được hiển thị dưới đây. Mục tiêu là điền vào mô hình dữ liệu của tôi ( mDataObject) và sau đó đưa nó vào UI. Nên cho phép xoay màn hình bất cứ lúc nào mà không bất ngờ.

class MyActivity {

    private MyDataObject mDataObject = null;
    private static MyThread mParserThread = null; // static, or make it singleton

    OnCreate() {
        ...
        Object retained = this.getLastNonConfigurationInstance();
        if(retained != null) {
            // data is already completely obtained before config change
            // by my previous self.
            // no need to create thread or show dialog at all
            mDataObject = (MyDataObject) retained;
            populateUI();
        } else if(mParserThread != null && mParserThread.isAlive()){
            // note: mParserThread is a static member or singleton object.
            // config changed during parsing in previous instance. swap handler
            // then wait for it to finish.
            mParserThread.setHandler(new MyHandler());
        } else {
            // no data and no thread. likely initial run
            // create thread, show dialog
            mParserThread = new MyThread(..., new MyHandler());
            mParserThread.start();
            showDialog(DIALOG_PROGRESS);
        }
    }

    // http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html
    public Object onRetainNonConfigurationInstance() {
        // my future self can get this without re-downloading
        // if it's already ready.
        return mDataObject;
    }

    // use Activity.showDialog instead of ProgressDialog.show
    // so the dialog can be automatically managed across config change
    @Override
    protected Dialog onCreateDialog(int id) {
        // show progress dialog here
    }

    // inner class of MyActivity
    private class MyHandler extends Handler {
        public void handleMessage(msg) {
            mDataObject = mParserThread.getDataObject();
            populateUI();
            dismissDialog(DIALOG_PROGRESS);
        }
    }
}

class MyThread extends Thread {
    Handler mHandler;
    MyDataObject mDataObject;

    // constructor with handler param
    public MyHandler(..., Handler h) {
        ...
        mHandler = h;
    }

    public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
    public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller

    public void run() {
        mDataObject = new MyDataObject();
        // do the lengthy task to fill mDataObject with data
        lengthyTask(mDataObject);
        // done. notify activity
        mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
    }
}

Đó là những gì làm việc cho tôi. Tôi không biết liệu đây có phải là phương pháp "chính xác" như được thiết kế bởi Android hay không - họ cho rằng "phá hủy / tái tạo hoạt động trong khi xoay màn hình" thực sự giúp mọi việc dễ dàng hơn, vì vậy tôi đoán nó không quá phức tạp.

Hãy cho tôi biết nếu bạn thấy một vấn đề trong mã của tôi. Như đã nói ở trên tôi không thực sự biết nếu có bất kỳ tác dụng phụ.


1
Cảm ơn rất nhiều! Các gợi ý onRetainNonConfigurationInstance()getLastNonConfigurationInstance()giúp tôi giải quyết vấn đề của tôi. Đồng ý
Sven

15

Vấn đề nhận thức ban đầu là mã sẽ không tồn tại thay đổi hướng màn hình. Rõ ràng điều này đã được "giải quyết" bằng cách chương trình tự xử lý thay đổi hướng màn hình, thay vì để khung UI làm điều đó (thông qua cách gọi onDestroy)).

Tôi sẽ gửi rằng nếu vấn đề tiềm ẩn là chương trình sẽ không tồn tại trênDestroy (), thì giải pháp được chấp nhận chỉ là một cách giải quyết khiến chương trình gặp các vấn đề và lỗ hổng nghiêm trọng khác. Hãy nhớ rằng khung Android đặc biệt tuyên bố rằng hoạt động của bạn có nguy cơ bị phá hủy gần như bất cứ lúc nào do các trường hợp nằm ngoài sự kiểm soát của bạn. Do đó, hoạt động của bạn phải có khả năng tồn tại trên onDestroy () và tiếp theo onCreate () vì bất kỳ lý do nào, không chỉ là thay đổi hướng màn hình.

Nếu bạn chấp nhận xử lý thay đổi hướng màn hình để tự giải quyết vấn đề của OP, bạn cần xác minh rằng các nguyên nhân khác của onDestroy () không dẫn đến lỗi tương tự. Bạn có thể làm điều này? Nếu không, tôi sẽ hỏi liệu câu trả lời "được chấp nhận" có thực sự là một câu trả lời hay không.


14

Giải pháp của tôi là mở rộng ProgressDialoglớp học để có được của riêng tôi MyProgressDialog.
Tôi đã xác định lại show()dismiss()các phương pháp để khóa hướng trước khi hiển thị Dialogvà mở khóa lại khi Dialogbị loại bỏ. Vì vậy, khi Dialoghiển thị và hướng của thiết bị thay đổi, hướng của màn hình sẽ duy trì cho đến khi dismiss()được gọi, sau đó hướng màn hình sẽ thay đổi theo giá trị cảm biến / hướng thiết bị.

Đây là mã của tôi:

public class MyProgressDialog extends ProgressDialog {
private Context mContext;

public MyProgressDialog(Context context) {
    super(context);
    mContext = context;
}

public MyProgressDialog(Context context, int theme) {
    super(context, theme);
    mContext = context;
}

public void show() {
    if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    else
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    super.show();
}

public void dismiss() {
    super.dismiss();
    ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}

}

8

Tôi đã đối mặt với vấn đề tương tự, và tôi đã đưa ra một giải pháp không được sử dụng bằng ProgressDialog và tôi nhận được kết quả nhanh hơn.

Những gì tôi đã làm là tạo ra một bố cục có ProgressBar trong đó.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ProgressBar
    android:id="@+id/progressImage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    />
</RelativeLayout>

Sau đó, trong phương thức onCreate làm như sau

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.progress);
}

Sau đó thực hiện tác vụ dài trong một chuỗi và khi hoàn thành, Runnable sẽ đặt chế độ xem nội dung thành bố cục thực mà bạn muốn sử dụng cho hoạt động này.

Ví dụ:

mHandler.post(new Runnable(){

public void run() {
        setContentView(R.layout.my_layout);
    } 
});

Đây là những gì tôi đã làm và tôi thấy rằng nó chạy nhanh hơn hiển thị ProgressDialog và nó ít xâm phạm hơn và theo quan điểm của tôi tốt hơn.

Tuy nhiên, nếu bạn muốn sử dụng ProgressDialog, thì câu trả lời này không dành cho bạn.


Giải pháp này là thanh lịch trong một trường hợp sử dụng đơn giản, nhưng có nhược điểm. Bạn cần xây dựng lại chế độ xem nội dung đầy đủ. setContentView(R.layout.my_layout);nó không thích đáng; bạn cần đặt tất cả người nghe, đặt lại dữ liệu, v.v.
rds

@rds bạn nói đúng. Đây thực sự chỉ là một giải pháp cho một trường hợp đơn giản hoặc nếu bạn cần thực hiện một số thao tác nặng trong phương pháp onCreate trước khi hiển thị chế độ xem của bạn.
Pzanno

Tôi không hiểu lắm. Thay vì cài đặt trình nghe trong onCreate (), như chúng ta thường làm, chúng ta có thể thiết lập chúng trong run (). Am i thiếu cái gì ở đây?
Mã nhà thơ

7

Tôi phát hiện ra một giải pháp cho vấn đề này mà tôi chưa thấy ở nơi nào khác. Bạn có thể sử dụng một đối tượng ứng dụng tùy chỉnh để biết liệu bạn có thực hiện các tác vụ nền hay không, thay vì cố gắng thực hiện điều này trong hoạt động bị phá hủy và được tạo lại khi thay đổi hướng. Tôi viết blog về điều này ở đây .


1
Tạo một tùy chỉnh Applicationthường được sử dụng để duy trì trạng thái ứng dụng toàn cầu. Tôi không nói rằng nó không hoạt động, nhưng nó có vẻ quá phức tạp. Từ tài liệu "Thông thường không cần phải phân lớp Ứng dụng.". Tôi phần lớn thích câu trả lời của sonxurxo.
rds

7

Tôi sẽ đóng góp cách tiếp cận của tôi để xử lý vấn đề xoay vòng này. Điều này có thể không liên quan đến OP vì anh ta không sử dụng AsyncTask, nhưng có thể những người khác sẽ thấy nó hữu ích. Nó khá đơn giản nhưng dường như nó làm được việc cho tôi:

Tôi có một hoạt động đăng nhập với một AsyncTasklớp lồng nhau được gọi là BackgroundLoginTask.

Theo tôi, BackgroundLoginTasktôi không làm bất cứ điều gì khác thường ngoại trừ việc thêm một kiểm tra null khi gọi lệnh ProgressDialogbãi nhiệm:

@Override
protected void onPostExecute(Boolean result)
{    
if (pleaseWaitDialog != null)
            pleaseWaitDialog.dismiss();
[...]
}

Điều này là để xử lý trường hợp tác vụ nền kết thúc trong khi Activitykhông nhìn thấy được và do đó, hộp thoại tiến trình đã bị onPause()phương thức loại bỏ .

Tiếp theo, trong Activitylớp cha mẹ của tôi , tôi tạo các thẻ điều khiển tĩnh toàn cục cho AsyncTasklớp của tôi và của tôi ProgressDialog(cái AsyncTask, được lồng nhau, có thể truy cập các biến này):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

Điều này phục vụ hai mục đích: Thứ nhất, nó cho phép tôi Activityluôn truy cập vào AsyncTaskđối tượng ngay cả từ một hoạt động mới, xoay vòng. Thứ hai, nó cho phép tôi BackgroundLoginTasktruy cập và loại bỏProgressDialog ngay cả sau khi xoay.

Tiếp theo, tôi thêm phần này vào onPause(), làm cho hộp thoại tiến trình biến mất khi chúng ta Activityrời khỏi nền trước (ngăn chặn sự cố "buộc đóng" xấu xí đó):

    if (pleaseWaitDialog != null)
    pleaseWaitDialog.dismiss();

Cuối cùng, tôi có những điều sau đây trong onResume()phương pháp của mình :

if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }

Điều này cho phép Dialogxuất hiện lại sau khi Activityđược tạo lại.

Đây là toàn bộ lớp học:

public class NSFkioskLoginActivity extends NSFkioskBaseActivity {
    private static BackgroundLoginTask backgroundLoginTask;
    private static ProgressDialog pleaseWaitDialog;
    private Controller cont;

    // This is the app entry point.
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (CredentialsAvailableAndValidated())
        {
        //Go to main menu and don't run rest of onCreate method.
            gotoMainMenu();
            return;
        }
        setContentView(R.layout.login);
        populateStoredCredentials();   
    }

    //Save current progress to options when app is leaving foreground
    @Override
    public void onPause()
    {
        super.onPause();
        saveCredentialsToPreferences(false);
        //Get rid of progress dialog in the event of a screen rotation. Prevents a crash.
        if (pleaseWaitDialog != null)
        pleaseWaitDialog.dismiss();
    }

    @Override
    public void onResume()
    {
        super.onResume();
        if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }
    }

    /**
     * Go to main menu, finishing this activity
     */
    private void gotoMainMenu()
    {
        startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class));
        finish();
    }

    /**
     * 
     * @param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password.
     */
    private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue)
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE);
        SharedPreferences.Editor prefEditor = settings.edit();
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
        prefEditor.putString(USERNAME, usernameText.getText().toString());
        prefEditor.putString(PASSWORD, pswText.getText().toString());
        if (setValidatedBooleanTrue)
        prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true);
        prefEditor.commit();
    }

    /**
     * Checks if user is already signed in
     */
    private boolean CredentialsAvailableAndValidated() {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
                MODE_PRIVATE);
        if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true)
         return true;   
        else
        return false;
    }

    //Populate stored credentials, if any available
    private void populateStoredCredentials()
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
            MODE_PRIVATE);
        settings.getString(USERNAME, "");
       EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
       usernameText.setText(settings.getString(USERNAME, ""));
       EditText pswText = (EditText) findViewById(R.id.editTextPassword);
       pswText.setText(settings.getString(PASSWORD, ""));
    }

    /**
     * Validate credentials in a seperate thread, displaying a progress circle in the meantime
     * If successful, save credentials in preferences and proceed to main menu activity
     * If not, display an error message
     */
    public void loginButtonClick(View view)
    {
        if (phoneIsOnline())
        {
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
           //Call background task worker with username and password params
           backgroundLoginTask = new BackgroundLoginTask();
           backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString());
        }
        else
        {
        //Display toast informing of no internet access
        String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable);
        Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT);
        toast.show();
        }
    }

    /**
     * 
     * Takes two params: username and password
     *
     */
    public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean>
    {       
       private Exception e = null;

       @Override
       protected void onPreExecute()
       {
           cont = Controller.getInstance();
           //Show progress dialog
           String pleaseWait = getResources().getString(R.string.pleaseWait);
           String commWithServer = getResources().getString(R.string.communicatingWithServer);
            if (pleaseWaitDialog == null)
              pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true);

       }

        @Override
        protected Boolean doInBackground(Object... params)
        {
        try {
            //Returns true if credentials were valid. False if not. Exception if server could not be reached.
            return cont.validateCredentials((String)params[0], (String)params[1]);
        } catch (Exception e) {
            this.e=e;
            return false;
        }
        }

        /**
         * result is passed from doInBackground. Indicates whether credentials were validated.
         */
        @Override
        protected void onPostExecute(Boolean result)
        {
        //Hide progress dialog and handle exceptions
        //Progress dialog may be null if rotation has been switched
        if (pleaseWaitDialog != null)
             {
            pleaseWaitDialog.dismiss();
                pleaseWaitDialog = null;
             }

        if (e != null)
        {
         //Show toast with exception text
                String networkError = getResources().getString(R.string.serverErrorException);
                Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT);
            toast.show();
        }
        else
        {
            if (result == true)
            {
            saveCredentialsToPreferences(true);
            gotoMainMenu();
            }
            else
            {
            String toastText = getResources().getString(R.string.invalidCredentialsEntered);
                Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
            toast.show();
            } 
        }
        }

    }
}

Tôi không có nghĩa là một nhà phát triển Android dày dạn, vì vậy hãy bình luận.


1
Hấp dẫn! Đặc biệt đối với những người trong chúng ta sử dụng AsyncTask. Chỉ cần thử giải pháp của bạn, và nó dường như chủ yếu làm việc. Có một vấn đề: ProgressDialog dường như chấm dứt sớm một chút sau khi xoay vòng KHI ProgressDialog vẫn hoạt động. Tôi sẽ chơi xung quanh để xem chính xác những gì đang xảy ra và cách khắc phục nó. Nhưng tôi không còn gặp phải những sự cố đó nữa!
Scott Biggie

1
Tìm thấy một sửa chữa. Có vẻ như vấn đề ở đây là ProgressDialog tĩnh. Khi các phép quay làm gián đoạn ProgressDialog, đôi khi nó nhận được phương thức .dismiss () được gọi sau khi được khởi động lại trong Activity mới. Bằng cách tạo ProgressDialog được tạo với mỗi Hoạt động, chúng tôi đảm bảo rằng ProgressDialog mới này không bị giết cùng với Hoạt động cũ. Tôi cũng đảm bảo rằng ProgressDialog được đặt thành null bất cứ khi nào nó bị loại bỏ (để hỗ trợ thu gom rác). Vì vậy, chúng tôi có một giải pháp ở đây! Chúc mừng những người sử dụng AsyncTask!
Scott Biggie

4

Di chuyển nhiệm vụ dài đến một lớp riêng biệt. Thực hiện nó như một mô hình quan sát chủ đề. Bất cứ khi nào hoạt động được tạo ra đăng ký và trong khi đóng unregister với lớp nhiệm vụ. Lớp tác vụ có thể sử dụng AsyncTask.


1
Tôi không thấy nó sẽ giúp như thế nào. Bạn có thể giải thích chi tiết hơn về cách điều này ngăn chặn các vấn đề tôi đang gặp phải.
Heikki Toivonen

1
Như Haseman cho biết, nó ngăn không cho phụ trợ truy cập vào các thành phần UI và chúng ta có thể tách giao diện người dùng khỏi phụ trợ, phụ trợ chạy trong luồng riêng biệt và nó tiếp tục chạy ngay cả sau khi màn hình được định hướng lại và Đăng ký UnRegister với tác vụ Backend để cập nhật trạng thái . Ví dụ thực tế tôi đã giải quyết bằng cách này là tôi có Nhiệm vụ tải xuống, tôi đã chuyển nó sang một luồng riêng biệt, bất cứ khi nào luồng được tạo, tôi đăng ký hủy đăng ký với nó.
Vinay

Ok, tôi đang xem xét lại vấn đề này và tôi không nghĩ rằng tôi vẫn hoàn toàn hiểu câu trả lời này. Giả sử chúng ta có hoạt động chính bắt đầu AsyncTask để thực hiện thao tác mạng chạy dài mà chúng ta không muốn làm gián đoạn trong quá trình thay đổi hướng màn hình. Tôi không thấy cách hoạt động mới có thể gửi tin nhắn đến AsyncTask được bắt đầu bởi hoạt động cũ. Bạn có thể đưa ra một ví dụ mã?
Heikki Toivonen

@Heikki, ý của tôi dưới đây là gì?
beetstra

4

Thủ thuật là hiển thị / loại bỏ hộp thoại trong AsyncTask trong khi onPreExecute / onPostExecute như bình thường, mặc dù trong trường hợp thay đổi định hướng tạo / hiển thị một phiên bản mới của hộp thoại trong hoạt động và chuyển tham chiếu của nó đến tác vụ.

public class MainActivity extends Activity {
    private Button mButton;
    private MyTask mTask = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        MyTask task = (MyTask) getLastNonConfigurationInstance();
        if(task != null){
            mTask = task;
            mTask.mContext = this;
            mTask.mDialog = ProgressDialog.show(this, "", "", true);        
        }

        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                mTask = new MyTask(MainActivity.this);
                mTask.execute();
            }
        });
    }


    @Override
    public Object onRetainNonConfigurationInstance() {
        String str = "null";
        if(mTask != null){
            str = mTask.toString();
            mTask.mDialog.dismiss();
        }
        Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
        return mTask;
    }



    private class MyTask extends AsyncTask<Void, Void, Void>{
        private ProgressDialog mDialog;
        private MainActivity mContext;


        public MyTask(MainActivity context){
            super();
            mContext = context;
        }


        protected void onPreExecute() {
            mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
        }

        protected void onPostExecute(Void result) {
            mContext.mTask = null;
            mDialog.dismiss();
        }


        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(5000);
            return null;
        }       
    }
}

4

Tôi đã làm nó như thế này:

    package com.palewar;
    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;

    public class ThreadActivity extends Activity {


        static ProgressDialog dialog;
        private Thread downloadThread;
        final static Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {

                super.handleMessage(msg);

                dialog.dismiss();

            }

        };

        protected void onDestroy() {
    super.onDestroy();
            if (dialog != null && dialog.isShowing()) {
                dialog.dismiss();
                dialog = null;
            }

        }

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

            downloadThread = (Thread) getLastNonConfigurationInstance();
            if (downloadThread != null && downloadThread.isAlive()) {
                dialog = ProgressDialog.show(ThreadActivity.this, "",
                        "Signing in...", false);
            }

            dialog = ProgressDialog.show(ThreadActivity.this, "",
                    "Signing in ...", false);

            downloadThread = new MyThread();
            downloadThread.start();
            // processThread();
        }

        // Save the thread
        @Override
        public Object onRetainNonConfigurationInstance() {
            return downloadThread;
        }


        static public class MyThread extends Thread {
            @Override
            public void run() {

                try {
                    // Simulate a slow network
                    try {
                        new Thread().sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    handler.sendEmptyMessage(0);

                } finally {

                }
            }
        }

    }

Bạn cũng có thể thử và cho tôi biết nó có hiệu quả với bạn hay không


Mã onDestroy hoàn toàn không thể được thực thi, từ trang của Nhà phát triển : "Lưu ý cột" Có thể giết được "trong bảng trên - đối với các phương thức được đánh dấu là có thể giết được, sau khi phương thức đó trả về quy trình lưu trữ hoạt động có thể bị giết bởi hệ thống bất cứ lúc nào mà không có dòng mã nào khác được thực thi "
ilomambo

Tôi tin chắc rằng "onRetainNonConfigurationInstance ()" là phương pháp được sử dụng cho những trường hợp như vậy ... công việc nyc
Nitin Bansal

Tôi đang đối mặt với vấn đề tương tự, vì Sachin Gurnani có sử dụng khai báo tĩnh để khắc phục vấn đề của mình không. stackoverflow.com/questions/12058774/ từ
Steven Du

2

Nếu bạn tạo một nền tảng Servicethực hiện tất cả các công việc nặng nhọc (yêu cầu / phản hồi tcp, không sắp xếp lại ), ViewActivitycó thể bị hủy và tạo lại mà không bị rò rỉ cửa sổ hoặc mất dữ liệu. Điều này cho phép hành vi được đề xuất của Android, đó là hủy một Hoạt động trên mỗi thay đổi cấu hình (ví dụ: đối với mỗi thay đổi hướng).

Nó phức tạp hơn một chút, nhưng đó là cách tốt nhất để gọi yêu cầu máy chủ, xử lý trước / xử lý dữ liệu, v.v.

Bạn thậm chí có thể sử dụng Serviceđể xếp hàng từng yêu cầu đến một máy chủ, vì vậy việc xử lý những việc đó trở nên dễ dàng và hiệu quả.

Hướng dẫn dev có một chươngServices đầy đủ về .


A Servicelà công việc nhiều hơn một AsyncTasknhưng có thể là một cách tiếp cận tốt hơn trong một số tình huống. Nó không nhất thiết phải tốt hơn, phải không? Điều đó đang được nói, tôi không hiểu làm thế nào điều này giải quyết vấn đề của cái ProgressDialogbị rò rỉ từ chính Activity. Nơi nào bạn khởi động ProgressDialog? Bạn bỏ nó ở đâu?
rds

2

Tôi có một triển khai cho phép hủy bỏ hoạt động khi thay đổi hướng màn hình, nhưng vẫn phá hủy hộp thoại trong hoạt động được tạo lại thành công. tôi sử dụng...NonConfigurationInstance để đính kèm tác vụ nền cho hoạt động được tạo lại. Khung Android bình thường xử lý việc tạo lại chính hộp thoại, không có gì thay đổi ở đó.

Tôi đã phân lớp AsyncTask thêm một trường cho hoạt động 'sở hữu' và một phương pháp để cập nhật chủ sở hữu này.

class MyBackgroundTask extends AsyncTask<...> {
  MyBackgroundTask (Activity a, ...) {
    super();
    this.ownerActivity = a;
  }

  public void attach(Activity a) {
    ownerActivity = a;
  }

  protected void onPostExecute(Integer result) {
    super.onPostExecute(result);
    ownerActivity.dismissDialog(DIALOG_PROGRESS);
  }

  ...
}

Trong lớp hoạt động của mình, tôi đã thêm một trường backgroundTaskđề cập đến nền tảng 'sở hữu' và tôi cập nhật trường này bằng cách sử dụng onRetainNonConfigurationInstancegetLastNonConfigurationInstance .

class MyActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    ...
    if (getLastNonConfigurationInstance() != null) {
      backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
      backgroundTask.attach(this);
    }
  }

  void startBackgroundTask() {
    backgroundTask = new MyBackgroundTask(this, ...);
    showDialog(DIALOG_PROGRESS);
    backgroundTask.execute(...);
  }

  public Object onRetainNonConfigurationInstance() {
    if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
      return backgroundTask;
    return null;
  }
  ...
}

Gợi ý cải tiến hơn nữa:

  • Xóa backgroundTask tham chiếu trong hoạt động sau khi tác vụ kết thúc để giải phóng bất kỳ bộ nhớ hoặc tài nguyên nào khác được liên kết với nó.
  • Xóa ownerActivity tham chiếu trong nền sau khi hoạt động bị hủy trong trường hợp nó sẽ không được tạo lại ngay lập tức.
  • Tạo BackgroundTaskgiao diện và / hoặc bộ sưu tập để cho phép các loại tác vụ khác nhau chạy từ cùng một hoạt động sở hữu.

2

Nếu bạn duy trì hai bố cục, tất cả các luồng UI sẽ bị chấm dứt.

Nếu bạn sử dụng AsynTask, thì bạn có thể dễ dàng gọi .cancel()phương thức bên trong onDestroy()phương thức hoạt động hiện tại.

@Override
protected void onDestroy (){
    removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
    if (loginTask != null){
        if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
            loginTask.cancel(true); //cancel AsyncTask
    }
    super.onDestroy();
}

Đối với AsyncTask, hãy đọc thêm trong phần "Hủy tác vụ" tại đây .

Cập nhật: Đã thêm điều kiện để kiểm tra trạng thái, vì nó chỉ có thể bị hủy nếu nó đang ở trạng thái chạy. Cũng lưu ý rằng AsyncTask chỉ có thể được thực thi một lần.


2

Đã thử thực hiện giải pháp của jfelectron vì đây là " giải pháp vững chắc cho những vấn đề phù hợp với" Cách thức Android ", nhưng phải mất một thời gian để tra cứu và kết hợp tất cả các yếu tố được đề cập. Kết thúc với điều này hơi khác biệt, và tôi nghĩ rằng giải pháp thanh lịch hơn, được đăng ở đây hoàn toàn.

Sử dụng một IntentService được kích hoạt từ một hoạt động để thực hiện tác vụ chạy dài trên một luồng riêng biệt. Dịch vụ kích hoạt lại Ý định quảng bá dính vào hoạt động cập nhật hộp thoại. Hoạt động sử dụng showDialog (), onCreateDialog () và onPrepareDialog () để loại bỏ sự cần thiết phải truyền dữ liệu liên tục trong đối tượng ứng dụng hoặc gói saveInstanceState. Điều này sẽ hoạt động cho dù ứng dụng của bạn bị gián đoạn như thế nào.

Lớp hoạt động:

public class TesterActivity extends Activity {
private ProgressDialog mProgressDialog;
private static final int PROGRESS_DIALOG = 0;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button b = (Button) this.findViewById(R.id.test_button);
    b.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            buttonClick();
        }
    });
}

private void buttonClick(){
    clearPriorBroadcast();
    showDialog(PROGRESS_DIALOG);
    Intent svc = new Intent(this, MyService.class);
    startService(svc);
}

protected Dialog onCreateDialog(int id) {
    switch(id) {
    case PROGRESS_DIALOG:
        mProgressDialog = new ProgressDialog(TesterActivity.this);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setMax(MyService.MAX_COUNTER);
        mProgressDialog.setMessage("Processing...");
        return mProgressDialog;
    default:
        return null;
    }
}

@Override
protected void onPrepareDialog(int id, Dialog dialog) {
    switch(id) {
    case PROGRESS_DIALOG:
        // setup a broadcast receiver to receive update events from the long running process
        IntentFilter filter = new IntentFilter();
        filter.addAction(MyService.BG_PROCESS_INTENT);
        registerReceiver(new MyBroadcastReceiver(), filter);
        break;
    }
}

public class MyBroadcastReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(MyService.KEY_COUNTER)){
            int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
            mProgressDialog.setProgress(count);
            if (count >= MyService.MAX_COUNTER){
                dismissDialog(PROGRESS_DIALOG);
            }
        }
    }
}

/*
 * Sticky broadcasts persist and any prior broadcast will trigger in the 
 * broadcast receiver as soon as it is registered.
 * To clear any prior broadcast this code sends a blank broadcast to clear 
 * the last sticky broadcast.
 * This broadcast has no extras it will be ignored in the broadcast receiver 
 * setup in onPrepareDialog()
 */
private void clearPriorBroadcast(){
    Intent broadcastIntent = new Intent();
    broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
    sendStickyBroadcast(broadcastIntent);
}}

Lớp IntentService:

public class MyService extends IntentService {

public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
public static final String KEY_COUNTER = "counter";
public static final int MAX_COUNTER = 100;

public MyService() {
  super("");
}

@Override
protected void onHandleIntent(Intent intent) {
    for (int i = 0; i <= MAX_COUNTER; i++) {
        Log.e("Service Example", " " + i);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction(BG_PROCESS_INTENT);
        broadcastIntent.putExtra(KEY_COUNTER, i);
        sendStickyBroadcast(broadcastIntent);
    }
}}

Mục nhập tệp kê khai:

trước phần ứng dụng:

uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission android:name="android.permission.BROADCAST_STICKY"

bên trong phần ứng dụng

service android:name=".MyService"

2

Đây là giải pháp đề xuất của tôi:

  • Di chuyển AsyncTask hoặc Thread sang một đoạn được giữ lại, như được giải thích ở đây . Tôi tin rằng đó là một thực hành tốt để di chuyển tất cả các cuộc gọi mạng đến các mảnh. Nếu bạn đã sử dụng các đoạn, một trong số chúng có thể chịu trách nhiệm cho các cuộc gọi. Mặt khác, bạn có thể tạo một đoạn chỉ để thực hiện yêu cầu, như bài viết được liên kết đề xuất.
  • Đoạn này sẽ sử dụng giao diện người nghe để báo hiệu hoàn thành / thất bại nhiệm vụ. Bạn không phải lo lắng về sự thay đổi định hướng ở đó. Đoạn này sẽ luôn có liên kết chính xác đến hoạt động hiện tại và hộp thoại tiến trình có thể được nối lại một cách an toàn.
  • Làm cho hộp thoại tiến bộ của bạn là một thành viên của lớp học của bạn. Trong thực tế, bạn nên làm điều đó cho tất cả các hộp thoại. Trong phương thức onPause, bạn nên loại bỏ chúng, nếu không bạn sẽ rò rỉ một cửa sổ về thay đổi cấu hình. Các trạng thái bận rộn nên được giữ bởi các mảnh. Khi đoạn được gắn vào hoạt động, bạn có thể hiển thị lại hộp thoại tiến trình, nếu cuộc gọi vẫn đang chạy. Một void showProgressDialog()phương thức có thể được thêm vào giao diện trình nghe hoạt động phân đoạn cho mục đích này.

Giải pháp hoàn hảo, nhưng không hiểu tại sao câu trả lời này bị lu mờ từ người khác !!!
blackkara

2

Tôi đã đối mặt với tình huống tương tự. Những gì tôi đã làm chỉ nhận được một ví dụ cho hộp thoại tiến trình của tôi trong toàn bộ ứng dụng.

Đầu tiên, tôi đã tạo một lớp DialogSingleton để chỉ lấy một thể hiện (mẫu Singleton)

public class DialogSingleton
{
    private static Dialog dialog;

    private static final Object mLock = new Object();
    private static DialogSingleton instance;

    private DialogSingleton()
    {

    }

    public static DialogSingleton GetInstance()
    {
        synchronized (mLock)
        {
            if(instance == null)
            {
                instance = new DialogSingleton();
            }

            return instance;
        }
    }

    public void DialogShow(Context context, String title)
    {
        if(!((Activity)context).isFinishing())
        {
            dialog = new ProgressDialog(context, 2);

            dialog.setCanceledOnTouchOutside(false);

            dialog.setTitle(title);

            dialog.show();
        }
    }

    public void DialogDismiss(Context context)
    {
        if(!((Activity)context).isFinishing() && dialog.isShowing())
        {
            dialog.dismiss();
        }
    }
}

Như tôi trình bày trong lớp này, tôi có hộp thoại tiến trình là thuộc tính. Mỗi khi tôi cần hiển thị hộp thoại tiến trình, tôi sẽ nhận được cá thể duy nhất và tạo một ProgressDialog mới.

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

Khi tôi hoàn thành nhiệm vụ nền, tôi gọi lại thể hiện duy nhất và bỏ qua hộp thoại của nó.

DialogSingleton.GetInstance().DialogDismiss(this);

Tôi lưu trạng thái tác vụ nền trong tùy chọn chia sẻ của tôi. Khi tôi xoay màn hình, tôi hỏi tôi có chạy tác vụ này không: (onCreate)

if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString()))
{
    DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!");
} // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running).

Khi tôi bắt đầu chạy một tác vụ nền:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

Khi tôi hoàn thành chạy một tác vụ nền:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");

DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);

Tôi hy vọng nó sẽ giúp.


2

Đây là một câu hỏi rất cũ xuất hiện trên thanh bên vì một số lý do.

Nếu tác vụ nền chỉ cần tồn tại trong khi hoạt động ở phía trước, thì giải pháp "mới" là lưu trữ luồng nền (hoặc, tốt nhất là AsyncTask) trong một đoạn được giữ lại , như được mô tả trong hướng dẫn dành cho nhà phát triển này và nhiều câu hỏi và trả lời .

Một mảnh được giữ lại tồn tại nếu hoạt động bị hủy để thay đổi cấu hình, nhưng không phải khi hoạt động bị hủy trong nền hoặc ngăn xếp trở lại. Do đó, tác vụ nền vẫn phải bị gián đoạn nếu isChangingConfigurations()sai trong onPause().


2

Tôi là một người mới trong Android và tôi đã thử nó và nó đã hoạt động.

public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> {
        ProgressDialog progressDialog = new ProgressDialog(Login.this);
        int ranSucess=0;
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            progressDialog.setTitle("");    
            progressDialog.isIndeterminate();
            progressDialog.setCancelable(false);
            progressDialog.show();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            return null;
        }
        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            progressDialog.dismiss();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
        }
}

1

Tôi đã thử MỌI THỨ. Đã dành ngày thử nghiệm. Tôi không muốn chặn hoạt động quay. Kịch bản của tôi là:

  1. Một hộp thoại tiến trình hiển thị thông tin động cho người dùng. Ví dụ: "Kết nối với máy chủ ...", "Đang tải xuống dữ liệu ...", v.v.
  2. Một chủ đề làm các công việc nặng và cập nhật hộp thoại
  3. Cập nhật giao diện người dùng với kết quả ở cuối.

Vấn đề là, khi xoay màn hình, mọi giải pháp trên cuốn sách đều thất bại. Ngay cả với lớp AsyncTask, đây là cách Android chính xác để xử lý các tình huống này. Khi xoay màn hình, Bối cảnh hiện tại mà luồng bắt đầu đang hoạt động sẽ biến mất và điều đó làm rối tung hộp thoại đang hiển thị. Vấn đề luôn là Hộp thoại, bất kể tôi đã thêm bao nhiêu thủ thuật vào mã (chuyển các bối cảnh mới sang các luồng đang chạy, giữ lại trạng thái luồng thông qua các phép quay, v.v ...). Sự phức tạp mã ở cuối luôn luôn rất lớn và luôn có điều gì đó có thể sai.

Giải pháp duy nhất hiệu quả với tôi là thủ thuật Activity / Dialog. Nó đơn giản và thiên tài và tất cả đều là bằng chứng xoay vòng:

  1. Thay vì tạo Hộp thoại và yêu cầu hiển thị nó, hãy tạo một Hoạt động đã được đặt trong tệp kê khai với android: theme = "@ android: style / Theme.Dialog". Vì vậy, nó trông giống như một hộp thoại.

  2. Thay showDialog (DIALOG_ID) bằng startActivityForResult (yourActivityDialog, yourCode);

  3. Sử dụng onActivityResult trong Hoạt động gọi để nhận kết quả từ luồng thực thi (ngay cả các lỗi) và cập nhật giao diện người dùng.

  4. Trên 'ActivityDialog' của bạn, sử dụng các luồng hoặc AsyncTask để thực thi các tác vụ dài và onRetainNonConfigurationInstance để lưu trạng thái "hộp thoại" khi xoay màn hình.

Điều này là nhanh chóng và hoạt động tốt. Tôi vẫn sử dụng các hộp thoại cho các tác vụ khác và AsyncTask cho những thứ không yêu cầu hộp thoại liên tục trên màn hình. Nhưng với kịch bản này, tôi luôn đi theo mẫu Hoạt động / Hộp thoại.

Và, tôi đã không thử nó, nhưng thậm chí có thể chặn Activity / Dialog đó xoay, khi luồng đang chạy, tăng tốc mọi thứ, trong khi cho phép Activity hoạt động xoay.


Đẹp ngoại trừ các tham số phải được truyền qua một Intent, điều này hạn chế hơn mức Objectcho phép củaAsyncTask
rds

@Rui Tôi cũng đã sử dụng phương pháp này cho năm ngoái. Bây giờ nó đã xảy ra với tôi mặc dù điều này cũng không chính xác. Tại sao Google thậm chí sẽ có Hộp thoại nếu đây là cách để 'khắc phục' vấn đề này? Vấn đề tôi thấy là nếu bạn mở ActivityB (Theme.Dialog) từ ActivityA thì ActivityA sẽ được chuyển xuống ngăn xếp Activity do đó được đánh dấu là sẵn sàng để giết bởi OS nếu cần. Do đó, nếu bạn có một quá trình chạy dài và đang hiển thị một số loại 'hộp thoại' tiến bộ giả và mất quá nhiều thời gian và bộ nhớ chạy chậm ... ActivityA bị giết và không có gì để quay lại khi quá trình hoàn tất.
rf43

1

Ngày nay, có một cách khác biệt hơn nhiều để xử lý các loại vấn đề này. Cách tiếp cận điển hình là:

1. Đảm bảo dữ liệu của bạn được phân tách chính xác từ UI:

Bất cứ điều gì là một quá trình nền nên được giữ lại Fragment(đặt cái này với Fragment.setRetainInstance(). Cái này trở thành 'lưu trữ dữ liệu liên tục' của bạn trong đó mọi dữ liệu mà bạn muốn giữ lại được giữ lại. Sau sự kiện thay đổi định hướng, nó Fragmentvẫn có thể truy cập được trong bản gốc thông qua một FragmentManager.findFragmentByTag()cuộc gọi (khi bạn tạo nó, bạn nên cung cấp cho nó một thẻ chứ không phải ID vì nó không được gắn vào a View).

Xem hướng dẫn phát triển Xử lý thay đổi thời gian chạy để biết thông tin về cách thực hiện chính xác và lý do tại sao nó là tùy chọn tốt nhất.

2. Đảm bảo bạn đang giao tiếp chính xác và an toàn giữa các quy trình nền và giao diện người dùng của bạn:

Bạn phải đảo ngược quá trình liên kết của bạn. Tại thời điểm quá trình nền của bạn tự gắn vào một View- thay vào đó, quá trình nền của bạn Viewsẽ tự gắn vào quá trình nền. Nó có ý nghĩa hơn phải không? Các Viewhành động 's phụ thuộc vào quá trình nền, trong khi quá trình nền không phụ thuộc vào các Viewphương tiện .Đây thay đổi liên kết đến một tiêu chuẩn Listenergiao diện. Say quá trình của bạn (bất kể lớp nó là - cho dù đó là một AsyncTask, Runnablehoặc bất cứ điều gì) định nghĩa một OnProcessFinishedListener, khi quá trình này được thực hiện nó nên gọi người nghe rằng nếu nó tồn tại.

Đây câu trả lời là một mô tả ngắn gọn đẹp về cách làm người nghe tùy chỉnh.

3. Liên kết UI của bạn với quy trình dữ liệu bất cứ khi nào UI được tạo (bao gồm cả thay đổi hướng):

Bây giờ bạn phải lo lắng về việc can thiệp vào tác vụ nền với bất kỳ Viewcấu trúc hiện tại nào của bạn . Nếu bạn đang xử lý các thay đổi định hướng của mình đúng cách (không phải configChangeshack mọi người luôn khuyến nghị), thì Dialoghệ thống của bạn sẽ được hệ thống tạo lại. Điều này rất quan trọng, điều đó có nghĩa là trên thay đổi định hướng, tất cả Dialogcác phương pháp vòng đời của bạn đều bị thu hồi. Vì vậy, trong bất kỳ phương thức nào trong số này ( onCreateDialogthường là một nơi tốt), bạn có thể thực hiện một cuộc gọi như sau:

DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
if (f != null) {
    f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
        public void onProcessFinished() {
            dismiss();
        }
    });
 }

Xem vòng đời của Fragment để quyết định nơi cài đặt trình nghe phù hợp nhất với việc triển khai cá nhân của bạn.

Đây là một cách tiếp cận chung để cung cấp một giải pháp mạnh mẽ và đầy đủ cho vấn đề chung được hỏi trong câu hỏi này. Có thể có một vài phần nhỏ bị thiếu trong câu trả lời này tùy thuộc vào kịch bản cá nhân của bạn, nhưng đây thường là cách tiếp cận đúng nhất để xử lý đúng các sự kiện thay đổi định hướng.


1

tôi đã tìm thấy và giải pháp dễ dàng hơn để xử lý các chủ đề khi thay đổi định hướng. Bạn chỉ có thể giữ một tham chiếu tĩnh cho hoạt động / đoạn của mình và xác minh xem nó có rỗng không trước khi hành động trên ui. Tôi đề nghị sử dụng một thử bắt quá:

 public class DashListFragment extends Fragment {
     private static DashListFragment ACTIVE_INSTANCE;

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

        ACTIVE_INSTANCE = this;

        new Handler().postDelayed(new Runnable() {
            public void run() {
                try {
                        if (ACTIVE_INSTANCE != null) {
                            setAdapter(); // this method do something on ui or use context
                        }
                }
                catch (Exception e) {}


            }
        }, 1500l);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        ACTIVE_INSTANCE = null;
    }


}

1

Nếu bạn đang vật lộn với việc phát hiện các sự kiện thay đổi định hướng của hộp thoại ĐỘC LẬP TÀI LIỆU THAM KHẢO HOẠT ĐỘNG , phương pháp này hoạt động rất tốt. Tôi sử dụng điều này bởi vì tôi có lớp hộp thoại riêng có thể được hiển thị trong nhiều Hoạt động khác nhau nên tôi không biết Hoạt động nào sẽ được hiển thị. Với phương pháp này, bạn không cần thay đổi AndroidManifest, lo lắng về các tham chiếu Hoạt động, và bạn không cần một hộp thoại tùy chỉnh (như tôi có). Tuy nhiên, bạn cần một chế độ xem nội dung tùy chỉnh để bạn có thể phát hiện các thay đổi hướng bằng cách sử dụng chế độ xem cụ thể đó. Đây là ví dụ của tôi:

Thiết lập

public class MyContentView extends View{
    public MyContentView(Context context){
        super(context);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig){
        super.onConfigurationChanged(newConfig);

        //DO SOMETHING HERE!! :D
    }
}

Thực hiện 1 - Hộp thoại

Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();

Thực hiện 2 - AlertDialog.Builder

AlertDialog.Builder builder = new AlertDialog.Builder(context);
//set up dialog builder
builder.setView(new MyContentView(context));        //Can use this method
builder.setCustomTitle(new MycontentView(context)); // or this method
builder.build().show();

Thực hiện 3 - ProgressDialog / AlertDialog

ProgressDialog progress = new ProgressDialog(context);
//set up progress dialog
progress.setView(new MyContentView(context));        //Can use this method
progress.setCustomTitle(new MyContentView(context)); // or this method
progress.show();

1

Đây là giải pháp của tôi khi tôi đối mặt với nó: ProgressDialogkhông phải là một Fragmentđứa trẻ, vì vậy lớp tùy chỉnh "" của tôi ProgressDialogFragmentcó thể mở rộng DialogFragmentthay vào đó để giữ hộp thoại hiển thị cho các thay đổi cấu hình.

import androidx.annotation.NonNull;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle; 
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;

 /**
 * Usage:
 * To display the dialog:
 *     >>> ProgressDialogFragment.showProgressDialogFragment(
 *              getSupportFragmentManager(), 
 *              "fragment_tag", 
 *              "my dialog title", 
 *              "my dialog message");
 *              
 * To hide the dialog
 *     >>> ProgressDialogFragment.hideProgressDialogFragment();
 */ 


public class ProgressDialogFragment extends DialogFragment {

    private static String sTitle, sMessage;
    private static ProgressDialogFragment sProgressDialogFragment;

    public ProgressDialogFragment() {
    }

    private ProgressDialogFragment(String title, String message) {
        sTitle = title;
        sMessage = message;
    }


    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return ProgressDialog.show(getActivity(), sTitle, sMessage);
    }

    public static void showProgressDialogFragment(FragmentManager fragmentManager, String fragmentTag, String title, String message) {
        if (sProgressDialogFragment == null) {
            sProgressDialogFragment = new ProgressDialogFragment(title, message);
            sProgressDialogFragment.show(fragmentManager, fragmentTag);

        } else { // case of config change (device rotation)
            sProgressDialogFragment = (ProgressDialogFragment) fragmentManager.findFragmentByTag(fragmentTag); // sProgressDialogFragment will try to survive its state on configuration as much as it can, but when calling .dismiss() it returns NPE, so we have to reset it on each config change
            sTitle = title;
            sMessage = message;
        }

    }

    public static void hideProgressDialogFragment() {
        if (sProgressDialogFragment != null) {
            sProgressDialogFragment.dismiss();
        }
    }
}

Thách thức là giữ lại tiêu đề và thông báo hộp thoại trong khi xoay màn hình khi chúng đặt lại về chuỗi trống mặc định, mặc dù hộp thoại vẫn hiển thị

Có 2 cách tiếp cận để giải quyết điều này:

Cách tiếp cận đầu tiên: Tạo hoạt động sử dụng hộp thoại để giữ trạng thái trong khi thay đổi cấu hình trong tệp kê khai:

android:configChanges="orientation|screenSize|keyboardHidden"

Cách tiếp cận này không được Google ưa thích.

Cách tiếp cận thứ hai: về onCreate()phương thức của hoạt động , bạn cần giữ lại DialogFragmentbằng cách xây dựng lại ProgressDialogFragmentmột lần nữa với tiêu đề & thông báo như sau nếu savedInstanceStatekhông phải là null:

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

 if (savedInstanceState != null) {
      ProgressDialogFragment saveProgressDialog = (ProgressDialogFragment) getSupportFragmentManager()
              .findFragmentByTag("fragment_tag");
      if (saveProgressDialog != null) {
          showProgressDialogFragment(getSupportFragmentManager(), "fragment_tag", "my dialog title", "my dialog message");
      }
  }
}

0

Có vẻ quá 'nhanh và bẩn' là sự thật vì vậy xin vui lòng chỉ ra những sai sót nhưng những gì tôi thấy có hiệu quả là ...

Trong phương thức onPostExecute của AsyncTask của tôi, tôi chỉ cần bọc '.dismiss' cho hộp thoại tiến trình trong một khối thử / bắt (với một lần bắt trống) và sau đó chỉ cần bỏ qua ngoại lệ được nêu ra. Có vẻ sai để làm nhưng dường như không có hiệu ứng xấu (ít nhất là đối với những gì tôi đang làm sau đó là bắt đầu một hoạt động khác đi qua kết quả của truy vấn chạy dài của tôi dưới dạng Bổ sung)


Có phải bạn đang nói rằng thực sự không có rò rỉ bộ nhớ khi các nền tảng Android cho biết cửa sổ bị rò rỉ?
rds

Hộp thoại tiến trình của tôi là một biến thành viên của lớp hoạt động nên tôi giả sử rằng khi hoạt động bị hủy và được tạo lại, nó sẽ là rác được thu thập và không có rò rỉ. Tôi có lầm không?
Simon

Vâng, tôi nghĩ đó là sai. Như bạn nói, Activitycó một tài liệu tham khảo đến Dialog. Khi cấu hình được thay đổi, cái đầu tiên Activitybị hủy, có nghĩa là tất cả các trường được đặt thành null. Nhưng cấp thấp WindowManagercũng có một tham chiếu đến Dialog(vì nó chưa bị loại bỏ). Cái mới Activitycố gắng tạo một cái mới Dialog(trong preExecute()) và trình quản lý cửa sổ đưa ra một ngoại lệ nghiêm trọng ngăn bạn làm như vậy. Thật vậy, nếu nó làm như vậy, sẽ không có cách nào để phá hủy hoàn toàn Dialogdo đó giữ một tham chiếu đến ban đầu Activity. Tôi có đúng không
rds

0

Giải pháp đơn giản và linh hoạt nhất là sử dụng AsyncTask với tham chiếu tĩnh đến ProgressBar . Điều này cung cấp một giải pháp đóng gói và do đó có thể tái sử dụng cho các vấn đề thay đổi định hướng. Giải pháp này đã phục vụ tôi rất tốt cho việc thay đổi các tác vụ không đồng bộ bao gồm tải xuống internet, liên lạc với Dịch vụ và quét hệ thống tệp. Giải pháp đã được thử nghiệm tốt trên nhiều phiên bản Android và mẫu điện thoại. Một bản demo hoàn chỉnh có thể được tìm thấy ở đây với sự quan tâm cụ thể về DownloadFile.java

Tôi trình bày như sau đây là một ví dụ khái niệm

public class SimpleAsync extends AsyncTask<String, Integer, String> {
    private static ProgressDialog mProgressDialog = null;
    private final Context mContext;

    public SimpleAsync(Context context) {
        mContext = context;
        if ( mProgressDialog != null ) {
            onPreExecute();
        }
    }

    @Override
    protected void onPreExecute() {
        mProgressDialog = new ProgressDialog( mContext );
        mProgressDialog.show();
    }

    @Override
    protected void onPostExecute(String result) {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
            mProgressDialog = null;
        }
    }

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

    @Override
    protected String doInBackground(String... sUrl) {
        // Do some work here
        publishProgress(1);
        return null;
    }

    public void dismiss() {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
        }
    }
}

Cách sử dụng trong Hoạt động của Android rất đơn giản

public class MainActivity extends Activity {
    DemoServiceClient mClient = null;
    DownloadFile mDownloadFile = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.main );
        mDownloadFile = new DownloadFile( this );

        Button downloadButton = (Button) findViewById( R.id.download_file_button );
        downloadButton.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
            }
        });
    }

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

0

Khi bạn thay đổi định hướng, Android sẽ hủy hoạt động đó và tạo hoạt động mới. Tôi đề nghị sử dụng trang bị thêm với Rx java. xử lý sự cố tự động.

Sử dụng các phương pháp này khi gọi thêm.

.subscribeOn (Lên lịch.io ()) .observeOn (AndroidSchedulers.mainThread ())

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.