안드로이드 자바스크립트 리턴값 - andeuloideu jabaseukeulibteu liteongabs

말 그대로 웹 페이지를 보여주는 뷰, 안드로이드의 컴포넌트중 하나다. 안드로이드가 iOS에 비해 웹뷰가 좋은 이유는.. 웹뷰의 종류가 기본형 하나이기 때문일 것이다. iOS는 UIWebView라고 있지만, WKWebView라고 좀 더 진보된 웹뷰를 권장하는데, 이 웹뷰가 기본형이 아니라서 import를 하고 그런 작업들이 있기 때문이다. 그렇기에 처음 접하는 사람에겐 iOS가 귀찮다.

어쨌든, 앱의 기본이 웹페이지를 띄워주는 것이고.. 여기에 플레이스토어에 출시하기 위한 껍데기, 네이티브의 기능을 덧붙여주면 하이브리드앱이 된다. 물론, 웹이 주가 아니라 네이티브가 주면서 웹은 거드는 것도 하이브리드 앱이라 할 수 있다. 정의를 굳이 딱 정하려 하기 보다는 네이티브의 기능(카메라, 지도 등)과 웹의 기능이 혼합되어있는 것으로 생각하면 된다. 그렇다면 이 기능을 어떻게 연결할 수 있는가,

바로 자바스크립트 함수다. 웹 뷰내에 열린 페이지안에 있는 자바스크립트 함수는 네이티브 함수를 호출할 수 있으며, 반대로 네이티브안에서 자바스크립트의 함수를 호출할 수 있다. 또한, 매개변수(파라미터)를 전달할 수 있으며, 그에 대한 리턴값도 얻을 수 있다. 

이 네이티브의 기능들을 호출할때 하나 하나 작성하기 귀찮을 경우 생각할 수 있는 것이 하이브리드 프레임워크(라이브러리들의 집합이랄까)이다. 대표적인 예가 폰갭과 코르도바 인데.. 얘네는  원래는 한 뿌리로 시작했지만, 어도비인가가 둘 중하나를 인수해서 갈라진 것으로 알고 있다. 호출 방식이나 이런건 거의 비슷..

이 프레임워크들을 사용하면 매우 간단하다. 웹의 js함수 한 두줄로, 네이티브의 카메라 기능을 호출할 수 있는 등.. 매우 코드가 간결해진다. 또한, 기본적으로 프레임워크의 이름을 단 웹뷰를 사용하기 때문에 세팅들도 왠만한건 다 되어있어 편리한 것이 가장 큰 장점이다. 단점이라면 외부의 프레임워크를 넣어서 하기 때문에 용량이 커지고, 프레임워크가 업데이트할 수록 그에 맞춰 코드들도 변경해줘야하는 번거로움이 있다. 즉, 안드로이드 버전 업 + 프레임워크 버전 업까지 신경써야하는 것이다.

어쨌든, 이 포스팅은 프레임워크가 아닌 안드로이드 내장 웹뷰를 사용하면서, 그에 대한 세팅 몇 가지를 소개할 것이다.

[레이아웃]

처음 말했듯이, 안드로이드에서 제공하는 웹뷰이기 때문에 간단하다.

[코드]

public class ContentActivity extends AppCompatActivity { WebView contentWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_content); contentWebView = (WebView) findViewById(R.id.contentWebView); WebSettings webSettings = contentWebView.getSettings(); webSettings.setSaveFormData(true); // 폼 입력 값 저장 여부 webSettings.setSupportZoom(true); // 줌 사용 여부 : HTML Meta태그에 적어놓은 설정이 우선 됨 webSettings.setBuiltInZoomControls(true); // 줌 사용 여부와 같이 사용해야 하는 설정(안드로이드 내장 기능) webSettings.setDisplayZoomControls(false); // 줌 사용 시 하단에 뜨는 +, - 아이콘 보여주기 여부 webSettings.setJavaScriptEnabled(true); // 자바스크립트 사용 여부 webSettings.setDomStorageEnabled(true); // 웹뷰내의 localStorage 사용 여부 webSettings.setGeolocationEnabled(true); // 웹뷰내의 위치 정보 사용 여부 //webSettings.setJavaScriptCanOpenWindowsAutomatically(true); // 웹뷰내의 JS의 window.open()을 허용할 것인지에 대한 여부 if (Build.VERSION.SDK_INT >= 16) { webSettings.setAllowFileAccessFromFileURLs(true); webSettings.setAllowUniversalAccessFromFileURLs(true); } if (Build.VERSION.SDK_INT >= 21){ webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // HTTPS HTTP의 연동, 서로 호출 가능하도록 }

