NewWindowWebView

window.open에 대응하는 클래스

init

  • WebView Bridge를 설정한다.

  • webview setting을 변경한다.

init {
    Log.i("NewWindowWebView","initialize")

    androidBridge = AndroidBridge()
    this.addJavascriptInterface(androidBridge, "AndroidBridge")
    this.webViewClient = WebClient()
    this.webChromeClient = ChroniumClient()

    //웹페이지 줌인/아웃 관련 설정
    this.settings?.javaScriptEnabled = true
    this.settings?.javaScriptCanOpenWindowsAutomatically = true
    this.settings?.setSupportMultipleWindows(true)
    this.settings?.domStorageEnabled = true
    this.settings?.allowFileAccess = true
    //webView.getSettings().setSupportZoom(true);
    //webView.getSettings().setBuiltInZoomControls(true);
    //webView.getSettings().setDisplayZoomControls(false);

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

}

constructor

constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int): super(context, attrs, defStyle)

AndroidBridge

AndroidBridge

  • JS interface 동작을 정의한다.

LocationListener

  • 서버에 위치 정보 값을 전달한다.

val mGetLocationAndResultSendListener : LocationListener by lazy {
    object : LocationListener {
        override fun onLocationChanged(location: Location?) {
            Log.i(TAG, "=== location : ${location?.latitude} / ${location?.longitude} ===")

            val jsonParams = JsonObject()
            jsonParams.addProperty("lat",location?.latitude)
            jsonParams.addProperty("lon",location?.longitude)

            responseSuccessOrFail(COMMON_SUCCESS, jsonParams)
        }
        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
        }
        override fun onProviderDisabled(provider: String?) {
        }
        override fun onProviderEnabled(provider: String?) {
        }
    }
}

goHome

  • 홈화면으로 돌아가는 동작을 정의한다.

@JavascriptInterface
fun goHome() {
    Logger.e(TAG, "goHome")
}

postMessage

  • 서버에 메시지를 전달한다.


@JavascriptInterface
fun postMessage(postMessage: String?) {
    /* ex
     {
      "name": "setTopMenu",
      "params": {
        "preBt": "Y",
        "title": "하이브리드 CallTest"
      },
      "success": "nativeCall.successCB",
      "fail": "nativeCall.failCB"
    }
     */

    postMessage?.let{
        Log.i(TAG, "postMessage:$postMessage")
        val parseMessage = Gson().fromJson(postMessage, DataModel.PostMessage::class.java) //param 을동적으로 파싱..
        //성공실패여부
        success = parseMessage.success?:""
        fail = parseMessage.fail?:""

        when(WebViewFunctionType.valueOf(parseMessage.name)){
            WebViewFunctionType.setTopMenu -> { //상단 탭관련 내용
                setTopMenu(parseMessage)
            }
            WebViewFunctionType.addHistory ->{ //화면 액션에 따른 히스토리를 관리함
                addHistory(parseMessage)
            }
            WebViewFunctionType.navigation ->{ //화면별 이동을 구분해서 동작
                moveViewNavigation(parseMessage)
            }//navigation end
            WebViewFunctionType.closePopup ->{ //스크립트로 오픈된 팝업형태의 창을 닫음
                closePopup(parseMessage)
            }
            WebViewFunctionType.deviceInfo -> { //디바이스 기기 정보 관련 전달
                deviceInfoSend(parseMessage)
            }
            WebViewFunctionType.sendSms -> { //문자메세지 SEND 관련
                sendSMS(parseMessage)
            }
            WebViewFunctionType.getSnsUserInfo -> { //유저관련 회원가입, 로그인...
                getSnsUserInfo(parseMessage)
            }
            WebViewFunctionType.toastMsg -> { //토스트 메세지 출력
                showToastMsg(parseMessage)
            }
            WebViewFunctionType.downloadImage -> { //이미지를 다운로드 한다
                downloadImg(parseMessage)
            }
            WebViewFunctionType.webShare -> { //웹에서 공유바를 show 한다.
                showWebSharedBar(parseMessage)
            }
            WebViewFunctionType.setLoginInfo -> { //유저정보를 저장 한다.
                setLoginInfo(parseMessage)
            }
            WebViewFunctionType.getLocation -> { //위치정보를 전달한다.
                getCurrentLocation(parseMessage)
            }
            WebViewFunctionType.openUrl -> { //위치정보를 전달한다.
                moveOpenUrl(parseMessage)
            }



            //... 계속 추가중







        }//when end

    }

}//postMessage end

