Android WebView'da javascript'ten dönüş değeri nasıl alınır?


83

Android'de Javascript'ten bir dönüş değeri almak istiyorum. Bunu iPhone ile yapabilirim ama Android ile yapamıyorum. LoadUrl kullandım, ancak bir nesne yerine void döndürdü. Birisi bana yardım edebilir mi?

Yanıtlar:


79

Aynı Keithama daha kısa cevap

webView.addJavascriptInterface(this, "android");
webView.loadUrl("javascript:android.onData(functionThatReturnsSomething)");

Ve işlevi uygulayın

@JavascriptInterface
public void onData(String value) {
   //.. do something with the data
}

Kaldırmak unutmayın onDatadan proguardlisteye (eğer ProGuard etkinleştirdiyseniz)


1
proguardkısmı detaylandırır mısın?
eugene

@eugene ne olduğunu bilmiyorsanız muhtemelen kullanmıyorsunuzdur. Her neyse, uygulamanızı tersine mühendisliği zorlaştıran etkinleştirecek bir şey
Kirill Kulakov

1
Teşekkürler. Proje kökünde proguard-project.txt olduğunu biliyorum ama bundan fazlası yok. Soruyorum çünkü çözümünüz çalışıyor ama SIGSEGV alıyorum. document.getElementById("my-div").scrollTopŞuradan kaydırma konumu almayı değerlendiriyorum SwipeRefreshLayout::canChildScrollUp(). Kısa sürede çok fazla aranmasından kaynaklanıyor olabilir. ya da proguardbunun ne olduğunu bilmediğim için şüpheleniyorum ..
eugene

Burada yorum yapılan tüm bilgisayar korsanları yerine kabul edilen yanıt bu olmalı
Romit Kumar

64

İşte bunu nasıl başarabileceğinize dair bir hack:

Bu Müşteriyi Web Görünümünüze ekleyin:

final class MyWebChromeClient extends WebChromeClient {
        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            Log.d("LogTag", message);
            result.confirm();
            return true;
        }
    }

Şimdi javascript çağrınızda şunları yapın:

webView.loadUrl("javascript:alert(functionThatReturnsSomething)");

Şimdi onJsAlert çağrısında "mesaj" döndürülen değeri içerecektir.


17
Bu son derece değerli bir hack; özellikle javascript üzerinde kontrol sahibi olmadığınız ve mevcut işlevlerle yetinmek zorunda olduğunuz bir uygulama oluştururken. JS uyarılarını public boolean onConsoleMessage(ConsoleMessage consoleMessage)yerine onJsAlertve daha sonra yaptığınız javascript çağrısında webView.loadUrl("javascript:console.log(functionThatReturnsSomething)");- JS uyarılarını yalnız bırakarak aynı sonucu alabileceğinizi unutmayın .
Mick Byrne

Çözüm WebChromeClient.onConsoleMessagedaha temiz görünüyor, ancak bazı HTC cihazlarda çalışmadığını unutmayın. Daha fazla bilgi için bu soruya bakın .
Piotr

3
AddJavascriptInterface kullanılması tercih edilmez mi?
Keith

@Keith: Javascript'in sonuçlarını yazdığı bir nesneye ihtiyaç duyarsınız, ancak bu sorudaki mevcut javascript'e dokunamadığınız ve mevcut js böyle bir nesneye yazmadığı için js arayüz yöntemi burada yardımcı olmuyor ( işte böyle anladım).
Ray

1
@RayKoopa Mutlaka doğru değil. Hedef web sayfasındaki mevcut javascript'i düzenleyemeyebilirsiniz, ancak yine mWebView.addJavascriptInterface(YourObject mYourObject,String yourNameForObject);de işlevi çağırarak iletilen nesneye yöntemleri çağırabilirsinizmWebView.loadUrl("javascript:yourNameForObject.yourSetterMethod(valueToSet)")
Chris - Jr

21

addJavascriptInterface()Javascript ortamına bir Java nesnesi eklemek için kullanın . Javascript'inizin "dönüş değerini" sağlamak için o Java nesnesi üzerinde bir yöntem çağırmasını sağlayın.



Js'niz bu nesneye yazmazsa ve js'ye dokunamazsanız, bu yardımcı olmaz.
Ray

