จะจัดการกับการเปลี่ยนแปลงการวางแนวหน้าจอเมื่อไดอะล็อกความคืบหน้าและเธรดพื้นหลังทำงานอย่างไร


524

โปรแกรมของฉันทำกิจกรรมเครือข่ายบางอย่างในเธรดพื้นหลัง ก่อนที่จะเริ่มต้นจะปรากฏขึ้นในกล่องโต้ตอบความคืบหน้า ไดอะล็อกถูกยกเลิกบนตัวจัดการ ทั้งหมดนี้ทำงานได้ดียกเว้นเมื่อการวางแนวหน้าจอเปลี่ยนไปในขณะที่กล่องโต้ตอบทำงาน (และเธรดพื้นหลังจะทำงาน) ณ จุดนี้แอพขัดข้องหรือ deadlocks หรือเข้าสู่ขั้นตอนแปลก ๆ ที่แอพไม่ทำงานเลยจนกว่าเธรดทั้งหมดจะถูกฆ่า

ฉันจะจัดการกับการเปลี่ยนแปลงการวางแนวหน้าจอได้อย่างสง่างามได้อย่างไร?

โค้ดตัวอย่างด้านล่างตรงกับที่โปรแกรมจริงของฉันทำ:

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

ซ้อนกัน:

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)

ฉันพยายามที่จะยกเลิกกล่องโต้ตอบความคืบหน้าใน onSaveInstanceState แต่นั่นก็เป็นการป้องกันความผิดพลาดทันที เธรดพื้นหลังยังคงดำเนินต่อไปและ UI อยู่ในสถานะวาดบางส่วน จำเป็นต้องฆ่าแอปทั้งหมดก่อนที่จะเริ่มทำงานอีกครั้ง


1
เมื่อพิจารณาถึงคำตอบที่คุณได้รับคุณควรเปลี่ยนคำตอบที่ยอมรับเพื่อความดีที่สุดใช่ไหม
rds

ดูคำถามจากอดีตstackoverflow.com/questions/456211/…
rds

3
ทุกคนมีคำอธิบายที่ดีเยี่ยมและเป็นไปได้ในการแก้ไขปัญหานี้ ผ่านhttp://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ Lemme ทราบว่าสิ่งนี้ช่วยได้หรือไม่
arcamax

2
มีคำอธิบายที่ค่อนข้างสมบูรณ์เกี่ยวกับวิธีการเก็บงานพื้นหลังแบบอะซิงโครนัสข้ามการวางแนวหน้าจอในโพสต์บล็อกนี้ ลองดูสิ!
Adrian Monk

เพียงตั้งค่า android: configChanges = "ปฐมนิเทศ | screenSize" เป็น Activity ใน manifest.It จะหยุด android เพื่อสร้างกิจกรรมใหม่
Jawad Zeb

คำตอบ:


155

เมื่อคุณสลับการหมุนทิศทาง Android จะสร้างมุมมองใหม่ คุณอาจได้รับความเสียหายเนื่องจากเธรดพื้นหลังของคุณกำลังพยายามเปลี่ยนสถานะในสถานะเก่า (อาจมีปัญหาเนื่องจากเธรดพื้นหลังของคุณไม่ได้อยู่ในเธรด UI)

ฉันขอแนะนำให้ทำให้ mHandler ระเหยและอัปเดตเมื่อการวางแนวเปลี่ยนไป


14
คุณอาจระบุสาเหตุของความผิดพลาด ฉันกำจัดข้อผิดพลาดแล้ว แต่ฉันยังไม่ทราบวิธีการคืนค่า UI กลับสู่สถานะเดิมก่อนการเปลี่ยนทิศทางในลักษณะที่เชื่อถือได้ แต่คำตอบของคุณทำให้ฉันก้าวไปข้างหน้าดังนั้นการให้รางวัลเป็นคำตอบ
Heikki Toivonen

4
คุณควรได้รับ onStart ในกิจกรรมของคุณเมื่อการปฐมนิเทศมีการเปลี่ยนแปลง โดยพื้นฐานแล้วคุณต้องกำหนดค่ามุมมองใหม่โดยใช้ข้อมูลเก่า ดังนั้นฉันขอแนะนำให้ขอสถานะเป็นตัวเลข udpates จากแถบความคืบหน้าและสร้างมุมมองใหม่เมื่อคุณได้รับ 'onStart' ใหม่ฉันจำไม่ได้ว่า offhand ถ้าคุณได้รับกิจกรรมใหม่เช่นกัน แต่การล่าสัตว์ผ่านเอกสารจะช่วยได้
haseman

6
เมื่อเร็ว ๆ นี้ฉันสามารถส่งต่อว่าคุณจะได้รับกิจกรรมใหม่เมื่อแอปของคุณเปลี่ยนทิศทาง (คุณได้รับมุมมองใหม่) หากคุณพยายามอัปเดตมุมมองเก่าคุณจะได้รับการยกเว้นเนื่องจากมุมมองเก่ามีบริบทแอปพลิเคชันที่ไม่ถูกต้อง (กิจกรรมเก่าของคุณ) คุณสามารถแก้ไขได้โดยส่ง myActivity.getApplicationContext () แทนที่จะเป็นตัวชี้ไปยังกิจกรรมนั้น ๆ
haseman

1
ใครสามารถอธิบายการใช้งาน / ประโยชน์ของความผันผวนในบริบทนี้
Jawad Zeb

2
@ Nepster ใช่ฉันก็สงสัยเกี่ยวกับเรื่องนั้นเช่นกัน มันจะดีถ้ามีคนอธิบายเกี่ยวกับความผันผวน
RestInPeace

261

แก้ไข:วิศวกร Google ไม่แนะนำวิธีการนี้ตามที่อธิบายโดย Dianne Hackborn (aka hackbod ) ในโพสต์ StackOverflowนี้ ลองอ่านโพสต์บล็อกนี้สำหรับข้อมูลเพิ่มเติม


คุณต้องเพิ่มสิ่งนี้ลงในการประกาศกิจกรรมในรายการ:

android:configChanges="orientation|screenSize"

ดังนั้นดูเหมือนว่า

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

ปัญหาคือว่าระบบจะทำลายกิจกรรมเมื่อมีการเปลี่ยนแปลงในการกำหนดค่าที่เกิดขึ้น ดูConfigurationChanges

ดังนั้นการวางสิ่งนั้นไว้ในไฟล์กำหนดค่าจะเป็นการหลีกเลี่ยงระบบที่จะทำลายกิจกรรมของคุณ แทนที่จะเรียกใช้onConfigurationChanged(Configuration)เมธอด


21
นี่เป็นทางออกที่ดีที่สุดอย่างแน่นอน ตามที่มันหมุนเค้าโครง (พฤติกรรมที่คุณคาดหวังในตอนแรก) เพียงให้แน่ใจว่าได้ใส่ android: configChanges = "ปฐมนิเทศ | keyboardHidden" (เพราะโทรศัพท์ที่มีคีย์บอร์ดแนวนอน)
nikib3ro