setTopMenu

  • 상단바의 제목과 back 버튼을 정의한다.

fun setTopMenu(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.SetTopMenuParams::class.java)
    //해당작업진행
    with(parseParams){
        Log.i(TAG, preBt) //Y/N android back은 물리버튼이 있기때문에 의미가 있나...
        Log.i(TAG, title) //text_top_title?.text = title
    }
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

addHistory

  • history를 추가한다.

fun addHistory(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.AddHistoryParams::class.java)
    //해당작업진행
    with(parseParams){
        Log.i(TAG, url)
        Log.i(TAG, param.toString())
        Log.i(TAG, type)

        if(mContext is CommonWebViewConroller){
            mContext.historyList.add(WebHistory(url, param.toString(), type))
        }

    }
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

moveViewNavigation

  • 요청값에 따라 화면을 이동시킨다.

fun moveViewNavigation(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.NavigationParams::class.java)
    //해당작업진행

    val naviType = parseParams.type.toUpperCase()
    when(naviType){
        "C" -> { //현재 보여지는 화면을 닫는다.
            mContext?.finish()
        }
        "N" -> { //기존화면 그대로, new page 새로운 액티비티에서 웹뷰를 보여준다.
            val intent: Intent = Intent(mContext, MainWebViewActivity::class.java)
            intent.putExtra("url", parseParams.url)
            intent.putExtra("param", parseParams.param?.toString())
            intent.putExtra("viewType", naviType)
            mContext?.startActivityForResult(intent, REQUEST_NEW_WEBVIEW_OPEN)
        }
        "N-R", "N-RT", "N-RB", "N-RTB" -> { //현재 화면이 finish 되고 새로운 액티비티에서 웹뷰를 보여준다.
            val intent: Intent = Intent(mContext, MainWebViewActivity::class.java)
            intent.putExtra("url", parseParams.url)
            intent.putExtra("param", parseParams.param?.toString())
            intent.putExtra("viewType", naviType)
            intent.putExtra("rootView", true)
            mContext?.startActivityForResult(intent, REQUEST_NEW_WEBVIEW_OPEN)
            mContext?.finish()
        }
        "B" -> { //현재 보여지는 화면의 history back(히스토리없으면 finish)
            (mContext as? CommonWebViewConroller)?.backAction()
        }
        "M" -> { // 메인으로 이동
            this@CustomWebView.loadUrl(MAIN_PAGE_URL)
        }
        "P" ->{ //기존화면 그대로, 새로화면 열고 로직처리되면 닫히면서, 기존창에서 스크립트를 호출
            val intent: Intent = Intent(mContext, MainWebViewActivity::class.java)
            intent.putExtra("url", parseParams.url)
            intent.putExtra("param", parseParams.param?.toString())
            intent.putExtra("viewType", naviType)
            mContext?.startActivityForResult(intent, REQUEST_NEW_WEBVIEW_CALLBACK_OPEN)
        }
        "SETTING" ->{ //권한 관련 등의 이유로 설정화면으로 이동할때
            //TODO 무슨권한 페이지로?
            val builder = AlertDialog.Builder(context)
            builder.setTitle("확인")
            builder.setMessage("설정페이지로 이동하시겠습니까?")
            builder.setPositiveButton(android.R.string.yes) { d, _ ->
                d.dismiss()

                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                    Uri.parse("package: ${mContext?.packageName}"))
                intent.addCategory(Intent.CATEGORY_DEFAULT)
                mContext?.startActivityForResult(intent, REQUEST_SETTING_OPEN)

            }
            builder.setNegativeButton(android.R.string.no){d, _->
                d.dismiss()
            }
            builder.show()

        }
        "EXIT" -> {
            mContext?.finishApp("종료하시겠습니까?")
        }else -> { //예외적으로 처리할 navi 동작을 정의

        }

    }//when end

    responseSuccessOrFail(COMMON_SUCCESS, null)
}

closePopUp

  • 종료 팝업을 호출한다.

