Android - 将数据从 AsyncTask 传递到 Activity

Android - passing data from AsyncTask to Activity

我正在尝试将字符串从 AsyncTask 传回我的 Activity。浏览其他类似问题(例如here),我决定使用接口侦听器。

接口:

package com.example.myapplication;

public interface GetListener {
    void passJSONGet(String s);
}

AsyncTask class:

package com.example.myapplication;

import android.content.Context;
import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;


public class DataGetter extends AsyncTask<String, Void, String> {

    Context context;
    private GetListener GetListener;
    public String jsonString;

    DataGetter(Context ctx, GetListener listener) {
        this.context = ctx;
        this.GetListener = listener;
    }

    @Override
    protected String doInBackground(String... params) {
        String ip = params[0];
        String scriptname = params[1];
        String db = params[2];
        String urladress = "http://" + ip + "/"+ scriptname +".php";

        try {
            URL url = new URL(urladress);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoInput(true);
            connection.setDoOutput(true);

            OutputStream os = connection.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");

            String data = URLEncoder.encode("database", "UTF-8") + "=" + URLEncoder.encode(db, "UTF-8");

            writer.write(data);
            writer.flush();
            writer.close();

            BufferedReader reader = new BufferedReader(new
                    InputStreamReader(connection.getInputStream()));
            StringBuilder sb = new StringBuilder();

            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                break;
            }
            return sb.toString();
        } catch (Exception e) {
            return e.getMessage();
        }
    }

    @Override
    protected void onPostExecute(String result) {
        GetListener.passJSONGet(result);
    }
}

我的Activity:

import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONArray;
import org.json.JSONException;


public class WeatherDisplay extends AppCompatActivity implements GetListener {

    private JSONArray jsonArray;
    private String jsonString;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        
       // other code
      
        DataGetter dataGetter = new DataGetter(this, WeatherDisplay.this);
        dataGetter.execute(ip, "readLatestOutside", "weather");
        Toast.makeText(this, this.jsonString, Toast.LENGTH_LONG).show();
        this.jsonArray = new JSONArray(this.jsonString);

       // furter code
}

    @Override
    public void passJSONGet(String jsonstring) {
        this.jsonString = jsonstring;
    }

JSON 已从服务器正确获取并在 onPostExecute 中正常显示,但是,稍后在 WeatherDisplay 中不可见(Toast 显示为空,变量仍然是空指针)。

如何解决这个问题?我没有经验,可能漏掉了一些琐碎的东西。

我想稍微解释一下 Android 运行 您的代码可能会有帮助。

所以在后台,Android 正在 运行 无限循环中执行一些代码。该循环的一部分是检查消息队列,这基本上是任务交付的地方 - 它需要执行的代码。理想情况下,它会以足够快的速度完成每个循环中所需的所有工作,以至于每秒可以管理 60 个循环。如果它有太多事情要做,那就是它开始放慢速度并感觉笨拙的地方。

有些任务可能会被执行,比如正在创建 Activity,因此它可能希望系统 运行 它的 onCreate 代码 - 每个任务都会发生一次Activity。您可能已经注意到它只发生一次,那就是您放置无限循环的地方,对吧?是为了让执行陷入困境,直到 AsyncTask 交付结果?

问题是您正在阻止主循环工作 - 在完成该任务之前它无法继续执行任何操作,而您是故意阻止它。这通常是非常糟糕的,这就是为什么我们鼓励您不要在该主线程(运行 是主循环程序的主线程)上执行缓慢的操作 - 它创建的工作花费的时间太长,并使整个 UI反应迟钝(UI处理也是需要运行的任务)

所以总的来说这很糟糕,但是 AsyncTasks 实际工作的方式是它们 运行 在另一个线程上(所以它们不会阻塞主线程,就像让另一个独立的人工作一样关于东西) - 但他们在主线程上传递结果。他们 post 一条消息到主线程的消息队列,告诉它 运行 onPostExecute 代码。

但是系统在处理队列中较早的任务之前无法获取该消息。如果它正忙于 运行 无限循环等待 AsyncTask 的结果,它永远不会收到该消息,变量永远不会更新,循环永远不会看到它正在等待的变化为了。这就像有人举着一条线,拒绝移动,直到他们看到线下有人试图交付的东西


这是一堆背景知识,但要点是你不应该像那样阻塞线程,除非它是你创建的特殊线程,所以你知道它没问题并且不会干扰其他任何东西。将 Android 更多地视为一个 event-driven 系统 - 您编写的代码应在某些事情发生时执行,例如创建 Activity 时(ònCreate)或按钮被创建时按下(onClick)或当 AsyncTask 完成时(onPostExecute)。所有这些方法都以“on”开头是有原因的! “当事情发生时,这样做......”

因此,当您的任务完成时,它 运行s onPostExecute 这就是您处理接收结果的所有代码应该去的地方。它实际上不需要在 onPostExecute 方法内——就像您将自己的方法放在 passJSONGet 方法内以保持事情井井有条,但它是从 onPostExecute 调用的,这就是事件触发代码 运行.

因此,无论您需要对结果做什么,都可以从 onPostExecute 调用它。当发生这种情况时,它会执行您告诉它要做的事情。更新变量、敬酒、用一些新数据填充布局中的视图等等!

将代码更改为:

  @Override
protected void onCreate(Bundle savedInstanceState) {
    
   // other code
  
    DataGetter dataGetter = new DataGetter(this, WeatherDisplay.this);
    dataGetter.execute(ip, "readLatestOutside", "weather");
    

   // furter code
}
@Override
public void passJSONGet(String jsonstring) {
    this.jsonString = jsonstring;
    this.jsonArray = new JSONArray(this.jsonString);
    Toast.makeText(this, this.jsonString, Toast.LENGTH_LONG).show();
    
}

你的流程有误。所有 UI 工作都应在 onPostExecute

之后完成