7
@PacMani: Şu anda yüklü olan Web sayfası bağlamında kendi JavaScript'inizi değerlendirmek için loadUrl("javascript:...")(API Seviye 1-18) veya evaluateJavascript()(API Seviye 19+) kullanın.
CommonsWare

@CommonsWare: Teşekkürler, anladım;) ancak yönetici js'nin "kötüye javascript uyarıları" (eyeroll) üzerinden değiştirilebileceğine karar verdi, bu yüzden arayüz yöntemini kullanmaya başladım.
Ray

12

İşte bugün bulduğum şey. İş parçacığı açısından güvenlidir, makul derecede etkilidir ve bir Android Web Görünümü için Java'dan eşzamanlı Javascript yürütülmesine izin verir .

Android 2.2 ve sonraki sürümlerde çalışır. (Commons-lang gerektirir, çünkü kod parçacıklarımın bir Javascript dizesi olarak eval () 'a iletilmesine ihtiyacım var. Kodu tırnak içine değil, içine kaydırarak bu bağımlılığı kaldırabilirsiniz function(){})

Önce bunu Javascript dosyanıza ekleyin:

function evalJsForAndroid(evalJs_index, jsString) {
    var evalJs_result = "";
    try {
        evalJs_result = ""+eval(jsString);
    } catch (e) {
        console.log(e);
    }
    androidInterface.processReturnValue(evalJs_index, evalJs_result);
}

Ardından, bunu Android etkinliğinize ekleyin:

private Handler handler = new Handler();
private final AtomicInteger evalJsIndex = new AtomicInteger(0);
private final Map<Integer, String> jsReturnValues = new HashMap<Integer, String>();
private final Object jsReturnValueLock = new Object();
private WebView webView;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    webView = (WebView) findViewById(R.id.webView);
    webView.addJavascriptInterface(new MyJavascriptInterface(this), "androidInterface");
}

public String evalJs(final String js) {
    final int index = evalJsIndex.incrementAndGet();
    handler.post(new Runnable() {
        public void run() {
            webView.loadUrl("javascript:evalJsForAndroid(" + index + ", " +
                                    "\"" + StringEscapeUtils.escapeEcmaScript(js) + "\")");
        }
    });
    return waitForJsReturnValue(index, 10000);
}

private String waitForJsReturnValue(int index, int waitMs) {
    long start = System.currentTimeMillis();

    while (true) {
        long elapsed = System.currentTimeMillis() - start;
        if (elapsed > waitMs)
            break;
        synchronized (jsReturnValueLock) {
            String value = jsReturnValues.remove(index);
            if (value != null)
                return value;

            long toWait = waitMs - (System.currentTimeMillis() - start);
            if (toWait > 0)
                try {
                    jsReturnValueLock.wait(toWait);
                } catch (InterruptedException e) {
                    break;
                }
            else
                break;
        }
    }
    Log.e("MyActivity", "Giving up; waited " + (waitMs/1000) + "sec for return value " + index);
    return "";
}

private void processJsReturnValue(int index, String value) {
    synchronized (jsReturnValueLock) {
        jsReturnValues.put(index, value);
        jsReturnValueLock.notifyAll();
    }
}

private static class MyJavascriptInterface {
    private MyActivity activity;

    public MyJavascriptInterface(MyActivity activity) {
        this.activity = activity;
    }

    // this annotation is required in Jelly Bean and later:
    @JavascriptInterface
    public void processReturnValue(int index, String value) {
        activity.processJsReturnValue(index, value);
    }
}

2
Yukarıdaki kod için teşekkür ederim - Android projemde benimsedim. Ancak, kötü Java API tasarımından kaynaklanan bir tuzak vardır. Satır: jsReturnValueLock.wait (waitMs - (System.currentTimeMillis () - start)); Wait () işlevi bağımsız değişkeninin değeri bazen 0 olabilir. Ve bu "sonsuza kadar bekle" anlamına gelir - ara sıra "yanıt yok" hatası alıyordum. Bunu test ederek çözdüm: if (geçen> = waitMS) break; ve aşağıda System.currentTimeMillis () 'i tekrar çağırmamak, ancak geçen değeri kullanarak. Yukarıdaki kodu düzenlemek ve düzeltmek en iyisidir.
gregko

Vay canına çok teşekkürler! Ben de onu görüyordum ve nereden geldiğini asla anlayamadım.
Keith