fun closePopup(parseMessage: DataModel.PostMessage){
    val parseParams = Gson().fromJson(parseMessage.params, DataModel.ClosePopupParams::class.java)
    //해당작업진행
    with(parseParams){

        val func:String = function
        val param:String = param?.toString()?: ""

        val intent = Intent()
        intent.putExtra("function",func)
        intent.putExtra("param",param)
        mContext?.setResult(Activity.RESULT_OK, intent)
        mContext?.finish()

    }
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

deviceInfoSend

  • 기기 정보를 넘겨준다.

  • 필수 권한 정보승인여부를 넘겨준다.

fun deviceInfoSend(parseMessage: DataModel.PostMessage?){
    val responseJson = JsonObject()
    responseJson.addProperty("appVer", context.getAppVersionName())
    responseJson.addProperty("build", context.getAppVersionCode())
    responseJson.addProperty("os",context.getAppOs(0))
    responseJson.addProperty("osVer",context.getOsVersion())
    responseJson.addProperty("model",context.getDeviceModel())
    responseJson.addProperty("uuid",context.getDeviceUuid())
    responseJson.addProperty("pushToken",context.getPushToken())
    responseJson.addProperty("isFirst",SharedPreferenceManager.isFirst())

    val widthHeight = context.getWidthAndHeight("px",mContext!!)
    responseJson.addProperty("width",widthHeight[0]) //width
    responseJson.addProperty("height",widthHeight[1]) //height

    responseJson.addProperty("carrierName",context.getTelecomName()) //통신사
    responseJson.addProperty("carrierCode",context.getTelecomCode()) //통신사

    //권한 (아이폰 안드로이드 권한 다름 체크 )
    //TODO 권한 전달 작업
    val userPermMap = context.getUserPermission()
    responseJson.addProperty("p-mike", userPermMap["mic"]) //마이크권한
    responseJson.addProperty("p-photo", userPermMap["file"]) //사진권한 (file)
    responseJson.addProperty("p-camera", userPermMap["camera"]) //카메라권한
    responseJson.addProperty("p-contact", userPermMap["contact"]) //연락처권한
    responseJson.addProperty("p-location", userPermMap["location"]) //위치권한
    responseJson.addProperty("p-phone", userPermMap["phone"]) //폰권한
    responseJson.addProperty("p-push", userPermMap["push"]) //푸시권한

    responseSuccessOrFail(COMMON_SUCCESS, responseJson)
}

sendSMS

  • 문자를 전송한다.

fun sendSMS(parseMessage: DataModel.PostMessage){

    val parseParams = Gson().fromJson(parseMessage.params, DataModel.sendSmsParams::class.java)
    //해당작업진행
    with(parseParams){
        //val numbers = number
        var resultNumbers = ""
        number.forEach {
            resultNumbers += "${it.toString().replace("\"", "")};"
        }

        val msg:String = message

        try{
            val smsUri = Uri.parse("smsto: $resultNumbers")
            val sendIntent = Intent(Intent.ACTION_SENDTO, smsUri)
            sendIntent.putExtra("sms_body", msg)
            mContext?.startActivity(sendIntent)

        }catch (e:Exception){
            Log.e(TAG, e.toString())
        }
    }
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

getSnsUserInfo

  • sns로 로그인 한 사용자의 채널을 가져온다.

fun getSnsUserInfo(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.snsUserInfoParams::class.java)

    if (parseParams.type == "0") { //0 카톡 , 1 페북
        Log.i(TAG, "0 카톡")
        commonWebviewCont?.kakaoLogin?.customKakaoLogin(mContext)
    }else{
        Log.i(TAG, "1 페북")
        commonWebviewCont?.facebookLogin?.callFaceBook()
    }

}

showToastMsg

  • JS에서 전달받은 토스트 메시지를 출력한다.

fun showToastMsg(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, JsonObject::class.java)
        Toast.makeText(mContext, parseParams["msg"].toString(), Toast.LENGTH_SHORT).show()
    responseSuccessOrFail(COMMON_SUCCESS, null)
}

downloadImg

  • 이미지를 다운로드한다.

