来自AsynchTask / Handler和Thread优先级的Android动态UI更新

时间:2014-05-13 14:36:24

标签: android multithreading user-interface

我正在使用AsynchTask来托管一个无限运行的模拟器,并在每个模拟步骤后发布结果。

将背景中的模拟循环限制在最高25Hz,并且仅使用结果调用javascript函数,它可以正常运行"罚款"。

除了在浏览器中更新webgl模型,看起来足够快,我还有两件事要从Android UI更新:FPS指标和带有表示某些值的TextViews的面板。如果我们忘记了FPS:

onProgressUpdate()函数已限制为以25Hz调用,以刷新模型。现在我使用另一个时间变量来限制在此方法中调用另一个更新UI面板textViews的方法。它限制在1Hz,低于我实际想要的但足够快的信息类型。该方法尽可能干净,所有视图都先前加载到一个变量,我每次都不加载它们。

效果是什么:看起来像更新5个textViews需要一秒钟所有的UI冻结,触摸动作非常滞后......

我通过以下方式降低了后台任务的优先级:

@Override
protected Boolean doInBackground(ModelSimulation... params) {
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
...

在doInBackground方法的末尾使用了Thread.yield()。这改善了我所解释的行为,没有这些命令,行为甚至更糟。

我的问题是:

- 如果不是使用后台任务而是使用处理程序和我自己的线程,我会减少更多优先级吗?

- 服务会改善UI的行为吗?

- 为什么更新5个textViews需要花费这么长时间才能调用最终必须使用gpu更改webgl模型的javascript函数?

- Android没有准备好动态应用吗?像测试传感器的应用程序如何更快地更新UI?因为没有像textViews这样的标准组件? (比浏览器比textView更快)

注意:即使减少刷新限制,每次更新HUD时都会产生延迟效果。实际上我谈的是5个textViews,但只更新FPS指示器会产生相同的暂停。看起来必须切换到UI线程的唯一事实已经消耗了这段时间。

编辑1:

@Override
protected Boolean doInBackground(ModelSimulation... params) {
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

    if(simulator.getSimulatorStatus().equals(SimulatorStatus.Connected)){
        try {
            while (true){
                //TODO Propagate
                long dur = (System.nanoTime()-time_tmp_data);
                if(dur<Parameters.Simulator.min_hud_model_refreshing_interval_ns){
                    try {
                        long sleep_dur = (Parameters.Simulator.min_hud_model_refreshing_interval_ns-(System.nanoTime()-time_tmp_data))/1000000;
                        if(sleep_dur>0){
                            Thread.sleep(sleep_dur);
                        }
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                time_tmp_data = System.nanoTime();


                SpacecraftState sstate = propagate();
                int progress = (int)((extrapDate.durationFrom(finalDate)/mission.sim_duration)*100); 

                if(sstate!=null){
                    SimResults results = new SimResults(sstate, progress);
                    simulator.getSimulationResults().updateSimulation(results.spacecraftState, results.sim_progress);

                    publishProgress();
                }
                if(isCancelled())
                    break;
                Thread.yield();
            }
        } catch (OrekitException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            simulator.showMessage(simulator.getContext().getString(R.string.sim_orekit_prop_error)+": "+e.getMessage());
        }
    }
    return true;
}


@Override
protected void onProgressUpdate(Void... values) {
    //Update model by push
    simulator.getSimulationResults().pushSimulationModel();
    //Update GUI HUD
    if(time_tmp_gui==0 || (System.nanoTime()-time_tmp_gui)>Parameters.Simulator.min_hud_panel_refreshing_interval_ns){

        time_tmp_gui = System.nanoTime();
        simulator.getSimulationResults().updateHUD();
    }
}

如果我评论线模拟器.getSimulationResults()。updateHUD();或直接该方法的内容,它工作&#34;罚款&#34;。此方法仅更改一些textviews文本:

public synchronized void updateHUD(){
    //Log.d("Sim",System.currentTimeMillis()+": "+"pre update gui 1");
    activity.runOnUiThread( new Runnable() {
        @SuppressLint("ResourceAsColor")
        public void run() {
            if(view != null){
                if(panel_time != null)
                    panel_time.setText(info.time.replace("T", "  "));
                if(panel_progress != null)
                    panel_progress.setProgress(info.progress);
                if(panel_vel != null){
                    panel_vel.setText("Vel. "+String.format("%.2f", info.velocity)+" Km/s");
                    if(info.velocity>config.limit_velocity)
                        panel_vel.setTextColor(activity.getResources().getColor(R.color.panel_limit));
                    else
                        panel_vel.setTextColor(activity.getResources().getColor(R.color.panel_value));
                }
                if(panel_accel != null){
                    panel_accel.setText("Accel. "+String.format("%.2f", info.acceleration)+" Km/s2");
                    if(info.acceleration>config.limit_acceleration)
                        panel_accel.setTextColor(activity.getResources().getColor(R.color.panel_limit));
                    else
                        panel_accel.setTextColor(activity.getResources().getColor(R.color.panel_value));
                }
                if(panel_radium != null)
                    panel_radium.setText("Orbit radium: "+String.format("%.1f", info.orbit_radium)+" Km");
                if(panel_mass != null)
                    panel_mass.setText("Mass: "+String.format("%.1f", info.mass)+" Kg");
                if(panel_roll != null)
                    panel_roll.setText("Rol: "+String.format("%.1f", (180*info.roll/Math.PI))+"º");
                if(panel_pitch != null)
                    panel_pitch.setText("Pitch: "+String.format("%.1f", (180*info.pitch/Math.PI))+"º");
                if(panel_yaw != null)
                    panel_yaw.setText("Yaw: "+String.format("%.1f", (180*info.yaw/Math.PI))+"º");
            }
        }
    });
    //Log.d("Sim",System.currentTimeMillis()+": "+"post update gui 1");
}

编辑2:我实际上可以删除runOnUiThread,因为它已经在那个线程,但效果是一样的,这不是问题。

编辑3:我试图评论方法updateHUD()的所有行,只留下这两行:

if(panel_time != null)
panel_time.setText(info.time.replace("T", "  "));

效果几乎相同,如果我触摸任何textView,动画会像定期冻结一样进行

编辑4:

我注意到AsyncTask中的进程花费的时间比可用的步骤时间长,所以它从不睡觉。即使模拟步骤长于可用时间,我也建立了10ms的安全保护时间。所以,我每100ms至少有10ms的空闲时间。效果仍然相同。我在25Hz的浏览器和1Hz的单个textview文本更新。如果我禁用textview更新,webgl模型将平滑动画。另一方面,如果我也启用了textview更新,每次更新文本时,都会有一些毫秒的时间来阻止浏览器动画及其对触摸的响应。如果我增加任务优先级,这种效果会变得更糟。我尝试设置一个500毫秒的巨大守卫,但冻结效果仍然出现。我正在使用XWalkView,当UI线程正在执行时,它是否会阻止此视图的交互?

我无法理解为什么4核2 RAMgb设备需要更多时间来计算相同的模拟,而不是Linux或Windows桌面PC。我有25Hz - > 40ms的可用时间,步数大约需要70ms。在PC中,我可以将模拟实时保持在25Hz。与其他操作系统相比,Android背景中是否有这么多狗屎?

3 个答案:

答案 0 :(得分:0)

您的代码必须存在其他问题。尝试在这里发布您的AsyncTask。

你也可以尝试一些非常基本的东西,如:

创建一个每25Hz循环的新线程,并使用UI元素的post()方法或Activity的runInUiThread()更新UI。看看UI线程中是否还有任何代码仍然可以运行,这可以在UI线程之外完成。

答案 1 :(得分:0)

我尝试了除了最逻辑的东西之外的所有东西,在没有连接调试器的情况下尝试应用程序。

模拟比PC更慢的原因,以释放UI事件...所有这些都是因为调试器从设备中获取了大量资源。所以,我想从这一点开始,我将不得不在没有调试器的情况下测试应用程序,这就迫使我每次重启电话以避免等待调试器连接&#34;。

感谢所有尝试过的人。

答案 2 :(得分:0)

我可能错了,但我认为你synchronization simulator.getSimulationResults()对象的simulator问题。我看不到getSimulationResults()类的实现和getSimulationResults()返回的对象的实现,但我想simulator.getSimulationResults().updateSimulation(...)每次都返回相同的对象?如果是这样,那么它可能看起来像这样:

  1. 在AsyncTaks中调用SimulationResults。如果此方法已同步,则此调用将锁定AsyncTaks线程的updateSimulation(...)对象。
  2. publishProgress()返回,并且publishProgress()被调用,但onProgressUpdate(Void... values)仅在UI线程中安排onProgressUpdate(Void... values)
  3. 可以启动AsyncTaks线程中的新迭代,因为UI线程获取控件并执行onProgressUpdate(Void... values)。因此,AsyncTaks线程迈出了第一步。
  4. UI线程获取控件并执行synchronized void updateHUD()updateHUD()方法,但SimulationResults无法执行,因为updateSimulation(...)对象被AsyncTaks线程锁定在onProgressUpdate(Void... values)方法中。因此UI线程将控件返回给OS。这可能会发生很多次。
  5. 因此,只有当UI线程在AsyncTask线程中未调用updateSimulation(...)方法的时候获得控件时,才能执行public synchronized void update HUD()方法和UI线程中的所有事件。

    您可以通过替换public void update HUD()上的{{1}}来检查这个想法,然后在TextView中随机写一些内容。


    无论如何,在这种情况下使用AsyncTask不是最好的主意。 AsyncTask在TheadPool中执行,但在Android系统中,此池只能由一个线程组成。因此,所有AsyncTask将在一个线程中逐个执行。