简体   繁体   中英

Get return value which assigned in interface method as a method?

I have an implemented method in my class which assign instance variable. I want to create a method which waits until interface method runned and then returns instance variable which is assigned in there. If I couldn't find any solution,I need to re-code my class to handle this problem. As an example :

public class MyClass {
    private String /*or AnyObject*/ string;
    @Override
    public void onData(String value) {
    this.string=value;
    }
    public void callOnData(/*some param*/){
    //does some work and calls onData();
    }
    public String whatIwantTo(){
    //if onData called 
     //return this.string;
    //else wait until it recevied.
    }
}

After that I can call whatIwantTo() method from myMain class.

If you wonder What I tried, It looks like:

public class MyClass {
private String /*or AnyObject*/ string;
private static final class Lock {}
private final Object lock = new Lock();
void lock() {
    synchronized (lock) {
        while (string == null) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public void onData(String value) {
    this.string = value;
    synchronized (lock) {
        lock.notify();
    }
}

public void callOnData(/*some param*/) {
    //does some work and calls onData();
}

public String whatIwantTo() {
    callOnData();//No need to check if it is null at this time.
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            lock();
        }
    });
    thread.start();
    try { 
        /*If I use this it freezes and never notifyed from onData*/
        /*Because thread is locked so it doesn't receive any data*/
        /*If I don't use this it returns null*/
        /*And then calling getString() from myMain class returns right value.*/
        thread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return this.string;
}
public String getString() {
    return this.string;
}

}

and in myMain class:

String returned=whatIwantTo();
System.out.print(""+returned)//returns null or never reached.

OK. After @JBNizet request, I copy all code what I used is and I use it in android:

package makgun.webview;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 *  Created by makgun on 19.03.2017.
 */
public class MyClass {
    private Context context;
    private String string;
    private WebView webView;
    List<Notify> lists;
    private static final class Lock { }
    private final Object lock = new Lock();
    private final CountDownLatch latch = new CountDownLatch(1);
    MyClass(Context context) {
        this.context = context;
    }
    public interface Notify {
        void onNotify(String result);
    }

    void onNotifyListener(Notify notify) {
        if (lists == null)
            lists = new ArrayList<>();
        lists.add(notify);
    }

    private void setNotify(String result) {
        if (lists != null)
            for (Notify notify : lists)
                notify.onNotify(result);
    }
    String xyz(){
        try {
            synchronized (lock) {
                while (string ==null) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.d("makgun", "xyz_after_wait");
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        Log.d("makgun","xyz_return : "+ string);
        return string;
    }
    private void ny(){
        try {
            synchronized (lock) {
                Log.d("makgun", "ny()");
                lock.notify();
                lock.notifyAll();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface", "JavascriptInterface"})
    private void initJs(){
        webView = new WebView(context);
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webView.addJavascriptInterface(this, "Android");
    }

    private void runJs(String html) {
        webView.loadDataWithBaseURL("", html, "text/html", "charset=UTF-8", null);
    }
    @JavascriptInterface
    public String onData(String value) {
        Log.d("makgun",value);
        setNotify(value);//For now I can read it via this custom interface
        string =value;
        Log.d("makgun","string Setted");
        latch.countDown();
        return value;
    }
    private String LoadData(String inFile) {
        String tContents = "";
        try {
            InputStream stream = context.getResources().getAssets().open(inFile);
            int size = stream.available();
            byte[] buffer = new byte[size];
            stream.read(buffer);
            stream.close();
            tContents = new String(buffer);
        } catch (IOException e) {
            // Handle exceptions here
        }
        return tContents;
    }
    String getHtml() {
        return LoadData("script.html");
    }
    public void initJS() throws ExecutionException, InterruptedException {
        Activity activity=((Activity)context);
        Callable<Void> callable = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                initJs();
                return null;
            }
        };
        FutureTask<Void> task = new FutureTask<>(callable);
        activity.runOnUiThread(task);
        task.get(); // Blocks
    }
    public void runJS(final String html) throws ExecutionException, InterruptedException {
        Activity activity=((Activity)context);
        Callable<Void> callable = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                runJs(html);
                return null;
            }
        };
        FutureTask<Void> task = new FutureTask<>(callable);
        activity.runOnUiThread(task);
        task.get(); // Blocks
    }
    String whatIwantTo(String html) throws ExecutionException, InterruptedException {
        Log.d("makgun","initJS_started");
        long startTime=System.currentTimeMillis();
        initJS();
        long temp=System.currentTimeMillis();
        Log.d("makgun","initJS_finished in ["+(temp-startTime)+" ms]");
        runJS(html);
        Log.d("makgun","runJS_finished in ["+(System.currentTimeMillis()-temp)+" ms]");
        /*After this step it will call onData() but latch.await() locks before interface reached.*/
        // latch.await();
        return string;
    }


}

This is html which will load to webView (named as script.html):

<!doctype html>
<html lang="en-US">
<head>
</head>
<body>
<script>
Android.onData('Hello World I am here!');
</script>
<!--
 Empty Body Just For Test
-->
</body>
</html>

And Finally what I used from MainActivity is:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button button = (Button) findViewById(R.id.button);
    final MyClass myClass=new MyClass(MainActivity.this);
    myClass.onNotifyListener(new MyClass.Notify() {
        @Override
        public void onNotify(String result) {
            Log.d("makgun","OnNotify result: ["+result+"]");
        }
    });
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            String returned = null;
            try {
                returned=myClass.whatIwantTo(myClass.getHtml());
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.d("makgun","returned ["+returned+"]");
       }
    });
}