fun downloadImg(parseMessage: DataModel.PostMessage) {
    (mContext as? CommonWebViewConroller)?.showLoadingBar()
    var counting = 0

    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.downloadImgParams::class.java)

    parseParams.info.forEach { info ->
        val downFileinfo =
            Gson().fromJson(info, DataModel.downloadImgInfo::class.java)

        ApiManager.downloadImg(downFileinfo.link)
            .flatMap { img -> saveFileProcess(img, downFileinfo.name) }
            .subscribeOn(Schedulers.single()) //순차 진행...
            .observeOn(AndroidSchedulers.mainThread())

            .doOnComplete { //complete 시 dispose 자동
                Log.i(TAG, "complete downloadImgApiService")
                counting += 1
                Log.i(TAG, "=== ${counting * 100 / parseParams.info.size()} % ===")
                if(counting == parseParams.info.size()){ //last index
                    (mContext as? CommonWebViewConroller)?.hideLoadingBar()
                    val complete = context.getString(R.string.toast_msg_file_down_ok)
                    Toast.makeText(mContext, complete, Toast.LENGTH_SHORT).show()
                }
            }
            .doOnError { //다운로드중 error 발생
                    error ->
                Log.e(TAG, error.message)
                (mContext as? CommonWebViewConroller)?.hideLoadingBar()
                val failed = context.getString(R.string.toast_msg_file_down_fail)
                Toast.makeText(mContext, failed, Toast.LENGTH_SHORT).show()
            }
            .doOnNext {file->
                Log.i(TAG, file.absolutePath)
            }
            .subscribe()

    }
}

showWebSharedBar

  • 공유하기 동작에 대한 것을 정의한다.

fun showWebSharedBar(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, JsonObject::class.java)

    val intent = Intent(Intent.ACTION_SEND)
    intent.type = "text/plain"
    val shareBodyText = parseParams["text"]?.toString()
    intent.putExtra(Intent.EXTRA_SUBJECT, "Subject/Title")
    intent.putExtra(Intent.EXTRA_TEXT, shareBodyText)
    mContext?.startActivity(Intent.createChooser(intent, "공유 선택"))

    responseSuccessOrFail(COMMON_SUCCESS, null)
}

responseSuccessOrFail

  • Success / Fail JS를 호출한다.

@JavascriptInterface
fun responseSuccessOrFail(type:Int, params:JsonObject?){
    val resultCallJs = if(type == COMMON_SUCCESS){
        if(success != "") {
            "javascript:$success( ${params?.toString()?:""} )"
        }else{
            return
        }

    }else{ //fail
        if(fail != "") {
            "javascript:$fail( ${params?.toString()?:""} )"
        }else{
            return
        }
    }

    this@CustomWebView.post {
        loadUrl(resultCallJs)
    }

}

setLoginInfo

  • 로그인 이후 서버에서 가져온 사용자 정보를 저장한다.

fun setLoginInfo(parseMessage: DataModel.PostMessage){
    val parseParams:DataModel.loginUserInfoParams? =
        Gson().fromJson(parseMessage.params, DataModel.loginUserInfoParams::class.java)

    if(parseParams != null){
        //user 정보 저장
        SharedPreferenceManager.setIuid(parseParams.iuid?:"")
    }

}

getCurrentLocation

  • 현재 위치정보를 가져와 서버에 전달한다.

fun getCurrentLocation(parseMessage: DataModel.PostMessage){
    if(mContext?.getLocationCheckPermission()!!){// 권한 체크
        getAndSendLocation()
    }else{
        //위치 권한만 요청
        mContext.requestPermissionLocation(mContext)
    }

}

getAndSendLocation

  • 위치정보를 가져온다.

@SuppressLint("MissingPermission")
fun getAndSendLocation(){
    val mLocationManager = mContext?.getSystemService(AppCompatActivity.LOCATION_SERVICE) as LocationManager

    // GPS / NETWORK 사용 유무 먼저 확인후 진행
    // 둘다 켜져있으면 GPS 우선
    if(mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
            0, 10F, mGetLocationAndResultSendListener)
        return
    }else{
        if(mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)){
            mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
                0, 10F, mGetLocationAndResultSendListener)
        }else{
            responseSuccessOrFail(COMMON_FAIL, null)
            Toast.makeText(mContext, "위치서비스 및 네트워크를 활성화 해주십시요.", Toast.LENGTH_SHORT).show()
        }
        return
    }

}

moveOpenUrl

  • 아웃링크로 이동하는 동작을 정의한다.

