Android의 WebView에서 자바 스크립트에서 반환 값을 얻는 방법은 무엇입니까?


82

Android의 Javascript에서 반환 값을 얻고 싶습니다. 아이폰으로는 할 수 있지만 안드로이드로는 할 수 없습니다. loadUrl을 사용했지만 개체 대신 void를 반환했습니다. 아무도 나를 도울 수 있습니까?

답변:


79

과 동일 Keith하지만 짧은 대답

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

그리고 기능을 구현하십시오

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

목록 onData에서 제거하는 것을 잊지 마십시오 proguard(proguard를 활성화 한 경우).


1
proguard부품 에 대해 자세히 설명해 주 시겠습니까?
유진

@eugene이 무엇인지 모른다면 아마 사용하지 않을 것입니다. 어쨌든 그 뭔가 열심히 리버스 엔지니어링하여 응용 프로그램을 만드는 가능하게
키릴 쿨라 코브

1
감사. 프로젝트 루트에 proguard-project.txt가 있지만 그 이상은 아닙니다. 귀하의 솔루션이 작동하기 때문에 묻고 있지만 SIGSEGV를 얻습니다. document.getElementById("my-div").scrollTop에서 스크롤 위치를 얻기 위해 평가 중 입니다 SwipeRefreshLayout::canChildScrollUp(). 단기간에 전화가 너무 많이 걸려서 그런 것 같습니다. 또는 proguard나는 그냥 .. 무엇인지 모르기 때문에 나는 의심한다
유진

이것은 여기에 언급 된 모든 해킹보다는 받아 들여진 대답이어야합니다
Romit Kumar

64

이를 달성하는 방법에 대한 해킹이 있습니다.

이 클라이언트를 WebView에 추가하십시오.

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

이제 자바 스크립트 호출에서 다음을 수행하십시오.

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

이제 onJsAlert 호출에서 "message"에 반환 된 값이 포함됩니다.


17
이것은 매우 귀중한 해킹입니다. 특히 자바 스크립트를 제어 할 수없고 기존 기능을 처리해야하는 앱을 빌드 할 때 특히 그렇습니다. public boolean onConsoleMessage(ConsoleMessage consoleMessage)대신을 사용하여 JS 경고를 가로 채지 않고도 동일한 결과를 얻을 수 있습니다. onJsAlert그런 다음 JavaScript 호출에서 수행 webView.loadUrl("javascript:console.log(functionThatReturnsSomething)");하는 방식으로 JS 경고를 그대로 둡니다.
Mick Byrne 2012

의 솔루션은 WebChromeClient.onConsoleMessage더 깨끗해 보이지만 일부 HTC 장치에서는 작동하지 않습니다. 자세한 내용은 이 질문 을 참조하십시오.
Piotr

3
addJavascriptInterface를 사용하는 것이 바람직하지 않은 이유는 무엇입니까?
Keith

@Keith : 자바 스크립트가 결과를 작성하는 객체가 필요하지만이 질문에서 기존 자바 스크립트를 건드릴 수없고 기존 js가 이러한 객체에 쓰지 않기 때문에 js 인터페이스 메서드는 여기에서 도움이되지 않습니다 ( 그것이 내가 그것을 이해 한 방법입니다).
레이

1
@RayKoopa 반드시 사실은 아닙니다. 대상 웹 페이지 내에서 기존 javascript를 편집 할 수 없더라도 mWebView.addJavascriptInterface(YourObject mYourObject,String yourNameForObject);함수를 호출하여 전달 된 객체에 대한 메서드를 호출 할 수 있습니다.mWebView.loadUrl("javascript:yourNameForObject.yourSetterMethod(valueToSet)")
Chris-Jr

20

addJavascriptInterface()Javascript 환경에 Java 개체를 추가하는 데 사용 합니다. Javascript가 해당 Java 개체에 대한 메서드를 호출하여 "반환 값"을 제공하도록합니다.



js 가이 객체에 쓰지 않고 js를 만질 수 없다면 이것은 도움이되지 않습니다.
레이

7
@PacMani : loadUrl("javascript:...")(API 레벨 1-18) 또는 evaluateJavascript()(API 레벨 19+)를 사용하여 현재로드 된 웹 페이지의 컨텍스트에서 자신의 JavaScript를 평가하십시오.
CommonsWare

@CommonsWare : 감사합니다.;) 그러나 관리자는 js가 "자바 스크립트 경고 악용"(eyeroll)보다 수정 될 수 있다고 결정했기 때문에 인터페이스 방법을 사용했습니다.
Ray

12

오늘 제가 생각해 낸 것이 있습니다. 스레드로부터 안전하고 합리적으로 효율적이며 Android WebView 용 Java에서 동기식 Javascript 실행을 허용합니다 .

Android 2.2 이상에서 작동합니다. (Javascript 문자열로 eval ()에 전달 된 코드 조각이 필요하기 때문에 commons-lang이 필요합니다. 코드를 따옴표가 아니라에서 래핑하여이 종속성을 제거 할 수 있습니다. function(){})

먼저 다음을 Javascript 파일에 추가하십시오.

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

그런 다음 Android 활동에 다음을 추가하십시오.

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
위의 코드를 주셔서 감사합니다. Android 프로젝트에서 채택했습니다. 그러나 잘못된 Java API 설계로 인해 함정이 있습니다. 줄 : jsReturnValueLock.wait (waitMs-(System.currentTimeMillis ()-start)); wait () 함수 인수의 값은 때때로 0으로 평가 될 수 있습니다. 이것은 "무한히 대기"를 의미합니다. 가끔 "응답 없음"오류가 발생했습니다. 다음 테스트로 해결했습니다. if (elapsed> = waitMS) break; 아래 System.currentTimeMillis ()를 다시 호출하지 않고 경과 값을 사용합니다. 위의 코드를 수정하고 수정하는 것이 가장 좋습니다.
gregko 2013 년

와우 감사합니다! 나는 그 교수형도 보았고 그것이 어디에서 오는지 결코 알지 못했습니다.
Keith

1
그러나 button.click listener 등에서 evalJS를 실행하면. waitForJsReturnValue로 인해 evalJs ()가 중단 될 수 있습니다. 따라서 handler.post () 내부의 webView.loadUrl ()은 button.click 리스너가 반환되지 않았기 때문에 실행할 기회가 없을 수 있습니다.
victorwoo

그러나 따옴표가 아닌 코드를 래핑하는 방법은 function(){}무엇입니까? jsString을 배치하는 방법 {}?
victorwoo


5

@Felix Khazin이 제안한 솔루션이 작동하지만 한 가지 핵심 포인트가 누락되었습니다.

에서 웹 페이지 WebView가로드 된 후 자바 스크립트 호출이 이루어져야합니다 . 이 추가 WebViewClient받는 WebView과 함께 WebChromeClient.

전체 예 :

@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

사용자 지정 체계를 사용하는 대체 변형 :

주요 활동

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 (접힌 자산에 위치)

<!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

다음과 같이 할 수 있습니다.

[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);
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.