在Android中从线程更新UI

55

我想在一个更新进度条的线程中更新我的UI。不幸的是,在“runnable”中更新进度条的可绘制部分时,进度条会消失! 在另一方面,在onCreate()中更改进度条的可绘制部分是有效的!

有什么建议吗?

public void onCreate(Bundle savedInstanceState) {
    res = getResources();
    super.onCreate(savedInstanceState);
    setContentView(R.layout.gameone);
    pB.setProgressDrawable(getResources().getDrawable(R.drawable.green)); //**Works**/
    handler.postDelayed(runnable, 1);       
}

private Runnable runnable = new Runnable() {
    public void run() {  
        runOnUiThread(new Runnable() { 
            public void run() 
            { 
                //* The Complete ProgressBar does not appear**/                         
                pB.setProgressDrawable(getResources().getDrawable(R.drawable.green)); 
            } 
        }); 
    }
}
7个回答

64
你应该借助AsyncTask(一个智能后台线程)和ProgressDialog来完成这个任务,具体请参考文档的说明:
AsyncTask 可以正确且轻松地使用 UI 线程。这个类允许在后台执行操作并在 UI 线程上发布结果,而无需操纵线程和/或处理程序。
异步任务由在后台线程上运行的计算和其结果在 UI 线程上发布的方式定义。异步任务由 3 种通用类型 Params、Progress 和 Result 和 4 个步骤 begin、doInBackground、processProgress 和 end 定义。 4 个步骤 当执行异步任务时,任务经过 4 个步骤: onPreExecute():在任务执行后立即在 UI 线程上调用。此步骤通常用于设置任务,例如在用户界面中显示进度条。 doInBackground(Params...):在 onPreExecute() 完成执行后立即在后台线程上调用。此步骤用于执行可能需要很长时间的后台计算。异步任务的参数传递到此步骤。计算的结果必须由此步骤返回,并将传递回最后一步。此步骤还可以使用 publishProgress(Progress...) 发布一个或多个进度单元。这些值在 onProgressUpdate(Progress...) 步骤中在 UI 线程上发布。 onProgressUpdate(Progress...):在调用 publishProgress(Progress...) 后在 UI 线程上调用。执行的时间未定义。此方法用于在后台计算仍在执行时在用户界面中显示任何形式的进度。例如,它可以用于动画化进度条或在文本字段中显示日志。 onPostExecute(Result):在后台计算完成后在 UI 线程上调用。将后台计算的结果作为参数传递给此步骤。线程规则 必须遵循一些线程规则才能使此类正常工作: 任务实例必须在 UI 线程上创建。execute(Params...) 必须在 UI 线程上调用。不要手动调用 onPreExecute()、onPostExecute(Result)、doInBackground(Params...)、onProgressUpdate(Progress...)。任务只能执行一次(如果尝试进行第二次执行,则会抛出异常)。

示例代码 在这个示例中适配器的作用并不重要,更重要的是要理解你需要使用AsyncTask来显示进度对话框。

private class PrepareAdapter1 extends AsyncTask<Void,Void,ContactsListCursorAdapter > {
    ProgressDialog dialog;
    @Override
    protected void onPreExecute() {
        dialog = new ProgressDialog(viewContacts.this);
        dialog.setMessage(getString(R.string.please_wait_while_loading));
        dialog.setIndeterminate(true);
        dialog.setCancelable(false);
        dialog.show();
    }
    /* (non-Javadoc)
     * @see android.os.AsyncTask#doInBackground(Params[])
     */
    @Override
    protected ContactsListCursorAdapter doInBackground(Void... params) {
        cur1 = objItem.getContacts();
        startManagingCursor(cur1);

        adapter1 = new ContactsListCursorAdapter (viewContacts.this,
                R.layout.contact_for_listitem, cur1, new String[] {}, new int[] {});

        return adapter1;
    }

    protected void onPostExecute(ContactsListCursorAdapter result) {
        list.setAdapter(result);
        dialog.dismiss();
    }
}

6
AsyncTasks 应该主要用于短时间操作(最多几秒钟)。 [...] - http://developer.android.com/reference/android/os/AsyncTask.html - Paolo Rovelli

26
我见过的最简单的解决方案是通过视图的post()方法向UI线程提供短执行。这是必需的,因为UI方法不可重入。实现此操作的方法如下:
package android.view;

public class View;

public boolean post(Runnable action);

post() 方法对应于 SwingUtilities.invokeLater()。 不幸的是,我没有找到与 SwingUtilities.invokeAndWait() 相对应的简单方法, 但可以通过使用监视器和标志来基于前者构建后者。

因此,您可以通过这种方式节省创建处理程序的时间。您只需要找到您的视图,然后在其上发布即可。 如果您倾向于使用带有 ID 的资源,则可以通过 findViewById() 找到您的视图。结果代码非常简单:

/* inside your non-UI thread */

view.post(new Runnable() {
        public void run() {
            /* the desired UI update */
        }
    });
}

注意:与SwingUtilities.invokeLater()相比,View.post()方法返回一个布尔值,表示视图是否有关联的事件队列。由于我无论如何都仅用于fire and forget调用invokeLater()或post(),因此没有检查结果值。基本上,您应该在视图上调用onAttachedToWindow()后才调用post()。

此致敬礼


如果“view”是对实际对象(如“进度条”)的引用,则离开创建整个视图的活动或片段将回收“view”资源,导致“null”。post(〜); - Alston
如何通过post方式终止looper,请参见此处:https://dev59.com/nm025IYBdhLWcg3wQDhb#30562809 - user502187

10
如果您使用了Handler(我看到您使用了,希望您在UI线程上创建了其实例),那么请不要在runnable内部使用runOnUiThread()。当您从非UI线程执行操作时,才需要使用runOnUiThread(),但是Handler已经会在UI线程上执行您的runnable
尝试像这样做:
private Handler mHandler = new Handler();
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.gameone);
    res = getResources();
    // pB.setProgressDrawable(getResources().getDrawable(R.drawable.green)); **//Works**
    mHandler.postDelayed(runnable, 1);
}

private Runnable runnable = new Runnable() {
    public void run() {
        pB.setProgressDrawable(getResources().getDrawable(R.drawable.green));
        pB.invalidate(); // maybe this will even not needed - try to comment out
    }
};

10

使用 AsyncTask 类(而不是 Runnable)。它有一个名为 onProgressUpdate 的方法,可以影响用户界面(在 UI 线程中调用)。


9
你需要在UI线程中创建一个处理程序(Handler),然后使用它将消息从其他线程发送或发布到UI以更新UI。

0

如果您不喜欢AsyncTask,可以使用观察者模式。在该示例中,将ResponseHandler作为活动的内部类,然后有一个字符串消息,将设置进度条的百分比...您需要确保对UI的任何更改都在ResponseHandler中执行,以避免冻结UI,然后您的工作线程(例如EventSource)可以执行所需的任务。

我会使用AsyncTask,但是观察者模式可以用于自定义原因,而且更容易理解。此外,我不确定这种方式是否被广泛接受或是否100%有效。我现在正在下载Android插件进行测试。


0

根据官方文档的建议,您可以使用AsyncTask来处理持续时间短于5ms的工作项。如果任务需要更长时间,请注意其他替代方法。

HandlerThreadThreadAsyncTask的另一种选择。如果您需要从HandlerThread更新UI,请在UI线程Looper上发布消息,UI线程Handler可以处理UI更新。

示例代码:

Android: Toast in a thread


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