24
นี่เป็นพฤติกรรมที่ฉันคาดหวัง อย่างไรก็ตามเอกสารประกอบระบุว่ากิจกรรมถูกทำลาย "เนื่องจากทรัพยากรแอปพลิเคชันใด ๆ รวมถึงไฟล์เลย์เอาต์สามารถเปลี่ยนแปลงได้ตามค่าการกำหนดค่าใด ๆ ดังนั้นวิธีที่ปลอดภัยเพียงอย่างเดียวในการจัดการการเปลี่ยนแปลงการกำหนดค่าคือการดึงทรัพยากรทั้งหมดใหม่" และนอกจากorientationนี้ยังมีเหตุผลอีกมากมายที่การกำหนดค่าให้เปลี่ยนแปลง: keyboardHidden(ฉันได้แก้ไขคำตอบของ wiki แล้ว), uiMode(ตัวอย่างเช่นการเข้าหรือออกจากโหมดรถยนต์การเปลี่ยนโหมดกลางคืน) และอื่น ๆ ฉันสงสัยว่านี่เป็นเรื่องจริง คำตอบที่ดี
rds

116
นี่ไม่ใช่วิธีการแก้ปัญหาที่ยอมรับได้ มันแค่ปกปิดปัญหาที่แท้จริง
rf43

18
ใช้งานได้ แต่ไม่แนะนำโดย Google
Ed Burnette

21
โปรดอย่าปฏิบัติตามวิธีการนี้ที่นี่ DDosAttack นั้นถูกต้องสมบูรณ์ ลองนึกภาพคุณกำลังสร้างกล่องโต้ตอบความคืบหน้าสำหรับการดาวน์โหลดหรืออย่างอื่นที่ใช้เวลานาน ในฐานะผู้ใช้คุณจะไม่ทำกิจกรรมนั้นและจ้องไปที่มัน คุณจะเปลี่ยนไปที่หน้าจอหลักหรือไปที่แอพอื่นเช่นเกมหรือโทรศัพท์อาจเข้ามาหรือมีสิ่งอื่นที่หิวโหยซึ่งจะทำลายกิจกรรมของคุณในที่สุด ถ้าเช่นนั้นจะเป็นอย่างไร คุณกำลังเผชิญกับปัญหาเดิมซึ่งไม่ได้รับการแก้ไขด้วยเคล็ดลับเล็ก ๆ น้อย ๆ ที่เป็นระเบียบ กิจกรรมจะถูกสร้างขึ้นใหม่อีกครั้งเมื่อผู้ใช้กลับมา
tiguchi

68

ฉันคิดวิธีแก้ปัญหาที่มั่นคงสำหรับปัญหาเหล่านี้ที่สอดคล้องกับ 'Android Way' ของสิ่งต่าง ๆ ฉันมีการดำเนินงานที่ดำเนินมายาวนานโดยใช้รูปแบบ IntentService

นั่นคือกิจกรรมของฉันออกอากาศ IntentService ทำหน้าที่บันทึกข้อมูลในฐานข้อมูลและจากนั้นจะกระจายIntents ที่เหนียว ส่วนเหนียวเป็นสิ่งสำคัญแม้ว่ากิจกรรมจะหยุดชั่วคราวในช่วงเวลาหลังจากที่ผู้ใช้เริ่มต้นทำงานและพลาดการออกอากาศแบบเรียลไทม์จาก IntentService เรายังคงสามารถตอบสนองและรับข้อมูลจากกิจกรรมการโทรได้ ProgressDialogสามารถทำงานกับรูปแบบนี้ได้ค่อนข้างดีonSaveInstanceState()สามารถทำงานร่วมกับรูปแบบนี้อย่างมากด้วย

โดยทั่วไปคุณจะต้องบันทึกการตั้งค่าสถานะที่คุณมีการโต้ตอบความคืบหน้าในกลุ่มอินสแตนซ์ที่บันทึกไว้ อย่าบันทึกวัตถุโต้ตอบความคืบหน้าเพราะจะทำให้กิจกรรมทั้งหมดรั่วไหล เพื่อให้มีการจัดการแบบถาวรไปยังกล่องโต้ตอบความคืบหน้าฉันเก็บไว้เป็นการอ้างอิงที่อ่อนแอในวัตถุประยุกต์ ในการเปลี่ยนแปลงการวางแนวหรือสิ่งอื่นใดที่ทำให้กิจกรรมหยุดชั่วคราว (การโทรผู้ใช้ไปถึงบ้านเป็นต้น) จากนั้นกลับมาทำงานต่อฉันเลิกการโต้ตอบเก่าและสร้างการโต้ตอบใหม่ในกิจกรรมที่สร้างขึ้นใหม่

สำหรับกล่องโต้ตอบความคืบหน้าไม่ จำกัด นี่เป็นเรื่องง่าย สำหรับรูปแบบแถบความคืบหน้าคุณต้องใส่ความคืบหน้าล่าสุดที่ทราบในชุดข้อมูลและข้อมูลใด ๆ ที่คุณใช้อยู่ในเครื่องในกิจกรรมเพื่อติดตามความคืบหน้า ในการกู้คืนความคืบหน้าคุณจะใช้ข้อมูลนี้เพื่อวางไข่แถบความคืบหน้าอีกครั้งในสถานะเดียวกับก่อนและจากนั้นอัปเดตตามสถานะปัจจุบันของสิ่งต่าง ๆ

ดังนั้นเพื่อสรุปการใช้งานที่ยาวนานใน IntentService ควบคู่ไปกับการใช้อย่างรอบคอบonSaveInstanceState()ช่วยให้คุณสามารถติดตามบทสนทนาและเรียกคืนได้อย่างมีประสิทธิภาพผ่านกิจกรรมวงจรชีวิตของกิจกรรม บิตของรหัสกิจกรรมที่เกี่ยวข้องอยู่ด้านล่าง คุณจะต้องใช้ตรรกะใน BroadcastReceiver ของคุณเพื่อจัดการกับ Intent ของ Sticky อย่างเหมาะสม แต่มันอยู่นอกเหนือขอบเขตของสิ่งนี้

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

ดูเหมือนจะเป็นทางออกที่ดี
Derekyy

"ฉันมีการปฏิบัติการที่ใช้เวลานานโดยใช้รูปแบบ IntentService" นี่ไม่ใช่วิธีการแก้ปัญหาที่สมบูรณ์แบบเพราะมันเหมือนกับการยิงปืนใหญ่ออกเป็นกระจอกและรหัสสำเร็จรูปมากมายคุณสามารถรับชม youtube.com/watch?v=NJsq0TU0qeg
Kamil Nekanowicz

28

ฉันพบปัญหาเดียวกัน กิจกรรมของฉันต้องแยกวิเคราะห์ข้อมูลบางส่วนจาก URL และช้า ดังนั้นฉันสร้างเธรดที่จะทำแล้วแสดงกล่องโต้ตอบความคืบหน้า ฉันปล่อยให้เธรดโพสต์ข้อความกลับไปยังเธรด UI ผ่านHandlerเมื่อเสร็จสิ้น ในHandler.handleMessageฉันได้รับวัตถุข้อมูล (พร้อมตอนนี้) จากเธรดและเติมลงใน UI มันคล้ายกับตัวอย่างของคุณมาก

