【问题标题】:implementing Handler interaction with the UI thread on a continuous loop在连续循环中实现 Handler 与 UI 线程的交互
【发布时间】:2019-04-18 03:38:34
【问题描述】:

请耐心等待,我对 android 还是有点陌生​​。

所以我正在制作一个复杂的按钮监听器。此侦听器应能识别短按、以某种方式闪烁(如正常的 onClick 功能)以及长按。

短按时,它将通过增加/减少值来更新(整数)textView。如果用户按住textView的按钮,它会不断增加/减少,直到按钮被释放。

到目前为止,我发现了 2 个关于此类问题的热门话题:

我对这些线程中的答案的问题是我觉得它们可以进一步分解。我不想创建一个单独的类来保存有关按钮侦听器的所有内容,而是想将在处理程序线程上运行的物理“任务”分离到它自己的类中。事实上,我相信这是 android 文档建议做的事情

https://developer.android.com/training/multiple-threads/communicate-ui#java

现在我有一个工作实现,用于在处理程序上的任务和 UI 线程之间正常传递数据以进行单个更改。但是,一旦我开始添加循环,它就会停止正常工作。如果我做一个 for 循环(即从 0 到 9),它似乎开始忽略帖子的“延迟”部分,并执行所有增量,并在全部完成后更新 UI。如果我尝试使用 while 循环,它永远不会更新 textView,原因与 for 循环可能失败的原因类似。

这是我实现的类,下面我会解释:

public class AutoUpdatingListener implements View.OnTouchListener, View.OnClickListener, View.OnLongClickListener {

    private TextView textView;
    private int textNumber;
    private String decision;
    private AutoUpdateTask updateTask;
    private static final String MSG_KEY = "textData";

    private final Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            Bundle bundle = msg.getData();
            int data = bundle.getInt(MSG_KEY);

            textView.setText(String.format(Locale.getDefault(), "%d", data));
        }
    };

    public AutoUpdatingListener(TextView textView, String decision) {
        this.decision = decision;
        this.textView = textView;
        this.textNumber = Integer.parseInt(textView.getText().toString());

        updateTask = new AutoUpdateTask(handler, textNumber, decision, MSG_KEY);
    }

    public void onClick(View v) {
        textNumber = (decision.equals("increase")) ? ++textNumber : (decision.equals("decrease")) ? --textNumber : textNumber;
        textView.setText(String.format(Locale.getDefault(),"%d", textNumber));
    }

    public boolean onLongClick(View v) {
        autoUpdate = true;

        handler.postDelayed(updateTask, 500);

        return true;
    }

    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            autoUpdate = false;
            handler.removeCallbacks(updateTask);
        }

        return false;
    }
}

这里是任务对象:

public class AutoUpdateTask implements Runnable {
    private Handler handler;
    private int textNumber;
    private final String decision;
    private final String MSG_KEY;

    public AutoUpdateTask(Handler handler, int tempo, String decision, final String MSG_KEY) {
        this.handler = handler;
        this.tempo = tempo;
        this.decision = decision;
        this.MSG_KEY = MSG_KEY;
    }

    @Override
    public void run() {
        // 2nd variation would be to place the loop here
        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        for (int i = 0; i < 10; i++) {
            textNumber = decision.equals("increase") ? ++tempo : decision.equals("decrease") ? --tempo : tempo;

            Message msg = handler.obtainMessage();
            Bundle bundle = new Bundle();
            bundle.putInt(MSG_KEY, tempo);
            msg.setData(bundle);
            handler.sendMessage(msg);

            SystemClock.sleep(500);
        }
    }
}

这一切都在onCreate 方法中的以下上下文中使用:

AutoIncrementingListener customListener = new AutoIncrementingListener(textView, "increase");
upArrow.setOnClickListener(customListener);
upArrow.setOnLongClickListener(customListener);
upArrow.setOnTouchListener(customListener);

