Android - 将数据从 AsyncTask 传递到 Activity

Posted

技术标签:

【中文标题】Android - 将数据从 AsyncTask 传递到 Activity【英文标题】:Android - passing data from AsyncTask to Activity 【发布时间】:2020-12-12 14:13:07 【问题描述】:

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

界面:

package com.example.myapplication;

public interface GetListener 
    void passJSONGet(String s);

AsyncTask 类:

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);
    


我的活动:

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 显示为空,变量仍然是空指针)。

我该如何解决这个问题?我缺乏经验,可能错过了一些琐碎的事情。

【问题讨论】:

您的onCreate 代码创建AsyncTask,将其设置为在后台运行,然后立即跳转到下一行并显示Toast - 您还没有准备好!稍后任务完成,运行onPostExecute,这基本上最终将您的变量设置在Activity 中,仅此而已。您应该在 passJSONGet 中创建 toast,因为那是您实际接收要显示的信息的地方 以防万一您不知道,代码通常会逐行执行,如果您要获取一些数据,则必须等待结果返回才能继续(它会阻塞线程)。这就是所谓的同步执行,这一切都是一起发生的。相反的是异步执行,它通常被称为async,基本上是“东西可以在不同的时间运行和完成”,所以你可以继续做其他事情。 AsyncTasks 是一种在后台做一些事情的方法 - 你将它们关闭,然后它们会返回结果并做任何事情 @cactustictacs 我附加了passJSONGet 以在WeatherDisplay 活动中设置一个布尔标志,在该标志上的“无所事事”循环使应用程序崩溃。我怎么能等待AsyncTask 返回字符串,以便我可以将其转换为JSON array(根据代码,暂时忽略Toast)? 好的,我添加了一个带有大量背景的答案,以帮助您了解一般情况,但您根本不应该等待结果 - 您的代码旨在对 @ 做出反应987654339@ 完成,通过运行onPostExecute 中的任何内容。这就是你对结果做任何你需要做的事情的地方。看看,希望对你有帮助! 【参考方案1】:

我认为稍微解释一下 Android 如何运行您的代码可能会有所帮助。

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

一些可能会被执行的任务是创建一个Activity,所以它可能希望系统运行它的onCreate代码——每个Activity发生一次。您可能已经注意到它只发生一次,这就是您放置无限循环的地方,对吗?在AsyncTask 提供结果之前将执行困在其中?

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

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

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


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

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

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

【讨论】:

【参考方案2】:

将代码改为:

  @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 之后完成

【讨论】:

以上是关于Android - 将数据从 AsyncTask 传递到 Activity的主要内容,如果未能解决你的问题,请参考以下文章

从 AsyncTask Android 返回数据

在 Android 中,如何从 AsyncTask 更改 TextView? [复制]

Android - AsyncTask 之后的 Intent 数据

如何从android中的AsyncTask类中获取数据?

如何使用 asynctask 将图像从 android 上传到 php

示例:使用AsyncTask的Android双向网络套接字