首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何正确使用AsyncTask以避免缓慢的UI?

如何正确使用AsyncTask以避免缓慢的UI?
EN

Stack Overflow用户
提问于 2013-07-17 07:40:55
回答 4查看 3.6K关注 0票数 3

在我的UI中,我有一个需要实时更新的ImageView (箭头图像)。

箭头有两种可能的运动:

  • 旋转到永远指向北方
  • 当用户的位置发生变化时移动

我已经有了这两种方法来正常工作。

唯一的问题是我的UI非常慢,有时会卡住。同时,我的手机总是变得非常热,而应用程序正在运行。Logcat有时也告诉我

跳过*帧。应用程序可能在其主线程上做了太多的工作。

我被告知使用AsyncTask,以避免强调我的UI。因此,我在AsyncTask中进行箭头更新工作。然而,问题仍然存在。我的UI仍然很慢。

我想我的AsyncTask实现应该有问题。我把它们贴在这里如下:

代码语言:javascript
运行
复制
    public class ArrowheadUpdater extends AsyncTask<Float, Integer, Float> { // Float: azimuth, Integer: state

        private ImageView arrowheadToRotate;
        private float rotationAngle; // also in radians

        // constructor
        public ArrowheadUpdater(ImageView _arrowheadToRotate) {

            arrowheadToRotate = _arrowheadToRotate;
            rotationAngle = -1;
        }

        protected void onPreExecute(Float _azimuth) {
            super.onPreExecute();
        }

        @Override
        // executed first to get the angle to rotate
        protected Float doInBackground(Float... arg0) {

            rotationAngle = (float) (Constant.MAP_ORIENTATION_OFFSET + arg0[0]);

            return rotationAngle;
        }

        protected void onProgressUpdated(Integer... progress) {
            super.onProgressUpdate(progress);
        }

        protected void onPostExecute(Float result) {
            super.onPostExecute(result);

              \\ rotation happens here
            rotateImageView(ShowPathActivity.this, arrowheadToRotate, R.drawable.marker, result);

              \\ moving happens here
            movaImageView(arrowhead, MapView.historyXSeries, MapView.historyYSeries);
        }

,我这样称呼AsyncTask:

代码语言:javascript
运行
复制
// called when sensor values change
    public void onSensorChanged(SensorEvent event) { // is roughly called 350 times in 1s
                //...
        if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {

            compassChangedTimes++;

            magneticField[0] = event.values[0];
            magneticField[1] = event.values[1];
            magneticField[2] = event.values[2];

            SensorManager.getRotationMatrix(RotationM, I, gravity, magneticField);
            SensorManager.getOrientation(RotationM, direction);

            if (compassChangedTimes % 50 == 0) {
                         // HERE!!!!!!!!!!
                new ArrowheadUpdater(arrowhead).execute(direction[0]);
            }
        }

        if (startFlag)
            dataCollector.saveDataShowPath(acceleration, magneticField, startTime, currentTime);
    }

我应该把两个箭头更新方法放在doInBackground()而不是onPostExecute()中吗?

但是doInBackground()中的行可以更新UI吗?我不确定。

我的AsyncTask**?** 有什么问题吗?

其他猜测或评论是非常欢迎的!

更多线索:

我只是注意到,当我进入这个活动时,UI非常慢,并且经常被卡住。但过了一段时间,比如说10秒,它就变得可以接受地平滑了。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-07-17 08:13:25

AsyncTasks用于在后台执行繁重/冗长的操作,并将结果推送到UI上。

在您的示例中,您的AsyncTask没有执行繁重的操作,因此您可能可以放弃它。

此外,在当前代码中,UI更新的频率没有限制。您可以使用Handler实现这样的限制。

代码语言:javascript
运行
复制
public static final int ARROW_MESSAGES = 0;

private Float angle;

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // Discard other messages
        removeMessages(ARROW_MESSAGES);
        // UI update
        rotateImageView(ShowPathActivity.this, arrowheadToRotate, R.drawable.marker, angle);
        movaImageView(arrowhead, MapView.historyXSeries, MapView.historyYSeries);

    }
}


// (...)
if (compassChangedTimes % 50 == 0) {
    float res = (float) (Constant.MAP_ORIENTATION_OFFSET + direction[0]);
    if (Math.abs(res - angle) > 1) {
        angle = res;
        handler.sendEmptyMessage(ARROW_MESSAGES);
    }
}

原则是向处理程序发送消息,但处理程序将只选择第一条消息并丢弃其余消息(在发布之前还可以测试处理程序以确定是否已经存在任何消息,我不确定哪一条更有效)。

这样,UI将使用最新的角度值进行更新,而不会尝试显示所有中间阶段。

另外,diff测试避免了不必要的更新(我假设1度足够精确,您甚至可能希望在那里设置一个更大的值)。

票数 2
EN

Stack Overflow用户

发布于 2013-07-17 07:55:16

你的问题是,你每秒钟接到350个对这个函数的调用,而忽略“垃圾”数据却做得不好。

首先,将尽可能多的逻辑移动到您的compassChangedTimes条件中(同时,您应该重置计数以避免溢出)。更好的是,决定是否根据传感器更改阈值而不是任意数量的样本更新UI (如果传感器报告一个常量值,并且偶尔调用onChangeEvent()函数,会发生什么?)

其次,使用低通滤波器来帮助剔除不相关的更新。在传感器的Android文档中应该有这样的一个例子。

票数 1
EN

Stack Overflow用户

发布于 2013-07-17 07:50:27

UI非常慢,有时会陷入困境,因为您可能在主UI线程中做了一些事情,花费了很多时间。

不能在doInBackground()方法中更新UI。建议在这种方法中做一些事情需要花费大量的时间。例如,下载数据或图片。

准备好数据后,将调用onPostExecute(),您可以在这些方法中更新UI。

++++++++++++++++++++++++++++++++++++++

你的AsyncTask是正确的。

您不能在doInBackground()中更新UI。因此,两种箭头更新方法都不能应用到doInBackground()方法中.

事实上,我认为您没有必要使用AsyncTask。

问题是,onSensorChanged()在1s中调用350次,而您在其中做的工作太多了。

代码语言:javascript
运行
复制
        SensorManager.getRotationMatrix(RotationM, I, gravity, magneticField);
        SensorManager.getOrientation(RotationM, direction);

如果startFlag是真的:

代码语言:javascript
运行
复制
        dataCollector.saveDataShowPath(acceleration, magneticField, startTime, currentTime);

您可以像这样更改onSensorChanged()方法:

代码语言:javascript
运行
复制
  public void onSensorChanged(SensorEvent event) {

        compassChangedTimes++;

        if (compassChangedTimes % 50 == 0) {
            if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {


                magneticField[0] = event.values[0];
                magneticField[1] = event.values[1];
                magneticField[2] = event.values[2];

                SensorManager.getRotationMatrix(RotationM, I, gravity, magneticField);
                SensorManager.getOrientation(RotationM, direction);

                new ArrowheadUpdater(arrowhead).execute(direction[0]);
            }
            if (startFlag)
                dataCollector.saveDataShowPath(acceleration, magneticField, startTime, currentTime);
        }
    }
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17693967

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档