所以本质上,如果用户进行短按,它会更新字段并完成操作。如果用户按住按钮,它将首先转到 longClick 侦听器,并创建一个任务,该任务由对处理程序的引用、要增加的文本、“增加”确定和唯一的消息键组成。

然后任务会延迟发布到处理程序线程上,并执行(数字递增),然后创建一条消息发送回 UI 线程并更新 textView。这个过程一直持续到用户释放按钮。

这都是理论上的,正如我在上面解释的那样,它目前并没有按照定义的那样工作。

更新

我已经将循环放入任务的run() 方法中。在这个循环中,我添加了一个 sleep(500) 调用来阻止增量同时触发。

但是,UI 不更新的问题仍然存在(尽管发送了消息并实现了handleMessage() 功能)。 IE:大约 5 秒后,当它完成循环后,它最终更新了 textView。

结论

我正在放弃这个实现,对于那些遇到这个问题寻求答案的人,我深表歉意。我希望这个线程对您自己的实现有所了解。

通过上述更新,我通过调试意识到,无论执行时间如何,消息都是在循环完成后一起发送的。对于那些寻求这种实现的人,他们将不得不找出如何走捷径。

【问题讨论】:

    标签: android multithreading handler


    【解决方案1】:

    你不能以这种方式做你正在做的事情。 原因是在 onLongClick 期间,您会运行一个一次性运行的循环,而不会给 UI 刷新时间。

    这是什么意思?

    嗯,你实际上是在发送 10 条消息,一条接一条地延迟 500 毫秒。这意味着它们将同时到达,一个接一个(它们之间没有延迟),只是在第一个到达之前您将有 500 毫秒的间隔。

    您需要做的是增加延迟,以便每条消息都有自己的延迟,并且比前一条长 500 毫秒。

    handler.postDelayed(updateTask, 500*(i+1));
    

    这是第一件事。

    第二个问题是您在 AutoUpdatingListener 的 C'Tor 中创建了 updateTask,因此如果更新事件只是增加了,每个更新任务都会发送相同的编号。

    问题是即使你改成这样的代码:

    for (int i = 0; i < 10; i++) {  // example usage of a long press (looping update)
            this.textNumber = Integer.parseInt(textView.getText().toString());
            updateTask = new AutoUpdateTask(handler, textNumber, decision, MSG_KEY);
            handler.postDelayed(updateTask, 500*(i+1));
        }
    

    它不起作用,因为在循环期间任务尚未运行,因此数字没有机会更新。

    您可以在循环期间作弊并增加数量,但这违背了所有逻辑。

    for (int i = 0; i < 10; i++) {  // example usage of a long press (looping update)
                this.textNumber = Integer.parseInt(textView.getText().toString());
                updateTask = new AutoUpdateTask(handler, textNumber+i, decision, MSG_KEY);
                handler.postDelayed(updateTask, 500*(i+1));
    
            }
    

    结论 您不能在同一线程中拥有发送增量的循环和执行增量的处理程序...

    【讨论】:

    • 感谢您对此迭代的反馈。我同意这种实现没有意义,并且您的解释与我的倾向正确对应。话虽如此,如果将循环放置在 AutoUpdateTask 的 run() 方法中,正如我在该方法中提到的那样,您对此有何看法?
    • 这样会更好,因为它会增加不同线程上的数字。当您发送下一个数字时,问题仍然存在等待 ui 更新,但如果您以足够长的延迟发送增量,您可能能够将其拉下来。
    • 如果您发现我的回答有帮助,请将其标记为已回答。如果没有,还缺少什么?
    • 虽然您的回答有助于弄清楚我的代码为何如此运行,但它并未提供解决问题的建议。我一直在等到我发现如何解决它,然后我才会提出答案。
    • 实际上,由于这个问题的问题现在已经揭示了更具体的问题,范围更小,为了这个问题相当广泛,我会选择你的答案。无论如何,我一直决定在这个项目中使用完全不同的语言,以使这些问题更容易解决。我非常感谢您的解释和帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-09
    • 1970-01-01
    • 2015-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-16
    相关资源
    最近更新 更多