android-fragments alert - Android碎片。 在屏幕旋轉或配置更改期間保留AsyncTask




dialogfragment developer (11)

我正在使用智能手機/平板電腦應用程序,只使用一個APK,並根據屏幕大小加載資源,最好的設計選擇似乎是通過ACL使用碎片。

這個應用程序一直工作正常,直到現在只是基於活動。 這是我如何在活動中處理AsyncTasks和ProgressDialogs的模擬類,以便即使在屏幕旋轉或通信中發生配置更改時也能使其工作。

我不會為了避免娛樂活動而改變清單,我有很多理由不想這樣做,但主要是因為官方文檔說這不是推薦的,而且我沒有做到這一點,所以請不要推薦路線。

public class Login extends Activity {

    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.login);
        //SETUP UI OBJECTS
        restoreAsyncTask();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (pd != null) pd.dismiss();
        if (asyncLoginThread != null) return (asyncLoginThread);
        return super.onRetainNonConfigurationInstance();
    }

    private void restoreAsyncTask();() {
        pd = new ProgressDialog(Login.this);
        if (getLastNonConfigurationInstance() != null) {
            asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
            if (asyncLoginThread != null) {
                if (!(asyncLoginThread.getStatus()
                        .equals(AsyncTask.Status.FINISHED))) {
                    showProgressDialog();
                }
            }
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... args) {
            try {
                //Connect to WS, recieve a JSON/XML Response
                //Place it somewhere I can use it.
            } catch (Exception e) {
                return true;
            }
            return true;
        }

        protected void onPostExecute(Boolean result) {
            if (result) {
                pd.dismiss();
                //Handle the response. Either deny entry or launch new Login Succesful Activity
            }
        }
    }
}

這段代碼工作正常,我有大約10,000個用戶沒有投訴,所以把這個邏輯複製到新的基於片段的設計中似乎是合乎邏輯的,但是,當然,它並不工作。

這裡是LoginFragment:

public class LoginFragment extends Fragment {

    FragmentActivity parentActivity;
    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    public interface OnLoginSuccessfulListener {
        public void onLoginSuccessful(GlobalContainer globalContainer);
    }

    public void onSaveInstanceState(Bundle outState){
        super.onSaveInstanceState(outState);
        //Save some stuff for the UI State
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setRetainInstance(true);
        //If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
        parentActivity = getActivity();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
        return loginLayout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //SETUP UI OBJECTS
        if(savedInstanceState != null){
            //Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
            @Override
            protected Boolean doInBackground(String... args) {
                try {
                    //Connect to WS, recieve a JSON/XML Response
                    //Place it somewhere I can use it.
                } catch (Exception e) {
                    return true;
                }
                return true;
            }

            protected void onPostExecute(Boolean result) {
                if (result) {
                    pd.dismiss();
                    //Handle the response. Either deny entry or launch new Login Succesful Activity
                }
            }
        }
    }
}

我不能使用onRetainNonConfigurationInstance()因為它必須從Activity而不是Fragment中調用,與getLastNonConfigurationInstance() 。 我在這裡讀了一些類似的問題,沒有回答。

我知道這可能需要一些解決方法才能讓這些東西在碎片中得到妥善組織,也就是說,我想保持相同的基本設計邏輯。

在配置更改過程中保留AsyncTask的正確方法是什麼,如果它仍在運行,則顯示progressDialog,同時考慮到AsyncTask是Fragment的內部類,並且它是調用AsyncTask.execute的Fragment本身()?


Answers

我最近發布了一篇文章,介紹如何使用保留的Fragment來處理配置更改。 它很好地解決了在旋轉更改中保留AsyncTask的問題。

TL; DR將在Fragment使用託管AsyncTaskFragment調用setRetainInstance(true) ,並將AsyncTask的進度/結果報告回它的Activity (或者它的目標Fragment ,如果您選擇使用描述的方法通過@Timmmm)通過保留的Fragment


我的第一個建議是避免內部的AsyncTasks ,你可以閱讀一個關於這個問題和答案的問題: Android:AsyncTask建議:私有類還是公共類?

之後,我開始使用非內部和...現在我看到很多好處。

第二個是,在Application類中保留一個正在運行的AsyncTask的引用 - http://developer.android.com/reference/android/app/Application.html

每次啟動一個AsyncTask,將其設置在應用程序上,當它完成時將其設置為空。