หลังจากการทดลองใช้และข้อผิดพลาดมากมายดูเหมือนว่าฉันจะพบวิธีแก้ปัญหา อย่างน้อยตอนนี้ฉันสามารถหมุนหน้าจอได้ทุกเวลาก่อนหรือหลังกระทู้เสร็จ ในการทดสอบทั้งหมดกล่องโต้ตอบจะปิดอย่างเหมาะสมและพฤติกรรมทั้งหมดเป็นไปตามที่คาดไว้

สิ่งที่ฉันทำแสดงไว้ด้านล่าง เป้าหมายคือการเติมโมเดลข้อมูลของฉัน ( mDataObject) จากนั้นเติมลงใน UI ควรอนุญาตให้หมุนหน้าจอได้ทุกเวลาโดยไม่แปลกใจ

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

นั่นคือสิ่งที่ได้ผลสำหรับฉัน ฉันไม่รู้ว่านี่เป็นวิธี "ถูกต้อง" ตามที่ออกแบบโดย Android หรือไม่พวกเขาอ้างว่า "ทำลาย / สร้างกิจกรรมใหม่ในระหว่างการหมุนหน้าจอ" จริง ๆ แล้วทำให้สิ่งต่าง ๆ ง่ายขึ้นหรือไม่ดังนั้นฉันคิดว่ามันไม่ควรยุ่งยาก

แจ้งให้เราทราบหากคุณพบปัญหาในรหัสของฉัน ดังที่ได้กล่าวไว้ข้างต้นฉันไม่รู้จริงๆว่ามีผลข้างเคียงใด ๆ


1
ขอบคุณมาก! คำแนะนำonRetainNonConfigurationInstance()และgetLastNonConfigurationInstance()ช่วยฉันในการแก้ปัญหาของฉัน ยกนิ้ว!
เวน

15

ปัญหาการรับรู้เดิมคือรหัสจะไม่รอดการเปลี่ยนแปลงการวางแนวหน้าจอ เห็นได้ชัดว่านี่คือ "แก้ไข" โดยให้โปรแกรมจัดการการเปลี่ยนแปลงการวางแนวหน้าจอแทนการปล่อยให้เฟรมเวิร์ก UI ทำ (ผ่านการเรียกใช้ OnDestroy)

ฉันจะส่งว่าถ้าปัญหาพื้นฐานคือว่าโปรแกรมจะไม่รอดในDestroy () แล้วทางออกที่ได้รับการยอมรับเป็นเพียงวิธีแก้ปัญหาที่ออกจากโปรแกรมที่มีปัญหาและช่องโหว่อื่น ๆ ที่ร้ายแรง จำไว้ว่ากรอบ Android ระบุว่ากิจกรรมของคุณมีความเสี่ยงที่จะถูกทำลายได้เกือบตลอดเวลาเนื่องจากสถานการณ์ที่อยู่นอกเหนือการควบคุมของคุณ ดังนั้นกิจกรรมของคุณจะต้องสามารถอยู่รอดได้ใน OnDestroy () และ onCreate () ไม่ว่าด้วยเหตุผลใด ๆ ไม่ใช่เพียงแค่การเปลี่ยนแนวหน้าจอ

หากคุณจะยอมรับการจัดการการวางแนวหน้าจอการเปลี่ยนแปลงตัวเองเพื่อแก้ปัญหาของ OP คุณต้องตรวจสอบว่าสาเหตุอื่น ๆ ของ onDestroy () ไม่ส่งผลให้เกิดข้อผิดพลาดเดียวกัน คุณสามารถทำสิ่งนี้ได้หรือไม่ ถ้าไม่ฉันจะถามว่าคำตอบ "ยอมรับ" เป็นคำตอบที่ดีหรือไม่


14

วิธีการแก้ปัญหาของฉันคือการขยายชั้นเรียนจะได้รับของตัวเองProgressDialog ฉันนิยามใหม่และวิธีการล็อคการวางแนวก่อนที่จะแสดงและปลดล็อคกลับเมื่อถูกไล่ออก ดังนั้นเมื่อมีการแสดงและการวางแนวของอุปกรณ์การเปลี่ยนแปลงการวางแนวของหน้าจอยังคงอยู่จนกว่าจะมีการเรียกแล้วการวางแนวหน้าจอจะเปลี่ยนไปตามเซ็นเซอร์ค่า / การวางแนวอุปกรณ์MyProgressDialog
show()dismiss()DialogDialogDialogdismiss()

นี่คือรหัสของฉัน:

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

ฉันประสบปัญหาเดียวกันนี้และฉันคิดวิธีแก้ปัญหาที่ไม่เกี่ยวข้องกับการใช้ ProgressDialog และฉันได้รับผลลัพธ์เร็วขึ้น

สิ่งที่ฉันทำคือสร้างเลย์เอาต์ที่มี ProgressBar อยู่

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

จากนั้นในเมธอด onCreate ทำสิ่งต่อไปนี้

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

จากนั้นทำภารกิจที่ยาวในเธรดและเมื่อเสร็จแล้วให้ Runnable ตั้งค่ามุมมองเนื้อหาเป็นเค้าโครงจริงที่คุณต้องการใช้สำหรับกิจกรรมนี้

ตัวอย่างเช่น:

