Android Task Threads and the UI
本文关键字:the UI and Threads Task Android | 更新日期: 2023-09-27 17:56:06
我正在尝试在后台/异步/... - 换句话说,我正在尝试将其从 UI 线程中移除。
我的代码如下:
private string ImportXML()
{
string returnFile = null;
ParseXML.ParseXML parsexml = new ParseXML.ParseXML (chosenFile);
ProgressDialog progress = new ProgressDialog(this);
progress.SetMessage(GetString(Resource.String.ImportXML));
progress.Indeterminate = true;
progress.Show();
Task xmlTask = new Task (() => returnFile = parsexml.ProcessXML());
xmlTask.Start();
while (!(xmlTask.IsCompleted || xmlTask.IsCanceled || xmlTask.IsFaulted))
progress.Show();
xmlTask.Wait();
progress.Hide();
return returnFile;
}
代码似乎正在同步运行或仍在主 UI 线程中运行,因为 UI 更新(我暂时尝试使用 while 循环进行)没有发生 - 我根本看不到进度。
该操作可能需要几秒钟 - 对于我的一些示例文件最多需要 10 秒 - 所以我不热衷于使用 AsyncTask,因为我认为不建议将其用于长时间的操作。
谁能告诉我我哪里出了问题?
我创建了一个示例AsyncTask
向您展示如何处理与 ui 线程分开的数据。此AsyncTask
将从资源加载String
并将其设置为 TextView
。
重要说明:通常,您永远不需要AsyncTask
即可从资源加载String
。我只是选择这个作为示例来展示如何在单独的线程中执行工作。
对于任何AsyncTask
,只需记住一件事:您在单独的线程中执行工作,而不是在 ui 线程中执行工作。当单独的线程完成时,无法保证你引用的 ui 元素仍然存在,甚至不能保证你的应用仍在运行。因此,如果您引用Activity
或Context
或某些 ui 元素(如TextView
或Button
或任何其他复杂对象,当后台线程完成时可能不再存在,您必须使用 WeakReference
. WeakReferences
通过允许对引用的对象进行垃圾回收来解决此问题的聪明方法。在AsyncTask
中,您可以检查所需的对象是否仍然存在,如果是,则在不产生内存泄漏的情况下执行工作。您可以使用如下WeakReference
:
WeakReference<TextView> textViewReference = new WeakReference<TextView>(textView);
如您所见WeakReference
泛型类型。如果你想引用一个TextView
,就像在这种情况下,你必须通过在WeakReference
之后写<TextView>
来告诉WeakReference
。如果你引用一个Context
你会写WeakReference<Context>
或者如果你引用一个SQliteDatabase
你会写WeakReference<SQLiteDatabase>
。我想你看到这里的模式。
将要引用的对象实例传递到WeakReference
的构造函数中。这是上面示例的(textView)
部分。如果您以后想从WeakReference
中取回实例,可以这样做:
TextView textView = textViewReference.get();
如果您引用的TextView
不再存在,get()
将仅返回 null。因此,要检查TextView
是否仍然存在,您可以简单地这样做:
TextView textView = textViewReference.get();
if(textView != null) {
// TextView still exists.
} else {
// TextView doesn't exist anymore. Most likely he has already been garbage collected.
}
如果您了解有关如何以及何时使用WeakReferences
的简单原则,那么实现AsyncTask
应该没有问题。我已经用注释解释了有关AsyncTask
的任何其他重要内容,只需查看源代码即可。如果您有任何其他问题,请随时提问。
public class ExampleTask extends AsyncTask<Void, Void, String> {
// We use WeakReferences for all complex objects to prevent memory leaks
private final WeakReference<TextView> textViewRefernece;
private final WeakReference<Context> contextReference;
// Primitives are fine. WeakReferences are not needed here.
private int resourceId;
public ExampleTask(Context context, TextView textView, int resourceId) {
// We create the WeakReferences to our Context and to the TextView in which we want the result to appear.
this.contextReference = new WeakReference<Context>(context);
this.textViewRefernece = new WeakReference<TextView>(textView);
this.resourceId = resourceId;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
// This method is executed on the ui thread just before the background thread starts.
// You can perform some inital setup here but mostly you can leave this empty
}
@Override
protected String doInBackground(Void... params) {
// This method is executed in a background thread. Here we can do our work.
// First we have to get the context from the WeakReference
Context context = contextReference.get();
// Now we check if the Context still exists. If the context is null it has already been garbage collected.
// That would happen for example if the app has been closed or even if it crashed.
if(context != null) {
// The context is not null which means it still exists we can continue and load the required String and return it
return context.getString(this.resourceId);
}
// If something went wrong we return null.
return null;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
// This method is executed on the ui thread after the background thread is finished.
// Here we get our final result and can set it to our TextView
// First we check if the String result is null. If it is not null the background thread has finished successfully and we can continue.
if(result != null) {
// Here we get our TextView from the WeakReference
TextView textView = textViewRefernece.get();
// Now we check if the TextView still exists. If it is null it has already been garbage collected.
// That would happen for example if the app has been closed while the AsyncTask has been running.
// Or simply if the Fragment has been replaced or the Activity has changed.
if(textView != null) {
// The TextView is not null which means it still exists. We can now set our text.
textView.setText(result);
}
}
}
}
是的,您创建一个任务以在非 UI 线程中执行某些工作。 然后,您告诉 UI 线程坐在那里等待其他任务完成。 在该任务完成之前,您不会让它继续或执行任何其他工作。 这阻塞了 UI 线程,并不比在 UI 线程中正确完成工作更好(开销实际上使情况变得更糟)。
您需要做的是向任务添加一个延续,该延续可以在任务完成时处理您想要执行的任何工作,然后让该方法结束,以便 UI 线程可以继续执行其他工作。
private Task<string> ImportXML()
{
ParseXML.ParseXML parsexml = new ParseXML.ParseXML(chosenFile);
ProgressDialog progress = new ProgressDialog(this);
progress.SetMessage(GetString(Resource.String.ImportXML));
progress.Indeterminate = true;
progress.Show();
var task = Task.Factory.StartNew(() => parsexml.ProcessXML());
task.ContinueWith(t => progress.Hide());
return task;
}
由于此方法必须是异步的,允许 UI 线程继续执行其工作,因此它需要返回 Task
,而不是实际字符串。 然后,此方法的调用方可以向此方法调用的结果添加延续以使用结果。