當一個片段/活動開始時,您可以檢查是否有任何AsyncTask正在運行(通過檢查應用程序中是否為null),然後將引用設置為任何您想要的(活動,片段等,以便您可以執行回調)。

這將解決您的問題:如果您只有1個AsyncTask在任何確定的時間運行,您可以添加一個簡單的參考:

AsyncTask<?,?,?> asyncTask = null;

否則,在應用程序中有一個HashMap並引用它們。

進度對話框可以遵循完全相同的原則。


I write samepl code to solve this problem

First step is make Application class:

public class TheApp extends Application {

private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();

@Override
public void onCreate() {
    super.onCreate();
    sTheApp = this;
}

public static TheApp get() {
    return sTheApp;
}

public void registerTask(String tag, AsyncTask<?,?,?> task) {
    tasks.put(tag, task);
}

public void unregisterTask(String tag) {
    tasks.remove(tag);
}

public AsyncTask<?,?,?> getTask(String tag) {
    return tasks.get(tag);
}
}

In AndroidManifest.xml

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:name="com.example.tasktest.TheApp">

Code in activity:

public class MainActivity extends Activity {

private Task1 mTask1;

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

    mTask1 = (Task1)TheApp.get().getTask("task1");

}

/*
 * start task is not running jet
 */
public void handletask1(View v) {
    if (mTask1 == null) {
        mTask1 = new Task1();
        TheApp.get().registerTask("task1", mTask1);
        mTask1.execute();
    } else
        Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show();

}

/*
 * cancel task if is not finished
 */
public void handelCancel(View v) {
    if (mTask1 != null)
        mTask1.cancel(false);
}

public class Task1 extends AsyncTask<Void, Void, Void>{

    @Override
    protected Void doInBackground(Void... params) {
        try {
            for(int i=0; i<120; i++) {
                Thread.sleep(1000);
                Log.i("tests", "loop=" + i);
                if (this.isCancelled()) {
                    Log.e("tests", "tssk cancelled");
                    break;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onCancelled(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }

    @Override
    protected void onPostExecute(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }
}

}

When activity orientation changes variable mTask is inited from app context. When task is finished variable is set to null and remove from memory.

For me its enough.


片段實際上可以使這更容易。 只需使用方法Fragment.setRetainInstance(boolean)來讓您的片段實例在配置更改中保留。 請注意,這是文檔中Activity.onRetainnonConfigurationInstance()的推薦替代方案。

如果由於某種原因,您確實不想使用保留的片段,那麼您可以採取其他方法。 請注意,每個片段都有一個由Fragment.getId()返回的唯一標識符。 您還可以通過Fragment.getActivity().isChangingConfigurations()來查看是否正在Fragment.getActivity().isChangingConfigurations()以進行配置更改。 因此,在您決定停止AsyncTask(最有可能在onStop()或onDestroy()中)的地方,您可以檢查配置是否正在更改,如果是這樣,請將其粘貼到片段標識符下的靜態SparseArray中,然後在onCreate()或onStart()函數中查看是否有可用的稀疏數組中的AsyncTask。


我的方法是使用委託設計模式,通常,我們可以在您的AysncTask.doInBackground()方法中將AsyncTask(委託人)的實際業務邏輯(從互聯網或數據庫讀取數據)分離為BusinessDAO(委託人) ,將實際任務委派給BusinessDAO,然後在BusinessDAO中實現單例過程機制,以便多次調用BusinessDAO.doSomething()將只觸發一次每次運行的實際任務並等待任務結果。 這個想法是在配置更改期間保留委託(即BusinessDAO),而不是委託者(即AsyncTask)。

  1. 創建/實現我們自己的應用程序,目的是在這裡創建/初始化BusinessDAO,以便我們的BusinessDAO的生命週期是應用程序範圍的,而不是活動範圍的,請注意,您需要更改AndroidManifest.xml以使用MyApplication:

    public class MyApplication extends android.app.Application {
      private BusinessDAO businessDAO;
    
      @Override
      public void onCreate() {
        super.onCreate();
        businessDAO = new BusinessDAO();
      }
    
      pubilc BusinessDAO getBusinessDAO() {
        return businessDAO;
      }
    
    }
    
  2. 我們現有的Activity / Fragement大部分是不變的,仍然將AsyncTask作為內部類實現,並且涉及到Activity / Fragement中的AsyncTask.execute(),現在的區別是AsyncTask會將實際任務委託給BusinessDAO,因此在配置更改期間,第二個AsyncTask將被初始化並執行,並且第二次調用BusinessDAO.doSomething(),但是,第二次調用BusinessDAO.doSomething()將不會觸發新的運行任務,而是等待當前正在運行的任務完成:

    public class LoginFragment extends Fragment {
      ... ...
    
      public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> {
        // get a reference of BusinessDAO from application scope.
        BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO();
    
        @Override
        protected Boolean doInBackground(String... args) {
            businessDAO.doSomething();
            return true;
        }
    
        protected void onPostExecute(Boolean result) {
          //Handle task result and update UI stuff.
        }
      }
    
      ... ...
    }
    
  3. 在BusinessDAO內部,實現單身處理機制,例如:

    public class BusinessDAO {
      ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1));
      Future<MyTask> myFutureTask = null;
    
      public void doSomething() {
        if (myFutureTask == null) {
          // nothing running at the moment, submit a new callable task to run.
          MyTask myTask = new MyTask();
          myFutureTask = completionExecutor.submit(myTask);
        }
        // Task already submitted and running, waiting for the running task to finish.
        myFutureTask.get();
      }
    
      // If you've never used this before, Callable is similar with Runnable, with ability to return result and throw exception.
      private class MyTask extends Callable<MyTask> {
        public MyAsyncTask call() {
          // do your job here.
          return this;
        }
      }
    
    }
    

我不是100%確定這是否可行,而且,示例代碼片段應該被視為偽代碼。 我只是想從設計層面給你提供一些線索。 任何反饋或建議,歡迎和讚賞。


我想你會喜歡我下面詳細介紹的非常全面和實用的例子。

