gpt4 book ai didi

android - AsyncTask和运行时配置使用简洁的代码示例更改: what approaches,,Android团队认可吗?

转载 作者:行者123 更新时间:2023-12-03 02:28:51 27 4
gpt4 key购买 nike

自从 AsyncTask 自2009年在Cupcake(API 3,Android 1.5)中引入以来,它一直被Android团队一如既往地推广:

  • Painless threading
  • an easy way to execute some work in a background thread and publish the results back on the UI thread”。
  • one of the simplest ways to fire off a new task from the UI thread

  • 他们提供的代码示例加强了这种简化的信息,特别是对于那些不得不使用 more painful ways中的线程的人。 AsyncTask非常吸引人。

    然而,在此后的许多年中,崩溃,内存泄漏和其他问题困扰着大多数选择在生产应用程序中使用 AsyncTask的开发人员。这通常是由于 Activity正在运行 destruction and recreation on runtime configuration change而导致的 AsyncTask doInBackground(Params...) (尤其是方向/旋转)引起的;当调用 onPostExecute(Result) 时, Activity已经被破坏,使UI引用处于无法使用的状态(甚至是 null)。

    而且,Android团队在此问题上缺乏明显,清晰,简洁的指导和代码示例,这只会使情况变得更糟,从而导致困惑以及各种变通办法和黑客攻击,其中一些还不错,有些则很糟糕:
  • Is AsyncTask really conceptually flawed or am I just missing something?
  • Best practice: AsyncTask during orientation change
  • Lock orientation until Asynctask finish

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

    在简洁的代码示例中,将 AsyncTaskActivity/ Fragment生命周期集成并在运行时配置更改后自动重启的规范(由Android团队认可)的最佳实践是什么?

    最佳答案

    提供的建议(适用于任何方法)

    不要保留对特定于UI的对象的引用

    Memory & Threading. (Android Performance Patterns Season 5, Ep. 3):

    You've got some threading object that's declared as an inner class of an Activity. The problem here is that the AsyncTask object now has an implicit reference to the enclosing Activity, and will keep that reference until the work object has been destroyed... Until this work completes, the Activity stays around in memory... This type of pattern also leads to common types of crashes seen in Android apps...

    The takeaway here is that you shouldn't hold references to any types of UI-specific objects in any of your threading scenarios.



    提供的方法

    尽管文档稀疏且分散,但Android团队至少提供了三种不同的方法来处理使用 AsyncTask进行的配置更改重启:
  • 取消/保存/重新启动生命周期方法中正在运行的任务
  • 使用WeakReference到UI对象
  • 使用“工作记录”
  • 在顶层 ActivityFragment中进行管理

    1.取消/保存/重新启动生命周期方法中正在运行的任务

    Using AsyncTask | Processes and Threads | Android Developers

    To see how you can persist your task during one of these restarts and how to properly cancel the task when the activity is destroyed, see the source code for the Shelves sample application.



    在Shelves应用程序中,对任务的引用作为字段保留在 Activity 中,以便可以在 Activity的生命周期方法中进行管理。但是,在看一下代码之前,需要注意一些重要的事情。

    首先,此应用是在将 AsyncTask 添加到平台之前编写的。源代码中包含一个与 AsyncTask稍后发布的类非常相似的类,称为 UserTask。对于此处的讨论, UserTask在功能上等效于 AsyncTask

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

    取消任务
  • 覆盖 onDestroy() ,取消任务,并将任务引用设置为null。 (我不确定在此处设置对null的引用是否会产生影响;如果您有更多信息,请发表评论,我将相应地更新答案)。
  • 如果在 AsyncTask#onCancelled(Object) 返回后需要清理或执行任何其他需要的工作,则覆盖 AsyncTask#doInBackground(Object[])
  • 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);
    }

    // ...
    }

    保存并重新启动任务
  • 覆盖 onSaveInstanceState(Bundle, PersistableBundle) ,取消任务,并保存有关任务的状态,以便在恢复实例状态后可以重新启动它们。
  • 覆盖 onRestoreInstanceState(Bundle, PersistableBundle) ,检索有关已取消任务的状态,并使用已取消任务状态中的数据启动新任务。
  • AddBookActivity.java
    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);
    }
    }
    }

    这是一种简单的方法,即使对于刚开始了解 Activity生命周期的初学者也应该有意义。它还具有不需要任务类本身之外的最少代码的优点,根据需要可以使用一到三个生命周期方法。 onDestroy() javadoc的“用法”部分中的一个简单的7行 AsyncTask代码段可以为我们省去很多麻烦。也许某些后代可以幸免。

    2.对UI对象使用WeakReferences
  • 将UI对象作为参数传递给 AsyncTask 的构造函数。将对这些对象的弱引用存储为WeakReference中的 AsyncTask 字段。
  • onPostExecute() 中,检查UI对象WeakReference是否不是null,然后直接对其进行更新。

  • Use an AsyncTask | Processing Bitmaps Off the UI Thread | Android Developers
    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
    // Use a WeakReference to ensure the ImageView can be garbage collected
    imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
    data = params[0];
    return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
    if (imageViewReference != null && bitmap != null) {
    final ImageView imageView = imageViewReference.get();
    if (imageView != null) {
    imageView.setImageBitmap(bitmap);
    }
    }
    }
    }

    The WeakReference to the ImageView ensures that the AsyncTask does not prevent the ImageView and anything it references from being garbage collected. There’s no guarantee the ImageView is still around when the task finishes, so you must also check the reference in onPostExecute(). The ImageView may no longer exist, if for example, the user navigates away from the activity or if a configuration change happens before the task finishes.



    这种方法比第一种更简单,更整洁,仅向任务类添加类型更改和空检查,而在其他任何地方均不添加其他代码。

    但是,这样做的代价是:该任务将运行到最后而不会在配置更改时被取消。如果您的任务很昂贵(CPU,内存,电池),具有副作用或需要在 Activity 重新启动时自动重新启动,则第一种方法可能是一个更好的选择。

    3.使用“工作记录”在顶层 Activity 或 fragment 中进行管理

    Memory & Threading. (Android Performance Patterns Season 5, Ep. 3)

    ...force the top-level Activity or Fragment to be the sole system responsible for updating the UI objects.

    For example, when you'd like to kick off some work, create a "work record" that pairs a View with some update function. When that block of work is finished, it submits the results back to the Activity using an Intent or a runOnUiThread(Runnable) call.

    The Activity can then call the update function with the new information, or if the View isn't there, just drop the work altogether. And, if the Activity that issued the work was destroyed, then the new Activity won't have a reference to any of this, and it will just drop the work, too.



    这是描述此方法的附图的屏幕快照:

    Work Records thread management approach diagram

    视频中未提供代码示例,因此这是我的基本实现:
    WorkRecord.java
    public class WorkRecord {
    public static final String ACTION_UPDATE_VIEW = "WorkRecord.ACTION_UPDATE_VIEW";
    public static final String EXTRA_WORK_RECORD_KEY = "WorkRecord.EXTRA_WORK_RECORD_KEY";
    public static final String EXTRA_RESULT = "WorkRecord.EXTRA_RESULT";
    public final int viewId;
    public final Callback callback;

    public WorkRecord(@IdRes int viewId, Callback callback) {
    this.viewId = viewId;
    this.callback = callback;
    }

    public interface Callback {
    boolean update(View view, Object result);
    }

    public interface Store {
    long addWorkRecord(WorkRecord workRecord);
    }
    }
    MainActivity.java
    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;
    }
    }
    MyTask.java
    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);
    }
    }

    (您启动任务的类(class))
      // ...
    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
    }
    });
    }

    显然,与其他两种方法相比,这种方法需要付出巨大的努力,并且对于许多实现而言都是过分杀伤力的。但是,对于执行大量线程工作的大型应用程序,这可以用作合适的基础体系结构。

    完全按照视频中的描述实现此方法,该任务将运行到最后,而不会在配置更改时被取消,就像上面的第二种方法一样。如果您的任务很昂贵(CPU,内存,电池),具有副作用或需要在 Activity重新启动时自动重新启动,则您需要修改此方法以适应取消,选择保存和重新启动任务的需要。或者只是坚持第一种方法;罗曼(Romain)对此有明确的愿景,并很好地实现了。

    更正

    这是一个很大的答案,很可能是我犯了错误和遗漏。如果发现任何问题,请发表评论,我将更新答案。谢谢!

    关于android - AsyncTask和运行时配置使用简洁的代码示例更改: what approaches,,Android团队认可吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35971070/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com