简体   繁体   中英

Android - passing data from AsyncTask to Activity

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

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, "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;
    }

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.

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 AsyncTask s 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 ( ò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!

Change the code to :

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM