Čítal som o AsyncTask
a vyskúšal som jednoduchý program uvedený nižšie. Ale zdá sa, že nefunguje. Ako to môžem spraviť, aby to fungovalo?
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 sa len zmeniť štítok po 5 sekundách v procese na pozadí.
Toto je môj súbor 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>
Dobre, snažíte sa získať prístup ku grafickému rozhraniu prostredníctvom iného vlákna. To v zásade nie je dobrý postup.
AsyncTask vykonáva všetko v doInBackground()
vnútri iného vlákna, ktoré nemá prístup ku grafickému rozhraniu, kde sú vaše pohľady.
Funkcie preExecute()
a postExecute()
vám ponúkajú prístup ku grafickému používateľskému rozhraniu pred a po vykonaní ťažkej operácie v tomto novom vlákne, dokonca môžete výsledok dlhej operácie odovzdať funkcii postExecute()
, aby sa potom zobrazili všetky výsledky spracovania.
Pozrite si tieto riadky, kde neskôr aktualizujete svoj TextView:
TextView txt = findViewById(R.id.output);
txt.setText("Executed");
vložte ich do onPostExecute()
Potom uvidíte text vášho TextView aktualizovaný po dokončení doInBackground
.
EDIT: Všimol som si, že váš poslucháč onClick nekontroluje, ktorý View bol vybraný. Zistil som, že najjednoduchší spôsob, ako to urobiť, je pomocou príkazov switch. Nižšie mám upravenú kompletnú triedu so všetkými návrhmi, aby som ušetril zmätok.
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
}
}
}
Som si istý, že sa vykonáva správne, ale vy sa snažíte zmeniť prvky používateľského rozhrania vo vlákne na pozadí a to nejde.
Upravte svoje volanie a AsyncTask takto:
Trieda volania
Poznámka: Osobne odporúčam používať onPostExecute()
všade tam, kde vykonávate vlákno AsyncTask, a nie v triede, ktorá rozširuje samotnú AsyncTask. Myslím, že to uľahčuje čítanie kódu, najmä ak potrebujete AsyncTask na viacerých miestach, ktoré spracúvajú výsledky mierne odlišne.
new LongThread() {
@Override public void onPostExecute(String result) {
TextView txt = (TextView) findViewById(R.id.output);
txt.setText(result);
}
}.execute("");
Trieda LongThread (rozširuje 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";
}
Presuňte tieto dva riadky:
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed");
z metódy doInBackground
vašej AsyncTask'a vložte ich do metódy onPostExecute
. Vaša AsyncTask
by mala vyzerať 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);
}
}