Я провожу некоторые эксперименты, чтобы получить опыт работы с потоками и задачами в разработке под Android с использованием последней версии Android Studio на Windows 10 Pro x64. Я написал две версии очень простого приложения с двумя кнопками, чтобы показать, как важно использовать отдельные потоки для очень тяжелых работ, чтобы приложение не зависало: одной кнопкой я могу изменить цвет фона макета, а другой кнопкой. Я запускаю счетчик от 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);
}
}
Второй исходный код, где я использую отдельную задачу для инкрементного цикла for, выглядит так:
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 (), но при этом я получаю тот же результат, что и в первом исходном коде: приложение застревает в ожидании окончания счетчика.
Как я могу показать инкрементное значение счетчика на панели действий, не останавливая интерфейс? Есть ли способ для доступа и изменения содержимого панели действий из другого потока? Есть ли другой подход, который я не знаю?