mHandler.post(new Runnable(){

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

นี่คือสิ่งที่ฉันทำและฉันพบว่ามันทำงานได้เร็วกว่าการแสดง ProgressDialog และมันรบกวนน้อยกว่าและมีความคิดเห็นที่ดีกว่า

อย่างไรก็ตามหากคุณต้องการใช้ ProgressDialog คำตอบนี้ไม่เหมาะสำหรับคุณ


วิธีแก้ปัญหานี้สวยงามในกรณีที่ใช้ง่าย แต่มีข้อเสีย คุณต้องสร้างมุมมองเนื้อหาแบบเต็ม setContentView(R.layout.my_layout);ไม่เพียงพอ คุณต้องตั้งค่าผู้ฟังทั้งหมดรีเซ็ตข้อมูล ฯลฯ
rds

@rds คุณพูดถูก นี่เป็นเพียงวิธีแก้ปัญหาสำหรับกรณีง่าย ๆ หรือถ้าคุณต้องการยกของหนักในวิธีการสร้างก่อนที่จะแสดงมุมมองของคุณ
Pzanno

ฉันไม่เข้าใจเลย แทนที่จะตั้งค่าฟังใน onCreate () ตามปกติเราจะทำได้เราสามารถตั้งค่าพวกเขาใน run () ฉันทำอะไรบางอย่างหายไปหรือเปล่า
Code Poet

7

ฉันค้นพบวิธีแก้ปัญหาสำหรับเรื่องนี้ซึ่งฉันยังไม่เคยเห็นที่ไหนเลย คุณสามารถใช้แอปพลิเคชันวัตถุที่กำหนดเองที่รู้ว่าคุณมีงานพื้นหลังดำเนินไปแทนที่จะพยายามทำสิ่งนี้ในกิจกรรมที่ถูกทำลายและสร้างขึ้นใหม่จากการเปลี่ยนทิศทาง ฉัน blogged เกี่ยวกับเรื่องนี้ในที่นี่


1
Applicationปกติแล้วการสร้างแบบกำหนดเองจะใช้เพื่อรักษาสถานะแอปพลิเคชันส่วนกลาง ฉันไม่ได้บอกว่ามันใช้งานไม่ได้ แต่มันดูซับซ้อนเกินไป จากเอกสาร "โดยปกติไม่จำเป็นต้องใช้คลาสย่อย" ฉันชอบคำตอบของ sonxurxo เป็นส่วนใหญ่
rds

7

ฉันจะสนับสนุนวิธีการจัดการปัญหาการหมุนครั้งนี้ สิ่งนี้อาจไม่เกี่ยวข้องกับ OP เนื่องจากเขาไม่ได้ใช้AsyncTaskแต่คนอื่นอาจเห็นว่ามีประโยชน์ มันค่อนข้างง่าย แต่ดูเหมือนว่าจะทำงานให้ฉัน:

ฉันมีกิจกรรมเข้าสู่ระบบด้วยซ้อนกันระดับที่เรียกว่าAsyncTaskBackgroundLoginTask

ในของฉันBackgroundLoginTaskฉันไม่ได้ทำอะไรออกจากสามัญยกเว้นที่จะเพิ่มการตรวจสอบ null เมื่อเรียกProgressDialog's ยกเลิก:

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

นี่คือการจัดการกรณีที่งานพื้นหลังเสร็จสิ้นในขณะที่Activityมองไม่เห็นและดังนั้นการโต้ตอบความคืบหน้าได้รับการออกโดยonPause()วิธีการ

ต่อไปในActivityชั้นเรียนผู้ปกครองของฉันฉันสร้างตัวจัดการแบบคงที่ทั่วโลกให้กับAsyncTaskชั้นเรียนของฉันและของฉันProgressDialog( AsyncTaskการซ้อนกันสามารถเข้าถึงตัวแปรเหล่านี้):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

สิ่งนี้มีจุดประสงค์สองประการ: ประการแรกมันช่วยให้ฉันActivityสามารถเข้าถึงAsyncTaskวัตถุได้ตลอดเวลาแม้จะเป็นกิจกรรมใหม่หลังการหมุน ประการที่สองจะอนุญาตให้ฉันBackgroundLoginTaskเข้าถึงและยกเลิกการProgressDialogหมุนได้แม้ในภายหลัง

ถัดไปฉันเพิ่มสิ่งนี้ลงไปonPause()ทำให้กล่องโต้ตอบความคืบหน้าหายไปเมื่อเราActivityออกจากเบื้องหน้า (ป้องกันไม่ให้เกิดความผิดพลาด "force close" ที่น่าเกลียด):

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

ในที่สุดฉันมีดังต่อไปนี้ในonResume()วิธีการของฉัน:

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

สิ่งนี้ทำให้Dialogสามารถปรากฏขึ้นอีกครั้งหลังจากที่Activityถูกสร้างขึ้นใหม่

นี่คือทั้งชั้นเรียน:

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

    }
}

ฉันไม่ได้เป็นนักพัฒนา Android ที่มีประสบการณ์ดังนั้นโปรดแสดงความคิดเห็น


1
! ที่น่าสนใจ โดยเฉพาะอย่างยิ่งสำหรับพวกเราที่ใช้ AsyncTask เพิ่งลองวิธีแก้ปัญหาของคุณและดูเหมือนว่าจะใช้งานได้เป็นส่วนใหญ่ มีปัญหาหนึ่งคือ ProgressDialog ดูเหมือนจะยุติลงเล็กน้อยหลังจากการหมุนขณะที่ ProgressDialog ยังคงทำงานอยู่ ฉันจะเล่นไปรอบ ๆ เพื่อดูว่าเกิดอะไรขึ้นและจะแก้ไขได้อย่างไร แต่ฉันไม่ได้รับความผิดพลาดเหล่านั้นอีกต่อไป!
Scott Biggs

1
พบการแก้ไข ดูเหมือนว่าปัญหาที่นี่คือ ProgressDialog คงที่ เมื่อการหมุนวนขัดจังหวะ ProgressDialog บางครั้งก็รับเมธอด .dismiss () ที่เรียกว่าหลังจากรีสตาร์ทในกิจกรรมใหม่ ด้วยการทำให้ ProgressDialog สร้างขึ้นด้วยแต่ละกิจกรรมเรามั่นใจว่า ProgressDialog ใหม่นี้จะไม่ถูกฆ่าพร้อมกับกิจกรรมเก่า ฉันยังแน่ใจด้วยว่า ProgressDialog ถูกตั้งค่าเป็นโมฆะทุกครั้งที่มันถูกไล่ออก (เพื่อช่วยในการเก็บขยะ) ดังนั้นเราจึงมีทางออกที่นี่! ส่งเสียงเชียร์ผู้ที่ใช้ AsyncTask!
Scott Biggs

4

ย้ายงานที่มีความยาวไปยังคลาสที่แยกต่างหาก นำมาใช้เป็นรูปแบบของผู้สังเกตการณ์ เมื่อใดก็ตามที่กิจกรรมถูกสร้างลงทะเบียนและในขณะที่ปิดการลงทะเบียนกับคลาสงาน คลาสงานสามารถใช้ AsyncTask


1
ฉันไม่เห็นว่าจะช่วยได้อย่างไร คุณช่วยอธิบายรายละเอียดเพิ่มเติมได้ว่ามันช่วยป้องกันปัญหาที่ฉันพบได้อย่างไร
Heikki Toivonen

1
ดังที่ Haseman บอกว่ามันป้องกันแบ็กเอนด์ในการเข้าถึงองค์ประกอบ UI และเราสามารถแยก UI จากแบ็กเอนด์แบ็กเอนด์ทำงานในเธรดแยกและมันจะยังคงทำงานต่อไปแม้หลังจากที่หน้าจอถูกวางแนวใหม่และลงทะเบียน UnRegister กับภารกิจแบ็กเอนด์ . ตัวอย่างจริงที่ฉันแก้ไขได้โดยใช้สิ่งนี้คือฉันมีภารกิจดาวน์โหลดฉันได้ย้ายหัวข้อนั้นไปยังเธรดแยกเมื่อใดก็ตามที่มีการสร้างเธรด
Vinay

ตกลงฉันกำลังทบทวนปัญหานี้อีกครั้งและฉันไม่คิดว่าฉันยังเข้าใจคำตอบนี้ทั้งหมด สมมติว่าเรามีกิจกรรมหลักเริ่ม AsyncTask เพื่อดำเนินการเครือข่ายที่ใช้เวลานานเราไม่ต้องการขัดจังหวะระหว่างการเปลี่ยนแนวหน้าจอ ฉันไม่เห็นว่ากิจกรรมใหม่สามารถส่งข้อความไปยัง AsyncTask ที่เริ่มโดยกิจกรรมเก่า คุณสามารถยกตัวอย่างรหัสได้หรือไม่?
Heikki Toivonen

@Heikki การใช้งานของฉันด้านล่างคุณหมายถึงอะไร?
beetstra

4

เคล็ดลับคือการแสดง / ยกเลิกไดอะล็อกภายใน AsyncTask ระหว่าง onPreExecute / onPostExecute ตามปกติแม้ว่าในกรณีของการวางแนวการเปลี่ยนแปลงให้สร้าง / แสดงอินสแตนซ์ใหม่ของไดอะล็อกในกิจกรรมและส่งต่อการอ้างอิงไปยังงาน

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