  1. 旋轉工作,並且對話生存。
  2. 您可以通過按下後退按鈕取消任務和對話框(如果您想要這種行為)。
  3. 它使用碎片。
  4. 設備旋轉時,活動下面的碎片佈局會正確更改。
  5. 有一個完整的源代碼下載和預編譯APK,所以你可以看到,如果行為是你想要的。

編輯

按照Brad Larson的要求,我已經復制了以下大多數鏈接解決方案。 另外,因為我發布它,我已被指向AsyncTaskLoader 。 我不確定它是否完全適用於相同的問題,但無論如何您都應該檢查一下。

使用帶進度對話框和設備旋轉的AsyncTask

工作解決方案!

我終於得到了一切工作。 我的代碼具有以下功能:

  1. 一個Fragment其佈局隨著方向而變化。
  2. 您可以在其中執行一些工作的AsyncTask
  3. 一個DialogFragment ,它顯示進度條中的任務進度(不僅僅是一個不確定的微調)。
  4. 旋轉在不中斷任務或解除對話的情況下工作。
  5. 後退按鈕關閉對話框並取消任務(儘管可以很容易地改變這種行為)。

我不認為在其他任何地方都可以找到工作的組合。

基本思路如下。 有一個MainActivity類包含一個片段 - MainFragmentMainFragment對於水平和垂直方向具有不同的佈局,並且setRetainInstance()為false,因此佈局可以更改。 這意味著當設備方向改變時, MainActivityMainFragment都被完全銷毀並重新創建。

另外我們有MyTask (從AsyncTask擴展)完成所有的工作。 我們無法將它存儲在MainFragment因為它會被銷毀,並且Google已經不贊成使用setRetainNonInstanceConfiguration()類的東西。 無論如何,這並不總是可用的,充其量也是一種醜陋的黑客。 相反,我們將MyTask存儲在另一個片段中,一個名為TaskFragment這個片段 setRetainInstance()設置為true,所以當設備旋轉時,這個片段不會被銷毀,並且MyTask被保留。

最後,我們需要告訴TaskFragment何時完成通知,並且在創建時使用setTargetFragment(<the MainFragment>)來完成。 當設備被旋轉並且MainFragment被銷毀並且一個新實例被創建時,我們使用FragmentManager來找到對話框(基於它的標籤)並且執行setTargetFragment(<the new MainFragment>) 。 這是非常多的。

還有其他兩件事情我需要做:首先取消任務,當對話框關閉時,第二次將取消消息設置為空,否則在旋轉設備時奇怪地忽略對話框。

代碼

我不會列出佈局,它們非常明顯,您可以在下面的項目下載中找到它們。

主要活動

這非常簡單。 我在這個活動中添加了一個回調,以便知道任務何時完成,但您可能不需要。 主要我只是想顯示片段活動回調機制,因為它非常整潔,你可能以前沒有看到它。

public class MainActivity extends Activity implements MainFragment.Callbacks
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public void onTaskFinished()
    {
        // Hooray. A toast to our success.
        Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
        // NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
        // the duration in milliseconds. ANDROID Y U NO ENUM? 
    }
}

