在连续循环中实现与 UI 线程的 Handler 交互
implementing Handler interaction with the UI thread on a continuous loop
请耐心等待,我对 android 还是有点陌生。
所以我正在制作一个复杂的按钮侦听器。此侦听器应识别短按、以某种方式闪烁(如正常的 onClick 功能),以及识别长按。
短按后,它将通过 increasing/decreasing 值更新(整数)textView。如果用户按住 textView 的按钮,它将持续 increase/decrease 直到按钮被释放。
到目前为止,我发现了 2 个关于此类问题的热门话题:
- Android - Hold Button to Repeat Action
- Continuously increase integer value as the button is pressed
我对这些线程中的答案的问题是我觉得它们可以进一步分解。我不想创建一个 class 来保存与按钮侦听器有关的所有内容,而是想将处理程序线程上 运行 的物理 "Task" 分离到它自己的 class 中。事实上,我相信这就是 android 文档推荐的做法
https://developer.android.com/training/multiple-threads/communicate-ui#java
现在我有一个工作实现,用于在处理程序上的任务和 UI 线程之间正常传递数据以进行单个更改。但是,一旦我开始添加循环,它就会停止正常工作。如果我做一个 for 循环(即从 0 到 9),它似乎开始忽略 post 的 "delay" 部分,并执行所有增量,并更新 UI 时一切都结束了。如果我尝试 while 循环,它永远不会更新 textView,原因与 for 循环可能失败的原因类似。
这是我实现的 classes,我将在下面解释:
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 侦听器,并创建一个由对处理程序的引用、要增加的文本、"increase" 确定和唯一消息键组成的任务。
然后任务被 post 延迟到处理程序线程上,并被执行(数字递增),然后它创建一条消息发送回 UI线程并更新 textView。此过程一直持续到用户释放按钮。
这都是理论上的,正如我上面所解释的,它目前并不能完全按照定义工作。
更新
我已经将循环放入任务的 run()
方法中。在这个循环中,我添加了一个 sleep(500)
调用来阻止增量同时触发。
但是,UI 没有更新的问题仍然存在(尽管已发送消息并实现了 handleMessage()
功能)。 I.E:大约 5 秒后完成循环,它最终更新了 textView。
结论
我放弃了这个实现,对于那些遇到这个问题寻求答案的人感到抱歉。我希望这个线程对您自己的实现有一些见解。
通过上面的更新,我通过调试发现,无论执行时间如何,循环完成后消息都是一起发送的。对于那些寻求这种实施方式的人来说,他们将不得不找出如何绕过这种现象。
你不能这样做。
原因是在 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));
}
结论
您不能在同一线程中拥有发送增量的循环和执行增量的处理程序...
请耐心等待,我对 android 还是有点陌生。
所以我正在制作一个复杂的按钮侦听器。此侦听器应识别短按、以某种方式闪烁(如正常的 onClick 功能),以及识别长按。
短按后,它将通过 increasing/decreasing 值更新(整数)textView。如果用户按住 textView 的按钮,它将持续 increase/decrease 直到按钮被释放。
到目前为止,我发现了 2 个关于此类问题的热门话题:
- Android - Hold Button to Repeat Action
- Continuously increase integer value as the button is pressed
我对这些线程中的答案的问题是我觉得它们可以进一步分解。我不想创建一个 class 来保存与按钮侦听器有关的所有内容,而是想将处理程序线程上 运行 的物理 "Task" 分离到它自己的 class 中。事实上,我相信这就是 android 文档推荐的做法
https://developer.android.com/training/multiple-threads/communicate-ui#java
现在我有一个工作实现,用于在处理程序上的任务和 UI 线程之间正常传递数据以进行单个更改。但是,一旦我开始添加循环,它就会停止正常工作。如果我做一个 for 循环(即从 0 到 9),它似乎开始忽略 post 的 "delay" 部分,并执行所有增量,并更新 UI 时一切都结束了。如果我尝试 while 循环,它永远不会更新 textView,原因与 for 循环可能失败的原因类似。
这是我实现的 classes,我将在下面解释:
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 侦听器,并创建一个由对处理程序的引用、要增加的文本、"increase" 确定和唯一消息键组成的任务。
然后任务被 post 延迟到处理程序线程上,并被执行(数字递增),然后它创建一条消息发送回 UI线程并更新 textView。此过程一直持续到用户释放按钮。
这都是理论上的,正如我上面所解释的,它目前并不能完全按照定义工作。
更新
我已经将循环放入任务的 run()
方法中。在这个循环中,我添加了一个 sleep(500)
调用来阻止增量同时触发。
但是,UI 没有更新的问题仍然存在(尽管已发送消息并实现了 handleMessage()
功能)。 I.E:大约 5 秒后完成循环,它最终更新了 textView。
结论
我放弃了这个实现,对于那些遇到这个问题寻求答案的人感到抱歉。我希望这个线程对您自己的实现有一些见解。
通过上面的更新,我通过调试发现,无论执行时间如何,循环完成后消息都是一起发送的。对于那些寻求这种实施方式的人来说,他们将不得不找出如何绕过这种现象。
你不能这样做。 原因是在 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));
}
结论 您不能在同一线程中拥有发送增量的循环和执行增量的处理程序...