如何从另一个线程中访问和修改操作栏内容



我正在做一些实验,以在使用Windows 10 Pro x64上的Android Studio最新版本的Android开发中体验线程和任务。我编写了一个非常简单的应用程序的两个版本,其中有两个按钮,以显示在非常繁重的工作中使用分离的线程是多么重要,这样应用程序就不会挂起:用一个按钮我可以更改布局背景色,用另一个按钮启动一个从0到10000000的计数器,在操作栏上显示增量值。此计数器会使应用程序的响应时间停滞数秒,因此在启动和结束时都无法更改背景色。在这种情况下,为这项繁重的工作创建一个单独的任务是一个好主意。

该应用程序的两个版本不同,因为第一个版本没有单独的计数器任务(因此在计数过程中可能会遇到应用程序没有响应(,而第二个版本有。

这是第一个版本的源代码:

package com.example.testasync;
import android.graphics.Color;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity {
ActionBar actionBar;
int flagBackgroundColor = 0;
LinearLayout linearLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
actionBar = getSupportActionBar();
actionBar.setTitle("Counter: 0");
linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
Button btnStartCounter = new Button(this);
Button btnChangeBackgroundColor = new Button(this);
btnStartCounter.setText("Start counter");
btnChangeBackgroundColor.setText("Change background color");
btnChangeBackgroundColor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (flagBackgroundColor) {
case 0:
linearLayout.setBackgroundColor(Color.BLACK);
flagBackgroundColor++;
break;
case 1:
linearLayout.setBackgroundColor(Color.RED);
flagBackgroundColor++;
break;
case 2:
linearLayout.setBackgroundColor(Color.GREEN);
flagBackgroundColor++;
break;
case 3:
linearLayout.setBackgroundColor(Color.GRAY);
flagBackgroundColor++;
break;
case 4:
linearLayout.setBackgroundColor(Color.WHITE);
flagBackgroundColor = 0;
break;
}
}
});
btnStartCounter.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int i;
actionBar.setTitle("Counter: 0");
for(i=0; i<10_000_000; i++) {
actionBar.setTitle("Counter: " + i);
}
}
});
linearLayout.addView(btnStartCounter);
linearLayout.addView(btnChangeBackgroundColor);
setContentView(linearLayout);
}
}

第二个源代码是:

package com.example.testasync;
import android.graphics.Color;
import android.os.AsyncTask;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
public class MainActivity extends AppCompatActivity {
ActionBar actionBar;
int flagBackgroundColor = 0;
LinearLayout linearLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
actionBar = getSupportActionBar();
actionBar.setTitle("Counter: 0");
linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
Button btnStartCounter = new Button(this);
Button btnChangeBackgroundColor = new Button(this);
btnStartCounter.setText("Start counter");
btnChangeBackgroundColor.setText("Change background color");
btnChangeBackgroundColor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (flagBackgroundColor) {
case 0:
linearLayout.setBackgroundColor(Color.BLACK);
flagBackgroundColor++;
break;
case 1:
linearLayout.setBackgroundColor(Color.RED);
flagBackgroundColor++;
break;
case 2:
linearLayout.setBackgroundColor(Color.GREEN);
flagBackgroundColor++;
break;
case 3:
linearLayout.setBackgroundColor(Color.GRAY);
flagBackgroundColor++;
break;
case 4:
linearLayout.setBackgroundColor(Color.WHITE);
flagBackgroundColor = 0;
break;
}
}
});
btnStartCounter.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyTask myTask = new MyTask();
myTask.execute();
}
});
linearLayout.addView(btnStartCounter);
linearLayout.addView(btnChangeBackgroundColor);
setContentView(linearLayout);
}
class MyTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
int i;
actionBar.setTitle("Counter: 0");
for(i=0; i<10_000_000; i++) {
actionBar.setTitle("Counter: " + i);
}
return null;
}
}
}

当我点击启动计数器的按钮时,我得到以下错误:

Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

在互联网上搜索时,我发现对于这样的问题,解决方案是使用runOnUiThread((,但这样做我得到了第一个源代码的相同结果:应用程序停滞等待计数器结束。如何在不冻结UI的情况下在操作栏上显示计数器的增量值?是否有从另一个线程访问和修改操作栏内容的方法?还有其他我不知道的方法吗?

您可以直接访问UI

new Handler(Looper.getMainLooper()).post(new Runnable()
{
@Override
public void run()
{
//Access UI code from here
}
});

或者您可以使用回调

从接口开始

public interface MyCallback {
void count(int i);
}

如何启动任务并接收回调

MyTask myTask = new MyTask(new MyCallback()
{
@Override
public void count(int i)
{
actionBar.setTitle("Counter: " + i);
}
});
myTask.execute();

扩展MyTask以包含回调

static class MyTask extends AsyncTask<Void, Void, Void>
{
private MyCallback _callback;
MyTask(MyCallback callback) {
_callback = callback;
}
@Override
protected Void doInBackground(Void... voids) {
for(int i = 0; i < 100; i++) {
_callback.count(i);
}
return null;
}
}

您可以使用onPostExecute与UI交互。请阅读参考

注意:CCD_ 2和CCD_。但是doInBackground只能从工作线程调用。

您不应该修改"doInBackground"方法中的数字。AsyncTask类具有名为onProgressUpdate的方法。只需覆盖它并从这里更新你的ui。从doInBackground方法调用onProgressUpdate(yourvalue),然后更新UI。还请注意,我将asyncTask类的第二个类型参数从Void更改为Int.

(我没有在IDE中实现解决方案,可能出现语法错误,但概念是这样的(

class MyTask extends AsyncTask<Void, Int, Void> {
@Override
protected Void doInBackground(Void... voids) {
int i;
actionBar.setTitle("Counter: 0");
for(i=0; i<10_000_000; i++) {
publishUpdate(i)
}
return null;
}
@Overrdie
protected void onProgressUpdate(Int.. ints){
actionBar.setTitle("Counter: " + ints[0]);
}
}

最新更新