Functie doorgeven als parameter in java

Ik raak bekend met het Android-framework en Java en wilde een algemene “NetworkHelper”-klasse maken die de meeste netwerkcode zou verwerken, zodat ik er gewoon webpagina’s vanaf kon bellen.

Ik heb dit artikel van developer.android.com gevolgd om mijn netwerkcursus te maken: http://developer.android.com/training/basics/network-ops/connecting.html

Code:

package com.example.androidapp;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.util.Log;
/**
 * @author tuomas
 * This class provides basic helper functions and features for network communication.
 */
public class NetworkHelper 
{
private Context mContext;
public NetworkHelper(Context mContext)
{
    //get context
    this.mContext = mContext;
}
/**
 * Checks if the network connection is available.
 */
public boolean checkConnection()
{
    //checks if the network connection exists and works as should be
    ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected())
    {
        //network connection works
        Log.v("log", "Network connection works");
        return true;
    }
    else
    {
        //network connection won't work
        Log.v("log", "Network connection won't work");
        return false;
    }
}
public void downloadUrl(String stringUrl)
{
    new DownloadWebpageTask().execute(stringUrl);
}
//actual code to handle download
private class DownloadWebpageTask extends AsyncTask<String, Void, String>
{
    @Override
    protected String doInBackground(String... urls)
    {
        // params comes from the execute() call: params[0] is the url.
        try {
            return downloadUrl(urls[0]);
        } catch (IOException e) {
            return "Unable to retrieve web page. URL may be invalid.";
        }
    }
    // Given a URL, establishes an HttpUrlConnection and retrieves
    // the web page content as a InputStream, which it returns as
    // a string.
    private String downloadUrl(String myurl) throws IOException 
    {
        InputStream is = null;
        // Only display the first 500 characters of the retrieved
        // web page content.
        int len = 500;
        try {
            URL url = new URL(myurl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(10000 );
            conn.setConnectTimeout(15000);
            conn.setRequestMethod("GET");
            conn.setDoInput(true);
            // Starts the query
            conn.connect();
            int response = conn.getResponseCode();
            Log.d("log", "The response is: " + response);
            is = conn.getInputStream();
            // Convert the InputStream into a string
            String contentAsString = readIt(is, len);
            return contentAsString;
        // Makes sure that the InputStream is closed after the app is
        // finished using it.
        } finally {
            if (is != null) {
                is.close();
            } 
        }
    }
    // Reads an InputStream and converts it to a String.
    public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException 
    {
        Reader reader = null;
        reader = new InputStreamReader(stream, "UTF-8");        
        char[] buffer = new char[len];
        reader.read(buffer);
        return new String(buffer);
    }
    // onPostExecute displays the results of the AsyncTask.
    @Override
    protected void onPostExecute(String result) 
    {
        //textView.setText(result);
        Log.v("log", result);
    }
} 

}

In mijn activiteitenklas gebruik ik de klas op deze manier:

connHelper = new NetworkHelper(this);

if (connHelper.checkConnection())
    {
        //connection ok, download the webpage from provided url
        connHelper.downloadUrl(stringUrl);
    }

Het probleem dat ik heb, is dat ik op de een of andere manier terug moet bellen naar de activiteit en dat het definieerbaar moet zijn in de functie “downloadUrl()”. Wanneer het downloaden bijvoorbeeld is voltooid, wordt de functie public void “handleWebpage(String data)” in activiteit aangeroepen met geladen string als parameter.

Ik heb wat gegoogeld en ontdekte dat ik op de een of andere manier interfaces moest gebruiken om deze functionaliteit te bereiken. Na het bekijken van een paar vergelijkbare stackoverflow-vragen/-antwoorden kreeg ik het niet werkend en ik weet niet zeker of ik de interfaces goed heb begrepen: Hoe geef ik methode door als parameter in Java?Om eerlijk te zijn is het gebruik van anonieme klassen nieuw voor mij en ik weet niet precies waar of hoe ik de voorbeeldcodefragmenten in de genoemde thread moet toepassen.

Dus mijn vraag is hoe ik de callback-functie kan doorgeven aan mijn netwerkklasse en deze kan aanroepen nadat het downloaden is voltooid? Waar gaat de interfaceverklaring naartoe, implementeert het trefwoord enzovoort?
Houd er rekening mee dat ik een beginner ben met Java (hoewel ik een andere programmeerachtergrond heb), dus ik zou een volledige uitleg op prijs stellen 🙂 Bedankt!


Antwoord 1, autoriteit 100%

Gebruik een callback-interface of een abstracte klasse met abstracte callback-methoden.

Voorbeeld van terugbelinterface:

