为什么UI个重进程之前的进程没有先执行
why UI processes before a heavy process are not executed first
考虑像这样的简单代码:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInastanceState){
super.onCreate(savedInastanceState);
setContentView(R.layout.main);
final ProgressDialog pdUpdate = new ProgressDialog(this);
final Button btn = (Button)findViewById(R.id.btnUpdate);
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view){
btn.setText(Calendar.getInstance().getTime().toString());
int i=0;
while (i<100){
i++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("test","i = " + i);
}
}
});
}
}
点击 btn
会发生两件事:更改按钮的文本并计数 i
到 100,延迟 100 毫秒(这只是模拟繁重过程的示例,例如读取文件、下载等)。
我知道实现此类代码的正确方法是使用 AsyncTask
但我的问题是关于如何编译此代码的。它是一个单线程应用程序。所以编译器先读取btn.setText(Calendar.getInstance().getTime().toString());
,只有在执行完这一行后才转到下一行代码(如果我错了请指正),但它并没有发生。为什么?
在 C#
中有一个 Refresh()
方法可以解决这个问题(只需在 UI 更改和应用更改后调用它)。 java?
中是否有任何类似的方法?感谢您的帮助。
编辑 1
问题是关于以下进程的顺序:
进程 1:
btn.setText(Calendar.getInstance().getTime().toString());
进程 2:
i++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("test","i = " + i);
}
无论哪个先到(我的意思是 btn.onClick(){process1;process2;}
或 btn.onClick(){process2;process1;}
),第二个进程总是先执行。我的意思是首先我在 Logcat 中看到 i
的计数,然后看到按钮文本的变化。
Android 正在管理自己的绘图周期。但是您正在与它共享主线程。您可以在计划重绘视图的视图上调用 invalidate()
。但是你必须在 Android 的主线程上腾出一些时间来刷新 UI 无论如何(setText()
可能正在调用 invalidate()
)。
nonono,只有你的方法 运行 finish 才能工作(exp: just sleep 100 msc without while loop)。你可以在 google 中找到 java 方法的工作原理。 (我的英文很差)
正如许多人在评论中指出的那样,由于您在 onClickListener
中执行 while
循环,因此 UI 线程永远没有机会 invalidate
您的按钮,从而重绘所述按钮。
我想如果你想解决这个问题,你可以简单地让另一个监听器与重绘过程完成相关。看下面这篇answer:
public class DrawListenerView extends View{
private Callback callback;
public DrawListenerView(Callback callback){
this.callback = callback;
}
@Override
protected void onDraw (Canvas canvas){
super.onDraw(canvas);
//add your method here you want to call
//Or use a Callback-pattern
callback.finish();
}
}
public interface Callback(){
public void finish();
}
按钮绘制完成后,您就可以开始循环睡眠了。这样就给你画好了新的文字,然后就睡了。
我不确定这个应用程序是什么,但是如果您不希望用户在任何时间段内再次单击它,您可以考虑在他们按下按钮后将其禁用一段时间.
在这种情况下,您不需要调用任何等效的 refresh()
。但是 Button.setText()
不会自动重绘视图层次结构。相反,它向上传递视图层次结构的信息,即文本已更改并且需要重绘。最终,此信息到达视图层次结构的根,进而通知 Choreographer
。 Choreographer
安排下一帧的绘图。这反过来会存储在您的 UI 线程的消息队列中。
因此,当您将 UI 线程置于休眠状态时,不会重绘布局,而是计划重绘。一旦您的线程空闲,消息队列中的消息就会开始执行。在某些时候,Choreographer
被重绘消息调用并命令视图层次结构重绘自身。
还要考虑像 Handler.post(Runnable)
、View.post(Runnable)
和它们的 postDelayed
对应方法,如果不需要进行大量计算,可以作为 AsyncTask
的替代方法,而是为以后安排一些操作(例如视图更新)。它们使用与上述相同的机制 - 将 Runnable
放入线程的消息队列中,然后在线程空闲时获取并执行。
考虑像这样的简单代码:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInastanceState){
super.onCreate(savedInastanceState);
setContentView(R.layout.main);
final ProgressDialog pdUpdate = new ProgressDialog(this);
final Button btn = (Button)findViewById(R.id.btnUpdate);
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view){
btn.setText(Calendar.getInstance().getTime().toString());
int i=0;
while (i<100){
i++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("test","i = " + i);
}
}
});
}
}
点击 btn
会发生两件事:更改按钮的文本并计数 i
到 100,延迟 100 毫秒(这只是模拟繁重过程的示例,例如读取文件、下载等)。
我知道实现此类代码的正确方法是使用 AsyncTask
但我的问题是关于如何编译此代码的。它是一个单线程应用程序。所以编译器先读取btn.setText(Calendar.getInstance().getTime().toString());
,只有在执行完这一行后才转到下一行代码(如果我错了请指正),但它并没有发生。为什么?
在 C#
中有一个 Refresh()
方法可以解决这个问题(只需在 UI 更改和应用更改后调用它)。 java?
中是否有任何类似的方法?感谢您的帮助。
编辑 1
问题是关于以下进程的顺序:
进程 1:
btn.setText(Calendar.getInstance().getTime().toString());
进程 2:
i++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("test","i = " + i);
}
无论哪个先到(我的意思是 btn.onClick(){process1;process2;}
或 btn.onClick(){process2;process1;}
),第二个进程总是先执行。我的意思是首先我在 Logcat 中看到 i
的计数,然后看到按钮文本的变化。
Android 正在管理自己的绘图周期。但是您正在与它共享主线程。您可以在计划重绘视图的视图上调用 invalidate()
。但是你必须在 Android 的主线程上腾出一些时间来刷新 UI 无论如何(setText()
可能正在调用 invalidate()
)。
nonono,只有你的方法 运行 finish 才能工作(exp: just sleep 100 msc without while loop)。你可以在 google 中找到 java 方法的工作原理。 (我的英文很差)
正如许多人在评论中指出的那样,由于您在 onClickListener
中执行 while
循环,因此 UI 线程永远没有机会 invalidate
您的按钮,从而重绘所述按钮。
我想如果你想解决这个问题,你可以简单地让另一个监听器与重绘过程完成相关。看下面这篇answer:
public class DrawListenerView extends View{
private Callback callback;
public DrawListenerView(Callback callback){
this.callback = callback;
}
@Override
protected void onDraw (Canvas canvas){
super.onDraw(canvas);
//add your method here you want to call
//Or use a Callback-pattern
callback.finish();
}
}
public interface Callback(){
public void finish();
}
按钮绘制完成后,您就可以开始循环睡眠了。这样就给你画好了新的文字,然后就睡了。
我不确定这个应用程序是什么,但是如果您不希望用户在任何时间段内再次单击它,您可以考虑在他们按下按钮后将其禁用一段时间.
在这种情况下,您不需要调用任何等效的 refresh()
。但是 Button.setText()
不会自动重绘视图层次结构。相反,它向上传递视图层次结构的信息,即文本已更改并且需要重绘。最终,此信息到达视图层次结构的根,进而通知 Choreographer
。 Choreographer
安排下一帧的绘图。这反过来会存储在您的 UI 线程的消息队列中。
因此,当您将 UI 线程置于休眠状态时,不会重绘布局,而是计划重绘。一旦您的线程空闲,消息队列中的消息就会开始执行。在某些时候,Choreographer
被重绘消息调用并命令视图层次结构重绘自身。
还要考虑像 Handler.post(Runnable)
、View.post(Runnable)
和它们的 postDelayed
对应方法,如果不需要进行大量计算,可以作为 AsyncTask
的替代方法,而是为以后安排一些操作(例如视图更新)。它们使用与上述相同的机制 - 将 Runnable
放入线程的消息队列中,然后在线程空闲时获取并执行。