แนวทางปฏิบัติที่ดี: AsyncTask ในระหว่างการเปลี่ยนการวางแนว


151

AsyncTask เป็นสิ่งที่ดีในการทำงานที่ซับซ้อนในหัวข้ออื่น

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

ดังนั้นฉันกำลังมองหา "แนวปฏิบัติที่ดีที่สุด" เพื่อหลีกเลี่ยงข้อผิดพลาดและป้องกันไม่ให้ AsyncTask ล้มเหลว

สิ่งที่ฉันเคยเห็นคือ:

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

ตัวอย่างโค้ดบางส่วน:

Android AsyncTasks ระหว่างการหมุนหน้าจอ, ส่วนที่ฉันและส่วนที่สอง

ShelvesActivity.java

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


มีสิ่งที่ซ้ำกันให้ตรวจสอบstackoverflow.com/questions/4584015/นี้
TeaCupApp

นี่คือจากบล็อกของ Mark Murphy ... AsyncTask และ ScreenRotation อาจช่วย ... link
Gopal

แม้ว่านี่จะเป็นโพสต์เก่า แต่IMO นี้เป็นวิธีที่ง่ายกว่า (และดีกว่า) มาก
DroidDev

ฉันแค่สงสัยว่าทำไมเอกสารไม่พูดเกี่ยวกับสถานการณ์ที่น่ารำคาญมาก
Sreekanth Karumanaghat

คำตอบ:


140

อย่าไม่ใช้android:configChangesเพื่อแก้ไขปัญหานี้ นี่เป็นการฝึกฝนที่แย่มาก

อย่าไม่ใช้Activity#onRetainNonConfigurationInstance()อย่างใดอย่างหนึ่ง นี่เป็นมอดุลาร์ที่น้อยกว่าและไม่เหมาะสำหรับFragmentแอพพลิเคชั่นพื้นฐาน

คุณสามารถอ่านบทความของฉันอธิบายวิธีจัดการกับการเปลี่ยนแปลงการกำหนดค่าโดยใช้Fragments มันแก้ปัญหาของการรักษาการAsyncTaskเปลี่ยนแปลงการหมุนอย่างดี คุณพื้นต้องโฮสต์ของคุณAsyncTaskภายในFragmentโทรsetRetainInstance(true)ในFragmentและรายงานAsyncTaskของความคืบหน้า / ผลกลับไปที่มันผ่านไว้ActivityFragment


26
เป็นความคิดที่ดี แต่ไม่ใช่ทุกคนที่ใช้ Fragments มีรหัสมรดกจำนวนมากที่เขียนมานานก่อนที่แฟรกเมนต์จะเป็นตัวเลือก
Scott Biggs

14
@ScottBiggs Fragments มีให้บริการผ่านห้องสมุดสนับสนุนตลอดทางจนถึง Android 1.6 และคุณสามารถให้ตัวอย่างของรหัสดั้งเดิมบางส่วนที่ยังคงใช้งานอยู่ซึ่งอาจมีปัญหาในการใช้งานส่วนสนับสนุนไลบรารี เพราะฉันไม่คิดว่าเป็นปัญหาจริงๆ
Alex Lockwood

4
@tactoth TabActivityผมไม่ได้รู้สึกว่าต้องแก้ไขปัญหาเหล่านี้ในคำตอบของฉันกับการใช้งาน พูดตามตรงฉันไม่แน่ใจว่าทำไมเราถึงพูดถึงเรื่องนี้ ... ทุกคนยอมรับว่าFragmentเป็นหนทางที่จะไป :)
Alex Lockwood

2
จะเกิดอะไรขึ้นถ้า AsyncTask ต้องถูกเรียกใช้จากแฟรกเมนต์ซ้อนกัน?
Eduardo Naveda

3
@AlexLockwood - "ทุกคนยอมรับว่าชิ้นส่วนเป็นวิธีที่จะไป" Devs ที่ Squared จะไม่เห็นด้วย!
JBeckton

36

ฉันมักจะแก้ปัญหานี้โดยให้ AsyncTasks ของฉันเริ่มการออกอากาศ Intent ในการโทรกลับ. onExExecute () ดังนั้นพวกเขาจึงไม่แก้ไขกิจกรรมที่เริ่มต้นพวกเขาโดยตรง กิจกรรมรับฟังการออกอากาศเหล่านี้ด้วย BroadcastReceivers แบบไดนามิกและดำเนินการตามนั้น

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

