Četl jsem o AsyncTask
a vyzkoušel jsem níže uvedený jednoduchý program. Ale zdá se, že nefunguje. Jak ho mohu zprovoznit?
public class AsyncTaskActivity extends Activity {
Button btn;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = (Button) findViewById(R.id.button1);
btn.setOnClickListener((OnClickListener) this);
}
public void onClick(View view){
new LongOperation().execute("");
}
private class LongOperation extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
for(int i=0;i<5;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed");
return null;
}
@Override
protected void onPostExecute(String result) {
}
@Override
protected void onPreExecute() {
}
@Override
protected void onProgressUpdate(Void... values) {
}
}
}
Snažím se jen změnit popisek po 5 sekundách v procesu na pozadí.
Toto je můj soubor main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
android:max="10"
android:padding="10dip">
</ProgressBar>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Progress" >
</Button>
<TextView android:id="@+id/output"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Replace"/>
</LinearLayout>
Dobře, snažíte se přistupovat ke grafickému uživatelskému rozhraní prostřednictvím jiného vlákna. To v zásadě není dobrý postup.
AsyncTask provádí vše v doInBackground()
uvnitř jiného vlákna, které nemá přístup ke grafickému uživatelskému rozhraní, kde jsou vaše pohledy.
Funkce preExecute()
a postExecute()
vám nabízejí přístup ke grafickému uživatelskému rozhraní před a po provedení těžké operace v tomto novém vlákně, dokonce můžete výsledek dlouhé operace předat funkci postExecute()
, aby se pak zobrazily všechny výsledky zpracování.
Viz tyto řádky, kde později aktualizujete svůj TextView:
TextView txt = findViewById(R.id.output);
txt.setText("Executed");
vložte je do onPostExecute()
Po dokončení akce doInBackground
se zobrazí aktualizovaný text v TextView.
EDIT: Všiml jsem si, že váš posluchač onClick nekontroluje, které View bylo vybráno. Zjistil jsem, že nejjednodušší způsob, jak to udělat, je pomocí příkazů switch. Níže mám upravenou kompletní třídu se všemi návrhy, abych ušetřil zmatek.
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings.System;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.view.View.OnClickListener;
public class AsyncTaskActivity extends Activity implements OnClickListener {
Button btn;
AsyncTask<?, ?, ?> runningTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = findViewById(R.id.button1);
// because we implement OnClickListener we only have to pass "this"
// (much easier)
btn.setOnClickListener(this);
}
@Override
public void onClick(View view) {
// detect the view that was "clicked"
switch (view.getId()) {
case R.id.button1:
if (runningTask != null) runningTask.cancel(true);
runningTask = new LongOperation();
runningTask.execute();
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// cancel running task(s) to avoid memory leaks
if (runningTask != null) runningTask.cancel(true);
}
private final class LongOperation extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... params) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// we were cancelled, stop sleeping!
}
}
return "Executed";
}
@Override
protected void onPostExecute(String result) {
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed"); // txt.setText(result);
// might want to change "executed" for the returned string passed
// into onPostExecute() but that is upto you
}
}
}
Jsem si jistý, že se provádí správně, ale vy se snažíte měnit prvky uživatelského rozhraní ve vlákně na pozadí a to nejde.
Upravte své volání a AsyncTask následovně:
Třída volání
Poznámka: Osobně doporučuji používat onPostExecute()
všude tam, kde spouštíte vlákno AsyncTask, a ne v samotné třídě, která rozšiřuje AsyncTask. Myslím, že to usnadňuje čtení kódu, zejména pokud potřebujete AsyncTask na více místech, která zpracovávají výsledky mírně odlišně.
new LongThread() {
@Override public void onPostExecute(String result) {
TextView txt = (TextView) findViewById(R.id.output);
txt.setText(result);
}
}.execute("");
Třída LongThread (rozšiřuje AsyncTask):
@Override
protected String doInBackground(String... params) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "Executed";
}
Přesuňte tyto dva řádky:
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed");
z metody doInBackground
vašeho AsyncTask'a vložte je do metody onPostExecute
. Vaše AsyncTask
by měla vypadat takto:
private class LongOperation extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
try {
Thread.sleep(5000); // no need for a loop
} catch (InterruptedException e) {
Log.e("LongOperation", "Interrupted", e);
return "Interrupted";
}
return "Executed";
}
@Override
protected void onPostExecute(String result) {
TextView txt = (TextView) findViewById(R.id.output);
txt.setText(result);
}
}