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,因为我认为不建议将其用于长时间的操作。

谁能告诉我我哪里出了问题?

Android Task Threads and the UI

我创建了一个示例AsyncTask向您展示如何处理与 ui 线程分开的数据。此AsyncTask将从资源加载String并将其设置为 TextView

重要说明:通常,您永远不需要AsyncTask即可从资源加载String。我只是选择这个作为示例来展示如何在单独的线程中执行工作。

对于任何AsyncTask,只需记住一件事:您在单独的线程中执行工作,而不是在 ui 线程中执行工作。当单独的线程完成时,无法保证你引用的 ui 元素仍然存在,甚至不能保证你的应用仍在运行。因此,如果您引用ActivityContext或某些 ui 元素(如TextViewButton或任何其他复杂对象,当后台线程完成时可能不再存在,您必须使用 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 ,而不是实际字符串。 然后,此方法的调用方可以向此方法调用的结果添加延续以使用结果。