สิ่งนี้เกี่ยวข้องกับค่าใช้จ่ายเพิ่มอีกเล็กน้อยเนื่องจากรันไทม์ต้องการจัดการการออกอากาศ แต่โดยทั่วไปฉันไม่สนใจ ฉันคิดว่าการใช้ LocalBroadcastManager แทนที่จะเป็นระบบเริ่มต้นที่กว้างหนึ่งจะเพิ่มความเร็วขึ้นเล็กน้อย


6
ถ้าคุณสามารถเพิ่มตัวอย่างในคำตอบมันจะมีประโยชน์มากขึ้น
Sankar V

1
ฉันคิดว่านี่เป็นวิธีแก้ปัญหาที่นำเสนอการมีเพศสัมพันธ์น้อยลงระหว่างกิจกรรมและชิ้นส่วน
Roger Garzon Nieto

7
นี่อาจเป็นส่วนหนึ่งของการแก้ปัญหา แต่ดูเหมือนว่ามันจะไม่สามารถแก้ปัญหาของ AsyncTask ที่ได้รับการสร้างขึ้นใหม่หลังจากการเปลี่ยนทิศทาง
miguel

4
จะเป็นอย่างไรถ้าคุณโชคไม่ดีและไม่มีกิจกรรมใด ๆ เกิดขึ้นระหว่างการออกอากาศ? (เช่นคุณอยู่กลางหมุน)
Sam

24

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

ตัวอย่างส่วนหนึ่งขึ้นอยู่กับเอกสารอย่างเป็นทางการรักษาวัตถุในระหว่างการกำหนดค่าเปลี่ยน

ในตัวอย่างนี้งานที่ต้องการเธรดพื้นหลังเป็นการโหลดรูปภาพจากอินเทอร์เน็ตไปยัง UI

Alex Lockwood ดูเหมือนจะถูกต้องว่าเมื่อพูดถึงการจัดการการเปลี่ยนแปลงการกำหนดค่ารันไทม์ด้วย AsyncTasks โดยใช้ "Retained Fragment" เป็นแนวปฏิบัติที่ดีที่สุด onRetainNonConfigurationInstance()ได้รับการคัดค้านใน Lint ใน Android Studio เอกสารอย่างเป็นทางการเตือนให้เราปิดการใช้android:configChangesงานจากการจัดการการเปลี่ยนแปลงการตั้งค่าตัวเอง ...

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

แล้วมีปัญหาว่าควรใช้ AsyncTask เลยสำหรับเธรดพื้นหลังหรือไม่

การอ้างอิงอย่างเป็นทางการสำหรับ AsyncTaskเตือน ...

AsyncTasks ควรใช้อย่างเหมาะสมสำหรับการดำเนินการสั้น ๆ (ไม่เกินสองสามวินาที) หากคุณต้องการให้เธรดทำงานต่อเนื่องเป็นเวลานานขอแนะนำอย่างยิ่งให้คุณใช้ API ต่าง ๆ ที่มีให้โดย java.util.concurrent pacakge เช่น ผู้บริหาร, ThreadPoolExecutor และ FutureTask

อีกวิธีหนึ่งสามารถใช้บริการโหลดเดอร์ (ใช้ CursorLoader หรือ AsyncTaskLoader) หรือผู้ให้บริการเนื้อหาเพื่อดำเนินการแบบอะซิงโครนัส

ฉันแบ่งโพสต์ที่เหลือออกเป็น:

  • ขั้นตอน; และ
  • รหัสทั้งหมดสำหรับขั้นตอนข้างต้น

