【问题标题】:Update UI from Thread in Android在 Android 中从 Thread 更新 UI
【发布时间】:2011-05-21 03:37:27
【问题描述】:

我想从更新进度条的线程更新我的 UI。不幸的是,当从“runnable”更新进度条的drawable时,进度条消失了! 在另一边的 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)); 
            } 
        }); 
    }
}

【问题讨论】:

    标签: android multithreading user-interface


    【解决方案1】:

    你应该在AsyncTask(一个智能后台线程)和ProgressDialog的帮助下做到这一点

    AsyncTask 允许正确且轻松地使用 UI 线程。此类允许在 UI 线程上执行后台操作并发布结果,而无需操作线程和/或处理程序。

    异步任务由在后台线程上运行的计算定义,其结果在 UI 线程上发布。异步任务由 3 种通用类型定义,称为 Params、Progress 和 Result,以及 4 个步骤,称为 begin、doInBackground、processProgress 和 end。

    四个步骤

    当一个异步任务被执行时,任务会经过4个步骤:

    onPreExecute(),在任务执行后立即在 UI 线程上调用。此步骤通常用于设置任务,例如通过在用户界面中显示进度条。

    doInBackground(Params...),在 onPreExecute() 执行完成后立即在后台线程上调用。此步骤用于执行可能需要很长时间的后台计算。异步任务的参数传递到这一步。计算的结果必须由这一步返回,并将传递回最后一步。此步骤还可以使用 publishProgress(Progress...) 来发布一个或多个进度单位。这些值在 UI 线程的 onProgressUpdate(Progress...) 步骤中发布。

    onProgressUpdate(Progress...),在调用 publishProgress(Progress...) 后在 UI 线程上调用。执行的时间是不确定的。此方法用于在后台计算仍在执行时在用户界面中显示任何形式的进度。例如,它可用于动画进度条或在文本字段中显示日志。

    onPostExecute(Result),在后台计算完成后在 UI 线程上调用。后台计算的结果作为参数传递给该步骤。 线程规则

    要使此类正常工作,必须遵循一些线程规则:

    任务实例必须在 UI 线程上创建。 必须在 UI 线程上调用 execute(Params...)。 不要手动调用 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();
        }
    }
    

    【讨论】:

    【解决方案2】:

    我见过的最简单的解决方案是提供短路 UI 线程的执行是通过视图的 post() 方法执行的。 这是必需的,因为 UI 方法不可重入。这 方法是:

    package android.view;
    
    public class View;
    
    public boolean post(Runnable action);
    

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

    所以你保存的是创建一个处理程序。你只需要 找到您的观点,然后在上面发布。您可以通过以下方式找到您的视图 findViewById() 如果您倾向于使用 id 资源。所结果的 代码很简单:

    /* inside your non-UI thread */
    
    view.post(new Runnable() {
            public void run() {
                /* the desired UI update */
            }
        });
    }
    

    注意:相比 SwingUtilities.invokeLater() 方法 View.post() 确实返回一个布尔值,指示是否 view 有一个关联的事件队列。由于我使用了 invokeLater() 分别post() 无论如何只是为了火而忘记, 我没有检查结果值。基本上你应该 仅在调用 onAttachedToWindow() 后调用 post() 在视图上。

    最好的问候

    【讨论】:

    • 如果view是对像progress bar这样的实际对象的引用,留下一个创建整个视图的活动或片段将回收view资源,导致null.post( ~);
    • 关于如何通过帖子杀死looper,请参见:stackoverflow.com/a/30562809/502187
    【解决方案3】:

    如果你使用Handler(我看到你这样做了,希望你在 UI 线程上创建了它的实例),那么不要在你的runnable 中使用runOnUiThread()runOnUiThread() 在您从非 UI 线程执行某项操作时使用,但 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
        }
    };
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        您需要在 UI 线程中创建一个Handler,然后使用它从您的其他线程发布或发送消息以更新 UI

        【讨论】:

          【解决方案6】:

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

          虽然我会使用 AsyncTask,但是观察者模式可能会因为自定义原因而变得很好,而且它更容易理解。我也不确定这种方式是否被广泛接受或100%有效。我正在下载和android插件来测试它

          【讨论】:

            【解决方案7】:

            按照官方documentation的建议,您可以使用AsyncTask处理持续时间小于5ms的工作项。如果您的任务需要更多时间,请寻找其他替代方案。

            HandlerThreadThreadAsyncTask 的一种替代方案。如果您需要从HandlerThread 更新 UI,请在 UI 线程 Looper 上发布消息,并且 UI 线程 Handler 可以处理 UI 更新。

            示例代码:

            Android: Toast in a thread

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-09-24
              • 2014-11-28
              • 1970-01-01
              • 2016-10-17
              • 2010-11-11
              相关资源
              最近更新 更多