MainFragment

這很長,但值得!

public class MainFragment extends Fragment implements OnClickListener
{
    // This code up to onDetach() is all to get easy callbacks to the Activity. 
    private Callbacks mCallbacks = sDummyCallbacks;

    public interface Callbacks
    {
        public void onTaskFinished();
    }
    private static Callbacks sDummyCallbacks = new Callbacks()
    {
        public void onTaskFinished() { }
    };

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        if (!(activity instanceof Callbacks))
        {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }
        mCallbacks = (Callbacks) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        mCallbacks = sDummyCallbacks;
    }

    // Save a reference to the fragment manager. This is initialised in onCreate().
    private FragmentManager mFM;

    // Code to identify the fragment that is calling onActivityResult(). We don't really need
    // this since we only have one fragment to deal with.
    static final int TASK_FRAGMENT = 0;

    // Tag so we can find the task fragment again, in another instance of this fragment after rotation.
    static final String TASK_FRAGMENT_TAG = "task";

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

        // At this point the fragment may have been recreated due to a rotation,
        // and there may be a TaskFragment lying around. So see if we can find it.
        mFM = getFragmentManager();
        // Check to see if we have retained the worker fragment.
        TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);

        if (taskFragment != null)
        {
            // Update the target fragment so it goes to this fragment instead of the old one.
            // This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
            // keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
            // use weak references. To be sure you aren't leaking, you may wish to make your own
            // setTargetFragment() which does.
            taskFragment.setTargetFragment(this, TASK_FRAGMENT);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);

        // Callback for the "start task" button. I originally used the XML onClick()
        // but it goes to the Activity instead.
        view.findViewById(R.id.taskButton).setOnClickListener(this);
    }

    @Override
    public void onClick(View v)
    {
        // We only have one click listener so we know it is the "Start Task" button.

        // We will create a new TaskFragment.
        TaskFragment taskFragment = new TaskFragment();
        // And create a task for it to monitor. In this implementation the taskFragment
        // executes the task, but you could change it so that it is started here.
        taskFragment.setTask(new MyTask());
        // And tell it to call onActivityResult() on this fragment.
        taskFragment.setTargetFragment(this, TASK_FRAGMENT);

        // Show the fragment.
        // I'm not sure which of the following two lines is best to use but this one works well.
        taskFragment.show(mFM, TASK_FRAGMENT_TAG);
//      mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
        {
            // Inform the activity. 
            mCallbacks.onTaskFinished();
        }
    }

TaskFragment

    // This and the other inner class can be in separate files if you like.
    // There's no reason they need to be inner classes other than keeping everything together.
    public static class TaskFragment extends DialogFragment
    {
        // The task we are running.
        MyTask mTask;
        ProgressBar mProgressBar;

        public void setTask(MyTask task)
        {
            mTask = task;

            // Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment.
            mTask.setFragment(this);
        }

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

            // Retain this instance so it isn't destroyed when MainActivity and
            // MainFragment change configuration.
            setRetainInstance(true);

            // Start the task! You could move this outside this activity if you want.
            if (mTask != null)
                mTask.execute();
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState)
        {
            View view = inflater.inflate(R.layout.fragment_task, container);
            mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar);

            getDialog().setTitle("Progress Dialog");

            // If you're doing a long task, you probably don't want people to cancel
            // it just by tapping the screen!
            getDialog().setCanceledOnTouchOutside(false);

            return view;
        }

        // This is to work around what is apparently a bug. If you don't have it
        // here the dialog will be dismissed on rotation, so tell it not to dismiss.
        @Override
        public void onDestroyView()
        {
            if (getDialog() != null && getRetainInstance())
                getDialog().setDismissMessage(null);
            super.onDestroyView();
        }

        // Also when we are dismissed we need to cancel the task.
        @Override
        public void onDismiss(DialogInterface dialog)
        {
            super.onDismiss(dialog);
            // If true, the thread is interrupted immediately, which may do bad things.
            // If false, it guarantees a result is never returned (onPostExecute() isn't called)
            // but you have to repeatedly call isCancelled() in your doInBackground()
            // function to check if it should exit. For some tasks that might not be feasible.
            if (mTask != null) {
                mTask.cancel(false);
            }

            // You don't really need this if you don't want.
            if (getTargetFragment() != null)
                getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
        }

        @Override
        public void onResume()
        {
            super.onResume();
            // This is a little hacky, but we will see if the task has finished while we weren't
            // in this activity, and then we can dismiss ourselves.
            if (mTask == null)
                dismiss();
        }

        // This is called by the AsyncTask.
        public void updateProgress(int percent)
        {
            mProgressBar.setProgress(percent);
        }

        // This is also called by the AsyncTask.
        public void taskFinished()
        {
            // Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
            // after the user has switched to another app.
            if (isResumed())
                dismiss();

            // If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
            // onResume().
            mTask = null;

            // Tell the fragment that we are done.
            if (getTargetFragment() != null)
                getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null);
        }
    }

