Update UI from an AsyncTaskLoader

后端 未结 5 1861
庸人自扰
庸人自扰 2021-02-10 01:21

I\'ve converted my AsyncTask to an AsyncTaskLoader (mostly to deal with configuration changes). I have a TextView I am using as a progres

相关标签:
5条回答
  • 2021-02-10 01:43

    Answering my own question, but from what I can tell, AsyncTaskLoader isn't the best to use if you need to update the UI.

    0 讨论(0)
  • 2021-02-10 01:45

    In the class in which you implement LoaderManager.LoaderCallback (presumably your Activity), there is an onLoadFinished() method which you must override. This is what is returned when the AsyncTaskLoader has finished loading.

    0 讨论(0)
  • 2021-02-10 01:47

    The best method is to use LiveData, 100% Working

    Step 1: Add lifecycle dependency or use androidx artifacts as yes during project creation

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

    Step 2: Create the loader class as follow, in loader create in public method to set the livedata that can be observed from activity or fragment. see the setLiveCount method in my loader class.

    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
    }
    }
    

    Step 3: Set the live data from activity and observe the change as follows

    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();
    }
    
    }
    

    Hope this will help you :) happy coding

    0 讨论(0)
  • 2021-02-10 01:49

    Emm... you shouldn't be doing this.

    because how an anonymous class access parent class Method or Field is by storing an invisible reference to the parent class.

    for example you have a 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.
        }
    }
    

    the compiler will actually generate something like this:

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

    So, when your parent Activity destroys (due to configuration change, for example), and your anonymous class still exists, the whole activity cannot be gc-ed. (because someone still hold a reference.)

    THAT BECOMES A MEMORY LEAK AND MAKE YOUR APP GO LIMBO!!!

    If it was me, I would implement the "onProgressUpdate()" for loaders like this:

    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));
        }
    }
    

    And in your Activity class

    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();
        }
    }
    

    remember to deleteObserver() during onDestroy() is important, this way the loader don't hold a reference to your activity forever. (the loader will probably be held alive during your Application lifecycle...)

    0 讨论(0)
  • 2021-02-10 02:00

    It's actually possible. You essentially need to subclass the AsyncTaskloader and implement a publishMessage() method, which will use a Handler to deliver the progress message to any class that implements the ProgressListener (or whatever you want to call it) interface.

    Download this for an example: http://www.2shared.com/file/VW68yhZ1/SampleTaskProgressDialogFragme.html (message me if it goes offline) - this was based of http://habrahabr.ru/post/131560/

    0 讨论(0)
提交回复
热议问题