public class SampleActivity extends Activity {
    //define callback interface
    interface MyCallbackInterface {
        void onDownloadFinished(String result);
    }
    //your method slightly modified to take callback into account 
    public void downloadUrl(String stringUrl, MyCallbackInterface callback) {
        new DownloadWebpageTask(callback).execute(stringUrl);
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //example to modified downloadUrl method
        downloadUrl("https://google.com", new MyCallbackInterface() {
            @Override
            public void onDownloadFinished(String result) {
                // Do something when download finished
            }
        });
    }
    //your async task class
    private class DownloadWebpageTask extends AsyncTask<String, Void, String> {
        final MyCallbackInterface callback;
        DownloadWebpageTask(MyCallbackInterface callback) {
            this.callback = callback;
        }
        @Override
        protected void onPostExecute(String result) {
            callback.onDownloadFinished(result);
        }
        //except for this leave your code for this class untouched...
    }
}

De tweede optie is nog beknopter. Je hoeft niet eens een abstracte methode te definiëren voor “onDownloaded event”, aangezien onPostExecuteprecies doet wat nodig is. Breid eenvoudig uw DownloadWebpageTaskuit met een anonieme inline-klasse binnen uw downloadUrl-methode.

   //your method slightly modified to take callback into account 
    public void downloadUrl(String stringUrl, final MyCallbackInterface callback) {
        new DownloadWebpageTask() {
            @Override
            protected void onPostExecute(String result) {
                super.onPostExecute(result);
                callback.onDownloadFinished(result);
            }
        }.execute(stringUrl);
    }
    //...

Antwoord 2, autoriteit 34%

GEEN interface, GEEN lib, GEEN Java 8 nodig!

Gewoon Callable<V>gebruiken van java.util.concurrent

public static void superMethod(String simpleParam, Callable<Void> methodParam) {
    //your logic code [...]
    //call methodParam
    try {
        methodParam.call();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Hoe het te gebruiken:

superMethod("Hello world", new Callable<Void>() {
                public Void call() {
                    myParamMethod();
                    return null;
                }
            }
    );

Waar myParamMethod()onze doorgegeven methode is als parameter (in dit geval methodParam).


Antwoord 3, autoriteit 24%

Ja, een interface is de beste manier IMHO. GWT gebruikt bijvoorbeeld het opdrachtpatroon met een interface zoals deze:

public interface Command{
    void execute();
}

Op deze manier kunt u een functie van een methode naar een andere doorgeven

public void foo(Command cmd){
  ...
  cmd.execute();
}
public void bar(){
  foo(new Command(){
     void execute(){
        //do something
     }
  });
}

Antwoord 4, autoriteit 10%

De kant-en-klare oplossing is dat dit niet mogelijk is in Java. Java accepteert geen Functies van hogere orde. Het kan echter worden bereikt door enkele “trucs”. Normaal gesproken is de interface degene die wordt gebruikt zoals u hebt gezien. Kijk hiervoor meer informatie. Je kunt ook reflectie gebruiken om dit te bereiken, maar dit is foutgevoelig.


Antwoord 5, autoriteit 4%

Het gebruik van interfaces is misschien wel de beste manier in Java Coding Architecture.

Maar het doorgeven van een Runnable-object zou ook kunnen werken, en het zou veel praktischer en flexibeler zijn, denk ik.

SomeProcess sp;
 public void initSomeProcess(Runnable callbackProcessOnFailed) {
     final Runnable runOnFailed = callbackProcessOnFailed; 
     sp = new SomeProcess();
     sp.settingSomeVars = someVars;
     sp.setProcessListener = new SomeProcessListener() {
          public void OnDone() {
             Log.d(TAG,"done");
          }
          public void OnFailed(){
             Log.d(TAG,"failed");
             //call callback if it is set
             if (runOnFailed!=null) {
               Handler h = new Handler();
               h.post(runOnFailed);
             }
          }               
     };
}
/****/
initSomeProcess(new Runnable() {
   @Override
   public void run() {
       /* callback routines here */
   }
});

Antwoord 6

Reflectie is nooit een goed idee, omdat het moeilijker te lezen en te debuggen is, maar als je 100% zeker weet wat je doet, kun je gewoon iets als set_method(R.id.button_profile_edit, “toggle_edit”) aanroepen om bij te voegen een methode voor een weergave. Dit is handig in fragment, maar nogmaals, sommige mensen zouden het als een anti-patroon beschouwen, dus wees gewaarschuwd.

public void set_method(int id, final String a_method)
{
    set_listener(id, new View.OnClickListener() {
        public void onClick(View v) {
            try {
                Method method = fragment.getClass().getMethod(a_method, null);
                method.invoke(fragment, null);
            } catch (Exception e) {
                Debug.log_exception(e, "METHOD");
            }
        }
    });
}
public void set_listener(int id, View.OnClickListener listener)
{
    if (root == null) {
        Debug.log("WARNING fragment", "root is null - listener not set");
        return;
    }
    View view = root.findViewById(id);
    view.setOnClickListener(listener);
}

Other episodes