MyTask

    // This is a fairly standard AsyncTask that does some dummy work.
    public static class MyTask extends AsyncTask<Void, Void, Void>
    {
        TaskFragment mFragment;
        int mProgress = 0;

        void setFragment(TaskFragment fragment)
        {
            mFragment = fragment;
        }

        @Override
        protected Void doInBackground(Void... params)
        {
            // Do some longish task. This should be a task that we don't really
            // care about continuing
            // if the user exits the app.
            // Examples of these things:
            // * Logging in to an app.
            // * Downloading something for the user to view.
            // * Calculating something for the user to view.
            // Examples of where you should probably use a service instead:
            // * Downloading files for the user to save (like the browser does).
            // * Sending messages to people.
            // * Uploading data to a server.
            for (int i = 0; i < 10; i++)
            {
                // Check if this has been cancelled, e.g. when the dialog is dismissed.
                if (isCancelled())
                    return null;

                SystemClock.sleep(500);
                mProgress = i * 10;
                publishProgress();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Void... unused)
        {
            if (mFragment == null)
                return;
            mFragment.updateProgress(mProgress);
        }

        @Override
        protected void onPostExecute(Void unused)
        {
            if (mFragment == null)
                return;
            mFragment.taskFinished();
        }
    }
}

下載示例項目

這裡是源代碼APK 。 對不起,ADT堅持在讓我做項目之前添加支持庫。 我相信你可以刪除它。


Have a look at below example , how to use retained fragment to retain background task:

public class NetworkRequestFragment extends Fragment {

    // Declare some sort of interface that your AsyncTask will use to communicate with the Activity
    public interface NetworkRequestListener {
        void onRequestStarted();
        void onRequestProgressUpdate(int progress);
        void onRequestFinished(SomeObject result);
    }

    private NetworkTask mTask;
    private NetworkRequestListener mListener;

    private SomeObject mResult;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Try to use the Activity as a listener
        if (activity instanceof NetworkRequestListener) {
            mListener = (NetworkRequestListener) activity;
        } else {
            // You can decide if you want to mandate that the Activity implements your callback interface
            // in which case you should throw an exception if it doesn't:
            throw new IllegalStateException("Parent activity must implement NetworkRequestListener");
            // or you could just swallow it and allow a state where nobody is listening
        }
    }

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

        // Retain this Fragment so that it will not be destroyed when an orientation
        // change happens and we can keep our AsyncTask running
        setRetainInstance(true);
    }

    /**
     * The Activity can call this when it wants to start the task
     */
    public void startTask(String url) {
        mTask = new NetworkTask(url);
        mTask.execute();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // If the AsyncTask finished when we didn't have a listener we can
        // deliver the result here
        if ((mResult != null) && (mListener != null)) {
            mListener.onRequestFinished(mResult);
            mResult = null;
        }
    }

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

        // We still have to cancel the task in onDestroy because if the user exits the app or
        // finishes the Activity, we don't want the task to keep running
        // Since we are retaining the Fragment, onDestroy won't be called for an orientation change
        // so this won't affect our ability to keep the task running when the user rotates the device
        if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) {
            mTask.cancel(true);
        }
    }

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

        // This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity)
        // When the orientation change occurs, onDetach will be called and since the Activity is being destroyed
        // we don't want to keep any references to it
        // When the Activity is being re-created, onAttach will be called and we will get our listener back
        mListener = null;
    }

    private class NetworkTask extends AsyncTask<String, Integer, SomeObject> {

        @Override
        protected void onPreExecute() {
            if (mListener != null) {
                mListener.onRequestStarted();
            }
        }

        @Override
        protected SomeObject doInBackground(String... urls) {
           // Make the network request
           ...
           // Whenever we want to update our progress:
           publishProgress(progress);
           ...
           return result;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            if (mListener != null) {
                mListener.onRequestProgressUpdate(progress[0]);
            }
        }

        @Override
        protected void onPostExecute(SomeObject result) {
            if (mListener != null) {
                mListener.onRequestFinished(result);
            } else {
                // If the task finishes while the orientation change is happening and while
                // the Fragment is not attached to an Activity, our mListener might be null
                // If you need to make sure that the result eventually gets to the Activity
                // you could save the result here, then in onActivityCreated you can pass it back
                // to the Activity
                mResult = result;
            }
        }

    }
}

