【问题标题】:Update UI from an AsyncTaskLoader从 AsyncTaskLoader 更新 UI
【发布时间】:2012-04-16 02:48:21
【问题描述】:

我已将AsyncTask 转换为AsyncTaskLoader(主要用于处理配置更改)。我有一个TextView 我正在用作进度状态,并在AsyncTask 中使用onProgressUpdate 来更新它。看起来 AsyncTaskLoader 没有等价物,所以在 loadInBackground 期间(在 AsyncTaskLoader 中)我正在使用这个:

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        ((TextView)getActivity().findViewById(R.id.status)).setText("Updating...");
    }
});

我在Fragment 中使用它,这就是我使用getActivity() 的原因。这工作得很好,除非发生配置更改,例如更改屏幕方向。我的AsyncTaskLoader 一直在运行(这就是我使用AsyncTaskLoader 的原因),但runOnUiThread 似乎被跳过了。

不确定为什么会被跳过,或者这是否是从 AsyncTaskLoader 更新 UI 的最佳方式。

更新:

我最终恢复为 AsyncTask,因为它似乎更适合 UI 更新。希望他们可以将 AsyncTaskAsyncTaskLoader 一起使用的功能合并。

【问题讨论】:

    标签: android android-asynctask android-ui asynctaskloader


    【解决方案1】:

    这实际上是可能的。您基本上需要继承AsyncTaskloader 并实现publishMessage() 方法,该方法将使用Handler 将进度消息传递给任何实现ProgressListener(或任何您想调用的接口)接口的类。

    下载此示例:http://www.2shared.com/file/VW68yhZ1/SampleTaskProgressDialogFragme.html(如果离线,请发消息给我)- 这是基于 http://habrahabr.ru/post/131560/

    【讨论】:

    • 我认为这是一个非常聪明的 hack。我从没想过。我一直在寻找使用 Loaders 发送进度更新的方法。我会试一试,让你知道。我认为您的解决方案应该有效!
    • 链接好像坏了。 Here 是一个有效的(我认为它是一样的)。
    【解决方案2】:

    Emm...你不应该这样做。

    因为匿名类如何访问父类的方法或字段是通过存储对父类的不可见引用来实现的。

    例如你有一个Activity:

    public class MyActivity
        extends Activity
    {
        public void someFunction() { /* do some work over here */ }
    
        public void someOtherFunction() {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    while (true)
                        someFunction();
                }
            };
            new Thread(r).start(); // use it, for example here just make a thread to run it.
        }
    }
    

    编译器实际上会生成如下内容:

    private static class AnonymousRunnable {
        private MyActivity parent;
        public AnonymousRunnable(MyActivity parent) {
            this.parent = parent;
        }
    
        @Override
        public void run() {
            while (true)
                parent.someFunction();
        }
    }
    

    因此,当您的父 Activity 销毁(例如由于配置更改),并且您的匿名类仍然存在时,整个活动不能被 gc-ed。 (因为有人仍然持有参考。)

    这会成为内存泄漏,让您的应用陷入困境!!!

    如果是我,我会为这样的加载器实现“onProgressUpdate()”:

    public class MyLoader extends AsyncTaskLoader<Something> {
        private Observable mObservable = new Observable();
        synchronized void addObserver(Observer observer) {
            mObservable.addObserver(observer);
        }
        synchronized void deleteObserver(Observer observer) {
            mObservable.deleteObserver(observer);
        }
    
        @Override
        public void loadInBackground(CancellationSignal signal)
        {
            for (int i = 0;i < 100;++i)
                mObservable.notifyObservers(new Integer(i));
        }
    }
    

    在你的Activity 课堂上

    public class MyActivity extends Activity {
        private Observer mObserver = new Observer() {
            @Override
            public void update(Observable observable, Object data) {
                final Integer progress = (Integer) data;
                mTextView.post(new Runnable() {
                    mTextView.setText(data.toString()); // update your progress....
                });
            }
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreated(savedInstanceState);
    
            MyLoader loader = (MyLoader) getLoaderManager().initLoader(0, null, this);
            loader.addObserver(mObserver);
        }
    
        @Override
        public void onDestroy() {
            MyLoader loader = (MyLoader) getLoaderManager().getLoader(0);
            if (loader != null)
                loader.deleteObserver(mObserver);
            super.onDestroy();
        }
    }
    

    onDestroy() 期间记住deleteObserver() 很重要,这样加载程序就不会永远持有对您活动的引用。 (加载器可能会在您的 Application 生命周期中保持活动状态...)

    【讨论】:

    • 看来您在 Observable 上缺少 setChanged(),不是吗?
    【解决方案3】:

    回答我自己的问题,但据我所知,如果您需要更新 UI,AsyncTaskLoader 并不是最好的选择。

    【讨论】:

      【解决方案4】:

      在您实现LoaderManager.LoaderCallback(大概是您的活动)的类中,有一个您必须重写的onLoadFinished() 方法。这是AsyncTaskLoader 完成加载后返回的内容。

      【讨论】:

      • 我可能弄错了,但是当AsyncTaskLoader 完成加载时不会调用onLoadFinished 吗?不完全确定这如何解决我的问题,但我是 AsyncTaskLoaders 的新手,所以我可能会遗漏一些东西。 onLoadFinished 是否等同于AsyncTask 中的onProgressUpdate
      • 相当于onPostExecute
      • 这不是我要找的。我需要onProgressUpdate 的等价物,所以我可以在后台进程运行时显示状态。我不认为AsyncTaskLoader 有一个。
      【解决方案5】:

      最好的方法是使用LiveData,100% Working

      第 1 步:在项目创建期间添加生命周期依赖或使用 androidx 工件为 yes

      implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
      

      步骤2:如下创建loader类,在loader create in public方法中设置可以从activity或fragment中观察到的livedata。请参阅我的加载程序类中的 setLiveCount 方法。

      package com.androidcodeshop.asynctaskloaderdemo;
      
      import android.content.Context;
      
      import androidx.annotation.NonNull;
      import androidx.annotation.Nullable;
      import androidx.lifecycle.MutableLiveData;
      import androidx.loader.content.AsyncTaskLoader;
      
      import java.util.ArrayList;
      
      public class ContactLoader extends AsyncTaskLoader<ArrayList<String>> {
      
      private MutableLiveData<Integer> countLive = new MutableLiveData<>();
      
      
      synchronized public void setLiveCount(MutableLiveData<Integer> observer) {
          countLive = (observer);
      }
      
      
      public ContactLoader(@NonNull Context context) {
          super(context);
      }
      
      @Nullable
      @Override
      public ArrayList<String> loadInBackground() {
          return loadNamesFromDB();
      }
      
      private ArrayList<String> loadNamesFromDB() {
      
          ArrayList<String> names = new ArrayList<>();
          for (int i = 0; i < 10; i++) {
              try {
                  Thread.sleep(1000);
                  names.add("Name" + i);
                  countLive.postValue(i);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          return names;
      }
      
      
      @Override
      protected void onStartLoading() {
          super.onStartLoading();
          forceLoad(); // forcing the loading operation everytime it starts loading
      }
      }
      

      第 3 步:从活动中设置实时数据并观察变化如下

      package com.androidcodeshop.asynctaskloaderdemo;
      
      import android.os.Bundle;
      import android.util.Log;
      import android.widget.Toast;
      
      import androidx.annotation.NonNull;
      import androidx.annotation.Nullable;
      import androidx.appcompat.app.AppCompatActivity;
      import androidx.lifecycle.MutableLiveData;
      import androidx.loader.app.LoaderManager;
      import androidx.loader.content.Loader;
      import androidx.recyclerview.widget.LinearLayoutManager;
      import androidx.recyclerview.widget.RecyclerView;
      
      import java.util.ArrayList;
      
      public class MainActivity extends AppCompatActivity implements 
      LoaderManager.LoaderCallbacks<ArrayList> {
      
      private ContactAdapter mAdapter;
      private ArrayList<String> mNames;
      private MutableLiveData<Integer> countLiveData;
      private static final String TAG = "MainActivity";
      
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          mNames = new ArrayList<>();
          mAdapter = new ContactAdapter(this, mNames);
          RecyclerView mRecyclerView = findViewById(R.id.recycler_view);
          mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
          mRecyclerView.setAdapter(mAdapter);
          countLiveData = new MutableLiveData<>();
          countLiveData.observe(this, new androidx.lifecycle.Observer<Integer>() {
              @Override
              public void onChanged(Integer integer) {
                  Log.d(TAG, "onChanged: " + integer);
                  Toast.makeText(MainActivity.this, "" + 
          integer,Toast.LENGTH_SHORT).show();
              }
          });
      
          // initialize the loader in onCreate of activity
          getSupportLoaderManager().initLoader(0, null, this);
          // it's deprecated the best way is to use viewmodel and livedata while loading data
      }
      
      @NonNull
      @Override
      public Loader onCreateLoader(int id, @Nullable Bundle args) {
      
          ContactLoader loader = new ContactLoader(this);
          loader.setLiveCount(countLiveData);
          return loader;
      }
      
      @Override
      public void onLoadFinished(@NonNull Loader<ArrayList> load, ArrayList data) {
          mNames.clear();
          mNames.addAll(data);
          mAdapter.notifyDataSetChanged();
      }
      
      
      @Override
      public void onLoaderReset(@NonNull Loader loader) {
          mNames.clear();
      }
      
      @Override
      protected void onDestroy() {
          super.onDestroy();
      }
      
      }
      

      希望这会帮助你 :) 快乐编码

      【讨论】:

        猜你喜欢
        • 2017-05-08
        • 1970-01-01
        • 2017-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-04
        相关资源
        最近更新 更多