Android – 从AsyncTask传递数据到Activity

huangapple go评论69阅读模式
英文:

Android - passing data from AsyncTask to Activity

问题

以下是翻译好的代码部分:

Interface:

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) {
        // doInBackground 方法的内容...
    }

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

My Activity:

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

public class WeatherDisplay extends AppCompatActivity implements GetListener {

    private JSONArray jsonArray;
    private String jsonString;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // onCreate 方法的内容...

        DataGetter dataGetter = new DataGetter(this, WeatherDisplay.this);
        dataGetter.execute(ip, "readLatestOutside", "weather");
    }

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

        // 其他后续代码...
    }
}

请注意,上述的翻译可能不会涵盖所有细节,但它们应该足以传达代码的意图和功能。如果您需要更详细的解释或进一步帮助,请随时提问。

英文:

I am trying to pass a string from AsyncTask back to my Activity. Browsing through other similar questions (eg. here), I decided to use an interface listener.

Interface:

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&lt;String, Void, String&gt; {

    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 = &quot;http://&quot; + ip + &quot;/&quot;+ scriptname +&quot;.php&quot;;

        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, &quot;UTF-8&quot;);

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

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

My 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, &quot;readLatestOutside&quot;, &quot;weather&quot;);
        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;
    }

The JSON is properly get from server and is seen in onPostExecute normally, however, it isn't visible later on in WeatherDisplay (the Toast is displayed empty, variable is still a nullpointer).

How do I resolve this issue? I am inexperienced and might've missed some trivial stuff.

答案1

得分: 1

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

因此,在后台,Android 正在一个无限循环中运行一些代码。循环的一部分是检查消息队列,基本上是任务传递的地方 - 需要执行的代码。理想情况下,在每个循环中完成所有所需的工作,以便它可以管理每秒 60 次循环。如果要做的事情太多,这就是它开始变慢并感觉卡顿的地方。

可能会执行的一些任务包括创建 Activity,因此它可能希望系统运行其 onCreate 代码 - 每个 Activity 都会执行一次。你可能已经注意到它只会发生一次,这就是你放置无限循环的地方,对吧?为了在那里捕获执行,直到 AsyncTask 传递其结果?

问题是你阻止了主循环的工作 - 它无法继续执行任何操作,直到完成该任务,并且你是故意阻塞它。这在一般情况下非常糟糕,这就是为什么鼓励你不要在主线程(运行主循环的线程)上执行耗时的操作 - 它会创建太长时间的工作,使整个 UI 反应不那么灵敏(UI 处理也是需要运行的任务)。

所以总的来说是不好的,但 AsyncTask 的实际工作方式是它们在另一个线程上运行(因此它们不会阻塞主线程,就像有另一个独立的人在处理工作) - 但它们会在主线程上交付结果。它们向主线程的消息队列发送一条消息,告诉它运行 onPostExecute 代码。

但在处理之前的任务之前,系统无法处理该消息。如果它忙于运行等待 AsyncTask 结果的无限循环,它将永远无法处理该消息,变量将永远不会更新,循环永远不会看到它正在等待的变化。就像有人阻止着一条线,除非他们看到某人在下游试图传递的东西,否则他们拒绝移动。


这是一些背景信息,但重点是除非是你创建的特殊线程,你不应该阻塞线程,这样不会干扰其他任何事情。将 Android 视为事件驱动的系统 - 当某些事件发生时,例如创建活动(onCreate)或按下按钮(onClick)或 AsyncTask 完成(onPostExecute),你编写的代码应该被执行。所有这些方法都以“on”开头,有一个原因!“当某事发生时,执行这个操作...”

因此,当你的任务完成时,它会运行 onPostExecute,这是处理接收结果的所有代码应该放置的地方。它不一定要在 onPostExecute 方法内部 - 就像你将你的代码放在 passJSONGet 方法中以保持组织结构一致,但是它是从 onPostExecute 调用的,那就是触发运行代码的事件。

所以,不管你需要用结果做什么,都从 onPostExecute 调用它。当发生这种情况时,它将执行你告诉它要做的事情。更新变量,制作一个弹出消息,使用一些新数据填充布局中的视图,总之无所不能!

英文:

I think a little explanation about how Android runs your code might help.

So in the background, Android is running some code in an endless loop. Part of that loop is to check a message queue, which is basically where tasks get delivered - code it needs to execute. Ideally it will get all the work required in each loop finished quickly enough that it can manage 60 loops per second. If it has too much to do, that's where it starts to slow down and feel janky.

Some of the tasks that might get executed are things like an Activity being created, so it might want the system to run its onCreate code - which happens once for each Activity. You've probably noticed it only happens once, and that's where you've put your endless loop, right? To kind of trap the execution in there, until the AsyncTask delivers its result?

The problem is you're stopping the main loop from working - it can't move on and do anything until it's finished that task, and you're blocking it on purpose. That's very bad in general, and it's why you're encouraged not to do slow operations on that main thread (the one that runs the main looper) - it creates work that takes too long, and makes the whole UI less responsive (UI handling is also tasks that need to run)

So that's bad in general, but the way AsyncTasks actually work is they run on another thread (so they're not blocking the main one, it's like having another independent person working on stuff) - but they deliver the result on the main thread. They post a message to the main thread's message queue, telling it to run the onPostExecute code.

But the system can't get to that message until it's handled the earlier tasks in the queue. And if it's busy running an endless loop waiting for the result of the AsyncTask, it will never get to that message, the variable will never be updated, and the loop will never see the change it's waiting for. It's like someone holding up a line, refusing to move until they see something that someone further down the line is trying to deliver


That's a bunch of background, but the point is you shouldn't ever block a thread like that, unless it's a special thread you created so you know it's ok and it's not interfering with anything else. Think of Android more as an event-driven system - you write code that should be executed when something happens, like when an Activity gets created (&#242;nCreate) or when a button is pressed(onClick) or when an AsyncTask completes (onPostExecute). There's a reason all these methods start with "on"! "When a thing happens, do this..."

So when your task completes, it runs onPostExecute and that's where all your code to handle receiving the result should go. It doesn't literally need to be inside the onPostExecute method - like you've put yours inside a passJSONGet method to keep things organised, but that gets called from onPostExecute, that's the event that triggers the code being run.

So whatever you need to do with the result, call it from onPostExecute. When that happens, it will do the stuff you've told it to do. Update a variable, make a toast, populate views in your layout with some new data, whatever!

答案2

得分: 1

更改代码为:

@Override
protected void onCreate(Bundle savedInstanceState) {

   // 其他代码

    DataGetter dataGetter = new DataGetter(this, WeatherDisplay.this);
    dataGetter.execute(ip, "readLatestOutside", "weather");

   // 更多代码
}

@Override
public void passJSONGet(String jsonstring) {
    this.jsonString = jsonstring;
    this.jsonArray = new JSONArray(this.jsonString);
    // 在onPostExecute之后执行所有UI工作
    Toast.makeText(this, this.jsonString, Toast.LENGTH_LONG).show();
}
英文:

Change the code to :

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

   // 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();
    
}

Your flow is wrong. All UI work should be done after onPostExecute

huangapple
  • 本文由 发表于 2020年8月24日 06:16:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/63552545.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定