我創建了一個非常小的開源後台任務庫,它基於Marshmallow AsyncTask但具有其他功能,如:

  1. 通過配置更改自動保留任務;
  2. UI回調(偵聽器);
  3. 設備旋轉時不會重新啟動或取消任務(就像Loaders所做的那樣);

該庫在內部使用一個沒有任何用戶界面的Fragment ,該界面在配置更改( setRetainInstance(true) )中保留。

你可以在GitHub上找到它: https://github.com/NeoTech-Software/Android-Retainable-Taskshttps://github.com/NeoTech-Software/Android-Retainable-Tasks

最基本的例子(版本0.2.0):

這個例子完全保留了這個任務,使用的代碼量非常有限。

任務:

private class ExampleTask extends Task<Integer, String> {

    public ExampleTask(String tag){
        super(tag);
    }

    protected String doInBackground() {
        for(int i = 0; i < 100; i++) {
            if(isCancelled()){
                break;
            }
            SystemClock.sleep(50);
            publishProgress(i);
        }
        return "Result";
    }
}

活動:

public class Main extends TaskActivityCompat implements Task.Callback {

    @Override
    public void onClick(View view){
        ExampleTask task = new ExampleTask("activity-unique-tag");
        getTaskManager().execute(task, this);
    }

    @Override
    public Task.Callback onPreAttach(Task<?, ?> task) {
        //Restore the user-interface based on the tasks state
        return this; //This Activity implements Task.Callback
    }

    @Override
    public void onPreExecute(Task<?, ?> task) {
        //Task started
    }

    @Override
    public void onPostExecute(Task<?, ?> task) {
        //Task finished
        Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show();
    }
}

You could make the AsyncTask a static field. If you need a context, you should ship your application context. This will avoid memory leaks, otherwise you'd keep a reference to your entire activity.


如果有人發現他們的方式來到這個線程,那麼我發現一個乾淨的方法是從app.Service運行異步任務(以START_STICKY開始),然後重新運行正在運行的服務的迭代以找出服務(以及異步任務)仍在運行;

    public boolean isServiceRunning(String serviceClassName) {
    final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
    final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);

    for (RunningServiceInfo runningServiceInfo : services) {
        if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
            return true;
        }
    }
    return false;
 }

如果是,重新添加DialogFragment (或其他),如果不是確保對話已被解除。

如果你使用v4.support.*庫,這是特別貼切的,因為(在撰寫本文時)他們已經知道setRetainInstance方法和查看分頁的問題。 此外,通過不保留實例,您可以使用不同的資源集重新創建活動(即新方向的不同視圖佈局)


僅限離線解決方案:

我遇到了同樣的問題(在觸發onPartialResults()之後,Android系統花了25秒鐘通過onPartialResults()獲得了演講的成績單。

我嘗試了以下代碼並且它有效:

Intent.putExtra
(
    RecognizerIntent.EXTRA_PREFER_OFFLINE,
    true
);

此解決方案適用於我的應用程序,如果您不使用在線模式(我通過手機設置下載語言包),它可能適合您。





android android-fragments progressdialog android-asynctask android-support-library