contentWebView.setWebViewClient(new MyWebViewClient(ContentActivity.this, refreshLayout)); contentWebView.setWebChromeClient(new MyWebChromeClient(ContentActivity.this)); contentWebView.addJavascriptInterface(new WebAppInterface(ContentActivity.this), "Android"); contentWebView.setDownloadListener(new DownloadListener() { // 웹뷰내 다운로드가 가능한 파일이 있다면! @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { try { Log.i("DownloadInfo", "url:" + url + "/userAgent:" + userAgent + "/contentDisposition:" + contentDisposition + "/mimetype:" + mimetype + "/contentLength:" + contentLength); DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); request.setMimeType(mimetype); request.addRequestHeader("User-Agent", userAgent); request.setDescription("Downloading file"); String fileName = contentDisposition.replace("inline; filename=", ""); fileName = fileName.replaceAll("\"", ""); request.setTitle(fileName); request.allowScanningByMediaScanner(); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName); DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); dm.enqueue(request); Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show(); } catch (Exception e) { Log.e("Exception", e.toString()); mainActivity.sendLogMsgPHP("WebViewDownloadManager:" + e.toString()); } } }); } }

현재 내가 사용하고 있는 웹뷰의 기본 세팅이다.

주석으로 처리된 것이 모든 것의 설명이다. 필요하면 쓰면 되고, 아니면 안쓰면 도

폼 입력값을 저장하거나, 자바스크립트 사용, localStorage 사용, 위치정보, 줌, Http & Https간 연동, 다운로드 리스너 등..

[자바스크립트 연동]

여기서 하이브리드앱의 꽃이 되는 코드는 

webSettings.setJavaScriptEnabled(true); // 자바스크립트 사용 여부 webView.addJavascriptInterface(new WebAppInterface(MainActivity.this), "Android");

이 두 줄이라 할 수 있다. 웹뷰 내에 자바스크립트를 사용가능하게 하는 설정과, 인터페이스.. 즉 웹과 네이티브간의 인터페이스를 Android란 이름으로 명명한다. 여기서 Android는 변수이므로 자유롭게 쓰면 된다.

여기서 사용한 Android.functionName으로 JS에서 호출이 가능하다.

function init(){ var result = JSON.parse(Android.getSystemData()); alert(result.myPhoneNum + " / " + result.pushToken); }

이 함수는 웹페이지의 body가 onLoad될 때 호출되는 함수인데, 여기서 result란 변수에는 Android.getSystemData()라는 네이티브 함수를 호출하고, 그에대한 리턴값을 제이슨으로 받아 문자열로 변환하는 것이다.

이처럼, JS -> Native 호출시엔 리턴 뿐만아니라 JSON값 까지 가능하다는 것! 반대로 Native -> JS 호출시엔 어떤 방식일까.

    public void insertAuthNum(String authNum){
        Log.i("insertAuthNum", "CALL : " + authNum);
        webView.loadUrl("javascript:insertAuthNum('" + authNum + "');");
    }

이처럼 웹뷰의 loadUrl에 javascript: 를 사용하여 해당 JS함수를 호출하면 된다. authNum이란 파라미터까지 전달하는 것이다. 그렇다면 해당 URL을 열고 그에 대한 리턴값을 얻어올 수 있을까? 물론 가능하다.

    public void insertAuthNum(final String authNum){
        Log.i("insertAuthNum", "CALL : " + authNum);
        // webView.loadUrl("javascript:insertAuthNum('" + authNum + "');");
        webView.post(new Runnable() {
            @Override
            public void run() {
                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                webView.evaluateJavascript("javascript:insertAuthNum('" + authNum + "');", new ValueCallback() {
                    @Override
                    public void onReceiveValue(String value) {
                        Log.i("onReceiveValue", value);
                    }
                });
            }
        });
    }

위에 작성한 loadUrl대신 evaluateJavascript를 통해 가능하다. 여기서는 ValueCallBack을 지원하기 때문에 js에서 return한 값을 onReceiveValue에서 가져올 수 있다. 다만 버전이 킷캣 이상인 경우 가능하다는 것을 참고하자.

iOS는 불편하게도, 이게 안된다. 웹 js에서 네이티브 함수를 호출하고 그에대한 리턴값을 받을 수가 없어, 네이티브 쪽에서 js함수를 다시 호출해야한다. 그래서 호출하는 js함수, 값을 리턴받는 js함수 두 개가 필요한 불편함이 있다.