Android 异步任务&;运行时配置更改:安卓团队支持哪些方法(使用简洁的代码示例)?

Android 异步任务&;运行时配置更改:安卓团队支持哪些方法(使用简洁的代码示例)?,android,android-asynctask,android-lifecycle,android-memory,android-configchanges,Android,Android Asynctask,Android Lifecycle,Android Memory,Android Configchanges,自2009年在Cupcake(API 3,安卓1.5)中引入以来,安卓团队一直将其推广为: “” “” “” 他们提供的代码示例强化了这种简单性的信息,特别是对于我们这些不得不在中使用线程的人来说异步任务非常吸引人 然而,在此后的许多年中,崩溃、内存泄漏和其他问题一直困扰着大多数选择在生产应用程序中使用AsyncTask的开发人员。这通常是由于异步任务运行时的活动(尤其是方向/旋转);调用时,活动已被销毁,使UI引用处于不可用状态(甚至null) Android团队在这个问题上缺乏明显、清晰

自2009年在Cupcake(API 3,安卓1.5)中引入以来,安卓团队一直将其推广为:

  • “”
  • “”
  • “”
他们提供的代码示例强化了这种简单性的信息,特别是对于我们这些不得不在中使用线程的人来说<代码>异步任务非常吸引人

然而,在此后的许多年中,崩溃、内存泄漏和其他问题一直困扰着大多数选择在生产应用程序中使用
AsyncTask
的开发人员。这通常是由于
异步任务
运行时的
活动
(尤其是方向/旋转);调用时,
活动
已被销毁,使UI引用处于不可用状态(甚至
null

Android团队在这个问题上缺乏明显、清晰、简洁的指导和代码示例,这只会让事情变得更糟,导致混乱以及各种解决方法和黑客,有些还不错,有些还很糟糕:

显然,由于可以在许多情况下使用
AsyncTask
,因此没有一种方法可以解决这个问题。然而,我的问题是关于选项的

对于将
异步任务
活动
/
片段
生命周期集成,并在运行时配置更改时自动重启,有哪些规范(Android团队认可)的最佳实践和简明的代码示例?

提供了建议(适用于任何方法) 不保留对UI特定对象的引用 发件人:

有一些线程对象被声明为的内部类。这里的问题是,对象现在有一个对封闭的
活动的隐式引用,并且将保留该引用,直到工作对象被销毁为止。。。在这项工作完成之前,
活动将一直保留在内存中。。。这种模式还会导致Android应用程序中常见的崩溃类型

这里的要点是,在任何线程场景中,都不应该持有对任何类型的UI特定对象的引用

提供的方法 尽管文档比较稀疏和分散,但Android团队已经提供了至少三种不同的方法来使用
AsyncTask
处理配置更改时的重启:

  • 在生命周期方法中取消/保存/重新启动正在运行的任务
  • 对UI对象使用
    WeakReference
    s
  • 使用“工作记录”在顶级
    活动
    片段
    中进行管理
  • 1.在生命周期方法中取消/保存/重新启动正在运行的任务 从

    要了解如何在其中一次重新启动期间持久化任务,以及如何在活动被销毁时正确取消任务,请参阅示例应用程序的源代码

    在Shelfs应用程序中,对任务的引用作为字段保存在中,以便可以在
    活动的生命周期方法中对其进行管理。但是,在查看代码之前,有两件重要的事情需要注意

    首先,该应用程序是在添加到平台之前编写的。源代码中包含一个与后来发布的
    AsyncTask
    非常相似的类,名为
    UserTask
    。在这里的讨论中,
    UserTask
    在功能上等同于
    AsyncTask

    其次,
    UserTask
    的子类被声明为
    活动的内部类。如前所述,这种方法现在被视为一种反模式(请参见上面的不要保留对UI特定对象的引用)。幸运的是,这个实现细节不会影响在生命周期方法中管理正在运行的任务的总体方法;但是,如果您选择在自己的应用程序中使用此示例代码,请在其他地方声明
    AsyncTask
    的子类

    取消任务
    • 覆盖、取消任务,并将任务引用设置为
      null
      。(我不确定将引用设置为
      null
      是否有任何影响;如果您有更多信息,请发表评论,我将相应地更新答案)

    • 如果您需要在返回后清理或执行任何其他需要的工作,请覆盖

    AddBookActivity.java
    public class AddBookActivity extends Activity implements View.OnClickListener,
            AdapterView.OnItemClickListener {
    
        // ...
    
        private SearchTask mSearchTask;
        private AddTask mAddTask;
    
        // Tasks are initialized and executed when needed
        // ...
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
    
            onCancelAdd();
            onCancelSearch();
        }
    
        // ...
    
        private void onCancelSearch() {
            if (mSearchTask != null && mSearchTask.getStatus() == UserTask.Status.RUNNING) {
                mSearchTask.cancel(true);
                mSearchTask = null;
            }
        }
    
        private void onCancelAdd() {
            if (mAddTask != null && mAddTask.getStatus() == UserTask.Status.RUNNING) {
                mAddTask.cancel(true);
                mAddTask = null;
            }
        }
    
        // ...
    
        // DO NOT DECLARE YOUR TASK AS AN INNER CLASS OF AN ACTIVITY
        // Instances of this class will hold an implicit reference to the enclosing
        // Activity as long as the task is running, even if the Activity has been
        // otherwise destroyed by the system.  Declare your task where you can be
        // sure it holds no implicit references to UI-specific objects (Views,
        // etc.), and do not hold explicit references to them in your own
        // implementation.
        private class AddTask extends UserTask<String, Void, BooksStore.Book> {
            // ...
    
            @Override
            public void onCancelled() {
                enableSearchPanel();
                hidePanel(mAddPanel, false);
            }
    
            // ...
        }
    
        // DO NOT DECLARE YOUR TASK AS AN INNER CLASS OF AN ACTIVITY
        // Instances of this class will hold an implicit reference to the enclosing
        // Activity as long as the task is running, even if the Activity has been
        // otherwise destroyed by the system.  Declare your task where you can be
        // sure it holds no implicit references to UI-specific objects (Views,
        // etc.), and do not hold explicit references to them in your own
        // implementation.
        private class SearchTask extends UserTask<String, ResultBook, Void>
                implements BooksStore.BookSearchListener {
    
            // ...
    
            @Override
            public void onCancelled() {
                enableSearchPanel();
    
                hidePanel(mSearchPanel, true);
            }
    
            // ...
        }
    
    public class AddBookActivity extends Activity implements View.OnClickListener,
            AdapterView.OnItemClickListener {
    
        // ...
    
        private static final String STATE_ADD_IN_PROGRESS = "shelves.add.inprogress";
        private static final String STATE_ADD_BOOK = "shelves.add.book";
    
        private static final String STATE_SEARCH_IN_PROGRESS = "shelves.search.inprogress";
        private static final String STATE_SEARCH_QUERY = "shelves.search.book";
    
        // ...
    
        @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
            // ...
            restoreAddTask(savedInstanceState);
            restoreSearchTask(savedInstanceState);
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            if (isFinishing()) {
                // ...
                saveAddTask(outState);
                saveSearchTask(outState);
            }
        }
    
        // ...
    
        private void saveAddTask(Bundle outState) {
            final AddTask task = mAddTask;
            if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
                final String bookId = task.getBookId();
                task.cancel(true);
    
                if (bookId != null) {
                    outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
                    outState.putString(STATE_ADD_BOOK, bookId);
                }
    
                mAddTask = null;
            }
        }
    
        private void restoreAddTask(Bundle savedInstanceState) {
            if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
                final String id = savedInstanceState.getString(STATE_ADD_BOOK);
                if (!BooksManager.bookExists(getContentResolver(), id)) {
                    mAddTask = (AddTask) new AddTask().execute(id);
                }
            }
        }
    
        private void saveSearchTask(Bundle outState) {
            final SearchTask task = mSearchTask;
            if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
                final String bookId = task.getQuery();
                task.cancel(true);
    
                if (bookId != null) {
                    outState.putBoolean(STATE_SEARCH_IN_PROGRESS, true);
                    outState.putString(STATE_SEARCH_QUERY, bookId);
                }
    
                mSearchTask = null;
            }
        }
    
        private void restoreSearchTask(Bundle savedInstanceState) {
            if (savedInstanceState.getBoolean(STATE_SEARCH_IN_PROGRESS)) {
                final String query = savedInstanceState.getString(STATE_SEARCH_QUERY);
                if (!TextUtils.isEmpty(query)) {
                    mSearchTask = (SearchTask) new SearchTask().execute(query);
                }
            }
        }
    
    这是一种直截了当的方法,即使对于刚刚熟悉
    活动
    生命周期的初学者来说也应该有意义。它还有一个优点,即不需要任务类本身之外的模拟代码,根据需要涉及一到三个生命周期方法。在
    AsyncTask
    的用法部分中,一个简单的7行
    onDestroy()
    代码片段可以让我们省去很多悲伤。也许有些后代可以幸免

    2.对UI对象使用WeakReference
    • 在的构造函数中将UI对象作为参数传递。将对这些对象的弱引用存储为
      AsyncTask
      中的字段

    • 在中,检查UI对象
      WeakReference
      s是否为
      null
      ,然后直接更新它们

    MainActivity.java
    public class AddBookActivity extends Activity implements View.OnClickListener,
            AdapterView.OnItemClickListener {
    
        // ...
    
        private SearchTask mSearchTask;
        private AddTask mAddTask;
    
        // Tasks are initialized and executed when needed
        // ...
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
    
            onCancelAdd();
            onCancelSearch();
        }
    
        // ...
    
        private void onCancelSearch() {
            if (mSearchTask != null && mSearchTask.getStatus() == UserTask.Status.RUNNING) {
                mSearchTask.cancel(true);
                mSearchTask = null;
            }
        }
    
        private void onCancelAdd() {
            if (mAddTask != null && mAddTask.getStatus() == UserTask.Status.RUNNING) {
                mAddTask.cancel(true);
                mAddTask = null;
            }
        }
    
        // ...
    
        // DO NOT DECLARE YOUR TASK AS AN INNER CLASS OF AN ACTIVITY
        // Instances of this class will hold an implicit reference to the enclosing
        // Activity as long as the task is running, even if the Activity has been
        // otherwise destroyed by the system.  Declare your task where you can be
        // sure it holds no implicit references to UI-specific objects (Views,
        // etc.), and do not hold explicit references to them in your own
        // implementation.
        private class AddTask extends UserTask<String, Void, BooksStore.Book> {
            // ...
    
            @Override
            public void onCancelled() {
                enableSearchPanel();
                hidePanel(mAddPanel, false);
            }
    
            // ...
        }
    
        // DO NOT DECLARE YOUR TASK AS AN INNER CLASS OF AN ACTIVITY
        // Instances of this class will hold an implicit reference to the enclosing
        // Activity as long as the task is running, even if the Activity has been
        // otherwise destroyed by the system.  Declare your task where you can be
        // sure it holds no implicit references to UI-specific objects (Views,
        // etc.), and do not hold explicit references to them in your own
        // implementation.
        private class SearchTask extends UserTask<String, ResultBook, Void>
                implements BooksStore.BookSearchListener {
    
            // ...
    
            @Override
            public void onCancelled() {
                enableSearchPanel();
    
                hidePanel(mSearchPanel, true);
            }
    
            // ...
        }
    
    public class AddBookActivity extends Activity implements View.OnClickListener,
            AdapterView.OnItemClickListener {
    
        // ...
    
        private static final String STATE_ADD_IN_PROGRESS = "shelves.add.inprogress";
        private static final String STATE_ADD_BOOK = "shelves.add.book";
    
        private static final String STATE_SEARCH_IN_PROGRESS = "shelves.search.inprogress";
        private static final String STATE_SEARCH_QUERY = "shelves.search.book";
    
        // ...
    
        @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
            // ...
            restoreAddTask(savedInstanceState);
            restoreSearchTask(savedInstanceState);
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            if (isFinishing()) {
                // ...
                saveAddTask(outState);
                saveSearchTask(outState);
            }
        }
    
        // ...
    
        private void saveAddTask(Bundle outState) {
            final AddTask task = mAddTask;
            if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
                final String bookId = task.getBookId();
                task.cancel(true);
    
                if (bookId != null) {
                    outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
                    outState.putString(STATE_ADD_BOOK, bookId);
                }
    
                mAddTask = null;
            }
        }
    
        private void restoreAddTask(Bundle savedInstanceState) {
            if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
                final String id = savedInstanceState.getString(STATE_ADD_BOOK);
                if (!BooksManager.bookExists(getContentResolver(), id)) {
                    mAddTask = (AddTask) new AddTask().execute(id);
                }
            }
        }
    
        private void saveSearchTask(Bundle outState) {
            final SearchTask task = mSearchTask;
            if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
                final String bookId = task.getQuery();
                task.cancel(true);
    
                if (bookId != null) {
                    outState.putBoolean(STATE_SEARCH_IN_PROGRESS, true);
                    outState.putString(STATE_SEARCH_QUERY, bookId);
                }
    
                mSearchTask = null;
            }
        }
    
        private void restoreSearchTask(Bundle savedInstanceState) {
            if (savedInstanceState.getBoolean(STATE_SEARCH_IN_PROGRESS)) {
                final String query = savedInstanceState.getString(STATE_SEARCH_QUERY);
                if (!TextUtils.isEmpty(query)) {
                    mSearchTask = (SearchTask) new SearchTask().execute(query);
                }
            }
        }
    
    (启动任务的类) 显然,与其他两种方法相比,这种方法是一项巨大的工作,而且对于许多实现来说都是过度的。但是,对于需要大量线程工作的大型应用程序,这可以作为合适的基础架构

    完全按照视频中描述的方法实现此方法,任务将运行到结束,而不会在配置更改时取消,就像上面的第二种方法一样。如果您的任务很昂贵(CPU、内存、电池),有副作用,或者需要在
    活动
    重新启动时自动重新启动,那么您需要修改此方法以适应canc
    public class MainActivity extends AppCompatActivity implements WorkRecord.Store {
      // ...
    
      private final Map<Long, WorkRecord> workRecords = new HashMap<>();
      private BroadcastReceiver workResultReceiver;
    
      // ...
    
      @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ...
    
        initWorkResultReceiver();
        registerWorkResultReceiver();
      }
    
      @Override protected void onDestroy() {
        super.onDestroy();
        // ...
    
        unregisterWorkResultReceiver();
      }
    
      // Initializations
    
      private void initWorkResultReceiver() {
          workResultReceiver = new BroadcastReceiver() {
            @Override public void onReceive(Context context, Intent intent) {
              doWorkWithResult(intent);
            }
          };
        }
    
      // Result receiver
    
      private void registerWorkResultReceiver() {
        final IntentFilter workResultFilter = new IntentFilter(WorkRecord.ACTION_UPDATE_VIEW);
        LocalBroadcastManager.getInstance(this).registerReceiver(workResultReceiver, workResultFilter);
      }
    
      private void unregisterWorkResultReceiver() {
        if (workResultReceiver != null) {
          LocalBroadcastManager.getInstance(this).unregisterReceiver(workResultReceiver);
        }
      }
    
      private void doWorkWithResult(Intent resultIntent) {
        final long key = resultIntent.getLongExtra(WorkRecord.EXTRA_WORK_RECORD_KEY, -1);
        if (key <= 0) {
          Log.w(TAG, "doWorkWithResult: WorkRecord key not found, exiting:"
              + " intent=" + resultIntent);
          return;
        }
    
        final Object result = resultIntent.getExtras().get(WorkRecord.EXTRA_RESULT);
        if (result == null) {
          Log.w(TAG, "doWorkWithResult: Result not found, exiting:"
              + " key=" + key
              + ", intent=" + resultIntent);
          return;
        }
    
        final WorkRecord workRecord = workRecords.get(key);
        if (workRecord == null) {
          Log.w(TAG, "doWorkWithResult: matching WorkRecord not found, exiting:"
              + " key=" + key
              + ", workRecords=" + workRecords
              + ", result=" + result);
          return;
        }
    
        final View viewToUpdate = findViewById(workRecord.viewId);
        if (viewToUpdate == null) {
          Log.w(TAG, "doWorkWithResult: viewToUpdate not found, exiting:"
              + " key=" + key
              + ", workRecord.viewId=" + workRecord.viewId
              + ", result=" + result);
          return;
        }
    
        final boolean updated = workRecord.callback.update(viewToUpdate, result);
        if (updated) workRecords.remove(key);
      }
    
      // WorkRecord.Store implementation
    
      @Override public long addWorkRecord(WorkRecord workRecord) {
        final long key = new Date().getTime();
        workRecords.put(key, workRecord);
        return key;
      }
    }
    
    public class MyTask extends AsyncTask<Void, Void, Object> {
      // ...
      private final Context appContext;
      private final long workRecordKey;
      private final Object otherNeededValues;
    
      public MyTask(Context appContext, long workRecordKey, Object otherNeededValues) {
        this.appContext = appContext;
        this.workRecordKey = workRecordKey;
        this.otherNeededValues = otherNeededValues;
      }
    
      // ...
    
      @Override protected void onPostExecute(Object result) {
        final Intent resultIntent = new Intent(WorkRecord.ACTION_UPDATE_VIEW);
        resultIntent.putExtra(WorkRecord.EXTRA_WORK_RECORD_KEY, workRecordKey);
        resultIntent.putExtra(WorkRecord.EXTRA_RESULT, result);
        LocalBroadcastManager.getInstance(appContext).sendBroadcast(resultIntent);
      }
    }
    
      // ...
      private WorkRecord.Store workRecordStore;
      private MyTask myTask;
    
      // ...
    
      private void initWorkRecordStore() {
        // TODO: get a reference to MainActivity and check instanceof WorkRecord.Store
        workRecordStore = (WorkRecord.Store) activity;
      }
    
      private void startMyTask() {
        final long key = workRecordStore.addWorkRecord(key, createWorkRecord());
        myTask = new MyTask(getApplicationContext(), key, otherNeededValues).execute()
      }
    
      private WorkRecord createWorkRecord() {
        return new WorkRecord(R.id.view_to_update, new WorkRecord.Callback() {
          @Override public void update(View view, Object result) {
            // TODO: update view using result
          }
        });
      }