ฉันทำแบบนี้แล้ว:

    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 {

                }
            }
        }

    }

คุณสามารถลองและแจ้งให้เราทราบว่ามันได้ผลสำหรับคุณหรือไม่


รหัส onDestroy อาจไม่ได้รับการดำเนินการเลยจากหน้าของผู้พัฒนา : "หมายเหตุคอลัมน์" Killable "ในตารางด้านบน - สำหรับวิธีการที่มีการทำเครื่องหมายว่าสามารถฆ่าได้หลังจากวิธีการนั้นส่งคืนกระบวนการโฮสต์กิจกรรมอาจถูกฆ่าโดย ระบบในเวลาใดก็ได้โดยไม่ต้องมีบรรทัดของรหัสอื่นที่จะดำเนินการ "
ilomambo

ฉันเชื่อมั่นว่า "onRetainNonConfigurationInstance ()" เป็นวิธีการที่ใช้ในกรณีเช่นนี้ ... งาน nyc
Nitin Bansal

ฉันกำลังประสบปัญหาที่คล้ายกันเนื่องจาก Sachin Gurnani ฉันใช้การประกาศคงที่เพื่อแก้ไขปัญหาของฉัน stackoverflow.com/questions/12058774/…
สตีเวนดู

2

หากคุณสร้างพื้นหลังServiceที่ใช้ในการยกของหนัก (การร้องขอ / ตอบกลับ tcp, unmarshalling) ทั้งหมดViewและActivityสามารถทำลายและสร้างใหม่ได้โดยไม่ต้องปิดหน้าต่างหรือข้อมูลสูญหาย สิ่งนี้อนุญาตให้พฤติกรรมที่ Android แนะนำซึ่งจะทำลายกิจกรรมในการเปลี่ยนแปลงการกำหนดค่าแต่ละครั้ง (เช่นสำหรับการเปลี่ยนการวางแนวแต่ละครั้ง)

มันซับซ้อนกว่าเล็กน้อย แต่เป็นวิธีที่ดีที่สุดสำหรับการร้องขอเซิร์ฟเวอร์เรียกข้อมูลล่วงหน้า / ประมวลผลข้อมูล ฯลฯ

คุณอาจใช้ Serviceคิวเพื่อร้องขอแต่ละครั้งไปยังเซิร์ฟเวอร์ดังนั้นจึงทำให้ง่ายและมีประสิทธิภาพในการจัดการสิ่งเหล่านั้น

คู่มือ dev มีเต็มบทที่เกี่ยวกับServices


A Serviceเป็นงานมากกว่าAsyncTaskแต่สามารถเป็นวิธีที่ดีกว่าในบางสถานการณ์ มันไม่จำเป็นต้องดีกว่าใช่ไหม ที่ถูกกล่าวว่าผมไม่เข้าใจวิธีการแก้ปัญหานี้ของที่รั่วไหลออกมาจากหลักProgressDialog Activityคุณติดตั้งProgressDialogที่ไหน คุณจะเลิกจ้างที่ไหน
rds

2

ฉันมีการนำไปใช้ซึ่งช่วยให้กิจกรรมถูกทำลายเมื่อมีการเปลี่ยนการวางแนวหน้าจอ แต่ก็ยังทำลายกล่องโต้ตอบในกิจกรรมที่สร้างใหม่ได้สำเร็จ ฉันใช้...NonConfigurationInstanceเพื่อแนบภารกิจพื้นหลังกับกิจกรรมที่สร้างขึ้นใหม่ เฟรมเวิร์ก Android ปกติจะจัดการสร้างกล่องโต้ตอบขึ้นมาใหม่เอง

ฉัน subclassed AsyncTask เพิ่มเขตข้อมูลสำหรับกิจกรรม 'เป็นเจ้าของ' และวิธีการอัปเดตเจ้าของนี้

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

  ...
}

ในชั้นเรียนกิจกรรมของฉันฉันเพิ่มเขตข้อมูลbackgroundTaskหมายถึง 'เจ้าของ' backgroundtask และผมอัปเดตข้อมูลนี้ใช้และ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;
  }
  ...
}

คำแนะนำสำหรับการปรับปรุงเพิ่มเติม:

  • ล้างการbackgroundTaskอ้างอิงในกิจกรรมหลังจากงานเสร็จสิ้นเพื่อปล่อยหน่วยความจำหรือทรัพยากรอื่น ๆ ที่เกี่ยวข้อง
  • ล้างownerActivityข้อมูลอ้างอิงใน backgroundtask ก่อนที่กิจกรรมจะถูกทำลายในกรณีที่จะไม่ถูกสร้างขึ้นใหม่ทันที
  • สร้างBackgroundTaskอินเทอร์เฟซและ / หรือการรวบรวมเพื่อให้งานประเภทต่าง ๆ สามารถเรียกใช้จากกิจกรรมการเป็นเจ้าของเดียวกัน

2

หากคุณมีสองโครงร่างเธรด UI ทั้งหมดควรถูกยกเลิก

หากคุณใช้ AsynTask คุณสามารถเรียก.cancel()ใช้เมธอดภายในonDestroy()วิธีการของกิจกรรมปัจจุบันได้อย่างง่ายดาย

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

สำหรับ AsyncTask, อ่านเพิ่มเติมใน "ยกเลิกงาน" ที่นี่

อัปเดต: เพิ่มเงื่อนไขเพื่อตรวจสอบสถานะเนื่องจากสามารถยกเลิกได้เฉพาะเมื่ออยู่ในสถานะใช้งาน นอกจากนี้โปรดทราบว่า AsyncTask สามารถดำเนินการได้เพียงครั้งเดียวเท่านั้น


2

พยายามใช้วิธีแก้ปัญหาของjfelectronเพราะเป็น " วิธีแก้ปัญหาที่มั่นคงสำหรับปัญหาเหล่านี้ที่สอดคล้องกับ 'Android Way' ของสิ่งต่าง ๆ " แต่ใช้เวลาในการค้นหาและรวบรวมองค์ประกอบทั้งหมดที่กล่าวถึง จบลงด้วยความแตกต่างเล็กน้อยนี้และฉันคิดว่าสง่างามมากขึ้นโซลูชันที่โพสต์ไว้ที่นี่ครบถ้วน

ใช้ IntentService ที่เรียกใช้จากกิจกรรมเพื่อดำเนินงานที่รันนานบนเธรดแยกต่างหาก บริการจะส่งการแจ้งเตือน Broadcast Intents ไปยังกิจกรรมที่อัพเดตไดอะล็อก กิจกรรมใช้ showDialog (), onCreateDialog () และ onPrepareDialog () เพื่อกำจัดความต้องการที่จะมีข้อมูลถาวรที่ส่งผ่านในวัตถุแอปพลิเคชันหรือบันเดิลที่บันทึกไว้ สิ่งนี้จะทำงานไม่ว่าแอปพลิเคชันของคุณจะถูกขัดจังหวะอย่างไร

ชั้นเรียนกิจกรรม:

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

คลาส 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);
    }
}}

รายการไฟล์รายการ:

ก่อนส่วนแอปพลิเคชัน:

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

ภายในส่วนแอปพลิเคชัน

service android:name=".MyService"

2