ขั้นตอน

  1. เริ่มต้นด้วย AsyncTask ขั้นพื้นฐานเป็นคลาสภายในของกิจกรรม (ไม่จำเป็นต้องเป็นคลาสภายใน แต่อาจจะสะดวกกว่า) ในขั้นตอนนี้ AsyncTask ไม่จัดการการเปลี่ยนแปลงการกำหนดค่ารันไทม์

    public class ThreadsActivity extends ActionBarActivity {
    
        private ImageView mPictureImageView;
    
        private class LoadImageFromNetworkAsyncTask
                              extends AsyncTask<String, Void, Bitmap> {
    
            @Override
            protected Bitmap doInBackground(String... urls) {
                return loadImageFromNetwork(urls[0]);
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                mPictureImageView.setImageBitmap(bitmap);
            }
        }
    
        /**
         * Requires in AndroidManifext.xml
         *  <uses-permission android:name="android.permission.INTERNET" />
         */
        private Bitmap loadImageFromNetwork(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeStream((InputStream)
                                              new URL(url).getContent());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            mPictureImageView =
                (ImageView) findViewById(R.id.imageView_picture);
        }
    
        public void getPicture(View view) {
            new LoadImageFromNetworkAsyncTask()
                .execute("http://i.imgur.com/SikTbWe.jpg");
        }
    
    }
  2. เพิ่มคลาสที่ซ้อนกัน RetainedFragment ซึ่งขยายคลาส Fragement และไม่มี UI ของตัวเอง เพิ่ม setRetainInstance (จริง) ไปยังเหตุการณ์ onCreate ของ Fragment นี้ ให้ขั้นตอนการตั้งค่าและรับข้อมูลของคุณ

    public class ThreadsActivity extends Activity {
    
        private ImageView mPictureImageView;
        private RetainedFragment mRetainedFragment = null;
        ...
    
        public static class RetainedFragment extends Fragment {
    
            private Bitmap mBitmap;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
                // The key to making data survive
                // runtime configuration changes.
                setRetainInstance(true);
            }
    
            public Bitmap getData() {
                return this.mBitmap;
            }
    
            public void setData(Bitmap bitmapToRetain) {
                this.mBitmap = bitmapToRetain;
            }
        }
    
        private class LoadImageFromNetworkAsyncTask
                        extends AsyncTask<String, Integer,Bitmap> {
        ....
  3. ในคลาสกิจกรรมนอกสุดของ onCreate () จัดการ RetainedFragment: อ้างอิงถ้ามันมีอยู่แล้ว (ในกรณีที่กิจกรรมเริ่มต้นใหม่); สร้างและเพิ่มหากไม่มีอยู่ จากนั้นหากมีอยู่แล้วให้รับข้อมูลจาก RetainedFragment และตั้งค่า UI ของคุณด้วยข้อมูลนั้น

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

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. เพิ่มและรหัสแถบความคืบหน้าแน่นอน:

    • เพิ่มแถบความคืบหน้าในเค้าโครง UI;
    • รับการอ้างอิงถึงในกิจกรรม oncreate ();
    • ทำให้มองเห็นและ Invisble ที่จุดเริ่มต้นและจุดสิ้นสุดของกระบวนการ
    • กำหนดความคืบหน้าในการรายงานต่อ UI ใน onProgressUpdate
    • เปลี่ยนพารามิเตอร์ AsyncTask 2nd Generic จาก Void เป็นประเภทที่สามารถจัดการกับการอัพเดตความคืบหน้า (เช่น Integer)
    • publishProgress ที่จุดปกติใน doInBackground ()

รหัสทั้งหมดสำหรับขั้นตอนข้างต้น

เค้าโครงกิจกรรม

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

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

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

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

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

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

    </RelativeLayout>
</ScrollView>

The Activity with: subclassed AsyncTask ชั้นใน คลาสย่อยภายใน RetainedFragment ที่จัดการการเปลี่ยนแปลงการกำหนดค่ารันไทม์ (เช่นเมื่อผู้ใช้หมุนหน้าจอ) และแถบความคืบหน้าที่แน่นอนในการปรับปรุงในช่วงเวลาปกติ ...

public class ThreadsActivity extends Activity {

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

    public static class RetainedFragment extends Fragment {

        private Bitmap mBitmap;

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

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

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

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

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

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

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

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

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

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

        final String retainedFragmentTag = "RetainedFragmentTag";

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

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

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

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

            // The Retained Fragment exists
        } else {

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

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

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

ในตัวอย่างนี้ฟังก์ชั่นห้องสมุด (อ้างอิงข้างต้นด้วยคำนำหน้าแพคเกจที่ชัดเจน com.example.standardapplibrary.android.Network) ที่ทำงานจริง ...

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

เพิ่มสิทธิ์ใด ๆ ที่งานพื้นหลังของคุณต้องการกับ AndroidManifest.xml ...

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

เพิ่มกิจกรรมของคุณใน AndroidManifest.xml ...

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

ยิ่งใหญ่ คุณควรเขียนบล็อกเกี่ยวกับเรื่องนี้
Akh

2
@AKh คุณหมายถึงการแนะนำคำตอบของฉันให้มีพื้นที่มากเกินไปใน Stackoverflow หรือไม่?
John Bentley

1
ฉันคิดว่าเขาแค่หมายความว่าคุณมีคำตอบที่ยอดเยี่ยมและคุณควรเขียนบล็อก! =) @JohnBentley
Sandy D.

@ SandyD.y เมื่อวานนี้ขอบคุณสำหรับการตีความในเชิงบวก ฉันหวังว่าเธอหรือเขาตั้งใจจะทำเช่นนั้น
John Bentley

ฉันก็คิดว่ามันเป็นคำตอบที่ยอดเยี่ยมและฉันก็ตีความอย่างนั้นเช่นกัน คำตอบที่สมบูรณ์มากเช่นนี้ยอดเยี่ยม !!
LeonardoSibela

3

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


ขอบคุณมากสำหรับคำตอบที่น่าสนใจนี้ มันเป็นทางออกที่ดีนอกเหนือจากที่กล่าวถึงในคำถามที่เกี่ยวข้อง
caw

5
น่าเสียดายที่โซลูชันนี้ใช้วิธีการที่เลิกใช้แล้ว
Damien

3

จาก @Alex Lockwood คำตอบและ @William & @quickdraw mcgraw คำตอบในโพสต์นี้: วิธีจัดการกับข้อความ Handler เมื่อกิจกรรม /แฟรกเมนต์ถูกหยุดชั่วคราวฉันเขียนวิธีแก้ปัญหาทั่วไป

วิธีการหมุนนี้ได้รับการจัดการและหากกิจกรรมไปที่พื้นหลังในระหว่างการเรียกใช้งาน async กิจกรรมจะได้รับการเรียกกลับ (onPreExecute, onProgressUpdate, onPostExecute & onCancelled) อีกครั้งดังนั้นจึงไม่มี IllegalStateException ถูกโยนออกมา (ดูวิธีจัดการ Handler ข้อความเมื่อกิจกรรม / ส่วนถูกหยุดชั่วคราว )

มันจะดีถ้ามีเหมือนกัน แต่มีประเภทอาร์กิวเมนต์ทั่วไปเช่น AsyncTask (เช่น: AsyncTaskFragment <Params, Progress, Result>) แต่ฉันไม่ได้จัดการที่จะทำมันอย่างรวดเร็วและไม่มีเวลาในขณะนี้ หากใครต้องการที่จะทำการปรับปรุงโปรดอย่าลังเล!

รหัส:

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

public class AsyncTaskFragment extends Fragment {

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

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

        private AsyncTaskFragment _fragment;

        private void setFragment(AsyncTaskFragment fragment) {

            _fragment = fragment;
        }

        @Override
        protected final void onPreExecute() {

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

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

        @Override
        protected final void onPostExecute(Object result) {

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

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

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

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

        @Override
        protected final void onCancelled() {

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

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

        private void sendMessage(int what, Object obj) {

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

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

            _fragment.handler.sendMessage(message);
        }
    }

    public interface AsyncTaskFragmentListener {

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

    private static class AsyncTaskFragmentPauseHandler extends PauseHandler {

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

            switch (message.what) {

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

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



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

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

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

    private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();

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



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

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

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



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

    public void setTask(Task task) {

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

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

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



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

    @Override
    public void onCreate(Bundle savedInstanceState) {

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

    @Override
    public void onResume() {

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

    @Override
    public void onPause() {

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

    @Override
    public void onAttach(Activity activity) {

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

    @Override
    public void onDetach() {

        super.onDetach();
        _listener = null;
    }

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



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

    public void execute(Object... params) {

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

    public void cancel(boolean mayInterruptIfRunning) {

        _task.cancel(mayInterruptIfRunning);
    }

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

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

        if (fragment == null) {

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

        return fragment;
    }

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

คุณจะต้องมี PauseHandler:

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

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

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

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

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

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

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

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

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

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

ตัวอย่างการใช้งาน:

public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {

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

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

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

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

                if(!fragment.isRunning()) {

                    fragment.setTask(new Task() {

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

                            // Do your async stuff

                            return null;
                        }
                    });

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

    @Override
    public void onPreExecute(String fragmentTag) {}

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

    @Override
    public void onCancelled(String fragmentTag) {}

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

        switch (fragmentTag) {

            case ASYNC_TASK_FRAGMENT_A: {

                // Handle ASYNC_TASK_FRAGMENT_A
                break;
            }
            case ASYNC_TASK_FRAGMENT_B: {

                // Handle ASYNC_TASK_FRAGMENT_B
                break;
            }
        }
    }
}

3

คุณสามารถใช้ตัวโหลดสำหรับสิ่งนี้ ตรวจสอบหมอที่นี่


2
ขณะนี้ตัวโหลดเลิกใช้แล้วใน Android API 28 (ตามที่ลิงก์จะแจ้งให้คุณทราบ)
Sumit

ตัวโหลดไม่ได้ถูกคัดค้านมันเป็นเพียงวิธีที่คุณเรียกพวกมันว่าถูกเปลี่ยนแปลง
EdgeDev

2

สำหรับผู้ที่ต้องการหลบเศษชิ้นส่วนคุณสามารถเก็บ AsyncTask ที่ทำงานตามการเปลี่ยนแปลงการวางแนวโดยใช้onRetainCustomNonConfigurationInstance ()และสายไฟบางส่วน

(ขอให้สังเกตว่าวิธีนี้เป็นอีกทางเลือกที่จะเลิกonRetainNonConfigurationInstance () )

ดูเหมือนว่าวิธีนี้ไม่ได้กล่าวถึงบ่อยครั้ง ฉันเขียนตัวอย่างการใช้งานอย่างง่ายเพื่ออธิบาย

ไชโย!

public class MainActivity extends AppCompatActivity {

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

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

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

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

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

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

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

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

interface ActivityDependant {

    void attach(Activity activity);

    void detach();
}

class AsyncTaskHolder implements ActivityDependant {

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

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

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

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

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

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

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

0

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

คุณควรจะดำเนินการและAsmykPleaseWaitTask AsmykBasicPleaseWaitActivityงานกิจกรรมและพื้นหลังของคุณจะทำงานได้ดีแม้คุณจะหมุนหน้าจอและสลับระหว่างแอปพลิเคชัน


-9

การหลีกเลี่ยงปัญหาอย่างรวดเร็ว (ไม่แนะนำ)

เพื่อหลีกเลี่ยงกิจกรรมที่จะทำลายและสร้างตัวเองคือการประกาศกิจกรรมของคุณในไฟล์รายการ: android: configChanges = "การวางแนว | keyboardHidden | screenSize

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

ตามที่กล่าวไว้ในเอกสาร

การวางแนวหน้าจอมีการเปลี่ยนแปลง - ผู้ใช้หมุนอุปกรณ์

หมายเหตุ: หากแอปพลิเคชันของคุณกำหนดเป้าหมาย API ระดับ 13 ขึ้นไป (ตามที่ประกาศโดยแอ็ตทริบิวต์ minSdkVersion และ targetSdkVersion) ดังนั้นคุณควรประกาศการกำหนดค่า "screenSize" เนื่องจากจะเปลี่ยนเมื่ออุปกรณ์สลับระหว่างแนวตั้งและแนวนอน


1
นี่เป็นการหลีกเลี่ยงที่ดีที่สุด developer.android.com/guide/topics/resources/… "หมายเหตุ: การจัดการการเปลี่ยนแปลงการกำหนดค่าด้วยตนเองสามารถทำให้ยากต่อการใช้ทรัพยากรทางเลือกมากขึ้นเนื่องจากระบบไม่ได้นำมาใช้โดยอัตโนมัติสำหรับคุณเทคนิคนี้ควรเป็นเทคนิคสุดท้าย เมื่อคุณต้องหลีกเลี่ยงการรีสตาร์ทเนื่องจากมีการเปลี่ยนแปลงการกำหนดค่าและไม่แนะนำสำหรับแอปพลิเคชันส่วนใหญ่ "
เดวิด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.