1
Ancak button.click listener, vb. İçinde evalJS çalıştırırsam. waitForJsReturnValue evalJs () 'nin kapanmasına neden olabilir. Dolayısıyla, handler.post () içindeki webView.loadUrl (), button.click dinleyicisi geri dönmediği için yürütme şansı olmayabilir.
victorwoo

Ama kodu tırnak içinde değil de içine nasıl sarabilirim function(){}? JsString nasıl yerleştirilir {}?
victorwoo

7

API üzerinde 19+, bunu yapmanın en iyi yolu çağırmaktır evaluateJavascript sizin WebView'da tarih:

webView.evaluateJavascript("foo.bar()", new ValueCallback<String>() {
    @Override public void onReceiveValue(String value) {
        // value is the result returned by the Javascript as JSON
    }
});

Daha ayrıntılı ilgili yanıt: https://stackoverflow.com/a/20377857


5

@Felix Khazin'in önerdiği çözüm işe yarıyor, ancak bir anahtar nokta eksik.

Javascript çağrısı, içindeki web sayfası WebViewyüklendikten sonra yapılmalıdır . Bunu ekle WebViewClientiçin WebViewbirlikte WebChromeClient.

Tam Örnek:

@Override
public void onCreate(Bundle savedInstanceState) {
    ...
    WebView webView = (WebView) findViewById(R.id.web_view);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebViewClient(new MyWebViewClient());
    webView.setWebChromeClient(new MyWebChromeClient());
    webView.loadUrl("http://example.com");
}

private class MyWebViewClient extends WebViewClient {
    @Override
    public void onPageFinished (WebView view, String url){
        view.loadUrl("javascript:alert(functionThatReturnsSomething())");
    }
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return false;
    }
}

private class MyWebChromeClient extends WebChromeClient {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
    Log.d("LogTag", message);
        result.confirm();
        return true;
    }
}

1

Özel bir şema kullanan alternatif bir varyant olarak:

Ana aktivite

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }

        final WebView webview = new CustomWebView(this);

        setContentView(webview);

        webview.loadUrl("file:///android_asset/customPage.html");

        webview.postDelayed(new Runnable() {
            @Override
            public void run() {

                //Android -> JS
                webview.loadUrl("javascript:showToast()");
            }
        }, 1000);
    }
}

CustomWebView

public class CustomWebView extends WebView {
    public CustomWebView(Context context) {
        super(context);

        setup();
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void setup() {
        setWebViewClient(new AdWebViewClient());

        getSettings().setJavaScriptEnabled(true);

    }

    private class AdWebViewClient extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {

            if (url.startsWith("customschema://")) {
                //parse uri
                Toast.makeText(CustomWebView.this.getContext(), "event was received", Toast.LENGTH_SHORT).show();

                return true;
            }


            return false;
        }

    }
}

customPage.html (katlanmış varlıklarda bulunur)

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript View</title>

    <script type="text/javascript">

        <!--JS -> Android-->
        function showToast() {
            window.location = "customschema://goto/";
        }

    </script>
</head>

<body>

</body>
</html>

0

Bunu şu şekilde yapabilirsiniz:

[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
    public WebView web_view;
    public static TextView textView;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);

        Window.AddFlags(WindowManagerFlags.Fullscreen);
        Window.ClearFlags(WindowManagerFlags.ForceNotFullscreen);

        // Set our view from the "main" layout resource
        SetContentView (Resource.Layout.main);

        web_view = FindViewById<WebView>(Resource.Id.webView);
        textView = FindViewById<TextView>(Resource.Id.textView);

        web_view.Settings.JavaScriptEnabled = true;
        web_view.SetWebViewClient(new SMOSWebViewClient());
        web_view.LoadUrl("https://stns.egyptair.com");
    }

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
    {
        Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

public class SMOSWebViewClient : WebViewClient
{
    public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
    {
        view.LoadUrl(request.Url.ToString());
        return false;
    }

    public override void OnPageFinished(WebView view, string url)
    {
        view.EvaluateJavascript("document.getElementsByClassName('notf')[0].innerHTML;", new JavascriptResult());
    }
}

public class JavascriptResult : Java.Lang.Object, IValueCallback
{
    public string Result;

    public void OnReceiveValue(Java.Lang.Object result)
    {
        string json = ((Java.Lang.String)result).ToString();
        Result = json;
        MainActivity.textView.Text = Result.Replace("\"", string.Empty);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.