fun moveOpenUrl(parseMessage: DataModel.PostMessage){
    val parseParams =
        Gson().fromJson(parseMessage.params, DataModel.openUrlParams::class.java)
    val openUrl = parseParams.param

    try{
        val ii = Intent(Intent.ACTION_VIEW)
        ii.data = Uri.parse(openUrl)
        mContext?.startActivity(ii)
    }catch (e: ActivityNotFoundException){ //해당 url로 호출 못 할 경우발생
        //TODO 스킴등을 호출로 못찾은 경우 실패 전달
        responseSuccessOrFail(COMMON_FAIL, null)
    }
}

WebClient

  • url 이동 동작을 정의한다.

  • html링크 클릭, url 인터셉터,폼 등록 등의 동작들을 정의할 때 사용한다.

 private inner class WebClient : WebViewClient() {

        /*
        override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
            Log.i(TAG, "request headers: ${request?.requestHeaders.toString()}")
            return super.shouldInterceptRequest(view, request)
        }*/

        //TODO 작성 완료 되면 24 이하 shouldOverrideUrlLoading 에도 작성
        @TargetApi(Build.VERSION_CODES.N)
        override fun shouldOverrideUrlLoading(targetWebView: WebView, request: WebResourceRequest): Boolean {
            if(targetWebView.copyBackForwardList().size > 1){
                Log.i(TAG, "===view.copyBackForwardList().size : ${targetWebView.copyBackForwardList().size}===")
                targetWebView.clearHistory()
            }
            (mContext as MainWebViewActivity).createNewWindowWebView(newWindowDepth, targetWebView)
            (mContext as MainWebViewActivity).showHideWidnowWebView(newWindowDepth, true)
            targetWebView.loadUrl(request.url.toString())
            return true
        }

        override fun shouldOverrideUrlLoading(targetWebView: WebView, url: String): Boolean {
            return false
        }

        override fun onPageStarted(targetWebView: WebView, url: String, favicon: Bitmap?) {
            super.onPageStarted(targetWebView, url, favicon)
            (mContext as? CommonWebViewConroller)?.showLoadingBar()
            Handler(Looper.getMainLooper()).postDelayed({
                (mContext as? CommonWebViewConroller)?.hideLoadingBar()
            }, 1000)

        }

        override fun onPageFinished(targetWebView: WebView, url: String) {
            super.onPageFinished(targetWebView, url)

            //중요사항!!! : 새창(new window) 의 history를 관리 하지 않으면 IllegalArgumentException 가 등장할수 있음.
            //"New WebView for popup window must not have been previously navigated."
            targetWebView.clearHistory()
            targetWebView.clearCache(true)

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                //noinspection deprecation
                CookieSyncManager.getInstance().sync()
            } else {
                //쿠키 수동 싱크 (메모리 -> 로컬저장) 그밖에 싱크는 webview 에서 자동으로 함.
                CookieManager.getInstance().flush()
            }

            //(mContext as? CommonWebViewConroller)?.hideLoadingBar()
        }
    }//WebClient class end

ChroniumClient

  • 새창 띄우기 동작에 관한 정의

private inner class ChroniumClient : WebChromeClient() {
    override fun onJsAlert(view: WebView, url: String, message: String, result: JsResult): Boolean {
        return super.onJsAlert(view, url, message, result)
    }

    override fun onReceivedTitle(view: WebView, title: String?) {
        super.onReceivedTitle(view, title)
        if (title != null) {
            //text_top_title?.text = title
        }
    }

    override fun onCloseWindow(window: WebView?) {
        (mContext as MainWebViewActivity).backAction()
    }

    /**
     * TODO 내부에서 n개 팝업 생성시... 고려
     * window.open (팝업) 호출시의 동작 정의
     */
    override fun onCreateWindow(view: WebView?,isDialog: Boolean,
        isUserGesture: Boolean,resultMsg: Message?): Boolean {

        //중요사항!!! : 새로운 웹뷰를 타겟팅 하지 않으면 IllegalArgumentException 가 발생 할 수 있음.
        //"New WebView for popup window must not have been previously navigated."
        val newWebView = NewWindowWebView(context).apply {
            this.newWindowDepth = 1
        }
        (mContext as? MainWebViewActivity)?.createNewWindowWebView(1, newWebView)
        val transport:WebViewTransport? = resultMsg?.obj as? WebViewTransport
        transport?.setWebView(newWebView)
        resultMsg?.sendToTarget()

        return true
    }
}//ChroniumClient class end

Last updated

Was this helpful?