นี่คือทางออกที่ฉันเสนอ:

  • ย้าย AsyncTask หรือด้ายไปเก็บไว้ส่วนตามที่อธิบายไว้ที่นี่ ฉันเชื่อว่าเป็นวิธีปฏิบัติที่ดีในการย้ายสายเครือข่ายทั้งหมดไปยังชิ้นส่วน หากคุณใช้แฟรกเมนต์อยู่แล้วหนึ่งในนั้นสามารถรับผิดชอบการโทรได้ มิฉะนั้นคุณสามารถสร้างชิ้นส่วนเพียงเพื่อดำเนินการตามคำขอเนื่องจากบทความที่เชื่อมโยงเสนอ
  • แฟรกเมนต์จะใช้อินเตอร์เฟสตัวฟังเพื่อส่งสัญญาณภารกิจเสร็จสิ้น / ล้มเหลว คุณไม่ต้องกังวลกับการเปลี่ยนแปลงปฐมนิเทศที่นั่น แฟรกเมนต์จะมีลิงก์ที่ถูกต้องไปยังกิจกรรมปัจจุบันและกล่องโต้ตอบความคืบหน้าสามารถดำเนินการต่อได้อย่างปลอดภัย
  • ทำให้กล่องโต้ตอบความคืบหน้าเป็นสมาชิกของชั้นเรียนของคุณ ในความเป็นจริงคุณควรทำเช่นนั้นสำหรับการโต้ตอบทั้งหมด ในวิธีการ onPause คุณควรปิดพวกเขามิฉะนั้นคุณจะรั่วซึมหน้าต่างในการเปลี่ยนแปลงการกำหนดค่า สถานะไม่ว่างควรถูกเก็บรักษาโดยแฟรกเมนต์ เมื่อแนบแฟรกเมนต์เข้ากับกิจกรรมคุณสามารถแสดงไดอะล็อกความคืบหน้าอีกครั้งหากการโทรยังคงทำงานอยู่ void showProgressDialog()วิธีการที่สามารถเพิ่มกับอินเตอร์เฟซผู้ฟังส่วนกิจกรรมเพื่อการนี้

โซลูชั่นที่สมบูรณ์แบบ แต่ไม่เข้าใจว่าทำไมคำตอบนี้ถูกบดบังจากผู้อื่น !!!
blackkara

2

ฉันต้องเผชิญกับสถานการณ์เดียวกัน สิ่งที่ฉันทำคือรับอินสแตนซ์เดียวสำหรับกล่องโต้ตอบความคืบหน้าของฉันในแอปพลิเคชันทั้งหมด

ก่อนอื่นฉันสร้างคลาส DialogSingleton เพื่อรับอินสแตนซ์เดียวเท่านั้น (รูปแบบซิงเกิล)

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

เมื่อฉันแสดงในคลาสนี้ฉันมีกล่องโต้ตอบความคืบหน้าเป็นแอตทริบิวต์ ทุกครั้งที่ฉันต้องการแสดงกล่องโต้ตอบความคืบหน้าฉันจะได้รับอินสแตนซ์ที่ไม่ซ้ำใครและสร้าง ProgressDialog ใหม่

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

เมื่อฉันทำภารกิจพื้นหลังเสร็จฉันจะโทรหาอินสแตนซ์ที่ไม่ซ้ำอีกครั้งและยกเลิกการโต้ตอบ

DialogSingleton.GetInstance().DialogDismiss(this);

ฉันบันทึกสถานะงานพื้นหลังในการตั้งค่าที่แชร์ของฉัน เมื่อฉันหมุนหน้าจอฉันถามว่าฉันมีงานที่ทำกิจกรรมนี้หรือไม่: (บนสร้าง)

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).

เมื่อฉันเริ่มทำงานพื้นหลัง:

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

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

เมื่อฉันรันงานพื้นหลังเสร็จแล้ว:

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

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

ฉันหวังว่ามันจะช่วย


2

นี่เป็นคำถามที่เก่ามากที่เกิดขึ้นบนแถบด้านข้างด้วยเหตุผลบางอย่าง

ถ้างานพื้นหลังเพียงความต้องการที่จะอยู่รอดในขณะที่กิจกรรมที่อยู่ในเบื้องหน้า "ใหม่" แก้ปัญหาคือการเป็นเจ้าภาพด้ายพื้นหลัง (หรือโดยเฉพาะอย่างยิ่งAsyncTask) ในส่วนที่เก็บไว้ตามที่อธิบายไว้ในนี้คู่มือสำหรับนักพัฒนาและหลาย Q

ชิ้นส่วนที่เก็บไว้จะมีชีวิตอยู่ถ้ากิจกรรมถูกทำลายสำหรับการเปลี่ยนแปลงการกำหนดค่า แต่จะไม่เกิดขึ้นเมื่อกิจกรรมถูกทำลายในพื้นหลังหรือกองหลัง ดังนั้นงานพื้นหลังยังคงต้องถูกขัดจังหวะถ้าเป็นเท็จในisChangingConfigurations()onPause()


2

ฉันเป็น Android ที่สดกว่าและฉันลองทำสิ่งนี้และใช้งานได้

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

ฉันลองทุกอย่างแล้ว ทดลองใช้วัน ฉันไม่ต้องการปิดกั้นกิจกรรมจากการหมุน สถานการณ์ของฉันคือ:

  1. กล่องโต้ตอบความคืบหน้าแสดงข้อมูลแบบไดนามิกให้กับผู้ใช้ เช่น: "กำลังเชื่อมต่อกับเซิร์ฟเวอร์ ... ", "กำลังดาวน์โหลดข้อมูล ... " ฯลฯ
  2. ด้ายทำสิ่งที่หนักและปรับปรุงไดอะล็อก
  3. อัปเดต UI ด้วยผลลัพธ์ในตอนท้าย