And the catlog with latch.await disabled!:

03-19 05:05:27.238 10963-10963/? D/makgun: InitJS_started
03-19 05:05:27.308 10963-10963/? D/makgun: initJS_finished in [71 ms]
03-19 05:05:27.318 10963-10963/? D/makgun: initJS_finished in [10 ms]
03-19 05:05:27.318 10963-10963/? D/makgun: returned [null]
03-19 05:05:27.438 10963-11153/? D/makgun: Hello World I am here!
03-19 05:05:27.438 10963-11153/? D/makgun: OnNotify result: [Hello World I am here!]
03-19 05:05:27.438 10963-11153/? D/makgun: string Setted

And Finally the catlog with latch.await NOT disabled!:

Nothing getted and app is freezed.

You can use a CountdownLatch to do that easily. wait() and notify() can be used to, but they're too low level, and hard to use correctly.

Here is a complete minimal example.

import java.util.concurrent.CountDownLatch;

public class MyClass {
    private String string;

    private final CountDownLatch latch = new CountDownLatch(1);

    public void onData(String value) {
        this.string = value;
        latch.countDown();
    }

    public void callOnData(/*some param*/) {
        new Thread(() -> {
            try {
                Thread.sleep(100L);
            }
            catch (InterruptedException e) {
            }
            onData("hello");
        }).start();
    }

    public String whatIwantTo() throws InterruptedException {
        callOnData();
        latch.await();
        return this.string;
    }

    public static void main(String[] args) throws InterruptedException {
        MyClass m = new MyClass();
        System.out.println(m.whatIwantTo());
    }
}

Thanks for your help. Especially to @JBNizet. ( I couldn't vote up your answer but thanks. ). After some research I found why it freezes. The problem is Android related not Java .Because Android seperates thread as ui and non-ui. So if I block it from ui thread,it freezes the interface thread as well. So I call method from inside of new Thread(Runnable) and now it works. This is how my code looks like:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button button = (Button) findViewById(R.id.button);
    final MyClass myClass=new MyClass(MainActivity.this);
    /*myClass.onNotifyListener(new MyClass.Notify() {
        @Override
        public void onNotify(String result) {
            Log.d("makgun","OnNotify result: ["+result+"]");
        }
    });*/ //No more needed!
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            final  String[] returned=new String[1];
            try {
                Thread t=new Thread(new Runnable() {
                @Override
                public void run() {
                    long a=System.currentTimeMillis();
                    returned[0] =myClass.whatIwantTo(myClass.getHtml());
                    Log.d("makgun","Returned in ["+(System.currentTimeMillis()-a)+" ms]");
                    }
                });
                t.start();
                try {
                    t.join(/*timeout*/);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.d("makgun","returned ["+returned[0]+"]");
       }
    });
}

I also gain experience that if I do it from in doInBackground, the interface finished time is about [150 - 200 ms] but for above code, the time is about [1000 - 1500 ms] even if I load same data from my assets. This is huge differents . I couldn't figure out its why.

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