从AsyncTaskLoader更新UI

5
我已将我的AsyncTask转换为AsyncTaskLoader(主要是为了处理配置更改)。我有一个作为进度状态的TextView,并且在AsyncTask中使用onProgressUpdate来更新它。看起来AsyncTaskLoader没有相应的方法,因此在AsyncTaskLoaderloadInBackground中,我正在使用以下内容:
getActivity().runOnUiThread(new Runnable() {
    public void run() {
        ((TextView)getActivity().findViewById(R.id.status)).setText("Updating...");
    }
});

我正在使用一个Fragment,这就是为什么我在使用getActivity()。这很有效,除了当发生配置更改(如更改屏幕方向)时。我的AsyncTaskLoader仍在运行(这就是为什么我在使用AsyncTaskLoader),但runOnUiThread似乎被跳过了。
不确定为什么会被跳过,或者是否这是从AsyncTaskLoader更新UI的最佳方式。
更新:
最终我回到了AsyncTask,因为它似乎更适合于UI更新。希望能够将与AsyncTask相似的工作与AsyncTaskLoader结合起来。
5个回答

4

我认为这是一个非常聪明的技巧。我从未想过。我一直在寻找使用加载器发送进度更新的方法。我会尝试一下并让你知道结果。我认为你的解决方案应该可行! - douggynix
链接似乎失效了。这里有一个有效的链接(我认为是同一个)。 - EmmanuelMess

4

嗯……你不应该这样做。

因为匿名类访问父类方法或字段的方式是通过存储对父类的一个不可见引用。

举个例子,你有一个 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销毁(例如由于配置更改),并且您的匿名类仍然存在时,整个活动无法被垃圾回收(因为某人仍然持有引用)。

这会导致内存泄漏并使您的应用程序陷入困境!!!

如果是我,我会像这样为加载器实现“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生命周期内一直保持活动状态...)


1
似乎你的 Observable 缺少 setChanged() 方法,不是吗? - Maxr1998

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

0
在实现LoaderManager.LoaderCallback的类中(可能是您的Activity),必须重写onLoadFinished()方法。这是在AsyncTaskLoader完成加载时返回的内容。

1
我可能错了,但是 onLoadFinished 不是在 AsyncTaskLoader 完成加载时被调用的吗?不太确定它如何解决我的问题,但我对 AsyncTaskLoaders 还很陌生,所以可能会漏掉一些东西。onLoadFinished 是否相当于 AsyncTask 中的 onProgressUpdate - Kris B
这是onPostExecute的等价方法。 - Jason Robinson
2
这不是我要找的。我需要类似于onProgressUpdate的等效方法,以便在后台进程运行时显示状态。我认为AsyncTaskLoader没有这个方法。 - Kris B

0

最佳方法是使用LiveData,百分之百有效

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

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

第二步:按照以下方式创建加载器类,在加载器中创建公共方法以设置可以从活动或片段观察的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();
}

}

希望这能对你有所帮助 :) 祝编程愉快

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接