ปัญหาคือเมื่อหมุนหน้าจอทุกวิธีในหนังสือล้มเหลว แม้จะมีคลาส AsyncTask ซึ่งเป็นวิธีที่ถูกต้องของ Android ในการจัดการกับสถานการณ์นี้ เมื่อหมุนหน้าจอบริบทปัจจุบันที่เธรดเริ่มต้นทำงานจะหายไปและเกิดความยุ่งเหยิงกับกล่องโต้ตอบที่แสดงขึ้น ปัญหาคือไดอะล็อกเสมอไม่ว่าฉันจะเพิ่มลูกเล่นให้กับโค้ดกี่ครั้ง (ผ่านบริบทใหม่ไปยังเธรดที่กำลังรันอยู่ ความซับซ้อนของรหัสในตอนท้ายนั้นใหญ่มากและมีบางสิ่งที่ผิดพลาดได้เสมอ

ทางออกเดียวที่ใช้ได้สำหรับฉันคือเคล็ดลับกิจกรรม / การสนทนา เรียบง่ายและเป็นอัจฉริยะและเป็นหลักฐานการหมุนทั้งหมด:

  1. แทนที่จะสร้างกล่องโต้ตอบและขอให้แสดงให้สร้างกิจกรรมที่กำหนดไว้ในรายการด้วย android: theme = "@ android: style / Theme.Dialog" ดังนั้นมันดูเหมือนโต้ตอบ

  2. แทนที่ showDialog (DIALOG_ID) ด้วย startActivityForResult (yourActivityDialog, yourCode);

  3. ใช้ onActivityResult ในการเรียกกิจกรรมเพื่อรับผลลัพธ์จากเธรดที่กำลังดำเนินการ (แม้แต่ข้อผิดพลาด) และอัพเดต UI

  4. บน 'ActivityDialog' ของคุณให้ใช้เธรดหรือ AsyncTask เพื่อทำงานที่ยาวนานและ onRetainNonConfigurationInstance เพื่อบันทึกสถานะ "โต้ตอบ" เมื่อหมุนหน้าจอ

เร็วและทำงานได้ดี ฉันยังคงใช้กล่องโต้ตอบสำหรับงานอื่น ๆ และ AsyncTask สำหรับบางสิ่งที่ไม่จำเป็นต้องมีการโต้ตอบบนหน้าจอ แต่ด้วยสถานการณ์นี้ฉันมักจะไปที่รูปแบบกิจกรรม / กล่องโต้ตอบ

และฉันไม่ได้ลอง แต่ก็เป็นไปได้ที่จะบล็อค Activity / Dialog นั้นจากการหมุนเมื่อเธรดกำลังทำงานเร่งความเร็วในขณะที่อนุญาตให้ Activity หมุน


Nice ยกเว้นพารามิเตอร์ที่จะต้องส่งผ่านIntentซึ่งมีข้อ จำกัด มากกว่าที่Objectได้รับอนุญาตโดยAsyncTask
rds

@ รุยฉันก็ใช้วิธีนี้เหมือนกันในปีที่แล้ว ตอนนี้มันเกิดขึ้นกับฉันแม้ว่ามันจะไม่ถูกต้องเช่นกัน ทำไม Google ถึงมีกล่องโต้ตอบถ้านี่เป็นวิธีการ 'แก้ไข' ปัญหานี้ ปัญหาที่ฉันเห็นคือถ้าคุณเปิด ActivityB (Theme.Dialog) จาก ActivityA แล้ว ActivityA จะถูกย้ายลงบน Activity stack ดังนั้นจึงถูกทำเครื่องหมายว่าพร้อมที่จะฆ่าโดย OS หากจำเป็น ดังนั้นหากคุณมีกระบวนการที่ใช้เวลานานและกำลังแสดง 'การโต้ตอบ' ความคืบหน้าบางส่วนและมันใช้เวลานานเกินไปและหน่วยความจำเหลือน้อย ... ActivityA ถูกฆ่าและไม่มีอะไรที่จะต้องกลับมาอีกเมื่อความคืบหน้าเสร็จสมบูรณ์
rf43

1

วันนี้มีวิธีที่แตกต่างมากขึ้นในการจัดการปัญหาประเภทนี้ วิธีการทั่วไปคือ:

1. ตรวจสอบว่ามีการแยกข้อมูลของคุณอย่างถูกต้องจาก UI:

สิ่งใดที่เป็นกระบวนการพื้นหลังควรจะอยู่ในที่เก็บไว้Fragment(ตั้งค่านี้ด้วยFragment.setRetainInstance(). นี้จะกลายเป็น 'การจัดเก็บข้อมูลแบบถาวร' ของคุณที่มีข้อมูลอะไรตามที่คุณต้องการเก็บไว้จะถูกเก็บไว้. หลังจากเหตุการณ์การเปลี่ยนแปลงการวางแนวนี้Fragmentจะยังคงสามารถเข้าถึงได้ในที่เดิม สถานะผ่านการFragmentManager.findFragmentByTag()โทร (เมื่อคุณสร้างคุณควรให้แท็กไม่ใช่ IDเนื่องจากไม่ได้เชื่อมต่อกับView)

ดูคู่มือการพัฒนาการจัดการรันไทม์เปลี่ยนแปลงสำหรับข้อมูลเกี่ยวกับการทำสิ่งนี้อย่างถูกต้องและทำไมจึงเป็นตัวเลือกที่ดีที่สุด

2. ให้แน่ใจว่าคุณกำลังเชื่อมต่ออย่างถูกต้องและปลอดภัยระหว่างกระบวนการพื้นหลังและ UI ของคุณ:

คุณต้องย้อนกลับกระบวนการเชื่อมโยงของคุณ ในขณะที่กระบวนการพื้นหลังของคุณแนบกับตัวเองView- คุณViewควรแนบตัวเองเข้ากับกระบวนการพื้นหลังแทน มันสมเหตุสมผลมากขึ้นใช่ไหม การViewกระทำของจะขึ้นอยู่กับกระบวนการพื้นหลังในขณะที่กระบวนการพื้นหลังไม่ได้ขึ้นอยู่กับView. ซึ่งหมายความว่าการเปลี่ยนลิงค์ไปยังListenerอินเตอร์เฟซมาตรฐาน กล่าวว่ากระบวนการของคุณ (สิ่งที่ชั้นมันเป็น - ไม่ว่าจะเป็นAsyncTask, Runnableหรืออะไรก็ตาม) กำหนดOnProcessFinishedListenerเมื่อกระบวนการจะทำก็ควรจะเรียกฟังว่าถ้ามันมีอยู่

คำตอบนี้เป็นคำอธิบายสั้นที่ดีของวิธีการทำ listeners แบบกำหนดเอง

3. เชื่อมโยง UI ของคุณเข้ากับกระบวนการข้อมูลทุกครั้งที่มีการสร้าง UI (รวมถึงการเปลี่ยนแปลงการวางแนว):

ตอนนี้คุณต้องกังวลเกี่ยวกับการเชื่อมต่องานพื้นหลังกับสิ่งที่Viewโครงสร้างปัจจุบันของคุณ หากคุณจัดการกับการเปลี่ยนแปลงการวางแนวของคุณอย่างถูกต้อง (ไม่ใช่configChangesแฮ็คที่คนมักแนะนำ) ระบบของคุณDialogจะถูกสร้างขึ้นใหม่ นี่เป็นสิ่งสำคัญหมายความว่าเมื่อมีการเปลี่ยนแปลงการปฐมนิเทศDialogวิธีการวงจรชีวิตทั้งหมดของคุณจะถูกเรียกคืน ดังนั้นในวิธีการเหล่านี้ ( onCreateDialogโดยปกติจะเป็นสถานที่ที่ดี) คุณสามารถโทรได้ดังต่อไปนี้:

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

ดูวงจรชีวิตของ Fragmentสำหรับการตัดสินใจว่าการตั้งค่าผู้ฟังเหมาะสมกับการใช้งานส่วนบุคคลของคุณอย่างไร

นี่เป็นวิธีการทั่วไปในการมอบโซลูชั่นที่สมบูรณ์และสมบูรณ์ให้กับปัญหาทั่วไปที่ถามในคำถามนี้ อาจมีชิ้นส่วนเล็ก ๆ น้อย ๆ ที่หายไปในคำตอบนี้ขึ้นอยู่กับสถานการณ์ของแต่ละบุคคล แต่นี่เป็นวิธีที่ถูกต้องที่สุดในการจัดการเหตุการณ์การเปลี่ยนทิศทาง


1

ฉันได้พบและแก้ไขปัญหาได้ง่ายขึ้นในการจัดการกระทู้เมื่อเปลี่ยนทิศทาง คุณสามารถทำการอ้างอิงแบบคงที่กับกิจกรรม / ส่วนของคุณและตรวจสอบว่ามันเป็นโมฆะก่อนที่จะดำเนินการกับ UI ฉันขอแนะนำให้ใช้ลองจับด้วย:

 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

หากคุณกำลังดิ้นรนกับการตรวจจับเหตุการณ์การเปลี่ยนทิศทางของกล่องโต้ตอบการอ้างอิงของกิจกรรมที่อ้างอิงวิธีนี้ใช้งานได้ดีอย่างน่าตื่นเต้น ฉันใช้สิ่งนี้เพราะฉันมีคลาสโต้ตอบของตัวเองที่สามารถแสดงได้ในหลาย ๆ กิจกรรมดังนั้นฉันจึงไม่ทราบว่ามันกำลังแสดงกิจกรรมใดอยู่เสมอด้วยวิธีนี้คุณไม่จำเป็นต้องเปลี่ยน AndroidManifest กังวลเกี่ยวกับการอ้างอิงกิจกรรม และคุณไม่ต้องการไดอะล็อกที่กำหนดเอง (อย่างที่ฉันมี) อย่างไรก็ตามคุณต้องการมุมมองเนื้อหาที่กำหนดเองเพื่อให้คุณสามารถตรวจจับการเปลี่ยนแปลงการวางแนวโดยใช้มุมมองนั้น นี่คือตัวอย่างของฉัน:

ติดตั้ง

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

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

        //DO SOMETHING HERE!! :D
    }
}

การใช้งาน 1 - กล่องโต้ตอบ

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

การใช้งาน 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();

การใช้งาน 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

นี่เป็นวิธีแก้ปัญหาของฉันเมื่อฉันพบเจอ: ProgressDialogไม่ใช่Fragmentลูกดังนั้นคลาสที่กำหนดเองของฉัน " ProgressDialogFragment" สามารถขยายได้DialogFragmentแทนเพื่อให้กล่องโต้ตอบปรากฏขึ้นสำหรับการเปลี่ยนแปลงการกำหนดค่า

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

ความท้าทายคือการรักษาชื่อและข้อความโต้ตอบในขณะที่หมุนหน้าจอในขณะที่พวกเขารีเซ็ตเป็นสตริงว่างเปล่าเริ่มต้นแม้ว่าการโต้ตอบยังคงปรากฏ

มี 2 ​​วิธีในการแก้ปัญหานี้:

วิธีแรก: ทำกิจกรรมที่ใช้กล่องโต้ตอบเพื่อรักษาสถานะระหว่างการเปลี่ยนแปลงการกำหนดค่าในไฟล์รายการ:

android:configChanges="orientation|screenSize|keyboardHidden"

Google ไม่ชอบวิธีการนี้

วิธีที่สอง: ในonCreate()วิธีการของกิจกรรมคุณจะต้องเก็บรักษาไว้DialogFragmentโดยสร้างใหม่ProgressDialogFragmentอีกครั้งด้วยชื่อและข้อความดังต่อไปนี้หากsavedInstanceStateไม่เป็นโมฆะ:

@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

ดูเหมือนไกลเกินไป 'รวดเร็วและสกปรก' เป็นจริงดังนั้นโปรดชี้ให้เห็นข้อบกพร่อง แต่สิ่งที่ฉันพบว่าทำงานได้ ...

ภายในเมธอด onPostExecute ของ AsyncTask ของฉันฉันแค่ใส่ '.dismiss' สำหรับกล่องโต้ตอบความคืบหน้าในบล็อก try / catch (ด้วย catch ที่ว่างเปล่า) จากนั้นก็ข้ามข้อยกเว้นที่ถูกยกขึ้นมา ดูเหมือนว่าผิดที่จะทำ แต่ปรากฏว่าไม่มีผลร้าย (อย่างน้อยสำหรับสิ่งที่ฉันทำในภายหลังซึ่งคือการเริ่มต้นกิจกรรมอื่นที่ผ่านในผลของแบบสอบถามยาวของฉันทำงานเป็นพิเศษ)


คุณกำลังบอกว่าไม่มีหน่วยความจำรั่วจริง ๆ เมื่อแพลตฟอร์ม Android บอกหน้าต่างรั่ว?
rds

กล่องโต้ตอบความคืบหน้าของฉันคือตัวแปรสมาชิกของคลาสกิจกรรมดังนั้นฉันจึงสันนิษฐานว่าเมื่อกิจกรรมถูกทำลายและสร้างใหม่มันจะถูกรวบรวมขยะและไม่มีการรั่วไหล ฉันผิดหรือเปล่า?
Simon

ใช่ฉันคิดว่ามันผิด ในขณะที่คุณกล่าวว่ามีการอ้างอิงถึงActivity Dialogเมื่อกำหนดค่าที่มีการเปลี่ยนแปลงครั้งแรกที่ถูกทำลายซึ่งหมายความว่าทุกสาขามีการกำหนดให้Activity nullแต่ระดับต่ำWindowManagerยังมีการอ้างอิงถึงDialog(เนื่องจากยังไม่ได้ออก) ความActivityพยายามใหม่ในการสร้างใหม่Dialog(ในpreExecute()) และตัวจัดการหน้าต่างจะยกข้อยกเว้นร้ายแรงที่ทำให้คุณไม่สามารถทำได้ อันที่จริงถ้ามันไม่เป็นเช่นนั้นจะมีวิธีการที่จะทำลายเรียบร้อยไม่มีเหตุนี้ทำให้การอ้างอิงถึงการเริ่มต้นDialog Activityฉันถูกไหม?
rds

0

ทางออกที่ง่ายและมีความยืดหยุ่นมากที่สุดคือการใช้AsyncTaskมีการอ้างอิงคงProgressBar นี่เป็นวิธีแก้ปัญหาห่อหุ้มและนำกลับมาใช้ใหม่เพื่อแก้ไขปัญหาการวางแนว การแก้ปัญหานี้ได้ทำหน้าที่ฉันดีแตกต่างกันสำหรับงาน asyncronous รวมถึงการดาวน์โหลดอินเทอร์เน็ตสื่อสารกับบริการและสแกนระบบแฟ้ม โซลูชันดังกล่าวได้รับการทดสอบอย่างดีสำหรับ Android หลายรุ่นและรุ่นโทรศัพท์ ตัวอย่างที่สมบูรณ์สามารถพบได้ที่นี่พร้อมความสนใจเฉพาะในDownloadFile.java

ฉันนำเสนอต่อไปนี้เป็นตัวอย่างแนวคิด

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

การใช้งานในกิจกรรม Android นั้นง่ายมาก

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

เมื่อคุณเปลี่ยนการวางแนว Android จะฆ่ากิจกรรมนั้นและสร้างกิจกรรมใหม่ ฉันแนะนำให้ใช้ชุดติดตั้งเพิ่มเติมกับ Rx java ซึ่งจัดการข้อขัดข้องโดยอัตโนมัติ

ใช้วิธีการเหล่านี้เมื่อมีการติดตั้งเพิ่มการโทร

.subscribeOn (Schedulers.io ()) .observeOn (AndroidSchedulers.mainThread ())

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.